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

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

#include "cfg.h"
#include "edv_types.h"
#include "edv_pixmap.h"
#include "edv_cursor.h"
#include "edv_mime_type.h"
#include "edv_mime_types_list.h"
#include "edv_history.h"
#include "edv_history_list.h"
#include "browser.h"
#include "imbr.h"
#include "archiver.h"
#include "recbin.h"
#include "endeavour2.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "edv_cb.h"
#include "edv_cfg_list.h"
#include "config.h"


typedef struct _edv_scrolled_window_data	edv_scrolled_window_data;
#define EDV_SCROLLED_WINDOW_DATA(p)		((edv_scrolled_window_data *)(p))


static gchar *G_STRCAT(gchar *s, const gchar *s2);

/* Accelerator Key Matching */
gint EDVMatchAccelKeyOPID(
	const cfg_item_struct *cfg_list, const gchar *parm,
	const guint accel_key, const guint accel_mods
);
cfg_accelkey_struct *EDVMatchAccelKey(
	const cfg_item_struct *cfg_list, const gchar *parm,
	const gint opid 
);

/* String Formatting */
gchar *EDVGetObjectSizeStr(edv_core_struct *core, const gulong x);

/* Pixmaps */
void EDVResizePixmap(
	GdkPixmap *pixmap, GdkBitmap *mask,
	gint width, gint height,
	GdkPixmap **pixmap_rtn, GdkBitmap **mask_rtn
);
gboolean EDVLoadPixmap(
	edv_core_struct *core,
	guint8 **data, const gchar *name,
	GdkPixmap **pixmap_rtn, GdkBitmap **mask_rtn
);
GdkPixmap *EDVNewProgressPixmap(
	const gint width, const gint height,
	const gfloat coeff,
	const gboolean draw_value,
	const GtkOrientation orientation, const gboolean reverse,
	GtkStyle *style,
	GdkBitmap **mask_rtn
);
GtkWidget *EDVNewPixmapWidget(
	edv_core_struct *core,
	guint8 **data, const gchar *name 
);

/* Cursors */
GdkCursor *EDVGetCursor(
	edv_core_struct *core, const edv_cursor_code code
);

/* GtkCTree Nodes */
gint EDVNodeGetLevel(GtkCTreeNode *node);
GtkCTreeNode *EDVNodeGetParent(GtkCTreeNode *node);
GtkCTreeNode *EDVNodeGetChild(GtkCTreeNode *node);
GtkCTreeNode *EDVNodeGetSibling(GtkCTreeNode *node);
GtkCTreeNode *EDVNodeGetToplevel(GtkCTree *ctree);

/* GtkCList & GtkCTree Utilities */
gint EDVCListGetSelectedLast(
	GtkCList *clist, GtkCListRow **row_ptr_rtn
);
GtkCTreeNode *EDVCTreeGetSelectedLast(
	GtkCTree *ctree, GtkCTreeRow **row_ptr_rtn
);
gboolean EDVNodeGetIndex(
	GtkCTree *ctree, GtkCTreeNode *node,
	gint *row_rtn, gint *column_rtn
);
GtkCTreeNode *EDVNodeGetByCoordinates(
	GtkCTree *ctree, const gint x, const gint y
);
void EDVScrollCListToPosition(
	GtkCList *clist, const gfloat hpos, const gfloat vpos
);

/* Endeavour Window Matching */
gboolean EDVMatchWindowFromToplevel(
	edv_core_struct *core, GtkWidget *toplevel,
	gint *browser_num,
	gint *imbr_num,
	gint *archiver_num,
	gint *recbin_num
);

/* Object Checking */
gboolean EDVIsObjectNameValid(const gchar *name);
gboolean EDVIsObjectNameHidden(const gchar *name);
gboolean EDVIsObjectHidden(edv_object_struct *obj);

/* Write Protect Utilities */
gboolean EDVCheckWriteProtect(
	edv_core_struct *core,
	const gboolean verbose, GtkWidget *toplevel
);

/* Permission Checking */
gboolean EDVCheckUIDIsOwner(gint effective_uid, gint owner_id);
gboolean EDVCheckIsOwner(edv_core_struct *core, gint owner_id);
gboolean EDVIsObjectAccessable(
	edv_core_struct *core, edv_object_struct *obj
);
gboolean EDVIsObjectReadable(
	edv_core_struct *core, edv_object_struct *obj
);
gboolean EDVIsObjectWriteable(
	edv_core_struct *core, edv_object_struct *obj
);
gboolean EDVIsObjectExecutable(
	edv_core_struct *core, edv_object_struct *obj
);

/* Internal Supported Object Type Checking */
gboolean EDVCheckImlibImage(
	edv_core_struct *core, const gchar *name
);
gboolean EDVCheckEDVArchiverArchive(
	edv_core_struct *core, const gchar *name
);

/* Object MIME Type Matching Utilities */
gint EDVMatchObjectIcon(
	edv_device_struct **device, const gint total_devices,
	edv_mime_type_struct **mimetype, const gint total_mimetypes,
	const edv_object_type type,
	const gchar *path,	/* Full path or just the name */
	const gboolean link_valid,
	const edv_permission_flags permissions,
	const gint icon_size,	/* 0 = small, 1 = medium, 2 = large */
	GdkPixmap **pixmap_closed, GdkBitmap **mask_closed,
	GdkPixmap **pixmap_opened, GdkBitmap **mask_opened,
	GdkPixmap **pixmap_extended, GdkBitmap **mask_extended,
	GdkPixmap **pixmap_hidden, GdkBitmap **mask_hidden
);
gint EDVMatchObjectTypeString(
	edv_mime_type_struct **mimetype, const gint total_mimetypes,
	const edv_object_type type,
	const edv_permission_flags permissions,
	const gchar *path,	/* Full path or just the name */
	gchar **type_str
);

/* Message Output */
void EDVMessage(
	const gchar *title, const gchar *message, const gchar *details,
	GtkWidget *toplevel
);
void EDVMessageInfo(
	const gchar *title, const gchar *message, const gchar *details,
	GtkWidget *toplevel
);
void EDVMessageWarning(
	const gchar *title, const gchar *message, const gchar *details,
	GtkWidget *toplevel
);
void EDVMessageError(
	const gchar *title, const gchar *message, const gchar *details,
	GtkWidget *toplevel
);
void EDVMessageObjectOPError(
	const gchar *title, 
	const gchar *message, 
	const gchar *path,
	GtkWidget *toplevel
);
void EDVMessageFromFile(
	const gchar *path, const gchar *title,
	gint cdialog_icon,
	GtkWidget *toplevel
);

/* Play Sound */
void EDVBeep(edv_core_struct *core);
void EDVPlaySoundBeep(edv_core_struct *core);
void EDVPlaySoundInfo(edv_core_struct *core);
void EDVPlaySoundQuestion(edv_core_struct *core);
void EDVPlaySoundWarning(edv_core_struct *core);
void EDVPlaySoundError(edv_core_struct *core);
void EDVPlaySoundCompleted(edv_core_struct *core);

/* Menu & Button Interaction */
static void EDVMenuButtonMenuHideCB(GtkWidget *widget, gpointer data);
static void EDVMenuButtonMenuPositionCB(
	GtkMenu *menu, gint *x, gint *y, gpointer data
);
void EDVMenuButtonMapMenu(GtkWidget *menu, GtkWidget *button);

/* Window Centering */
void EDVCenterWindowToWindow(GtkWidget *w1, GtkWidget *w2);
void EDVCenterWindowToWindowOffset(
	GtkWidget *w1, GtkWidget *w2, gint x_offset, gint y_offset
);

/* GtkEntry Setup Utilities */
void EDVEntrySetDND(edv_core_struct *core, GtkWidget *w);
void EDVEntrySetCompletePath(edv_core_struct *core, GtkWidget *w);

/* Scrolled Window Setup Utilities */
GtkWidget *EDVScrolledWindowNew(
	GtkPolicyType hscrollbar_policy,
	GtkPolicyType vscrollbar_policy,
	GtkWidget **event_box_rtn,
	GtkWidget **vbox_rtn
);
static gint EDVScrolledWindowEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static void EDVScrolledWindowDataDelete(gpointer data);

/* Copy Object Path Utilities */
void EDVCopyDiskObjectsToDDEPath(
	edv_core_struct *core,
	edv_object_struct **list, gint total,
	GtkWidget *owner
);
void EDVCopyDiskObjectsToDDEURL(
	edv_core_struct *core,
	edv_object_struct **list, gint total,
	GtkWidget *owner
);

/* Style Utilities */
GtkRcStyle *EDVCreateRCStyleFromCfg(const cfg_style_struct *style);

/* History Utilities */
void EDVAppendHistory(
	edv_core_struct *core,
	edv_history_type type,	/* One of EDV_HISTORY_* */
	gulong time_start,	/* Time the operation first started */
	gulong time_end,	/* Time the operation ended */
	gint status,		/* Result of operation */
	const gchar *src_path,	/* Source Object/Operation/Value */
	const gchar *tar_path,	/* Target Object/Operation/Value */
	const gchar *comments	/* Comments */
);


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

#define EDV_MENU_BUTTON_MAP_DATA_KEY	\
	"edv_menu_button_map_data"

#define EDV_SCROLLED_WINDOW_DATA_KEY	\
	"EDV_SCROLLED_WINDOW_DATA"


struct _edv_scrolled_window_data {
	gint		button,
			drag_last_x,
			drag_last_y;
	GdkCursor	*translate_cur;
};


static gchar *G_STRCAT(gchar *s, const gchar *s2)
{
	if(s != NULL) {
	    if(s2 != NULL) {
		gchar *sr = g_strconcat(s, s2, NULL);
		g_free(s);
		s = sr;
	    }
	} else {
	    if(s2 != NULL)
		s = STRDUP(s2);
	    else
		s = STRDUP("");
	}
	return(s);
}


/*
 *	Returns the Operation ID of the specified Accelerator Key
 *	found in the Cfg Item specified by parm or 0 if there is no
 *	match.
 */
gint EDVMatchAccelKeyOPID(
	const cfg_item_struct *cfg_list, const gchar *parm,
	const guint accel_key, const guint accel_mods
)
{
	guint uc_accel_key, clean_accel_mods;
	cfg_accelkey_list_struct *ak_list;

	if((cfg_list == NULL) || STRISEMPTY(parm))
	    return(0);

	/* Make sure that the specified accel key is upper case for
	 * easy case insensitive matching
	 */
	uc_accel_key = gdk_keyval_to_upper(accel_key);

	/* Make sure that the specified modifier keys only contain
	 * alt, ctrl, or shift
	 */
	clean_accel_mods = (accel_mods & (GDK_SHIFT_MASK |
				          GDK_CONTROL_MASK |
				          GDK_MOD1_MASK)
		     );

	ak_list = CFGItemListGetValueAccelkeyList(
	    cfg_list, parm
	);
	if(ak_list != NULL)
	{
	    cfg_accelkey_struct *ak;

	    GList *glist = ak_list->list;
	    while(glist != NULL)
	    {
		ak = CFG_ACCELKEY(glist->data);
		if(ak != NULL)
		{
		    if((gdk_keyval_to_upper(ak->key) == uc_accel_key) &&
		       (ak->modifiers == clean_accel_mods)
		    )
			return(ak->opid);
		}
		glist = g_list_next(glist);
	    }
	}

	return(0);
}

/*
 *	Returns the Accelerator Key that matches the specified Operation
 *	ID in the Cfg Item specified by parm.
 */
cfg_accelkey_struct *EDVMatchAccelKey(
	const cfg_item_struct *cfg_list, const gchar *parm,
	const gint opid 
)
{
	cfg_accelkey_list_struct *ak_list;

	if((cfg_list == NULL) || STRISEMPTY(parm) || (opid == 0))
	    return(NULL);

	ak_list = CFGItemListGetValueAccelkeyList(
	    cfg_list, parm
	);
	if(ak_list != NULL)
	{
	    GList *glist;
	    cfg_accelkey_struct *ak;

	    for(glist = ak_list->list;
	        glist != NULL;
	        glist = g_list_next(glist)
	    )
	    {
		ak = CFG_ACCELKEY(glist->data);
		if(ak == NULL)
		    continue;
		if(ak->opid == opid)
		    return(ak);
	    }
	}

	return(NULL);
}


/*
 *	Returns a statically allocated string describing the size
 *	of the object specified by i in units defined by the
 *	configuration on the given core structure.
 *
 *	The returned string will be no longer than 256 bytes and
 *	never NULL.
 */
gchar *EDVGetObjectSizeStr(edv_core_struct *core, const gulong x)
{
	gint comma_countdown, slen;
	gchar ss[80], *ss_ptr;
	static gchar ts[80], *ts_ptr;

	g_snprintf(ss, sizeof(ss), "%ld", x);
	slen = STRLEN(ss);

	/* 3 digits or less? (no commas needed) */
	if(slen <= 3)
	{
	    strcpy(ts, ss);
	    return(ts);
	}

	ts_ptr = ts;
	ss_ptr = ss;

	/* Initialize comma counter */
	comma_countdown = slen % 3;
	if(comma_countdown <= 0)
	    comma_countdown = 3;

	/* Iterate through size string until end is reached */
	while(*ss_ptr != '\0')
	{
	    /* Reset comma counter and put in a comma? */
	    if(comma_countdown <= 0)
	    {
		*ts_ptr++ = ',';
		comma_countdown = 3;
	    }

	    *ts_ptr++ = *ss_ptr++;
	    comma_countdown--;
	}

	/* Null terminate return string */
	*ts_ptr = '\0';

	return(ts);
}


/*
 *	Resizes the pixmap and mask to the specified new size.
 */
void EDVResizePixmap(
	GdkPixmap *pixmap, GdkBitmap *mask,
	gint width, gint height,
	GdkPixmap **pixmap_rtn, GdkBitmap **mask_rtn
)
{
	gint old_width, old_height;

	if(pixmap_rtn != NULL)
	    *pixmap_rtn = NULL;
	if(mask_rtn != NULL)
	    *mask_rtn = NULL;

	if((pixmap == NULL) || (width <= 0) || (height <= 0))
	    return;

	gdk_window_get_size(pixmap, &old_width, &old_height);
	if((old_width <= 0) || (old_height <= 0))
	    return;

	/* No change in size? */
	if((old_width == width) && (old_height == height))
	{
	    if(pixmap_rtn != NULL)
	    {
		if(pixmap != NULL)
		    gdk_pixmap_ref(pixmap);
		*pixmap_rtn = pixmap;
	    }
	    if(mask_rtn != NULL)
	    {
		if(mask != NULL)
		    gdk_bitmap_ref(mask);
		*mask_rtn = mask;
	    }
	    return;
	}

	/* Resize pixmap */
	if(pixmap_rtn != NULL)
	{
	    gint sw, sh, sbpl, tbpl;
	    guint8 *tdata, *sdata;
	    GdkGC *gc = GDK_GC_NEW();

	    /* Get source RGBA data from the specified pixmap */
	    sdata = gdk_get_rgba_image(
		pixmap,
		NULL,
		&sw, &sh, &sbpl
	    );
	    if(sdata == NULL)
	    {
		GDK_GC_UNREF(gc)
		return;
	    }

	    /* Create target RGBA data */
	    tbpl = width * 4;
	    tdata = (guint8 *)g_malloc(tbpl * height);
	    if(tdata == NULL)
	    {
		g_free(sdata);
		GDK_GC_UNREF(gc)
		return;
	    }

	    /* Copy/resize source RGBA data to the target RGBA data */
	    GUIImageBufferResize(
		4,
		sdata, sw, sh, sbpl,
		tdata, width, height, tbpl
	    );

	    g_free(sdata);

	    /* Create target pixmap */
	    *pixmap_rtn = GDK_PIXMAP_NEW(width, height);
	    if(*pixmap_rtn == NULL)
	    {
		g_free(tdata);
		GDK_GC_UNREF(gc)
		return;
	    }

	    /* Put resized target RGBA data to the target pixmap */
	    gdk_draw_rgb_32_image(
		*pixmap_rtn, gc,
		0, 0,
		width, height,
		GDK_RGB_DITHER_NORMAL,
		tdata, tbpl
	    );

	    /* Resize mask */
	    if(mask_rtn != NULL)
		*mask_rtn = GUICreateBitmapFromDataRGBA(
		    width, height, tbpl,
		    tdata, 0x80,
		    NULL
		);

	    g_free(tdata);
	    GDK_GC_UNREF(gc)
	}
}

/*
 *	Returns the pixmap and mask matched from the core's list of
 *	edv_pixmaps specified by name.
 *
 *	If no edv_pixmap exists in the list with the specified name
 *	then a new edv_pixmap will be added to the list.
 *
 *	The returned pixmap and mask will have a ref count added to
 *	them.
 *
 *	Returns TRUE on matched pixmap and mask or FALSE on error.
 */
gboolean EDVLoadPixmap(
	edv_core_struct *core,
	guint8 **data, const gchar *name,
	GdkPixmap **pixmap_rtn, GdkBitmap **mask_rtn 
)
{
	edv_pixmap *p;

	if(pixmap_rtn != NULL)
	    *pixmap_rtn = NULL;
	if(mask_rtn != NULL)
	    *mask_rtn = NULL;

	if((core == NULL) || (data == NULL) || (name == NULL))
	    return(FALSE);

	/* Look for an edv_pixmap in the given list that matches
	 * the specified name or add a new edv_pixmap to the list if
	 * one does not exist
	 */
	core->pixmap_list = EDVPixmapListNew(
	    core->pixmap_list,
	    data, name,
	    &p
	);

	if(p != NULL)
	{
	    /* Set return pixmap and mask and add a ref count to them */
	    if(pixmap_rtn != NULL)
	    {
		if(p->pixmap != NULL)
		    gdk_pixmap_ref(p->pixmap);
		*pixmap_rtn = p->pixmap;
	    }
	    if(mask_rtn != NULL)
	    {
		if(p->mask != NULL)
		    gdk_bitmap_ref(p->mask);
		*mask_rtn = p->mask;
	    }
	    return(TRUE);
	}
	else
	{
	    return(FALSE);
	}
}

/*
 *	Creates a new progress pixmap.
 *
 *	The width and height specifies the size of the pixmap in pixels.
 *
 *	The coeff specifies the coefficient from 0.0 to 1.0. If the
 *	coeff is -1.0 then an unknown value will be drawn.
 *
 *	If draw_value is NULL then the value will be drawn.
 *
 *	The orientation specifies the orientation of the progress.
 *
 *	If reverse is TRUE then the progress will be drawn from right
 *	to left or bottom to top based on the orientation.
 *
 *	The style specifies the reference GtkStyle to use. If this is
 *	NULL then the default GtkStyle will be used.
 *
 *	The *mask_rtn will be set to the created mask.
 *
 *	Returns the progress pixmap or NULL on error.
 */
GdkPixmap *EDVNewProgressPixmap(
	const gint width, const gint height,
	const gfloat coeff,
	const gboolean draw_value,
	const GtkOrientation orientation, const gboolean reverse,
	GtkStyle *style,
	GdkBitmap **mask_rtn
)
{
	gint	_width = width,
		_height = height;
	GdkRectangle trough_rect, bar_rect;
	GdkPixmap *pixmap;

	if(mask_rtn != NULL)
	    *mask_rtn = NULL;

	if(style == NULL)
	    style = gtk_widget_get_default_style();
	if(style == NULL)
	    return(NULL);

	/* Use the default width? */
	if(_width <= 0)
	{
	    switch(orientation)
	    {
	      case GTK_ORIENTATION_HORIZONTAL:
		_width = 80;
		break;
	      case GTK_ORIENTATION_VERTICAL:
		_width = 20;
		break;
	    }
	}
	/* Use the default height? */
	if(_height <= 0)
	{
	    switch(orientation)
	    {
	      case GTK_ORIENTATION_HORIZONTAL:
		_height = 20;
		break;
	      case GTK_ORIENTATION_VERTICAL:
		_height = 80;
		break;
	    }
	}

	/* Create the pixmap */
	pixmap = GDK_PIXMAP_NEW(_width, _height);
	if(pixmap == NULL)
	    return(NULL);

	/* Draw the background */
        gdk_draw_rectangle(
            pixmap, style->base_gc[GTK_STATE_NORMAL],
            TRUE,
            0, 0, _width, _height
        );

	/* Draw the outline */
	gdk_draw_rectangle(
            pixmap, style->fg_gc[GTK_STATE_NORMAL],
            FALSE,
            0, 0, _width - 1, _height - 1
        );

	/* Calculate the geometry of the trough */
	trough_rect.x = 2;
	trough_rect.y = 2;
	trough_rect.width = _width - (2 * trough_rect.x);
	trough_rect.height = _height - (2 * trough_rect.y);
	if((trough_rect.width <= 0) || (trough_rect.height <= 0))
	    return(pixmap);

	/* Calculate the geometry of the bar */
	switch(orientation)
	{
          case GTK_ORIENTATION_HORIZONTAL:
	    bar_rect.width = (coeff >= 0.0f) ?
		(gint)(trough_rect.width * MIN(coeff, 1.0f)) :
		trough_rect.width;
	    bar_rect.height = trough_rect.height;
	    if(reverse)
	    {
		bar_rect.x = _width - trough_rect.x - bar_rect.width;
		bar_rect.y = trough_rect.y;
	    }
	    else
	    {
		bar_rect.x = trough_rect.x;
		bar_rect.y = trough_rect.y;
	    }
            break;
	  case GTK_ORIENTATION_VERTICAL:
	    bar_rect.width = trough_rect.width;
	    bar_rect.height = (coeff >= 0.0f) ?
		(gint)(trough_rect.height * MIN(coeff, 1.0f)) :
		trough_rect.height;
	    if(reverse)
	    {
		bar_rect.x = trough_rect.x;
		bar_rect.y = _height - trough_rect.y - bar_rect.height;
 	    }
	    else
	    {
		bar_rect.x = trough_rect.x;
		bar_rect.y = trough_rect.y;
	    }
	    break;
	}

	/* Draw the bar */
	if((bar_rect.width > 0) && (bar_rect.height > 0))
	    gdk_draw_rectangle(
		pixmap, style->bg_gc[GTK_STATE_SELECTED],
		TRUE,
		bar_rect.x, bar_rect.y,
		bar_rect.width, bar_rect.height
	    );

	/* Draw the value? */
	if(draw_value && (style->font != NULL))
	{
	    gint x, y;
	    gchar *s;
	    GdkFont *font = style->font;
	    const gint font_height = font->ascent + font->descent;
	    GdkTextBounds b;
	    GdkGC *gc = style->text_gc[GTK_STATE_NORMAL];

	    s = g_strdup_printf(
		"%.0f%%",
		coeff * 100.0f
	    );
            gdk_string_bounds(font, s, &b);
	    x = 0;
	    y = 0;
	    switch(orientation)
	    {
	      case GTK_ORIENTATION_HORIZONTAL:
		x = trough_rect.x + ((trough_rect.width - b.width) / 2);
		y = trough_rect.y + ((trough_rect.height - font_height) / 2);
 		break;
	      case GTK_ORIENTATION_VERTICAL:
		x = trough_rect.x + ((trough_rect.width - b.width) / 2);
		y = trough_rect.y + ((trough_rect.height - font_height) / 2);
		break;
	    }
	    gdk_draw_string(
		pixmap, font, gc,
                x - b.lbearing,  
                y + font->ascent,
		s
	    );

	    gc = style->text_gc[GTK_STATE_SELECTED];
            gdk_gc_set_clip_rectangle(gc, &bar_rect);
            gdk_draw_string(
                pixmap, font, gc,
                x - b.lbearing,   
                y + font->ascent,
                s
            );   
            gdk_gc_set_clip_rectangle(gc, NULL);
	    g_free(s);
	}

	return(pixmap);
}

/*
 *	Creates a new GtkPixmap from the specified XPM data.
 *
 *	An edv_pixmap from the core's list of pixmaps will be used
 *	if one exists matching the specified name or a new edv_pixmap
 *	will be added to the list if one does not exist.
 */
GtkWidget *EDVNewPixmapWidget(
	edv_core_struct *core,
	guint8 **data, const gchar *name 
)
{
	GdkPixmap *pixmap, *mask;

	if(EDVLoadPixmap(core, data, name, &pixmap, &mask))
	{
	    GtkWidget *w = gtk_pixmap_new(pixmap, mask);
	    GDK_PIXMAP_UNREF(pixmap)
	    GDK_BITMAP_UNREF(mask)
	    return(w);
	}
	else
	{
	    return(NULL);
	}
}


/*
 *	Returns the GdkCursor specified by cursor_code or NULL on
 *	error or failed match.
 */
GdkCursor *EDVGetCursor(
	edv_core_struct *core, const edv_cursor_code code
)
{
	gint i;
	edv_cursor_struct *cur;

	if(core == NULL)
	    return(NULL);

	for(i = 0; i < core->total_cursors; i++)
	{
	    cur = core->cursor[i];
	    if(cur == NULL)
		continue;

	    if(cur->code == code)
		return(cur->cursor);
	}

	return(NULL);
}

/*
 *	Returns the level of the given node or 0 if the given node
 *	is NULL.
 */
gint EDVNodeGetLevel(GtkCTreeNode *node)
{
	GtkCTreeRow *row_ptr = (node != NULL) ? GTK_CTREE_ROW(node) : NULL;
	return((row_ptr != NULL) ? row_ptr->level : 0);
}

/*
 *	Returns the parent of the given node or NULL if the given node
 *	does not have a parent.
 */
GtkCTreeNode *EDVNodeGetParent(GtkCTreeNode *node)
{
	GtkCTreeRow *row_ptr = (node != NULL) ? GTK_CTREE_ROW(node) : NULL;
	return((row_ptr != NULL) ? row_ptr->parent : NULL);
}

/*
 *	Returns the first child of the given node or NULL if the node
 *	does not have any children.
 */
GtkCTreeNode *EDVNodeGetChild(GtkCTreeNode *node)
{
	GtkCTreeRow *row_ptr = (node != NULL) ? GTK_CTREE_ROW(node) : NULL;
	return((row_ptr != NULL) ? row_ptr->children : NULL);
}

/*
 *	Returns the next sibling of the given node or NULL if the node
 *	does not have a next sibling.
 */
GtkCTreeNode *EDVNodeGetSibling(GtkCTreeNode *node)
{
	GtkCTreeRow *row_ptr = (node != NULL) ? GTK_CTREE_ROW(node) : NULL;
	return((row_ptr != NULL) ? row_ptr->sibling : NULL);
}

/*
 *	Returns the first toplevel node on the given GtkCTree widget
 *	or NULL if the GtkCTree does not have one.
 */
GtkCTreeNode *EDVNodeGetToplevel(GtkCTree *ctree)
{
	/* Here we just return the first node, since it is always
	 * gauranteed to be the toplevel node.
	 */
	return((ctree != NULL) ? gtk_ctree_node_nth(ctree, 0) : NULL);
}


/*
 *	Returns the index of the last selected row on the given clist.
 *
 *	If row_ptr_rtn is not NULL and there exists a selected row then
 *	*row_ptr_rtn will be updated with the pointer to the row
 *	structure.
 *
 *	Returns -1 on error or no row selected, if a valid row index is
 *	returned it can be considered to be valid and allocated.
 */
gint EDVCListGetSelectedLast(
	GtkCList *clist, GtkCListRow **row_ptr_rtn
)
{
	gint row;
	GList *glist;

	if(row_ptr_rtn != NULL)
	    *row_ptr_rtn = NULL;

	if(clist == NULL)
	    return(-1);

	/* Get last selected row index or -1 if there is none */
	glist = clist->selection_end;
	row = (glist != NULL) ? (gint)glist->data : -1;

	/* Check if we need to get a row pointer and that the selected
	 * row index is in bounds
	 */
	if((row >= 0) && (row < clist->rows) &&
	   (row_ptr_rtn != NULL) && (clist->row_list != NULL)
	)
	{
	    glist = g_list_nth(clist->row_list, row);
	    *row_ptr_rtn = (glist != NULL) ?
		(GtkCListRow *)glist->data : NULL;
	    if(*row_ptr_rtn == NULL)
		row = -1;
	}

	return(row);
}

/*
 *	Returns the pointer of the last selected node on the given ctree.
 *
 *	If row_ptr_rtn is not NULL and there exists a selected node then
 *	*row_ptr_rtn will be updated with the pointer to the node's row
 *	structure.
 *
 *	Returns NULL on error or no node selected, if a valid node is
 *	returned it can be considered to be valid and allocated.
 */
GtkCTreeNode *EDVCTreeGetSelectedLast(
	GtkCTree *ctree, GtkCTreeRow **row_ptr_rtn
)
{
	GList *glist;
	GtkCList *clist;
	GtkCTreeNode *node;


	if(row_ptr_rtn != NULL)
	    *row_ptr_rtn = NULL;

	if(ctree == NULL)
	    return(NULL);

	clist = GTK_CLIST(ctree);

	/* Get last selected node or -1 if there is none */
	glist = clist->selection_end;
	node = (glist != NULL) ? (GtkCTreeNode *)glist->data : NULL;

	/* Check if we need to get a row pointer and that there is a
	 * selected node
	 */
	if((node != NULL) && (row_ptr_rtn != NULL))
	{
	    *row_ptr_rtn = GTK_CTREE_ROW(node);
	    if(*row_ptr_rtn == NULL)
		node = NULL;
	}

	return(node);
}


/*
 *	Returns the column and row indexes of the given GtkCTreeNode node
 *	relative to the given GtkCTree widget.
 *
 *	Returns TRUE if the indexes are found or FALSE on failed match.
 */
gboolean EDVNodeGetIndex(
	GtkCTree *ctree, GtkCTreeNode *node,
	gint *row_rtn, gint *column_rtn
)
{
	gint row;
	const GList *glist;
	GtkCList *clist;
	const GtkCTreeRow *row_ptr;


	/* Reset returns */
	if(row_rtn != NULL)
	    *row_rtn = -1;
	if(column_rtn != NULL)
	    *column_rtn = -1;

	if((ctree == NULL) || (node == NULL))
	    return(FALSE);

	clist = GTK_CLIST(ctree);

	/* Get the row pointer the given node */
	row_ptr = GTK_CTREE_ROW(node);
	if(row_ptr == NULL)
	    return(FALSE);

	/* Count rows until we encounter the given node's row */
	row = 0;
	glist = clist->row_list;
	while(glist != NULL)
	{
	    if(row_ptr == (const GtkCTreeRow *)glist->data)
		break;

	    row++;
	    glist = g_list_next(glist);
	}
	/* Failed to find row? */
	if(glist == NULL)
	    return(FALSE);

	/* Update returns */
	if(row_rtn != NULL)
	    *row_rtn = row;
	/* Column is always the ctree column */
	if(column_rtn != NULL)
	    *column_rtn = ctree->tree_column;

	return(TRUE);
}

/*
 *	Returns the tree node that matches the given coordinates on the
 *	given GtkCTree widget.
 */
GtkCTreeNode *EDVNodeGetByCoordinates(
	GtkCTree *ctree, const gint x, const gint y
)
{
	gint row, column;
	GtkCList *clist;

	if(ctree == NULL)
	    return(NULL);

	clist = GTK_CLIST(ctree);

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

	/* Get tree node matching the calculated row */
	return(gtk_ctree_node_nth(ctree, row));
}

/*
 *	Scroll the given clist to the given position.
 *
 *	A "value_changed" signal will be emitted to each of the clist's
 *	horizontal and vertical GtkAdjustments.
 */
void EDVScrollCListToPosition(
	GtkCList *clist, const gfloat hpos, const gfloat vpos
)
{
	GtkAdjustment *adj;

	if(clist == NULL)
	    return;

	adj = clist->hadjustment;
	if(adj != NULL)
	{
	    gfloat clipped_hpos = hpos;
	    if(clipped_hpos > (adj->upper - adj->page_size))
		clipped_hpos = adj->upper - adj->page_size;
	    if(clipped_hpos < adj->lower)
		clipped_hpos = adj->lower;
	    gtk_adjustment_set_value(adj, clipped_hpos);
	}

	adj = clist->vadjustment;
	if(adj != NULL)
	{
	    gfloat clipped_vpos = vpos;
	    if(clipped_vpos > (adj->upper - adj->page_size))
		clipped_vpos = adj->upper - adj->page_size;
	    if(clipped_vpos < adj->lower)
		clipped_vpos = adj->lower;
	    gtk_adjustment_set_value(adj, clipped_vpos);
	}
}


/*
 *	Checks which window the given toplevel GtkWindow belongs to.
 *
 *	Returns TRUE on match or FALSE on failed match.
 */
gboolean EDVMatchWindowFromToplevel(
	edv_core_struct *core, GtkWidget *toplevel,
	gint *browser_num,
	gint *imbr_num,
	gint *archiver_num,
	gint *recbin_num
)
{
	gint i;
	const edv_browser_struct *browser;
	const edv_imbr_struct *imbr;
	const edv_archiver_struct *archiver;
	const edv_recbin_struct *recbin;

	if(browser_num != NULL)
	    *browser_num = -1;
	if(imbr_num != NULL)
	    *imbr_num = -1;
	if(archiver_num != NULL)
	    *archiver_num = -1;
	if(recbin_num != NULL)
	    *recbin_num = -1;

	if((core == NULL) || (toplevel == NULL))
	    return(FALSE);

	/* Begin checking which window's toplevel matches the given
	 * toplevel
	 */

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

	    if(browser->toplevel == toplevel)
	    {
		if(browser_num != NULL)
		    *browser_num = i;
		return(TRUE);
	    }
	}

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

	    if(imbr->toplevel == toplevel)
	    {
		if(imbr_num != NULL)
		    *imbr_num = i;
		return(TRUE);
	    }
	}

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

	    if(archiver->toplevel == toplevel)
	    {
		if(archiver_num != NULL)
		    *archiver_num = i;
		return(TRUE);
	    }
	}

	/* Recycle Bin */
	recbin = core->recbin;
	if(recbin != NULL)
	{
	    if(recbin->toplevel == toplevel)
	    {
		if(recbin_num != NULL)
		    *recbin_num = i;
		return(TRUE);
	    }
	}

	return(FALSE);
}


/*
 *	Returns TRUE if the given disk object name has a valid notation.
 */
gboolean EDVIsObjectNameValid(const gchar *name)
{
	if(STRISEMPTY(name))
	    return(FALSE);

	/* No special notations allowed */
	if(!strcmp(name, ".."))
	    return(FALSE);
	if(!strcmp(name, "."))
	    return(FALSE);
	if(!strcmp(name, "?"))
	    return(FALSE);
	if(!strcmp(name, "*"))
	    return(FALSE);

	/* No blanks allowed as first char */
	if(*name == ' ')
	    return(FALSE);
	if(*name == '\t')
	    return(FALSE);

	/* No deliminators allowed */
	if(strchr(name, G_DIR_SEPARATOR) != NULL)
	    return(FALSE);

	return(TRUE);
}

/*
 *	Checks if the object's name makes it "hidden".
 *
 *	For a name to be considered "hidden", the first character
 *	must be a '.' and the second character must not be null or
 *	another '.' character.
 */
gboolean EDVIsObjectNameHidden(const gchar *name)
{
	if(name == NULL)
	    return(FALSE);

	if(name[0] != '.')
	    return(FALSE);

	if(name[1] == '\0')
	    return(FALSE);

	if(name[1] == '.')
	{
	    if(name[2] == '\0')
		return(FALSE);
	}

	return(TRUE);
}

gboolean EDVIsObjectHidden(edv_object_struct *obj)
{
	return((obj != NULL) ? EDVIsObjectNameHidden(obj->name) : FALSE);
}

/*
 *	Returns TRUE if the write protect is enabled on the given core
 *	structure's configuration list.
 *
 *	If verbose is set to TRUE and write protect is enabled then a 
 *	standard message will be printed to the user indicating that write
 *	protect is enabled and how to disable it.
 */
gboolean EDVCheckWriteProtect(
	edv_core_struct *core,
	const gboolean verbose, GtkWidget *toplevel
)
{
	gboolean write_protect;

	if(core == NULL)
	    return(FALSE);

	/* Get configuration */
	write_protect = CFGItemListGetValueI(
	    core->cfg_list, EDV_CFG_PARM_WRITE_PROTECT
	);
	/* Is write protect enabled and verbose specified? */
	if(write_protect && verbose)
	{
	    EDVPlaySoundWarning(core);
	    EDVMessageWarning(
#if defined(PROG_LANGUAGE_SPANISH)
"Escriba La Operación Negada",
"La operación especificada no se permite porque escribe\n\
protege está en.",
"Si usted desea realizar la operación de thr entonces\n\
usted debe girar escribe lejos protege yendo a\n\
Escenarios->Escribe Protege.",
#elif defined(PROG_LANGUAGE_FRENCH)
"L'Opération D'écriture Niée",
"L'opération spécifiée n'est pas permise parce que protége\n\
en écriture est sur.",
"Si vous souhaitez exécuter l'opération de thr alors vous\n\
devez tourner de protége en écriture en allant aux\n\
Montages->Protége En écriture.",
#elif defined(PROG_LANGUAGE_GERMAN)
"Schreiben Sie Betrieb",
"Verweigert wird, der. Der angegebene betrieb ist nicht\n\
zugelassen, weil schreibgeschützt auf ist.",
"Wenn sie wünschen, thr betrieb durchzuführen, dann\n\
müssen sie schreibgeschützt durch gehen zu\n\
Settings->Schreibgeschützt Ausschalten.",
#elif defined(PROG_LANGUAGE_ITALIAN)
"Scrivere L'Operazione Negata",
"L'operazione specificata non è permessa perché scrive\n\
protegge è su.",
"Se lei desidera eseguire l'operazione di thr poi lei\n\
deve spegnere scrive protegge da andare ai\n\
Montaggi->Scrive Protegge.",
#elif defined(PROG_LANGUAGE_DUTCH)
"Schrijf Werking Ontkende",
"De gespecificeerde werking is niet toegestaan omdat\n\
schrijft op beschermt, is.",
"Indien u wenst thr werking te verrichten dan beschermt\n\
u door gaan aan Settings->moet uitschakelen schrijft,\n\
beschermt, Schrijft.",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Escreva Operação Negado",
"A operação especificada não é permitida porque escreve\n\
protege está em.",
"Se deseja executar operação de thr então você deve\n\
desligar deve escrever protege por ir a\n\
Settings->Escreve Protege.",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Skriv Nektet Drift",
"Den spesifiserte driften tillater ikke fordi\n\
skrivebeskyttet er på.",
"Om de ønsker gjennomføre thr drift skrur da de av\n\
skrivebeskyttet ved å dra til Innstillinger->Skrivebeskyttet.",
#else
"Write Operation Denied",
"The specified operation is not permitted because\n\
Write Protect is on.",
"If you wish to perform this operation then you must turn\n\
off Write Protect by going to Settings->Write Protect.",
#endif
		toplevel
	    );
	}

	return(write_protect);
}


/*
 *	Checks if the given effective user id owns the object who's
 *	owner is the owner_id.
 */
gboolean EDVCheckUIDIsOwner(gint effective_uid, gint owner_id)
{
	return(
	   (effective_uid == 0) ||
	   (effective_uid == owner_id)
	);
}

/*
 *	Checks if the effective user id of this process owns the object
 *	sho's owner is the owner_id.
 */
gboolean EDVCheckIsOwner(edv_core_struct *core, gint owner_id)
{
	if(core == NULL)
	    return(FALSE);
	else
	    return(EDVCheckUIDIsOwner(core->effective_user_id, owner_id));
}

/*
 *	Checks if the user or group ID of the process can access the
 *	specified directory object.
 *
 *	Access is defined as the directory object being executable,
 *	since any directory set x means it is accessable.
 */
gboolean EDVIsObjectAccessable(
	edv_core_struct *core, edv_object_struct *obj
)
{
	gint euid, eguid;
	edv_permission_flags p;

	if((core == NULL) || (obj == NULL))
	    return(FALSE);

	euid = core->effective_user_id;
	eguid = core->effective_group_id;
	p = obj->permissions;

	/* Sticky and at least one catagory has execute permission? */
	if((p & EDV_PERMISSION_STICKY) &&
	   (p & (EDV_PERMISSION_UEXECUTE | EDV_PERMISSION_GEXECUTE |
		 EDV_PERMISSION_AEXECUTE))
	)
	    return(TRUE);	

	/* Anyone can execute? */
	if(p & EDV_PERMISSION_AEXECUTE)
	    return(TRUE);

	/* Group can execute and is member of group? */
	if((p & EDV_PERMISSION_GEXECUTE) &&
	   (eguid == obj->group_id)
	)
	    return(TRUE);

	/* Owner can execute, is root, or is the owner? */
	if((p & EDV_PERMISSION_UEXECUTE) &&
	   ((euid == 0) || (euid == obj->owner_id))
	)
	    return(TRUE);

	return(FALSE);
}

/*
 *	Checks if the user or group ID of the process can read the
 *	specified object.
 */
gboolean EDVIsObjectReadable(
	edv_core_struct *core, edv_object_struct *obj
)
{
	gint euid, eguid;
	edv_permission_flags p;

	if((core == NULL) || (obj == NULL))
	    return(FALSE);

	euid = core->effective_user_id;
	eguid = core->effective_group_id;
	p = obj->permissions;

	/* Sticky and at least one catagory has read permission? */
	if((p & EDV_PERMISSION_STICKY) &&
	   (p & (EDV_PERMISSION_UREAD | EDV_PERMISSION_GREAD |
		 EDV_PERMISSION_AREAD))
	)
	    return(TRUE);

	/* Anyone can read? */
	if(p & EDV_PERMISSION_AREAD)
	    return(TRUE);

	/* Group can read and is member of group? */
	if((p & EDV_PERMISSION_GREAD) &&
	   (eguid <= obj->group_id)
	)
	    return(TRUE);

	/* Owner can read and is a owner? */
	if((p & EDV_PERMISSION_UREAD) &&
	   ((euid == 0) || (euid == obj->owner_id))
	)
	    return(TRUE);

	return(FALSE);
}

/*
 *	Checks if the user or group ID of the process can write the
 *	specified object.
 */
gboolean EDVIsObjectWriteable(
	edv_core_struct *core, edv_object_struct *obj
)
{
	gint euid, eguid;
	edv_permission_flags p;

	if((core == NULL) || (obj == NULL))
	    return(FALSE);

	euid = core->effective_user_id;
	eguid = core->effective_group_id;
	p = obj->permissions;

	/* Sticky and at least one catagory has write permission? */
	if((p & EDV_PERMISSION_STICKY) &&
	   (p & (EDV_PERMISSION_UWRITE | EDV_PERMISSION_GWRITE |
		 EDV_PERMISSION_AWRITE))
	)
	    return(TRUE);

	/* Anyone can write? */
	if(p & EDV_PERMISSION_AWRITE)
	    return(TRUE);

	/* Group can write and is member of group? */
	if((p & EDV_PERMISSION_GWRITE) &&
	   (eguid <= obj->group_id)
	)
	    return(TRUE);

	/* Owner can write and is a owner? */
	if((p & EDV_PERMISSION_UWRITE) &&
	   ((euid == 0) || (euid == obj->owner_id))
	)
	    return(TRUE);

	return(FALSE);
}

/*
 *	Checks if the user or group ID of the process can execute the
 *	specified object.
 */
gboolean EDVIsObjectExecutable(
	edv_core_struct *core, edv_object_struct *obj
)
{
	gint euid, eguid;
	edv_permission_flags p;

	if((core == NULL) || (obj == NULL))
	    return(FALSE);

	euid = core->effective_user_id;
	eguid = core->effective_group_id;
	p = obj->permissions;

	/* Sticky and at least one catagory has execute permission? */
	if((p & EDV_PERMISSION_STICKY) &&
	   (p & (EDV_PERMISSION_UEXECUTE | EDV_PERMISSION_GEXECUTE |
		 EDV_PERMISSION_AEXECUTE))
	)
	    return(TRUE);

	/* Anyone can execute? */
	if(p & EDV_PERMISSION_AEXECUTE)
	    return(TRUE);

	/* Group can execute and is member of group? */
	if((p & EDV_PERMISSION_GEXECUTE) &&
	   (eguid <= obj->group_id)
	)
	    return(TRUE);

	/* Owner can execute and is a owner? */
	if((p & EDV_PERMISSION_UEXECUTE) &&
	   ((euid == 0) || (euid == obj->owner_id))
	)
	    return(TRUE);

	return(FALSE);
}


/*
 *	Returns TRUE if the object specified by path is supported by
 *	Imlib.
 */
gboolean EDVCheckImlibImage(
	edv_core_struct *core, const gchar *name
)
{
	gint i;
	const gchar *ext_list[] = IMLIB_IMAGE_FILE_EXTENSIONS;

	if((core == NULL) || STRISEMPTY(name))
	    return(FALSE);

	/* Check if this is an image file by checking the extension
	 * portion of the name
	 */
	for(i = 0; ext_list[i] != NULL; i += 2)
	{
	    if(EDVIsExtension(name, ext_list[i]))
		return(TRUE);
	}

	return(FALSE);
}

/*
 *	Returns TRUE if the object specified by path is an archive
 *	supported by Endeavour's archiver.
 */
gboolean EDVCheckEDVArchiverArchive(
	edv_core_struct *core, const gchar *name
)
{
	gint i;
	const gchar *ext_list[] = EDV_ARCHIVER_ARCHIVE_FILE_EXTENSIONS;

	if((core == NULL) || STRISEMPTY(name))
	    return(FALSE);

	/* Check if this is an image file by checking the extension
	 * portion of the name
	 */
	for(i = 0; ext_list[i] != NULL; i += 2)
	{
	    if(EDVIsExtension(name, ext_list[i]))
		return(TRUE);
	}

	return(FALSE);
}


/*
 *	Fetches the pixmap and mask pairs that best matches the given
 *	information. Devices and MIME Types will be realized as needed
 *	during this call.
 *
 *	Any of the given information may be NULL or 0.
 *
 *	The returned pixmap and mask pairs do not have any refcounts
 *	added to them.
 *
 *	Returns 0 on success or -1 on failed match.
 */
gint EDVMatchObjectIcon(
	edv_device_struct **device, const gint total_devices,
	edv_mime_type_struct **mimetype, const gint total_mimetypes,
	const edv_object_type type,
	const gchar *path,	/* Full path or name */
	const gboolean link_valid,
	const edv_permission_flags permissions,
	const gint icon_size,	/* 0 = small, 1 = medium, 2 = large */
	GdkPixmap **pixmap_closed, GdkBitmap **mask_closed,
	GdkPixmap **pixmap_opened, GdkBitmap **mask_opened,
	GdkPixmap **pixmap_extended, GdkBitmap **mask_extended,
	GdkPixmap **pixmap_hidden, GdkBitmap **mask_hidden
)
{
	gboolean got_match = FALSE;
	const gboolean is_file = (type == EDV_OBJECT_TYPE_FILE) ? TRUE : FALSE;
	gint i;
	GdkPixmap *pixmap[EDV_MIME_TYPE_TOTAL_ICON_STATES];
	GdkBitmap *mask[EDV_MIME_TYPE_TOTAL_ICON_STATES];

	/* Reset local icon lists */
	memset(pixmap, 0x00, EDV_MIME_TYPE_TOTAL_ICON_STATES * sizeof(GdkPixmap *));
	memset(mask, 0x00, EDV_MIME_TYPE_TOTAL_ICON_STATES * sizeof(GdkBitmap *));

	/* Reset returns */
	if(pixmap_closed != NULL)
	    *pixmap_closed = NULL;
	if(mask_closed != NULL)
	    *mask_closed = NULL;

	if(pixmap_opened != NULL)
	    *pixmap_opened = NULL;
	if(mask_opened != NULL)
	    *mask_opened = NULL;

	if(pixmap_extended != NULL)
	    *pixmap_extended = NULL;
	if(mask_extended != NULL)
	    *mask_extended = NULL;

	if(pixmap_hidden != NULL)
	    *pixmap_hidden = NULL;
	if(mask_hidden != NULL)
	    *mask_hidden = NULL;

/* Sets the pixmap & mask pairs from the MIME Type, realizing the MIME
 * type as needed
 *
 * If one or more pixmaps were obtained than got_match will be set to
 * TRUE
 */
#define GET_MIMETYPE_ICONS(_m_)	{		\
 edv_mime_type_icon_state n;			\
 const gint nstates = EDV_MIME_TYPE_TOTAL_ICON_STATES; \
						\
 /* Realize MIME Type first (as needed) to	\
  * ensire that the icons are loaded		\
  */						\
 EDVMimeTypeRealize((_m_), FALSE);		\
						\
 /* Get icons by the requested size */		\
 switch(icon_size) {				\
  case 0:	/* Small */			\
   for(n = 0; n < nstates; n++) {		\
    pixmap[n] = (_m_)->small_pixmap[n];		\
    mask[n] = (_m_)->small_mask[n];		\
   }						\
   break;					\
  case 1:	/* Medium */			\
   for(n = 0; n < nstates; n++) {		\
    pixmap[n] = (_m_)->medium_pixmap[n];	\
    mask[n] = (_m_)->medium_mask[n];		\
   }						\
   break;					\
  case 2:	/* Large */			\
   for(n = 0; n < nstates; n++) {		\
    pixmap[n] = (_m_)->large_pixmap[n];		\
    mask[n] = (_m_)->large_mask[n];		\
   }						\
   break;					\
 }						\
						\
 /* Check if we got a valid pixmap, if we did	\
  * then set got_match to TRUE */		\
 for(n = 0; n < nstates; n++) {			\
  if(pixmap[n] != NULL) {			\
   got_match = TRUE;				\
   break;					\
  }						\
 }						\
}

	/* Begin checking if the object matches a device or MIME Type */

	/* First check if the object is a link */
	if(type == EDV_OBJECT_TYPE_LINK)
	{
	    const gchar *mime_type_str = EDV_MIME_TYPE_TYPE_INODE_LINK;
	    edv_mime_type_struct *m;

	    /* Object is a link, now iterate through MIME Types list and
	     * find the MIME Type of class EDV_MIME_TYPE_CLASS_SYSTEM
	     * and use its icons
	     */
	    for(i = 0; i < total_mimetypes; i++)
	    {
		m = mimetype[i];
		if(m == NULL)
		    continue;

		/* Only handle if MIME Type class is a systems object */
		if((m->mt_class == EDV_MIME_TYPE_CLASS_SYSTEM) &&
		   !STRISEMPTY(m->type)
		)
		{
		    if(!strcmp(m->type, mime_type_str))
		    {
			GET_MIMETYPE_ICONS(m);
			got_match = TRUE;
			break;
		    }
		}
	    }
	}

	/* At this point got_match might be set to TRUE at any time to
	 * indicate that a match was made
	 */

	/* Check the devices list to see if this object matches
	 * one of the devices' mount path
	 */
	if((device != NULL) &&
	   ((path != NULL) ? g_path_is_absolute(path) : FALSE) &&
	   !got_match
	)
	{
	    edv_device_struct *d;

	    /* Iterate through the list of devices */
	    for(i = 0; i < total_devices; i++)
	    {
		d = device[i];
		if(d == NULL)
		    continue;

		if(STRISEMPTY(d->mount_path))
		    continue;

		/* Specified path matches device's mount path? */
		if(!strcmp(d->mount_path, path))
		{
		    gint n;
		    const gint nstates = MIN(
			EDV_DEVICE_TOTAL_ICON_STATES,
			EDV_MIME_TYPE_TOTAL_ICON_STATES
		    );

		    /* Realize this device first to ensure that the
		     * icons are loaded
		     */
		    EDVDeviceRealize(d, FALSE);

		    /* Get icons from device */
		    switch(icon_size)
		    {
		      case 0:	/* Small */
			for(n = 0; n < nstates; n++)
			{
			    pixmap[n] = d->small_pixmap[n];
			    mask[n] = d->small_mask[n];
			}
			break;
		      case 1:	/* Medium */
			for(n = 0; n < nstates; n++)
			{
			    pixmap[n] = d->medium_pixmap[n];
			    mask[n] = d->medium_mask[n];
			}
			break;
		      case 2:	/* Large */
			for(n = 0; n < nstates; n++)
			{
			    pixmap[n] = d->large_pixmap[n];
			    mask[n] = d->large_mask[n];
			}
			break;
		    }
		    /* Got match? */
		    for(n = 0; n < nstates; n++)
		    {
			if(pixmap[n] != NULL)
			{
			    got_match = TRUE;
			    break;
			}
		    }
		    break;
		}
	    }	/* Iterate through devices */
	}

	/* Check the MIME Types list to see if this object matches
	 * one of the MIME Types
	 */
	if((mimetype != NULL) && !got_match)
	{
	    const gchar	*value, *name;
	    edv_mime_type_struct *m;

	    if((path != NULL) ? g_path_is_absolute(path) : FALSE)
		name = g_basename(path);
	    else
		name = path;

	    /* Iterate through MIME Types list */
	    for(i = 0; i < total_mimetypes; i++)
	    {
		m = mimetype[i];
		if(m == NULL)
		    continue;

		value = m->value;
		if(STRISEMPTY(value))
		    continue;

		/* Handle by MIME Type class */
		switch(m->mt_class)
		{
		  case EDV_MIME_TYPE_CLASS_SYSTEM:
		    break;
		  case EDV_MIME_TYPE_CLASS_FORMAT:
		    if((name != NULL) && !got_match && is_file)
		    {
			if(EDVIsExtension(name, value))
			    GET_MIMETYPE_ICONS(m);
		    }
		    break;
		  case EDV_MIME_TYPE_CLASS_PROGRAM:
		    if((path != NULL) ? g_path_is_absolute(path) : FALSE)
		    {
			if(!strcmp(value, path))
			    GET_MIMETYPE_ICONS(m);
		    }
		    break;
		  case EDV_MIME_TYPE_CLASS_UNIQUE:
		    if((path != NULL) ? g_path_is_absolute(path) : FALSE)
		    {
			if(!strcmp(value, path))
			    GET_MIMETYPE_ICONS(m);
		    }
		    break;
		}

		if(got_match)
		    break;

	    }	/* Iterate through the MIME Types list */
	}



	/* If still did not get match, then use the basic system MIME
	 * types for the specified object
	 */
	if(!got_match)
	{
	    const gchar *mime_type_str = "";
	    edv_mime_type_struct *m;

	    /* Get MIME Type type string used for matching of system
	     * object type
	     */
	    switch(type)
	    {
	      case EDV_OBJECT_TYPE_UNKNOWN:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_UNKNOWN;
		break;

	      case EDV_OBJECT_TYPE_FILE:
		/* Check the file's permissions allow execution, in
		 * which case we use the file/executable MIME Type
		 * instead of file/regular
		 */
		if(permissions & (EDV_PERMISSION_UEXECUTE |
		    EDV_PERMISSION_GEXECUTE | EDV_PERMISSION_AEXECUTE)
		)
		    mime_type_str = EDV_MIME_TYPE_TYPE_INODE_EXECUTABLE;
		else
		    mime_type_str = EDV_MIME_TYPE_TYPE_INODE_FILE;
		break;

	      case EDV_OBJECT_TYPE_DIRECTORY:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_DIRECTORY;
		break;

	      case EDV_OBJECT_TYPE_LINK:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_LINK;
		break;

	      case EDV_OBJECT_TYPE_DEVICE_BLOCK:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_DEV_BLOCK;
		break;

	      case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_DEV_CHARACTER;
		break;

	      case EDV_OBJECT_TYPE_FIFO:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_FIFO;
		break;

	      case EDV_OBJECT_TYPE_SOCKET:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_SOCKET;
		break;

	      case EDV_OBJECT_TYPE_ERROR:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_ERROR;
		break;
	    }

	    /* Iterate through the MIME Types list */
	    for(i = 0; i < total_mimetypes; i++)
	    {
		m = mimetype[i];
		if(m == NULL)
		    continue;

		/* Only handle if the MIME Type class is system */
		if((m->mt_class == EDV_MIME_TYPE_CLASS_SYSTEM) &&
		   !STRISEMPTY(m->type)
		)
		{
		    if(!strcmp(m->type, mime_type_str))
		    {
			GET_MIMETYPE_ICONS(m);
			got_match = TRUE;
			break;
		    }
		}
	    }

	    /* If this object does not match any of the system
	     * MIME Types then return with the unknown icons
	     */
	    if(!got_match)
	    {
		m = EDVMimeTypesListMatchType(
		    mimetype, total_mimetypes,
		    NULL,
		    EDV_MIME_TYPE_TYPE_INODE_UNKNOWN,
		    FALSE		/* Case insensitive */
		);
		if(m != NULL)
		{
		    GET_MIMETYPE_ICONS(m);
		    got_match = TRUE;
		}
	    }
	}
#undef GET_MIMETYPE_ICONS


	/* Update returns */
	if(pixmap_closed != NULL)
	    *pixmap_closed = pixmap[EDV_MIME_TYPE_ICON_STATE_STANDARD];
	if(mask_closed != NULL)
	    *mask_closed = mask[EDV_MIME_TYPE_ICON_STATE_STANDARD];

	if(pixmap_opened != NULL)
	    *pixmap_opened = pixmap[EDV_MIME_TYPE_ICON_STATE_SELECTED];
	if(mask_opened != NULL)
	    *mask_opened = mask[EDV_MIME_TYPE_ICON_STATE_SELECTED];

	if(pixmap_extended != NULL)
	    *pixmap_extended = pixmap[EDV_MIME_TYPE_ICON_STATE_EXTENDED];
	if(mask_extended != NULL)
	    *mask_extended = mask[EDV_MIME_TYPE_ICON_STATE_EXTENDED];

	if(pixmap_hidden != NULL)
	    *pixmap_hidden = pixmap[EDV_MIME_TYPE_ICON_STATE_HIDDEN];
	if(mask_hidden != NULL)
	    *mask_hidden = mask[EDV_MIME_TYPE_ICON_STATE_HIDDEN];

	return(0);
}

/*
 *	Gets the MIME Type type string for the described object.
 *
 *	The mimetype and total_mimetypes specifies the MIME Types
 *	list.
 *
 *	The type specifies the object's type.
 *
 *	The permissions specifies the object's permissions.
 *
 *	The path specifies either the full path or just the name of
 *	the object.
 *
 *	The type_str specifies the pointer to the return MIME Type
 *	string, *type_str must not be modified or deleted.
 *
 *	Returns 0 on success or non-zero on error.
 */
gint EDVMatchObjectTypeString(
	edv_mime_type_struct **mimetype, const gint total_mimetypes,
	const edv_object_type type,
	const edv_permission_flags permissions,
	const gchar *path,	/* Full path or just the name */
	gchar **type_str
)
{
	gboolean got_match = FALSE;
	gint i;
	gchar *ltype_str = NULL;
	const gchar *name, *ext = NULL;
	edv_mime_type_struct *m;

	if(type_str != NULL)
	    *type_str = NULL;

	if((path != NULL) ? g_path_is_absolute(path) : FALSE)
	    name = g_basename(path);
	else
	    name = path;

	/* Parse extension from the specified name */
	if(name != NULL)
	{
	    const gchar *s = name;

	    /* Seek past first '.' character in the case of hidden
	     * files
	     */
 	    if(*s == '.')
		s++;

	    /* Seek to extension (if any), include the '.' as the first
	     * character if an extension is found
	     */
	    ext = strchr(s, '.');
	    if(ext != NULL)
	    {
		/* Extension must have atleast one character after it */
		if(*(ext + 1) == '\0')
		    ext = NULL;
	    }
	}

	/* Check the MIME Types list for a MIME Type that matches
	 * this object object
	 */
	if(mimetype != NULL)
	{
	    const gchar *value;

	    /* Iterate through the MIME Types list */
	    for(i = 0; i < total_mimetypes; i++)
	    {
		m = mimetype[i];
		if(m == NULL)
		    continue;

		value = m->value;
		if(STRISEMPTY(value))
		    continue;

		/* Handle by MIME Type class */
		switch(m->mt_class)
		{
		  case EDV_MIME_TYPE_CLASS_SYSTEM:
		    break;
		  case EDV_MIME_TYPE_CLASS_FORMAT:
		    if((name != NULL) && !got_match)
		    {
			if(EDVIsExtension(name, value))
			{
			    ltype_str = m->type;
			    if(ltype_str != NULL)
				got_match = TRUE;
			}
		    }
		    break;
		  case EDV_MIME_TYPE_CLASS_PROGRAM:
		    if((path != NULL) ? g_path_is_absolute(path) : FALSE)
		    {
			if(!strcmp(value, path))
			{
			    ltype_str = m->type;
			    if(ltype_str != NULL)
				got_match = TRUE;
			}
		    }
		    break;
		  case EDV_MIME_TYPE_CLASS_UNIQUE:
		    if((path != NULL) ? g_path_is_absolute(path) : FALSE)
		    {
			if(!strcmp(value, path))
			{
			    ltype_str = m->type;
			    if(ltype_str != NULL)
				got_match = TRUE;
			}
		    }
		    break;
		}

		if(got_match)
		    break;

	    }	/* Iterate through the MIME Types list */
	}

	/* If there was no match then set the MIME Type string to
	 * one based on the object's type
	 */
	if(!got_match)
	{
	    const gchar *mime_type_str = "";

	    /* Get MIME Type type string used for matching of system
	     * object type
	     */
	    switch(type)
	    {
	      case EDV_OBJECT_TYPE_UNKNOWN:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_UNKNOWN;
		break;
	      case EDV_OBJECT_TYPE_FILE:
		/* Check the file's permissions allow execution, in which
		 * case we use the file/executable MIME Type instead of
		 * file/regular
		 */
		if(permissions & (EDV_PERMISSION_UEXECUTE |
		    EDV_PERMISSION_GEXECUTE | EDV_PERMISSION_AEXECUTE)
		)
		    mime_type_str = EDV_MIME_TYPE_TYPE_INODE_EXECUTABLE;
		else
		    mime_type_str = EDV_MIME_TYPE_TYPE_INODE_FILE;
		break;
	      case EDV_OBJECT_TYPE_DIRECTORY:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_DIRECTORY;
		break;
	      case EDV_OBJECT_TYPE_LINK:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_LINK;
		break;
	      case EDV_OBJECT_TYPE_DEVICE_BLOCK:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_DEV_BLOCK;
		break;
	      case EDV_OBJECT_TYPE_DEVICE_CHARACTER:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_DEV_CHARACTER;
		break;
	      case EDV_OBJECT_TYPE_FIFO:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_FIFO;
		break;
	      case EDV_OBJECT_TYPE_SOCKET:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_SOCKET;
		break;
	      case EDV_OBJECT_TYPE_ERROR:
		mime_type_str = EDV_MIME_TYPE_TYPE_INODE_ERROR;
		break;
	    }

	    ltype_str = (gchar *)mime_type_str;
	}

	/* Update returns */
	if(type_str != NULL)
	    *type_str = ltype_str;

	return(0);
}


/*
 *	Displays the specified message on the CDialog.
 */
void EDVMessage(
	const gchar *title, const gchar *message, const gchar *details,
	GtkWidget *toplevel
)
{
	EDVMessageInfo(title, message, details, toplevel);
}
void EDVMessageInfo(
	const gchar *title, const gchar *message, const gchar *details,
	GtkWidget *toplevel
)
{
	CDialogSetTransientFor(toplevel);
	CDialogGetResponse(
	    title, message, details,
	    CDIALOG_ICON_INFO,
	    CDIALOG_BTNFLAG_OK |
	    ((details != NULL) ? CDIALOG_BTNFLAG_HELP : 0),
	    CDIALOG_BTNFLAG_OK
	);
	CDialogSetTransientFor(NULL);
}
void EDVMessageWarning(
	const gchar *title, const gchar *message, const gchar *details,
	GtkWidget *toplevel 
)
{
	CDialogSetTransientFor(toplevel);
	CDialogGetResponse(
	    title, message, details,
	    CDIALOG_ICON_WARNING,
	    CDIALOG_BTNFLAG_OK |
	    ((details != NULL) ? CDIALOG_BTNFLAG_HELP : 0),
	    CDIALOG_BTNFLAG_OK
	);
	CDialogSetTransientFor(NULL);
}
void EDVMessageError(
	const gchar *title, const gchar *message, const gchar *details,
	GtkWidget *toplevel                                            
)
{
	CDialogSetTransientFor(toplevel);
	CDialogGetResponse(
	    title, message, details,
	    CDIALOG_ICON_ERROR,
	    CDIALOG_BTNFLAG_OK |
	    ((details != NULL) ? CDIALOG_BTNFLAG_HELP : 0),
	    CDIALOG_BTNFLAG_OK  
	);
	CDialogSetTransientFor(NULL);
}
void EDVMessageObjectOPError(
	const gchar *title,
	const gchar *message, 
	const gchar *path,
	GtkWidget *toplevel
)
{
	gchar *s, *error_msg = STRDUP(message);

	if(error_msg != NULL)
	{
	    const gint len = STRLEN(error_msg);

	    *error_msg = (gchar)toupper((char)*error_msg);

	    if(len > 0)
	    {
		if(error_msg[len - 1] == '.')
		    error_msg[len - 1] = '\0';
	    }
	}

	if(path != NULL)
	    s = g_strdup_printf(
"%s:\n\
\n\
    %s",
		(error_msg != NULL) ? error_msg : "Operation error",
		path
	    );
	else
	    s = g_strdup_printf(
		"%s.",
		(error_msg != NULL) ? error_msg : "Operation error"
	    );

	g_free(error_msg);

	CDialogSetTransientFor(toplevel);
	CDialogGetResponse(
	    title, s, NULL,
	    CDIALOG_ICON_ERROR,
	    CDIALOG_BTNFLAG_OK,
	    CDIALOG_BTNFLAG_OK  
	);
	g_free(s);
	CDialogSetTransientFor(NULL);
}

/*
 *	Displays the contents of the specified file on the CDialog.
 *
 *	If the file does not exist or cannot be opened then nothing will
 *	be displayed.
 */
void EDVMessageFromFile(
	const gchar *path, const gchar *title,
	gint cdialog_icon,
	GtkWidget *toplevel
)
{
	const gint max_filesize = 80 * 100;
	gchar *buf;
	gint buf_len, bytes_read;
	struct stat stat_buf;
	FILE *fp = !STRISEMPTY(path) ? fopen(path, "rb") : NULL;
	if(fp == NULL)
	    return;

	if(fstat(fileno(fp), &stat_buf))
	{
	    fclose(fp);
	    return;
	}

	/* Get size of file and limit it to max_filesize */
	buf_len = MIN((gint)stat_buf.st_size, max_filesize);
	if(buf_len < 0)
	    buf_len = 0;
	buf = (gchar *)g_malloc((buf_len + 1) * sizeof(gchar));
	if(buf == NULL)
	{
	    fclose(fp);
	    return;
	}

	/* Read file */
	bytes_read = (gint)fread(buf, sizeof(gchar), (size_t)buf_len, fp);

	/* Close file */
	fclose(fp);

	/* Error reading file? */
	if(bytes_read < 0)
	{
	    g_free(buf);
	    return;
	}

	/* Null terminate */
	if(bytes_read <= buf_len)
	    buf[bytes_read] = '\0';
	else
	    buf[buf_len] = '\0';

	/* File contents empty? */
	if(*buf == '\0')
	{
	    g_free(buf);
	    return;
	}

	/* Display file contents on the CDialog */
	CDialogSetTransientFor(toplevel);
	CDialogGetResponse(
	    title, buf, NULL,
	    cdialog_icon,
	    CDIALOG_BTNFLAG_OK,
	    CDIALOG_BTNFLAG_OK
	);
	CDialogSetTransientFor(NULL);

	g_free(buf);
}


/*
 *	Plays the "Beep" sound.
 */
void EDVBeep(edv_core_struct *core)
{
	EDVPlaySoundBeep(core);
}
void EDVPlaySoundBeep(edv_core_struct *core)
{
	const cfg_item_struct *cfg_list = (core != NULL) ?
	    core->cfg_list : NULL;
	if(CFGItemListGetValueI(cfg_list, EDV_CFG_PARM_SOUND_USE_SYSTEM))
	{
	    gdk_beep();
	}
	else
	{
	    const gchar *s = CFGItemListGetValueS(
		cfg_list, EDV_CFG_PARM_SOUND_PLAY_BEEP
	    );
	    if(!STRISEMPTY(s))
	    {
	        gchar *cmd = g_strdup_printf("%s &", s);
	        EDVSystem(cmd);
	        g_free(cmd);
	    }
	}
}

/*
 *	Plays the "Info" sound.
 */
void EDVPlaySoundInfo(edv_core_struct *core)
{
	const cfg_item_struct *cfg_list = (core != NULL) ?
	    core->cfg_list : NULL;
	if(CFGItemListGetValueI(cfg_list, EDV_CFG_PARM_SOUND_USE_SYSTEM))
	{

	}
	else
	{   
	    const gchar *s = CFGItemListGetValueS(
		cfg_list, EDV_CFG_PARM_SOUND_PLAY_INFO
	    );
	    if(!STRISEMPTY(s))
	    {
		gchar *cmd = g_strdup_printf("%s &", s);
		EDVSystem(cmd);
		g_free(cmd);
	    }
	}
}

/*
 *	Plays the "Question" sound.
 */
void EDVPlaySoundQuestion(edv_core_struct *core)
{
	const cfg_item_struct *cfg_list = (core != NULL) ?
	    core->cfg_list : NULL;
	if(CFGItemListGetValueI(cfg_list, EDV_CFG_PARM_SOUND_USE_SYSTEM))
	{

	}
	else
	{   
	    const gchar *s = CFGItemListGetValueS(
		cfg_list, EDV_CFG_PARM_SOUND_PLAY_QUESTION
	    );
	    if(!STRISEMPTY(s))
	    {
		gchar *cmd = g_strdup_printf("%s &", s);
		EDVSystem(cmd);
		g_free(cmd);
	    }
	}
}

/*
 *	Plays the "Warning" sound.
 */
void EDVPlaySoundWarning(edv_core_struct *core)
{
	const cfg_item_struct *cfg_list = (core != NULL) ?
	    core->cfg_list : NULL;
	if(CFGItemListGetValueI(cfg_list, EDV_CFG_PARM_SOUND_USE_SYSTEM))
	{

	}
	else
	{   
	    const gchar *s = CFGItemListGetValueS(
		cfg_list, EDV_CFG_PARM_SOUND_PLAY_WARNING
	    );
	    if(!STRISEMPTY(s))
	    {
		gchar *cmd = g_strdup_printf("%s &", s);
		EDVSystem(cmd);
		g_free(cmd);
	    }
	}
}

/*
 *	Plays the "Error" sound.
 */
void EDVPlaySoundError(edv_core_struct *core)
{
	const cfg_item_struct *cfg_list = (core != NULL) ?
	    core->cfg_list : NULL;
	if(CFGItemListGetValueI(cfg_list, EDV_CFG_PARM_SOUND_USE_SYSTEM))
	{

	}
	else
	{   
	    const gchar *s = CFGItemListGetValueS(
		cfg_list, EDV_CFG_PARM_SOUND_PLAY_ERROR
	    );
	    if(!STRISEMPTY(s))
	    {
		gchar *cmd = g_strdup_printf("%s &", s);
		EDVSystem(cmd);
		g_free(cmd);
	    }
	}
}

/*
 *	Plays the "Completed" sound.
 */
void EDVPlaySoundCompleted(edv_core_struct *core)
{
	const cfg_item_struct *cfg_list = (core != NULL) ?
	    core->cfg_list : NULL;
	if(CFGItemListGetValueI(cfg_list, EDV_CFG_PARM_SOUND_USE_SYSTEM))
	{

	}
	else
	{   
	    const gchar *s = CFGItemListGetValueS(
		cfg_list, EDV_CFG_PARM_SOUND_PLAY_COMPLETED
	    );
	    if(!STRISEMPTY(s))
	    {
		gchar *cmd = g_strdup_printf("%s &", s);
		EDVSystem(cmd);
		g_free(cmd);
	    }
	}
}


/*
 *	GtkMenu "hide" signal callback.
 *
 *	Used to release the GtkButton associated with the the GtkMenu
 *	and disconnect this signal.
 */
static void EDVMenuButtonMenuHideCB(GtkWidget *widget, gpointer data)
{ 
	guint s;
	GList *glist;
	GtkWidget *w;
	if(widget == NULL)
	    return;

	/* Get the meuu and button map data from the GtkMenu */
	glist = (GList *)gtk_object_get_data(
	    GTK_OBJECT(widget), EDV_MENU_BUTTON_MAP_DATA_KEY
	);
	w = (glist != NULL) ? (GtkWidget *)glist->data : NULL;
	glist = g_list_next(glist);
	s = (glist != NULL) ? (guint)glist->data : 0;

	/* Release the GtkButton */
	if((w != NULL) ? GTK_IS_BUTTON(w) : FALSE)
	{
	    GtkButton *button = GTK_BUTTON(w);
	    button->in_button = 0;
	    gtk_signal_emit_by_name(GTK_OBJECT(w), "released");
	}

	/* Disconnect this signal */
	if(s > 0)
	    gtk_signal_disconnect(GTK_OBJECT(widget), s);

	/* Remove the menu and button and map data */
	gtk_object_remove_data(
	    GTK_OBJECT(widget), EDV_MENU_BUTTON_MAP_DATA_KEY
	);
}

/*
 *	GtkMenu position callback.
 *
 *	Moves the GtkMenu to the proper position based on the GtkButton
 *	specified by data.
 */
static void EDVMenuButtonMenuPositionCB(
	GtkMenu *menu, gint *x, gint *y, gpointer data
)
{
	gint width, height;
	GdkWindow *window;
	GtkWidget *w = (GtkWidget *)data;
	if((menu == NULL) || (x == NULL) || (y == NULL) || (w== NULL))
	    return;

	window = w->window;
	if(window == NULL)
	    return;

	gdk_window_get_root_position(window, x, y);
	gdk_window_get_size(window, &width, &height);
	*y = (*y) + height;
}

/*
 *	Maps the GtkMenu relative to the specified GtkButton.
 */
void EDVMenuButtonMapMenu(GtkWidget *menu, GtkWidget *button)
{
	if(menu == NULL)
	    return;

	/* Ungrab the GtkButton */
	if(button != NULL)
	{
	    if(GTK_WIDGET_HAS_GRAB(button))
		gtk_grab_remove(button);

/*
	    while(gtk_events_pending() > 0)
		gtk_main_iteration();
 */

	    if(GTK_IS_BUTTON(button))
	    {
		/* Send a "pressed" signal to the GtkButton to keep
		 * it pressed once the grab for the GtkButton has been
		 * removed and the GtkMenu has been mapped
		 */
		GTK_BUTTON(button)->in_button = 1;
		GTK_BUTTON(button)->button_down = 1;
		gtk_signal_emit_by_name(GTK_OBJECT(button), "pressed");
	    }
	}

	/* Map the menu */
	if(GTK_IS_MENU(menu))
	{
	    /* Connect the hide signal */
	    guint s = gtk_signal_connect(
		GTK_OBJECT(menu), "hide",
		GTK_SIGNAL_FUNC(EDVMenuButtonMenuHideCB), button
	    );
	    GList *data_list = NULL;

	    /* Set object data on the menu */
	    data_list = g_list_append(data_list, button);
	    data_list = g_list_append(data_list, (gpointer)s);
	    gtk_object_set_data_full(
		GTK_OBJECT(menu), EDV_MENU_BUTTON_MAP_DATA_KEY,
		data_list, (GtkDestroyNotify)g_list_free
	    );

	    /* Map the menu */
	    gtk_menu_popup(
		GTK_MENU(menu), NULL, NULL,
		EDVMenuButtonMenuPositionCB,	/* Position Callback */
		button,			/* Data */
		1,			/* Button Number */
		GDK_CURRENT_TIME	/* Time */
	    );
	}
}


/*
 *	Moves the GtkWindow w2 to be centered to GtkWindow w1.
 */
void EDVCenterWindowToWindow(GtkWidget *w1, GtkWidget *w2)
{
	EDVCenterWindowToWindowOffset(w1, w2, 0, 0);
}

/*
 *	Moves the GtkWindow w2 to be centered to GtkWindow w1 plus
 *	the given offsets.
 */
void EDVCenterWindowToWindowOffset(
	GtkWidget *w1, GtkWidget *w2, gint x_offset, gint y_offset
)
{
	gint x, y;
	gint width1, height1, width2, height2;
	GdkWindow *window1, *window2;


	if((w1 == NULL) || (w2 == NULL))
	    return;

	if(!GTK_IS_WINDOW(w1) || !GTK_IS_WINDOW(w2))
	    return;

	window1 = w1->window;
	window2 = w2->window;
	if((window1 == NULL) || (window2 == NULL))
	    return;

	gdk_window_get_root_origin(window1, &x, &y);
	gdk_window_get_size(window1, &width1, &height1);
	gdk_window_get_size(window2, &width2, &height2);

	gtk_widget_set_uposition(
	    w2,
	    ((width2 > 0) ?
		(x + (width1 / 2) - (width2 / 2)) : (x + 20)
	    ) + x_offset,
	    ((height2 > 0) ?
		(y + (height1 / 2) - (height2 / 2)) : (y + 20)
	    ) + y_offset
	);
}

/*
 *	Sets up the GtkEntry w to allow it to receive drag and drop.
 */
void EDVEntrySetDND(edv_core_struct *core, GtkWidget *w)
{
	const GtkTargetEntry dnd_tar_types[] = {
{"text/plain",			0,	EDV_DND_TYPE_INFO_TEXT_PLAIN},
{"text/uri-list",		0,	EDV_DND_TYPE_INFO_TEXT_URI_LIST},
{"STRING",			0,	EDV_DND_TYPE_INFO_STRING}
	};

	if((core == NULL) || (w == NULL))
	    return;

	if(!GTK_IS_ENTRY(w))
	    return;

	GUIDNDSetTar(
	    w,
	    dnd_tar_types,
	    sizeof(dnd_tar_types) / sizeof(GtkTargetEntry),
	    GDK_ACTION_COPY,			/* Actions */
	    GDK_ACTION_COPY,			/* Default action if same */
	    GDK_ACTION_COPY,			/* Default action */
	    EDVEntryDragDataReceivedCB,
	    core
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "drag_motion",
	    GTK_SIGNAL_FUNC(EDVEntryDragMotionCB), core
	);
}

/*
 *	Sets up the GtkEntry w to complete path when the TAB key is
 *	pressed.
 */
void EDVEntrySetCompletePath(edv_core_struct *core, GtkWidget *w)
{
	if((core == NULL) || (w == NULL))
	    return;

	if(!GTK_IS_ENTRY(w))
	    return;

	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(EDVEntryCompletePathCB), core
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(EDVEntryCompletePathCB), core
	);
}


/*
 *	Creates a new GtkScrolledWindow set up for improved scrolling.
 */
GtkWidget *EDVScrolledWindowNew(
	GtkPolicyType hscrollbar_policy,
	GtkPolicyType vscrollbar_policy,
	GtkWidget **event_box_rtn,
	GtkWidget **vbox_rtn
)
{
	edv_scrolled_window_data *d = EDV_SCROLLED_WINDOW_DATA(g_malloc0(
	    sizeof(edv_scrolled_window_data)
	));
	GtkWidget *parent, *w = gtk_scrolled_window_new(NULL, NULL);
	GtkScrolledWindow *sw = GTK_SCROLLED_WINDOW(w);

	d->button = 0;
	d->drag_last_x = 0;
	d->drag_last_y = 0;
	d->translate_cur = gdk_cursor_new(GDK_FLEUR);

	gtk_scrolled_window_set_policy(
	    sw, hscrollbar_policy, vscrollbar_policy
	);
	gtk_object_set_data_full(
	    GTK_OBJECT(w), EDV_SCROLLED_WINDOW_DATA_KEY,
	    d, EDVScrolledWindowDataDelete
	);
	parent = w;

	w = gtk_event_box_new();
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_FOCUS);
	gtk_widget_add_events(
	    w,
	    GDK_FOCUS_CHANGE_MASK |
	    GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
	    GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(EDVScrolledWindowEventCB), sw
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(EDVScrolledWindowEventCB), sw
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(EDVScrolledWindowEventCB), sw
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(EDVScrolledWindowEventCB), sw
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(EDVScrolledWindowEventCB), sw
	);
	gtk_scrolled_window_add_with_viewport(sw, w);
	gtk_widget_show(w);
	parent = w;

	w = gtk_vbox_new(FALSE, 0);
	if(vbox_rtn != NULL)
	    *vbox_rtn = w;
	gtk_container_add(GTK_CONTAINER(parent), w);
	gtk_widget_show(w);

	return(GTK_WIDGET(sw));
}

/*
 *	Scrolled window GtkEventBox event signal callback.
 */
static gint EDVScrolledWindowEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint status = FALSE;
	edv_scrolled_window_data *d;
	gboolean press;
	GdkEventFocus *focus;
	GdkEventKey *key;
	GdkEventButton *button;
	GdkEventMotion *motion;
	GtkScrolledWindow *sw = (GtkScrolledWindow *)data;
	if((widget == NULL) || (event == NULL) || (sw == NULL))
	    return(status);

#define ADJ_SET_EMIT(adj,v)	{		\
 gfloat v2 = (v);				\
 if(v2 > ((adj)->upper - (adj)->page_size))	\
  v2 = (adj)->upper - (adj)->page_size;		\
 if(v2 < (adj)->lower)				\
  v2 = (adj)->lower;				\
 gtk_adjustment_set_value((adj), v2);		\
}
#define ADJ_SET_DELTA_EMIT(adj,d)	{	\
 ADJ_SET_EMIT((adj), (adj)->value + (d));	\
}

	switch((gint)event->type)
	{
	  case GDK_FOCUS_CHANGE:
	    focus = (GdkEventFocus *)event;
	    if(focus->window == widget->window)
	    {
		if(focus->in && !GTK_WIDGET_HAS_FOCUS(widget))
	        {
		    GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
		    gtk_widget_queue_draw(widget);
		}
		else if(!focus->in && GTK_WIDGET_HAS_FOCUS(widget))
		{
		    GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
		    gtk_widget_queue_draw(widget);
		}
		status = TRUE;
	    }
	    break;

	  case GDK_KEY_PRESS:
	  case GDK_KEY_RELEASE:
	    key = (GdkEventKey *)event;
	    press = (key->type == GDK_KEY_PRESS) ? TRUE : FALSE;
#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_Up:
	      case GDK_KP_Up:
		if(press)
		{
		    GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(sw);
		    ADJ_SET_DELTA_EMIT(adj, -adj->step_increment);
		}
		SIGNAL_EMIT_STOP
		status = TRUE;
		break;
	      case GDK_Down:
	      case GDK_KP_Down:
		if(press)
		{
		    GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(sw);
		    ADJ_SET_DELTA_EMIT(adj, adj->step_increment);
		}
		SIGNAL_EMIT_STOP
		status = TRUE;
		break;
	      case '-':
	      case 'b':
	      case 'p':
	      case GDK_Page_Up:
	      case GDK_KP_Page_Up:
		if(press)
		{
		    GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(sw);
		    ADJ_SET_DELTA_EMIT(adj, -adj->page_increment);
		}
		status = TRUE;
		break;
	      case 'n':
	      case GDK_space:
	      case GDK_Page_Down:
	      case GDK_KP_Page_Down:
		if(press)
		{
		    GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(sw);
		    ADJ_SET_DELTA_EMIT(adj, adj->page_increment);
		}
		status = TRUE;
		break;
	      case GDK_Home:
	      case GDK_KP_Home:
		if(press)
		{
		    GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(sw);
		    ADJ_SET_EMIT(adj, adj->lower);
		}
		status = TRUE;
		break;
	      case GDK_End:
	      case GDK_KP_End:
		if(press)
		{
		    GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(sw);
		    ADJ_SET_EMIT(adj, adj->upper - adj->page_size);
		}
		status = TRUE;
		break;
	    }
#undef SIGNAL_EMIT_STOP
	    break;

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    if(button->window == widget->window)
	    {
		if(!GTK_WIDGET_HAS_FOCUS(widget))
		    gtk_widget_grab_focus(widget);
	    }
	    switch(button->button)
	    {
	      case GDK_BUTTON1:
	      case GDK_BUTTON2:
		d = EDV_SCROLLED_WINDOW_DATA(
		    GTK_OBJECT_GET_DATA(
			sw,
			EDV_SCROLLED_WINDOW_DATA_KEY
		    )
		);
		if((button->window == widget->window) &&
		   (d != NULL)
		)
		{
		    gdk_pointer_grab(
			button->window,
			FALSE,
			GDK_BUTTON_PRESS_MASK |
			    GDK_BUTTON_RELEASE_MASK |
			    GDK_POINTER_MOTION_MASK |
			    GDK_POINTER_MOTION_HINT_MASK,
			NULL,
			d->translate_cur,
			button->time
		    );
		    d->button = button->button;
		    d->drag_last_x = (gint)button->x;
		    d->drag_last_y = (gint)button->y;
		    status = TRUE;
		}
		break;
	    }
	    break;

	  case GDK_BUTTON_RELEASE:
	    button = (GdkEventButton *)event;
	    switch(button->button)
	    {
	      case GDK_BUTTON1:
	      case GDK_BUTTON2:
		d = EDV_SCROLLED_WINDOW_DATA(
		    GTK_OBJECT_GET_DATA(
			sw,
			EDV_SCROLLED_WINDOW_DATA_KEY
		    )
		);
		if(d != NULL)
		{
		    if(d->button > 0)
		    {
			d->button = 0;
			d->drag_last_x = 0;
			d->drag_last_y = 0;
			status = TRUE;
		    }
		}
		if(gdk_pointer_is_grabbed())
		    gdk_pointer_ungrab(button->time);
		break;
	    }
	    break;

	  case GDK_MOTION_NOTIFY:
	    motion = (GdkEventMotion *)event;
	    d = EDV_SCROLLED_WINDOW_DATA(
		GTK_OBJECT_GET_DATA(
		    sw,
		    EDV_SCROLLED_WINDOW_DATA_KEY
		)
	    );
	    if((d != NULL) ? ((d->button == GDK_BUTTON1) ||
			      (d->button == GDK_BUTTON2)) : FALSE
	    )
	    {
		gint dx, dy;
		GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(sw),
			      *vadj = gtk_scrolled_window_get_vadjustment(sw);
		if(motion->is_hint)
		{
		    gint x, y;
		    GdkModifierType mask;
		    gdk_window_get_pointer(
			motion->window, &x, &y, &mask
		    );
		    dx = x - d->drag_last_x;
		    dy = y - d->drag_last_y;
		}
		else
		{
		    dx = (gint)(motion->x - d->drag_last_x);
		    dy = (gint)(motion->y - d->drag_last_y);
		}
#define DO_SCROLL(adj,d)	{			\
 if((d) != 0) {						\
  if(((adj)->upper - (adj)->page_size) > (adj)->lower) {\
   gtk_adjustment_set_value(				\
    adj,						\
    CLIP(						\
     (adj)->value - (d),				\
     (adj)->lower, (adj)->upper - (adj)->page_size	\
    )							\
   );							\
  }							\
 }							\
}
		DO_SCROLL(hadj, dx);
		DO_SCROLL(vadj, dy);

#undef DO_SCROLL
		status = TRUE;
	    }
	    break;
	}

#undef ADJ_SET_DELTA_EMIT
#undef ADJ_SET_EMIT

	return(status);
}

static void EDVScrolledWindowDataDelete(gpointer data)
{
	edv_scrolled_window_data *d = EDV_SCROLLED_WINDOW_DATA(data);
	if(d == NULL)
	    return;

	GDK_CURSOR_DESTROY(d->translate_cur);
	g_free(d);
}


/*
 *	Coppies the paths of the given list of disk objects to the
 *	DDE as a space separated list of absolute paths.
 */
void EDVCopyDiskObjectsToDDEPath(
	edv_core_struct *core,
	edv_object_struct **list, gint total,
	GtkWidget *owner
)
{
	gint i;
	gchar *buf = NULL;
	edv_object_struct *obj;


	if((core == NULL) || (list == NULL) || (total <= 0))
	    return;


	for(i = 0; i < total; i++)
	{
	    obj = list[i];
	    if(obj == NULL)
		continue;

	    if(obj->full_path == NULL)
		continue;

	    buf = G_STRCAT(buf, obj->full_path);
	    if((i + 1) < total)
		buf = G_STRCAT(buf, " ");
	}

	if(buf != NULL)
	{
	    GUIDDESetString(
		owner,			/* Owner */
		GDK_SELECTION_PRIMARY,	/* Selection */
		GDK_CURRENT_TIME,	/* Time */
		buf			/* Data */
	    );
	    g_free(buf);
	}
}

/*
 *	Coppies the paths of the given list of disk objects to the
 *	DDE as a space separated list of urls (without the localhost
 *	address specified).
 */
void EDVCopyDiskObjectsToDDEURL(
	edv_core_struct *core,
	edv_object_struct **list, gint total,
	GtkWidget *owner
)
{
	gint i;
	gchar *buf = NULL;
	edv_object_struct *obj;

	if((core == NULL) || (list == NULL) || (total <= 0))
	    return;

	for(i = 0; i < total; i++)
	{
	    obj = list[i];
	    if(obj == NULL)
		continue;

	    if(obj->full_path == NULL)
		continue;

	    buf = G_STRCAT(buf, "file://");
	    buf = G_STRCAT(buf, obj->full_path);
	    if((i + 1) < total)
		buf = G_STRCAT(buf, " ");
	}

	if(buf != NULL)
	{
	    GUIDDESetString(
		owner,			/* Owner */
		GDK_SELECTION_PRIMARY,	/* Selection */
		GDK_CURRENT_TIME,	/* Time */
		buf			/* Data */
	    );
	    g_free(buf);
	}
}


/*
 *	Creates a new GtkRcStyle from the given cfg_style_struct.
 */
GtkRcStyle *EDVCreateRCStyleFromCfg(const cfg_style_struct *style)
{
	gint i;
	guint src_color_flags;
	GdkColor *tar_c;
	const cfg_color_struct *src_c;
	GtkRcStyle *tar_style;
	const cfg_style_struct *src_style = style;

#define STRDUP2(s)	((STRISEMPTY(s)) ? NULL : g_strdup(s))

	if(src_style == NULL)
	    return(NULL);

	tar_style = gtk_rc_style_new();

	g_free(tar_style->font_name);
	tar_style->font_name = STRDUP2(src_style->font_name);

	for(i = 0; i < CFG_STYLE_STATES; i++)
	{
	    src_color_flags = src_style->color_flags[i];
	    if(src_color_flags & CFG_STYLE_FG)
	    {
		tar_style->color_flags[i] |= GTK_RC_FG;
		src_c = &src_style->fg[i];
		tar_c = &tar_style->fg[i];
		GDK_COLOR_SET_COEFF(
		    tar_c, src_c->r, src_c->g, src_c->b
		)
	    }
	    if(src_color_flags & CFG_STYLE_BG)
	    {
		tar_style->color_flags[i] |= GTK_RC_BG;
		src_c = &src_style->bg[i];
		tar_c = &tar_style->bg[i];
		GDK_COLOR_SET_COEFF(
		    tar_c, src_c->r, src_c->g, src_c->b
		) 
	    }
	    if(src_color_flags & CFG_STYLE_TEXT)
	    {
		tar_style->color_flags[i] |= GTK_RC_TEXT;
		src_c = &src_style->text[i];
		tar_c = &tar_style->text[i];
		GDK_COLOR_SET_COEFF(
		    tar_c, src_c->r, src_c->g, src_c->b
		) 
	    }
	    if(src_color_flags & CFG_STYLE_BASE)
	    {
		tar_style->color_flags[i] |= GTK_RC_BASE;
		src_c = &src_style->base[i];
		tar_c = &tar_style->base[i];
		GDK_COLOR_SET_COEFF(
		    tar_c, src_c->r, src_c->g, src_c->b
		) 
	    }

	    g_free(tar_style->bg_pixmap_name[i]);
	    tar_style->bg_pixmap_name[i] = STRDUP2(
		src_style->bg_pixmap_name[i]
	    );
	}

	return(tar_style);
#undef STRDUP2
}

/*
 *	Appends a new operation history event item to the list on the
 *	given core structure.
 */
void EDVAppendHistory(
	edv_core_struct *core,
	edv_history_type type,	/* One of EDV_HISTORY_* */
	gulong time_start,	/* Time the operation first started */
	gulong time_end,	/* Time the operation ended */
	gint status,		/* Result of operation */
	const gchar *src_path,	/* Source Object/Operation/Value */
	const gchar *tar_path,	/* Target Object/Operation/Value */
	const gchar *comments	/* Comments */
)
{
	edv_history_struct *h;


	if(core == NULL)
	    return;

	/* Create new history event */
	h = EDVHistoryNew();
	if(h == NULL)
	    return;

	/* Set values */
	h->type = type;
	h->time_start = time_start;
	h->time_end = time_end;
	h->status = status;
	h->src_path = STRDUP(src_path);
	h->tar_path = STRDUP(tar_path);
	h->comments = STRDUP(comments);

	/* Append to history file */
	EDVHistoryAppendToFile(
	    CFGItemListGetValueS(
		core->cfg_list, EDV_CFG_PARM_FILE_HISTORY
	    ),
	    h
	);

	/* Emit history event added signal */
	EDVHistoryAddedEmit(core, -1, h);

	/* Delete history event */
	EDVHistoryDelete(h);
}
