#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#if defined(_WIN32)
# include <gdk/gdkwin32.h>
#else
# include <gdk/gdkx.h>
# define HAVE_X
#endif
#include "guiutils.h"
#include "guirgbimg.h"
#include "imgview.h"
#include "imgviewcrop.h"
#include "config.h"

#include "images/icon_zoom_in_16x16.xpm"
#include "images/icon_zoom_out_16x16.xpm"
#include "images/icon_zoom_onetoone_16x16.xpm"
#include "images/icon_zoom_tofit_16x16.xpm"
#include "images/icon_rotate_cw_20x20.xpm"
#include "images/icon_rotate_ccw_20x20.xpm"
#include "images/icon_mirror_horizontal_20x20.xpm"
#include "images/icon_mirror_vertical_20x20.xpm"
#include "images/icon_zoom_onetoone_20x20.xpm"
#include "images/icon_zoom_tofit_20x20.xpm"

#include "images/icon_iv_48x48.xpm"


/* ImgView Image */
imgview_image_struct *ImgViewImageNew(
	gint width, gint height, gint bpp, gint bpl
);
imgview_image_struct *ImgViewImageCopy(imgview_image_struct *img);
gint ImgViewImageInsertFrame(
	imgview_image_struct *img, gint frame_num,
	guint8 *buf,			/* Transfered */
	gulong delay
);
gint ImgViewImageAppendFrame(
	imgview_image_struct *img,
	guint8 *buf,			/* Transfered */
	gulong delay
);
gint ImgViewImageAppendFrameNew(imgview_image_struct *img);
void ImgViewImageDeleteFrame(
	imgview_image_struct *img, gint frame_num
);
imgview_frame_struct *ImgViewImageGetFrame(
	imgview_image_struct *img, gint frame_num
);
void ImgViewImageClear(
	imgview_image_struct *img, gint frame_num,
	guint32 v
);
void ImgViewImageSendRectangle(
	imgview_image_struct *image, gint frame_num,
	GdkDrawable *d, GdkGC *gc, gint quality,
	const GdkRectangle *rect
);
void ImgViewImageSend(
	imgview_image_struct *image, gint frame_num,
	GdkDrawable *d, GdkGC *gc, gint quality
);
void ImgViewImageDelete(imgview_image_struct *image);

/* Callbacks */
static gint ImgViewDeleteEventCB(GtkWidget *widget, GdkEvent *event, gpointer data);
static void ImgViewZoomInPressedCB(GtkWidget *widget, gpointer data);
static gint ImgViewZoomInTOCB(gpointer data);
static void ImgViewZoomOutPressedCB(GtkWidget *widget, gpointer data);
static gint ImgViewZoomOutTOCB(gpointer data);
static void ImgViewZoomReleasedCB(GtkWidget *widget, gpointer data);
static void ImgViewZoomOneToOneCB(GtkWidget *widget, gpointer data);
static void ImgViewZoomToFitCB(GtkWidget *widget, gpointer data);
static void ImgViewZoomPrevCB(GtkWidget *widget, gpointer data);
static void ImgViewRotateCW90CB(GtkWidget *widget, gpointer data);
static void ImgViewRotateCCW90CB(GtkWidget *widget, gpointer data);
static void ImgViewRotateCW180CB(GtkWidget *widget, gpointer data);
static void ImgViewMirrorHorizontalCB(GtkWidget *widget, gpointer data);
static void ImgViewMirrorVerticalCB(GtkWidget *widget, gpointer data);
static void ImgViewToolBarToggleCB(GtkWidget *widget, gpointer data);
static void ImgViewValuesToggleCB(GtkWidget *widget, gpointer data);
static void ImgViewStatusBarToggleCB(GtkWidget *widget, gpointer data);
static void ImgViewQualityPoorCB(GtkWidget *widget, gpointer data);
static void ImgViewQualityOptimalCB(GtkWidget *widget, gpointer data);
static void ImgViewQualityBestCB(GtkWidget *widget, gpointer data);
static gint ImgViewInfoLabelCrossingCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
);
static gint ImgViewInfoLabelExposeCB(
	GtkWidget *widget, GdkEventExpose *expose, gpointer data
);
static void ImgViewAdjustmentValueChangedCB(
	GtkAdjustment *adjustment, gpointer data
);
static gint ImgViewViewEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);
static gint ImgViewDrawViewIdleCB(gpointer data);
static gint ImgViewNextFrameTOCB(gpointer data);

/* Drawing */
static void ImgViewInfoLabelDraw(imgview_struct *iv);
static void ImgViewDrawView(
	imgview_struct *iv, 
	gboolean blit_from_original,
	const GdkRectangle *area
);

/* Utilities and Operations */
static gint ImgViewRInt(gfloat x);
gint ImgViewConvertUnitViewToOrigX(imgview_struct *iv, gint x);
gint ImgViewConvertUnitViewToOrigY(imgview_struct *iv, gint y);
gint ImgViewConvertUnitOrigToViewX(imgview_struct *iv, gint x);
gint ImgViewConvertUnitOrigToViewY(imgview_struct *iv, gint y);
static void ImgViewRealizeChange(imgview_struct *iv);
/* static void ImgViewZoomIterate(imgview_struct *iv, const gint dz); */
static void ImgViewZoomIterateCoeff(imgview_struct *iv, const gfloat dc);
static void ImgViewZoomRectangular(
	imgview_struct *iv,
	gint x0, gint x1,
	gint y0, gint y1
);

static void ImgViewBlitView(imgview_struct *iv);
static void ImgViewWMIconUpdate(imgview_struct *iv);
static void ImgViewBufferRecreate(imgview_struct *iv);

/* Public */
GtkAccelGroup *ImgViewGetAccelGroup(imgview_struct *iv);
gboolean ImgViewToplevelIsWindow(imgview_struct *iv);
GtkWidget *ImgViewGetToplevelWidget(imgview_struct *iv);
GtkDrawingArea *ImgViewGetViewWidget(imgview_struct *iv);
GtkMenu *ImgViewGetMenuWidget(imgview_struct *iv);
gboolean ImgViewIsLoaded(imgview_struct *iv);
imgview_image_struct *ImgViewGetImage(imgview_struct *iv);
guint8 *ImgViewGetImageData(
	imgview_struct *iv, gint frame_num,
	gint *width, gint *height, gint *bpp, gint *bpl,
	imgview_format *format
);
gint ImgViewGetCurrentFrame(imgview_struct *iv);
gint ImgViewGetTotalFrames(imgview_struct *iv);

void ImgViewClear(imgview_struct *iv);

static gint ImgViewSetImageNexus(
	imgview_struct *iv, 
	imgview_image_struct *img,	/* Transfered */
	gboolean zoom_to_fit
);
gint ImgViewSetImage(
	imgview_struct *iv,
	imgview_image_struct *img	/* Transfered */
);
gint ImgViewSetImageToFit(
	imgview_struct *iv,
	imgview_image_struct *img	/* Transfered */
);

static gint ImgViewLoadNexus(
	imgview_struct *iv,
	gint width, gint height,
	gint bytes_per_line,	/* Can be 0 to auto calculate */
	imgview_format format,	/* One of IMGVIEW_FORMAT_* */
	const guint8 *data,
	gboolean zoom_to_fit
);
gint ImgViewLoad(
	imgview_struct *iv,
	gint width, gint height,
	gint bytes_per_line,	/* Can be 0 to auto calculate */
	imgview_format format,	/* One of IMGVIEW_FORMAT_* */
	const guint8 *data
);
gint ImgViewLoadToFit(
	imgview_struct *iv,
	gint width, gint height,
	gint bytes_per_line,	/* Can be 0 to auto calculate */
	imgview_format format,	/* One of IMGVIEW_FORMAT_* */
	const guint8 *data
);

void ImgViewPlay(imgview_struct *iv);
void ImgViewPause(imgview_struct *iv);
void ImgViewSeek(imgview_struct *iv, gint frame_num);
gboolean ImgViewIsPlaying(imgview_struct *iv);

imgview_struct *ImgViewNew(
	gboolean show_toolbar,
	gboolean show_values,
	gboolean show_statusbar,
	gboolean show_image_on_wm_icon,
	gint quality,		/* From 0 to 2 (2 being best/slowest) */
	gboolean toplevel_is_window,
	GtkWidget **toplevel_rtn
);
void ImgViewSetChangedCB(
	imgview_struct *iv,
	void (*changed_cb)(
		imgview_struct *,
		imgview_image_struct *,
		gpointer
	),
	gpointer data
);
void ImgViewReset(imgview_struct *iv, gboolean need_unmap);
void ImgViewQueueDrawView(imgview_struct *iv);
void ImgViewQueueSendView(imgview_struct *iv);
void ImgViewDraw(imgview_struct *iv);
void ImgViewUpdateMenus(imgview_struct *iv);
void ImgViewShowToolBar(imgview_struct *iv, gboolean show);
void ImgViewShowStatusBar(imgview_struct *iv, gboolean show);
void ImgViewSetViewBG(
	imgview_struct *iv,
	GdkColor *c		/* 5 colors */
);

void ImgViewZoomIn(imgview_struct *iv);
void ImgViewZoomOut(imgview_struct *iv);
void ImgViewZoomOneToOne(imgview_struct *iv);
void ImgViewZoomToFit(imgview_struct *iv);
void ImgViewZoomPrev(imgview_struct *iv);

void ImgViewAllowCrop(imgview_struct *iv, gboolean allow_crop);
void ImgViewSetBusy(imgview_struct *iv, gboolean busy);
void ImgViewProgressUpdate(
	imgview_struct *iv, gfloat position, gboolean allow_gtk_iteration
);
void ImgViewMap(imgview_struct *iv);
void ImgViewUnmap(imgview_struct *iv);
void ImgViewDelete(imgview_struct *iv);


#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 ABSOLUTE_VAL(x)	(((x) < 0) ? ((x) * -1) : (x))


/* Title */
#define IMGVIEW_TITLE			"Image Viewer"

/* Minimum width */
#define IMGVIEW_MIN_WIDTH		200
#define	IMGVIEW_MIN_HEIGHT		(gint)(IMGVIEW_MIN_WIDTH * 0.75)

/* WM icon size in pixels */
#define IMGVIEW_WM_ICON_WIDTH		48
#define IMGVIEW_WM_ICON_HEIGHT		48

/* Zoom out maximum */
#define IMGVIEW_ZOOM_MIN		0.01f

/* Zoom in maximum */
#define IMGVIEW_ZOOM_MAX		50.0f

/* Zoom rate (in coeff per cycle) */
#define IMGVIEW_ZOOM_RATE		0.0025f


/* Tool bar height in pixels */
#define IMGVIEW_TOOLBAR_HEIGHT		(16 + (2 * 2))

/* Status bar height in pixels */
#define IMGVIEW_STATUSBAR_HEIGHT	16

/* Zoom repeat timeout interval in ms */
#define IMGVIEW_ZOOM_TIMEOUT_INT		100
#define IMGVIEW_ZOOM_INITIAL_TIMEOUT_INT	300

/* Zoom iteration pixels for button or keypresses */
#define IMGVIEW_ZOOM_ITERATION_PIXELS		50

#if 0
/* Scissor Bitmap */
static guint8 scissor_16x16_bm[] = {
0x00, 0x01,
0x00, 0x03,
0x00, 0x03,
0x04, 0x03,
0x0c, 0x03,
0x18, 0x03,
0x30, 0x03,
0x60, 0x03,
0xc0, 0x03,
0x80, 0x1e,
0xc0, 0x23,
0x20, 0x45,
0x20, 0x49,
0x20, 0x31,
0x20, 0x01,
0xc0, 0x01
};
#endif

/* Exactoknife Bitmap */
static guint8 exactoknife_mask_16x16_bm[] = {
0x00, 0x00,
0x00, 0xc0,
0x00, 0xe0,
0x00, 0x70,
0x00, 0x38,
0x00, 0x1c,
0x00, 0x0e,
0x00, 0x07,
0x80, 0x03,
0xc0, 0x01,
0xe0, 0x00,
0x70, 0x00,
0x78, 0x00,
0x3c, 0x00,
0x1e, 0x00,
0x0f, 0x00
};
static guint8 exactoknife_16x16_bm[] = {
0xff, 0xff,
0xff, 0x3f,
0xff, 0x1f,
0xff, 0x8f,
0xff, 0xc7,
0xff, 0xe3,
0xff, 0xf1,
0xff, 0xf8,
0x7f, 0xfc,
0x3f, 0xfe,
0x5f, 0xff,
0xaf, 0xff,
0xb7, 0xff,
0xdb, 0xff,
0xed, 0xff,
0xf0, 0xff
};

/* Color Probe Bitmap */
static guint8 color_probe_mask_16x16_bm[] = {
0x00, 0x60,
0x00, 0xf0,
0x00, 0xfc,
0x00, 0x7e,
0x00, 0x3f,
0x80, 0x3f,
0xc0, 0x1f,
0xe0, 0x0f,
0xf0, 0x07,
0xf8, 0x03,
0xfc, 0x01,
0xfe, 0x00,
0x7e, 0x00,
0x3e, 0x00,
0x1e, 0x00,
0x01, 0x00
};
static guint8 color_probe_16x16_bm[] = {
0xff, 0x9f,
0xff, 0x2f,
0xff, 0x03,
0xff, 0x81,
0xff, 0xc6,
0x7f, 0xcf,
0xbf, 0xef,
0x5f, 0xf4,
0x2f, 0xfa,
0x17, 0xfd,
0x8b, 0xfe,
0x45, 0xff,
0xa1, 0xff,
0xd1, 0xff,
0xe1, 0xff,
0xfe, 0xff
};


/*
 *	Creates a new ImgView image.
 *
 *	If bpl is 0 then it will be set to width * bpp.
 */
imgview_image_struct *ImgViewImageNew(
	gint width, gint height, gint bpp, gint bpl
)
{
	imgview_image_struct *img = IMGVIEW_IMAGE(g_malloc0(
	    sizeof(imgview_image_struct)
	));
	if(img == NULL)
	    return(NULL);

	/* Need to autocalculate bytes per line? */
	if(bpl <= 0)
	{
#if 1
	    bpl = width * bpp;
#else
	    /* Align bytes per line to 4 bytes by incrementing it
	     * until it matches the next 4 byte align value
	     */
	    bpl = width * bpp;
	    if(bpl > 4)
	    {
		while((bpl % 4) != 0)
		    bpl++;
	    }
	    else
	    {
		bpl = 4;
	    }
#endif
	}

	if(bpl < (width * bpp))
	    g_printerr(
"ImgViewImageNew(): Warning: bpl (%i) is less than width * bpp (%i)\n",
		bpl,
		width * bpp
	    );

	img->width = width;
	img->height = height;
	img->bpp = bpp;
	img->bpl = bpl;
	img->frames_list = NULL;
	img->nframes = 0;

	return(img);
}

/*
 *	Copies the ImgView image.
 */
imgview_image_struct *ImgViewImageCopy(imgview_image_struct *img)
{
	gint frame_size;
	guint8 *buf;
	GList *glist;
	imgview_frame_struct *frame;
	imgview_image_struct	*tar_img,
				*src_img = img;
	if(src_img == NULL)
	    return(NULL);

	tar_img = ImgViewImageNew(
	    src_img->width, src_img->height,
	    src_img->bpp, src_img->bpl
	);
	if(tar_img == NULL)
	    return(NULL);

	/* Calculate the size of each frame's buffer */
	frame_size = src_img->bpl * src_img->height;

	/* Copy each frame */
	for(glist = src_img->frames_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    frame = IMGVIEW_FRAME(glist->data);
	    if(frame == NULL)
		continue;

	    if((frame->buf != NULL) && (frame_size > 0))
	    {
		buf = (guint8 *)g_malloc(frame_size);
		if(buf != NULL)
		    memcpy(buf, frame->buf, frame_size);
	    }
	    else
	    {
		buf = NULL;
	    }

	    ImgViewImageAppendFrame(
		tar_img, buf, frame->delay
	    );
	}

	return(tar_img);
}

/*
 *	Inserts a frame to the ImgView image.
 *
 *	The image specified by buf must match the size specified by
 *	the ImgView image and will be transfered to the new frame.
 *
 *	Returns the new frame number.
 */
gint ImgViewImageInsertFrame(
	imgview_image_struct *img, gint frame_num,
	guint8 *buf,			/* Transfered */
	gulong delay
)
{
	gint new_frame_num;
	imgview_frame_struct *frame;

	if(img == NULL)
	{
	    g_free(buf);
	    return(-1);
	}

	/* Allocate new frame */
	frame = IMGVIEW_FRAME(g_malloc0(
	    sizeof(imgview_frame_struct)
	));
	if(frame == NULL)
	{
	    g_free(buf);
	    return(-1);
	}

	/* Set new frame values */
	frame->buf = buf;               /* Transfer */
	frame->delay = delay;

	/* Append or insert? */
	if((frame_num < 0) || (frame_num >= img->nframes))
	{
	    /* Append */
	    img->frames_list = g_list_append(
		img->frames_list, frame
	    );
	    new_frame_num = img->nframes;
	}
	else
	{
	    /* Insert */
	    img->frames_list = g_list_insert(
		img->frames_list, frame, frame_num
	    );
	    new_frame_num = frame_num;
	}

	/* Update number of frames on the ImgView image */
	img->nframes = g_list_length(img->frames_list);

	return(new_frame_num);
}

/*
 *	Appends a frame to the ImgView image.
 *
 *	The image specified by buf must match the size specified by
 *	the ImgView image and will be transfered to the new frame.
 *
 *	Returns the new frame number.
 */
gint ImgViewImageAppendFrame(
	imgview_image_struct *img,
	guint8 *buf,			/* Transfered */
	gulong delay
)
{
	return(ImgViewImageInsertFrame(
	    img, -1,
	    buf, delay
	));
}

/*
 *	Appends a new frame to the ImgView image.
 *
 *	The buffer will be allocated but its contents are undefined.
 */
gint ImgViewImageAppendFrameNew(imgview_image_struct *img)
{
	gint size;

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

	size = img->bpl * img->height;

	return(ImgViewImageAppendFrame(
	    img,
	    (size > 0) ? (guint8 *)g_malloc(size) : NULL,
	    0l
	));
}

/*
 *	Deletes the ImgView image's frame.
 */
void ImgViewImageDeleteFrame(
	imgview_image_struct *img, gint frame_num
)
{
	GList *glist;
	imgview_frame_struct *frame;

	if((img == NULL) || (frame_num < 0))
	    return;

	glist = g_list_nth(img->frames_list, (guint)frame_num);
	if(glist == NULL)
	    return;

	frame = IMGVIEW_FRAME(glist->data);
	if(frame != NULL)
	{
	    g_free(frame->buf);
	    g_free(frame);
	}
	img->frames_list = g_list_remove(
	    img->frames_list, frame
	);
	img->nframes = g_list_length(img->frames_list);
}

/*
 *	Returns the ImgView image's frame.
 */
imgview_frame_struct *ImgViewImageGetFrame(
	imgview_image_struct *img, gint frame_num
)
{
	GList *glist = (img != NULL) ?
	    g_list_nth(img->frames_list, frame_num) : NULL;
	return((glist != NULL) ? IMGVIEW_FRAME(glist->data) : NULL);
}

/*
 *	Clear the ImgView image's frame with the pixel value specified
 *	by v.
 *
 *	The pixel value v must be in RGBA format.
 */
void ImgViewImageClear(
	imgview_image_struct *img, gint frame_num,
	guint32 v
)
{
	gint width, height, bpp, bpl;
	guint8 v8[3];
	guint8 *line_ptr, *line_end, *ptr, *ptr_end;
	guint8 *buf;
	imgview_frame_struct *frame = ImgViewImageGetFrame(img, frame_num);
	if(frame == NULL)
	    return;

	width = img->width,
	height = img->height;
	bpp = img->bpp;
	bpl = img->bpl;
	buf = frame->buf;

	switch(bpp)
	{
	  case 4:
	    for(line_ptr = buf,
		line_end = line_ptr + (bpl * height);
		line_ptr < line_end;
		line_ptr += bpl
	    )
	    {
		ptr = line_ptr;
		ptr_end = ptr + (width * bpp);
		while(ptr < ptr_end)
		{
		    *(guint32 *)ptr = v;
		    ptr += bpp;
		}
	    }
	    break;

	  case 3:
	    v8[0] = (guint8)((v & 0x000000ff) >> 0);
	    v8[1] = (guint8)((v & 0x0000ff00) >> 8);
	    v8[2] = (guint8)((v & 0x00ff0000) >> 16);
	    for(line_ptr = buf,
		line_end = line_ptr + (bpl * height);
		line_ptr < line_end;
		line_ptr += bpl
	    )
	    {
		ptr = line_ptr;
		ptr_end = ptr + (width * bpp);
		while(ptr < ptr_end)
		{
		    *ptr++ = v8[0];
		    *ptr++ = v8[1];
		    *ptr++ = v8[2];
		}
	    }
	    break;

	  case 2:
	    v8[0] = (guint8)((v & 0x000000ff) >> 0);
	    v8[1] = (guint8)((v & 0x0000ff00) >> 8);
	    for(line_ptr = buf,
		line_end = line_ptr + (bpl * height);
		line_ptr < line_end;
		line_ptr += bpl
	    )
	    {
		ptr = line_ptr;
		ptr_end = ptr + (width * bpp);
		while(ptr < ptr_end)
		{
		    *(guint16 *)ptr = *(guint16 *)v8;
		    ptr += bpp;
		}
	    }
	    break;

	  case 1:
	    v8[0] = (guint8)((v & 0x000000ff) >> 0);
	    for(line_ptr = buf,
		line_end = line_ptr + (bpl * height);
		line_ptr < line_end;
		line_ptr += bpl
	    )
	    {
		ptr = line_ptr;
		ptr_end = ptr + (width * bpp);
		while(ptr < ptr_end)
		{
		    *ptr++ = v8[0];
		}
	    }
	    break;
	}
}

/*
 *	Sends a rectangular area of the ImgView image to the
 *	GdkDrawable.
 */
void ImgViewImageSendRectangle(
	imgview_image_struct *img, gint frame_num,
	GdkDrawable *d, GdkGC *gc,
	gint quality,
	const GdkRectangle *rect
)
{
	guint8 *buf;
	GList *glist;
	GdkRgbDither dither_level = GDK_RGB_DITHER_NORMAL;
	GdkRectangle lrect;
	imgview_frame_struct *frame = ImgViewImageGetFrame(img, frame_num);
	if((frame == NULL) || (d == NULL) || (gc == NULL) ||
	   (rect == NULL)
	)
	    return;

	glist = g_list_nth(img->frames_list, (guint)frame_num);
	frame = (glist != NULL) ? IMGVIEW_FRAME(glist->data) : NULL;
	if(frame == NULL)
	    return;

	buf = frame->buf;
	if(buf == NULL)
	    return;

	switch(quality)
	{
	  case 2:
	    dither_level = GDK_RGB_DITHER_MAX;
	    break;
	  case 1:
	    dither_level = GDK_RGB_DITHER_NORMAL;
	    break;
	  default:
	    dither_level = GDK_RGB_DITHER_NONE;
	    break;
	}

/* Rectangles do not work too well, need to be upper left 0 0 oriented
 * So fix the rectangle values so they're upper left 0 0 oriented
 */
	memcpy(&lrect, rect, sizeof(GdkRectangle));
	lrect.width += lrect.x;
	lrect.height += lrect.y;
	lrect.x = 0;
	lrect.y = 0;

	switch(img->bpp)
	{
	  case 4:
	    gdk_draw_rgb_32_image(
		d, gc,
		lrect.x, lrect.y,
		lrect.width, lrect.height,
		dither_level,
		(guchar *)buf,
		img->bpl
	    );
	    break;

	  case 3:
	    gdk_draw_rgb_image(
		d, gc,
		lrect.x, lrect.y,
		lrect.width, lrect.height,
		dither_level,
		(guchar *)buf,
		img->bpl
	    );
	    break;
	}
}

/*
 *	Sends the ImgView image to the GdkDrawable.
 */
void ImgViewImageSend(
	imgview_image_struct *img, gint frame_num,
	GdkDrawable *d, GdkGC *gc, gint quality
)
{
	GdkRectangle rect;

	if((img == NULL) || (d == NULL) || (gc == NULL))
	    return;

	rect.x = 0;
	rect.y = 0;
	rect.width = img->width;
	rect.height = img->height;

	ImgViewImageSendRectangle(img, frame_num, d, gc, quality, &rect);
}

/*
 *	Deletes the ImgView image.
 */
void ImgViewImageDelete(imgview_image_struct *img)
{
	GList *glist;
	imgview_frame_struct *frame;

	if(img == NULL)
	    return;

	for(glist = img->frames_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    frame = IMGVIEW_FRAME(glist->data);
	    if(frame == NULL)
		continue;

	    g_free(frame->buf);
	    g_free(frame);
	}
	g_list_free(img->frames_list);

	g_free(img);
}


/*
 *	ImgView toplevel GtkWindow "delete_event" signal callback.
 */
static gint ImgViewDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return(FALSE);

#if 0
/* Let calling function determine what close does, do not unmap or
 * do anything here!
 */
	ImgViewUnmap(iv);
#endif
	return(TRUE);
}

/*
 *	Zoom In GtkButton "pressed" signal callback.
 */
static void ImgViewZoomInPressedCB(GtkWidget *widget, gpointer data)
{
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	iv->view_zoom_idle = gtk_idle_add_priority(
	    G_PRIORITY_HIGH,
	    ImgViewZoomInTOCB, iv
	);
}

/*
 *	Zoom In idle or timeout callback.
 */
static gint ImgViewZoomInTOCB(gpointer data)
{
	gboolean is_initial = FALSE;
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return(FALSE);

	iv->view_zoom_idle = 0;

	/* Zoom in */
/*	ImgViewZoomIterate(iv, -IMGVIEW_ZOOM_ITERATION_PIXELS); */
	ImgViewZoomIterateCoeff(iv, 0.1f);

	/* Clip bounds, update adjustments and redraw */
	ImgViewRealizeChange(iv);
	ImgViewQueueDrawView(iv);

	/* Flush output and reset timeout for next iteration */
	if(iv->view_zoom_toid > 0)
	    gtk_timeout_remove(iv->view_zoom_toid);
	else
	    is_initial = TRUE;
	iv->view_zoom_toid = gtk_timeout_add(
	    is_initial ?
		IMGVIEW_ZOOM_INITIAL_TIMEOUT_INT : IMGVIEW_ZOOM_TIMEOUT_INT,
	    ImgViewZoomInTOCB, iv
	);

	return(FALSE);
}

/*
 *	Zoom Out GtkButton "pressed" signal callback.
 */
static void ImgViewZoomOutPressedCB(GtkWidget *widget, gpointer data)
{
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	iv->view_zoom_idle = gtk_idle_add_priority(
	    G_PRIORITY_HIGH,
	    ImgViewZoomOutTOCB, iv
	);
}

/*
 *	Zoom Out idle or timeout callback.
 */
static gint ImgViewZoomOutTOCB(gpointer data)
{
	gboolean is_initial = FALSE;
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return(FALSE);

	iv->view_zoom_idle = 0;

	/* Zoom out */
/*	ImgViewZoomIterate(iv, IMGVIEW_ZOOM_ITERATION_PIXELS); */
	ImgViewZoomIterateCoeff(iv, -0.1f);

	/* Clip bounds, update adjustments and redraw */
	ImgViewRealizeChange(iv);
	ImgViewQueueDrawView(iv);

	/* Flush output and reset timeout for next iteration */
	if(iv->view_zoom_toid > 0)
	    gtk_timeout_remove(iv->view_zoom_toid);
	else
	    is_initial = TRUE;
	iv->view_zoom_toid = gtk_timeout_add(
	    is_initial ? 
		IMGVIEW_ZOOM_INITIAL_TIMEOUT_INT : IMGVIEW_ZOOM_TIMEOUT_INT,
	    ImgViewZoomOutTOCB, iv
	);

	return(FALSE);
}

/*
 *	Zoom In or Zoom Out GtkButton "released" signal callback.
 */
static void ImgViewZoomReleasedCB(GtkWidget *widget, gpointer data)
{
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	/* Remove zoom idle and timeout */
	GTK_IDLE_REMOVE(iv->view_zoom_idle);
	iv->view_zoom_idle = 0;
	GTK_TIMEOUT_REMOVE(iv->view_zoom_toid);
	iv->view_zoom_toid = 0;
}


/*
 *	Zoom one to one callback.
 */
static void ImgViewZoomOneToOneCB(GtkWidget *widget, gpointer data)
{
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->orig_img == NULL)
	    return;

	/* Record current zoom and set new zoom */
	iv->last_view_x = GTK_ADJUSTMENT_GET_VALUE(iv->view_x_adj);
	iv->last_view_y = GTK_ADJUSTMENT_GET_VALUE(iv->view_y_adj);
	iv->view_last_zoom = iv->view_zoom;
	iv->view_zoom = 1.0f;

	/* Clip bounds, update adjustments and redraw */
	ImgViewRealizeChange(iv);
	ImgViewQueueDrawView(iv);
}

/*
 *	Zoom to fit callback.
 */
static void ImgViewZoomToFitCB(GtkWidget *widget, gpointer data)
{
	gint src_width, src_height;
	gfloat zoom;
	const GtkAdjustment *xadj, *yadj;
	imgview_image_struct *tar_img;
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->orig_img == NULL)
	    return;

	xadj = iv->view_x_adj;
	yadj = iv->view_y_adj;
	tar_img = iv->view_img;

	if((xadj == NULL) || (yadj == NULL) || (tar_img == NULL))
	    return;

	if((tar_img->width <= 0) || (tar_img->height <= 0))
	    return;

	src_width = (gint)(xadj->upper - xadj->lower);
	src_height = (gint)(yadj->upper - yadj->lower);
	if((src_width <= 0) || (src_height <= 0))
	    return;

	/* Calculate zoom in order to fit the target image on to the
	 * size of the view
	 */
	zoom = (gfloat)tar_img->width / (gfloat)src_width;
	if(zoom <= 0.0f)
	    return;

	if((tar_img->height / zoom) < src_height)
	    zoom = (gfloat)tar_img->height / (gfloat)src_height;

	/* Record current zoom and set new zoom */
	iv->last_view_x = GTK_ADJUSTMENT_GET_VALUE(xadj);
	iv->last_view_y = GTK_ADJUSTMENT_GET_VALUE(yadj);
	iv->view_last_zoom = iv->view_zoom;
	iv->view_zoom = zoom;

	/* Clip bounds, update adjustments and redraw */
	ImgViewRealizeChange(iv);
	ImgViewQueueDrawView(iv);
}

/*
 *	Restore Previous Zoom callback.
 */
static void ImgViewZoomPrevCB(GtkWidget *widget, gpointer data)
{
	gfloat zoom;
	gint x, y;
	GtkAdjustment *xadj, *yadj;
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	xadj = iv->view_x_adj;
	yadj = iv->view_y_adj;
	if((xadj == NULL) || (yadj == NULL) || (iv->orig_img == NULL))
	    return;

	/* Record current zoom and restore previous zoom */
	zoom = iv->view_last_zoom;
	x = (gint)iv->last_view_x;
	y = (gint)iv->last_view_y;

	/* Record current zoom and set new zoom */
	iv->last_view_x = GTK_ADJUSTMENT_GET_VALUE(xadj);
	iv->last_view_y = GTK_ADJUSTMENT_GET_VALUE(yadj);
	iv->view_last_zoom = iv->view_zoom;
	iv->view_zoom = zoom;
	xadj->value = (gfloat)x;
	yadj->value = (gfloat)y;

	/* Clip bounds, update adjustments and redraw */
	ImgViewRealizeChange(iv);
	ImgViewQueueDrawView(iv);
}

/*
 *	Rotate ClockWise 90 Degrees callback.
 */
static void ImgViewRotateCW90CB(GtkWidget *widget, gpointer data)
{
	gboolean was_playing;
	guint8 *tar_buf;
	GList *glist;
	imgview_frame_struct *frame;
	imgview_image_struct *src_img, *tar_img;
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->freeze_count > 0)
	    return;

	if(!ImgViewIsLoaded(iv))
	    return;

	was_playing = ImgViewIsPlaying(iv);

	/* Get the current image as the source image */
	src_img = ImgViewGetImage(iv);
	if(src_img == NULL)
	    return;

	/* Create the target image */
	tar_img = ImgViewImageNew(
	    src_img->height, src_img->width,	/* Rotated size */
	    src_img->bpp, 0
	);
	if(tar_img == NULL)
	    return;

	/* Copy/rotate each frame to the target image */
	for(glist = src_img->frames_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    frame = IMGVIEW_FRAME(glist->data);
	    if(frame == NULL)
		continue;

	    tar_buf = (guint8 *)g_malloc(
		tar_img->bpl * tar_img->height
	    );
	    GUIImageBufferRotateCW90(
		src_img->bpp,
		frame->buf,
		src_img->width, src_img->height, src_img->bpl,
		tar_buf,
		tar_img->width, tar_img->height, tar_img->bpl
	    );
	    ImgViewImageAppendFrame(
		tar_img,
		tar_buf, frame->delay
	    );
	}

	/* Set the rotated target image as the new image and zoom to
	 * fit, the source and target images should no longer be
	 * referenced
	 */
	ImgViewSetImageToFit(iv, tar_img);

	if(was_playing)
	    ImgViewPlay(iv);

	/* Report change */
	if(iv->changed_cb != NULL)
	    iv->changed_cb(
		iv,			/* ImgView */
		iv->orig_img,		/* Image */
		iv->changed_data	/* Data */
	    );
}

/*
 *	Rotate Counter ClockWise 90 Degrees callback.
 */
static void ImgViewRotateCCW90CB(GtkWidget *widget, gpointer data)
{
	gboolean was_playing;
	guint8 *tar_buf;
	GList *glist;
	imgview_frame_struct *frame;
	imgview_image_struct *src_img, *tar_img;
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->freeze_count > 0)
	    return;

	if(!ImgViewIsLoaded(iv))
	    return;

	was_playing = ImgViewIsPlaying(iv);

	/* Get the current image as the source image */
	src_img = ImgViewGetImage(iv);
	if(src_img == NULL)
	    return;

	/* Create the target image */
	tar_img = ImgViewImageNew(
	    src_img->height, src_img->width,	/* Rotated size */
	    src_img->bpp, 0
	);
	if(tar_img == NULL)
	    return;

	/* Copy/rotate each frame to the target image */
	for(glist = src_img->frames_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    frame = IMGVIEW_FRAME(glist->data);
	    if(frame == NULL)
		continue;

	    tar_buf = (guint8 *)g_malloc(
		tar_img->bpl * tar_img->height
	    );
	    GUIImageBufferRotateCCW90(
		src_img->bpp,
		frame->buf,
		src_img->width, src_img->height, src_img->bpl,
		tar_buf,
		tar_img->width, tar_img->height, tar_img->bpl
	    );
	    ImgViewImageAppendFrame(
		tar_img,
		tar_buf, frame->delay
	    );
	}

	/* Set the rotated target image as the new image and zoom to
	 * fit, the source and target images should no longer be
	 * referenced
	 */
	ImgViewSetImageToFit(iv, tar_img);

	if(was_playing)
	    ImgViewPlay(iv);

	/* Report change */
	if(iv->changed_cb != NULL)
	    iv->changed_cb(
		iv,			/* ImgView */
		iv->orig_img,		/* Image */
		iv->changed_data	/* Data */
	    );
}

/*
 *	Rotate ClockWise 180 Degrees callback.
 */
static void ImgViewRotateCW180CB(GtkWidget *widget, gpointer data)
{
	gboolean was_playing;
	guint8 *tar_buf;
	GList *glist;
	imgview_frame_struct *frame;
	imgview_image_struct *src_img, *tar_img;
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->freeze_count > 0)
	    return;

	if(!ImgViewIsLoaded(iv))
	    return;

	was_playing = ImgViewIsPlaying(iv);

	/* Get the current image as the source image */
	src_img = ImgViewGetImage(iv);
	if(src_img == NULL)
	    return;

	/* Create the target image */
	tar_img = ImgViewImageNew(
	    src_img->width, src_img->height,	/* Rotated size */
	    src_img->bpp, 0
	);
	if(tar_img == NULL)
	    return;

	/* Copy/rotate each frame to the target image */
	for(glist = src_img->frames_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    frame = IMGVIEW_FRAME(glist->data);
	    if(frame == NULL)
		continue;

	    tar_buf = (guint8 *)g_malloc(
		tar_img->bpl * tar_img->height
	    );
	    GUIImageBufferRotateCW180(
		src_img->bpp,
		frame->buf,
		src_img->width, src_img->height, src_img->bpl,
		tar_buf,
		tar_img->width, tar_img->height, tar_img->bpl
	    );
	    ImgViewImageAppendFrame(
		tar_img,
		tar_buf, frame->delay
	    );
	}

	/* Set the rotated target image as the new image and zoom to
	 * fit, the source and target images should no longer be
	 * referenced
	 */
	ImgViewSetImageToFit(iv, tar_img);

	if(was_playing)
	    ImgViewPlay(iv);

	/* Report change */
	if(iv->changed_cb != NULL)
	    iv->changed_cb(
		iv,			/* ImgView */
		iv->orig_img,		/* Image */
		iv->changed_data	/* Data */
	    );
}

/*
 *	Mirror Horizontal callback.
 */
static void ImgViewMirrorHorizontalCB(GtkWidget *widget, gpointer data)
{
	gboolean was_playing;
	guint8 *tar_buf;
	GList *glist;
	imgview_frame_struct *frame;
	imgview_image_struct *src_img, *tar_img;
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->freeze_count > 0)
	    return;

	if(!ImgViewIsLoaded(iv))
	    return;

	was_playing = ImgViewIsPlaying(iv);

	/* Get the current image as the source image */
	src_img = ImgViewGetImage(iv);
	if(src_img == NULL)
	    return;

	/* Create the target image */
	tar_img = ImgViewImageNew(
	    src_img->width, src_img->height,
	    src_img->bpp, 0
	); 
	if(tar_img == NULL)
	    return;

	/* Copy/mirror each frame to the target image */
	for(glist = src_img->frames_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    frame = IMGVIEW_FRAME(glist->data);
	    if(frame == NULL)
		continue;

	    tar_buf = (guint8 *)g_malloc(
		tar_img->bpl * tar_img->height
	    );
	    GUIImageBufferMirrorH(
		src_img->bpp,
		frame->buf,
		src_img->width, src_img->height, src_img->bpl,
		tar_buf,
		tar_img->width, tar_img->height, tar_img->bpl
	    );
	    ImgViewImageAppendFrame(
		tar_img,
		tar_buf, frame->delay
	    );
	}

	/* Set the mirrored target image as the new image and zoom to
	 * fit, the source and target images should no longer be
	 * referenced
	 */
	ImgViewSetImageToFit(iv, tar_img);

	if(was_playing)
	    ImgViewPlay(iv);

	/* Report change */
	if(iv->changed_cb != NULL)
	    iv->changed_cb(
		iv,			/* ImgView */
		iv->orig_img,		/* Image */
		iv->changed_data	/* Data */
	    );
}

/*
 *	Mirror Vertical callback.
 */
static void ImgViewMirrorVerticalCB(GtkWidget *widget, gpointer data)
{
	gboolean was_playing;
	guint8 *tar_buf;
	GList *glist;
	imgview_frame_struct *frame;
	imgview_image_struct *src_img, *tar_img;
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->freeze_count > 0)
	    return;

	if(!ImgViewIsLoaded(iv))
	    return;

	was_playing = ImgViewIsPlaying(iv);

	/* Get the current image as the source image */
	src_img = ImgViewGetImage(iv);
	if(src_img == NULL)
	    return;

	/* Create the target image */
	tar_img = ImgViewImageNew(
	    src_img->width, src_img->height,
	    src_img->bpp, 0
	);
	if(tar_img == NULL)
	    return;

	/* Copy/mirror each frame to the target image */
	for(glist = src_img->frames_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    frame = IMGVIEW_FRAME(glist->data);
	    if(frame == NULL)
		continue;

	    tar_buf = (guint8 *)g_malloc(
		tar_img->bpl * tar_img->height
	    );
	    GUIImageBufferMirrorV(
		src_img->bpp,
		frame->buf,
		src_img->width, src_img->height, src_img->bpl,
		tar_buf,
		tar_img->width, tar_img->height, tar_img->bpl
	    );
	    ImgViewImageAppendFrame(
		tar_img,
		tar_buf, frame->delay
	    );
	}

	/* Set the mirrored target image as the new image and zoom to
	 * fit, the source and target images should no longer be
	 * referenced
	 */
	ImgViewSetImageToFit(iv, tar_img);

	if(was_playing)
	    ImgViewPlay(iv);

	/* Report change */
	if(iv->changed_cb != NULL)
	    iv->changed_cb(
		iv,			/* ImgView */
		iv->orig_img,		/* Image */
		iv->changed_data	/* Data */
	    );
}


/*
 *	Show Tool Bar toggle callback.
 */
static void ImgViewToolBarToggleCB(GtkWidget *widget, gpointer data)
{
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->freeze_count > 0)
	    return;

	ImgViewShowToolBar(iv, !iv->toolbar_map_state);
}

/*
 *	Show Values toggle callback.
 */
static void ImgViewValuesToggleCB(GtkWidget *widget, gpointer data)
{
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->freeze_count > 0)
	    return;

	iv->show_values = !iv->show_values;
	ImgViewInfoLabelDraw(iv);	/* Redraw info_label */
	ImgViewUpdateMenus(iv);
	ImgViewQueueDrawView(iv);
}

/*
 *	Show Status Bar toggle callback.
 */
static void ImgViewStatusBarToggleCB(GtkWidget *widget, gpointer data)
{
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->freeze_count > 0)
	    return;

	ImgViewShowStatusBar(iv, !iv->statusbar_map_state);
}

/*
 *	Set quality to poor/fastest callback.
 */
static void ImgViewQualityPoorCB(GtkWidget *widget, gpointer data)
{
	const gint new_quality = 0;
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->freeze_count > 0)
	    return;

	if(iv->quality != new_quality)
	{
	    iv->quality = new_quality;

	    /* Update menus and redraw view to reflect new quality */
	    ImgViewUpdateMenus(iv);
	    ImgViewQueueDrawView(iv);
	}
}

/*
 *	Set quality to optimal callback.
 */
static void ImgViewQualityOptimalCB(GtkWidget *widget, gpointer data)
{
	const gint new_quality = 1;
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->freeze_count > 0)
	    return;

	if(iv->quality != new_quality)
	{
	    iv->quality = new_quality;

	    /* Update menus and redraw view to reflect new quality */
	    ImgViewUpdateMenus(iv);
	    ImgViewQueueDrawView(iv);
	}
}

/*
 *	Set quality to best/slowest callback.
 */
static void ImgViewQualityBestCB(GtkWidget *widget, gpointer data)
{
	const gint new_quality = 2;
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->freeze_count > 0)
	    return;

	if(iv->quality != new_quality)
	{
	    iv->quality = new_quality;

	    /* Update menus and redraw view to reflect new quality */
	    ImgViewUpdateMenus(iv);
	    ImgViewQueueDrawView(iv);
	}
}

/*
 *	ImgView Info Label GtkDrawingArea "enter_notify_event" and
 *	"leave_notify_event" signal callbacks.
 */
static gint ImgViewInfoLabelCrossingCB(
	GtkWidget *widget, GdkEventCrossing *crossing, gpointer data
)
{
	gchar text[256];
	GdkVisual *vis;
	const gchar *vis_name;
	gint depth;
	GdkWindow *window;
	GtkAdjustment *xadj, *yadj;
	GtkWidget *w;
	imgview_image_struct *src_img;
	imgview_struct *iv = IMGVIEW(data);
	if((widget == NULL) || (crossing == NULL) || (iv == NULL))
	    return(FALSE);

	/* Skip "leave_notify_event" events */
	if(crossing->type == GDK_LEAVE_NOTIFY)
	    return(TRUE);

	/* Is this event sent synthemtically (when we called 
	 * GUIShowTipsNow() below? If it is then skip it
	 */
	if(crossing->send_event)
	    return(TRUE);

	/* Get values from ImgView */
	xadj = iv->view_x_adj;
	yadj = iv->view_y_adj;
	w = iv->view_da;
	if((xadj == NULL) || (yadj == NULL) || (w == NULL))
	    return(TRUE);

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

	/* Get visual */
	vis_name = "";
	depth = 0;
	vis = gdk_window_get_visual(window);
	if(vis != NULL)
	{
	    switch((gint)vis->type)
	    {
	      case GDK_VISUAL_STATIC_GRAY:
		vis_name = "StaticGray";
		break;
	      case GDK_VISUAL_GRAYSCALE:
		vis_name = "GrayScale";
		break;
	      case GDK_VISUAL_STATIC_COLOR:
		vis_name = "StaticColor";
		break;
	      case GDK_VISUAL_PSEUDO_COLOR:
		vis_name = "PseudoColor";
		break;
	      case GDK_VISUAL_TRUE_COLOR:
		vis_name = "TrueColor";
		break;
	      case GDK_VISUAL_DIRECT_COLOR:
		vis_name = "DirectColor";
		break;
	    }
	    depth = vis->depth;
	}

	/* Check if original image is loaded */
	src_img = iv->orig_img;
	if((src_img == NULL) || !iv->show_values)
	{
	    GUISetWidgetTip(widget, NULL);
	    return(TRUE);
	}

	g_snprintf(
	    text, sizeof(text),
	    "Translate:%i,%i Size:%ix%i Zoom:%.0f%% Visual:%s Depth:%i",
	    (gint)xadj->value,
	    (gint)yadj->value,
	    (gint)(xadj->upper - xadj->lower),
	    (gint)(yadj->upper - yadj->lower),
	    iv->view_zoom * 100.0f,
	    vis_name,
	    depth
	);

	/* Update tip */
	GUISetWidgetTip(widget, text);
	GUIShowTipsNow(widget);

	return(TRUE);
}

/*
 *	ImgView Info Label GtkDrawingArea "expose_event" signal
 *	callback.
 */
static gint ImgViewInfoLabelExposeCB(
	GtkWidget *widget, GdkEventExpose *expose, gpointer data
)
{
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return(FALSE);

	ImgViewInfoLabelDraw(iv);

	return(TRUE);
}

/*
 *	ImgView GtkAdjustment "value_changed" signal callback.
 *
 *	This does not watch for the "changed" signal which is emitted
 *	when one of the ImgView's adjustments is updated by the 
 *	program (if from a pointer drag). This only checks if one of the
 *	scroll bar widgets was moved by the user.
 */
static void ImgViewAdjustmentValueChangedCB(
	GtkAdjustment *adjustment, gpointer data
)
{
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return;

	if(iv->freeze_count > 0)
	    return;

	/* Redraw info label */
	ImgViewInfoLabelDraw(iv);

	/* Redraw view when an adjustment has changed */
	ImgViewQueueDrawView(iv);
}

/*
 *	ImgView view GtkDrawingArea event signal callback.
 */
static gint ImgViewViewEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	gint etype, status = FALSE;
	gboolean need_grab_pointer = FALSE;
	imgview_image_struct *src_img, *tar_img;
	GdkCursor *cur = NULL;
	GdkWindow *window;
	GtkAdjustment *xadj, *yadj;
	GdkEventConfigure *configure;
	GdkEventExpose *expose;
	GdkEventKey *key;
	GdkEventButton *button;
	GdkEventMotion *motion;
	GdkEventCrossing *crossing;
	gboolean keypress;
	guint keyval, keymods;
	gint x, y;
	GdkModifierType mask;
	imgview_struct *iv = IMGVIEW(data);
	if((widget == NULL) || (event == NULL) || (iv == NULL))
	    return(status);

	/* Get view adjustments (may be NULL) */
	xadj = iv->view_x_adj;
	yadj = iv->view_y_adj;

	/* Get source and target images (may be NULL) */
	src_img = iv->orig_img;
	tar_img = iv->view_img;

	window = widget->window;

	/* Handle by event type */
	etype = (gint)event->type;
	switch(etype)
	{
	  case GDK_CONFIGURE:
	    configure = (GdkEventConfigure *)event;
	    /* Recreate the view image to match the new size of the
	     * view widget
	     */
	    ImgViewBufferRecreate(iv);
	    /* Update adjustments to reflect the new size and redraw */
	    ImgViewRealizeChange(iv);
	    ImgViewQueueDrawView(iv);
	    status = TRUE;
	    break;

	  case GDK_EXPOSE:
	    expose = (GdkEventExpose *)event;
	    /* Send the view image to the view's GdkDrawable and
	     * draw any markings
	     */
	    ImgViewDrawView(iv, FALSE, NULL);
	    status = TRUE;
	    break;

	  case GDK_ENTER_NOTIFY:
	    crossing = (GdkEventCrossing *)event;
	    keymods = crossing->state;
	    if(!(keymods & GDK_CONTROL_MASK) &&
	       !(keymods & GDK_MOD1_MASK) &&
	       !(keymods & GDK_SHIFT_MASK)
	    )
	    {
		/* When no key modifiers are held reset the window's
		 * cursor
		 */
		if(window != NULL)
		    gdk_window_set_cursor(window, NULL);
	    }
	    status = TRUE;
	    break;

	  case GDK_LEAVE_NOTIFY:
	    crossing = (GdkEventCrossing *)event;
	    status = TRUE;
	    break;

	  case GDK_KEY_PRESS:
	  case GDK_KEY_RELEASE:
#define SIGNAL_STOP_EMIT	{		\
 gtk_signal_emit_stop_by_name(			\
  GTK_OBJECT(widget),				\
   keypress ?					\
    "key_press_event" : "key_release_event"	\
 );						\
}
	    key = (GdkEventKey *)event;
	    keypress = ((etype == GDK_KEY_PRESS) ? TRUE : FALSE);
	    keyval = key->keyval;
	    keymods = key->state;
	    /* Handle by keyval */
	    /* Alt */
	    if((keyval == GDK_Alt_L) || (keyval == GDK_Alt_R))
	    {
		if(keypress)
		    keymods |= GDK_MOD1_MASK;
		else
		    keymods &= ~GDK_MOD1_MASK;
		status = TRUE;
	    }
	    /* Ctrl */
	    else if((keyval == GDK_Control_L) || (keyval == GDK_Control_R))
	    {
		if(keypress)
		    keymods |= GDK_CONTROL_MASK;
		else
		    keymods &= ~GDK_CONTROL_MASK;
		status = TRUE;
	    }
	    /* Shift */
	    else if((keyval == GDK_Shift_L) || (keyval == GDK_Shift_R))
	    {
		if(keypress)
		    keymods |= GDK_SHIFT_MASK;
		else
		    keymods &= ~GDK_SHIFT_MASK;
		status = TRUE;
	    }
	    /* Zoom In */
	    else if((keyval == GDK_plus) || (keyval == GDK_equal) ||
	            (keyval == GDK_KP_Add)
	    )
	    {
		if(keypress)
		{
/*
		    ImgViewZoomIterate(
			iv, -IMGVIEW_ZOOM_ITERATION_PIXELS
		    );
 */
		    ImgViewZoomIterateCoeff(iv, 0.1f);
		    ImgViewRealizeChange(iv);
		    ImgViewQueueDrawView(iv);
		}
		status = TRUE;
	    }
	    /* Zoom In Fast */
	    else if((keyval == GDK_Page_Up) ||
		    (keyval == GDK_KP_Page_Up)
	    )
	    {
		if(keypress)
		{
/*
		    ImgViewZoomIterate(
			iv, -IMGVIEW_ZOOM_ITERATION_PIXELS * 4
		    );
 */
		    ImgViewZoomIterateCoeff(iv, 0.25f);
		    ImgViewRealizeChange(iv);
		    ImgViewQueueDrawView(iv);
		}
		status = TRUE;
	    }
	    /* Zoom Out */
	    else if((keyval == GDK_minus) || (keyval == GDK_underscore) ||
		    (keyval == GDK_KP_Subtract)
	    )
	    {
		if(keypress)
		{
/*
		    ImgViewZoomIterate(
			iv, IMGVIEW_ZOOM_ITERATION_PIXELS
		    );
 */
		    ImgViewZoomIterateCoeff(iv, -0.1f);
		    ImgViewRealizeChange(iv);
		    ImgViewQueueDrawView(iv);
		}
		status = TRUE;
	    }
	    /* Zoom Out Fast */
	    else if((keyval == GDK_Page_Down) ||
		    (keyval == GDK_KP_Page_Down)
	    )
	    {
		if(keypress)
		{
/*
		    ImgViewZoomIterate(
			iv, IMGVIEW_ZOOM_ITERATION_PIXELS * 4
		    );
 */
		    ImgViewZoomIterateCoeff(iv, -0.25f);
		    ImgViewRealizeChange(iv);
		    ImgViewQueueDrawView(iv);
		}
		status = TRUE;
	    }
	    /* Zoom Previous */
	    else if(keyval == GDK_BackSpace)
	    {
		if(keypress)
		{
		    ImgViewZoomPrevCB(NULL, data);
		}
		status = TRUE;
	    }
	    /* Translate Up */
	    else if((keyval == GDK_Up) || (keyval == GDK_KP_Up))
	    {
		if(keypress && (xadj != NULL) && (yadj != NULL))
		{
		    gfloat zoom = iv->view_zoom;
		    const gfloat zoom_min = IMGVIEW_ZOOM_MIN;

		    /* Sanitize current zoom */
		    if(zoom < zoom_min)
			zoom = zoom_min;

		    /* Translate */
		    yadj->value -= (gfloat)yadj->step_increment / zoom;

		    ImgViewRealizeChange(iv);
		    ImgViewQueueDrawView(iv);
		}
		SIGNAL_STOP_EMIT
		status = TRUE;
	    }
	    /* Translate Down */
	    else if((keyval == GDK_Down) || (keyval == GDK_KP_Down))
	    {
		if(keypress && (xadj != NULL) && (yadj != NULL))
		{
		    gfloat zoom = iv->view_zoom;
		    const gfloat zoom_min = IMGVIEW_ZOOM_MIN;

		    /* Sanitize current zoom */
		    if(zoom < zoom_min)
			zoom = zoom_min;

		    /* Translate */
		    yadj->value += (gfloat)yadj->step_increment / zoom;

		    ImgViewRealizeChange(iv);
		    ImgViewQueueDrawView(iv);
		}
		SIGNAL_STOP_EMIT
		status = TRUE;
	    }
	    /* Translate Left */
	    else if((keyval == GDK_Left) || (keyval == GDK_KP_Left))
	    {
		if(keypress && (xadj != NULL) && (yadj != NULL))
		{
		    gfloat zoom = iv->view_zoom;
		    const gfloat zoom_min = IMGVIEW_ZOOM_MIN;

		    /* Sanitize current zoom */
		    if(zoom < zoom_min)
			zoom = zoom_min;

		    /* Translate */
		    xadj->value -= (gfloat)xadj->step_increment / zoom;

		    ImgViewRealizeChange(iv);
		    ImgViewQueueDrawView(iv);
		}
		SIGNAL_STOP_EMIT
		status = TRUE;
	    }
	    /* Translate Right */
	    else if((keyval == GDK_Right) || (keyval == GDK_KP_Right))
	    {
		if(keypress && (xadj != NULL) && (yadj != NULL))
		{
		    gfloat zoom = iv->view_zoom;
		    const gfloat zoom_min = IMGVIEW_ZOOM_MIN;

		    /* Sanitize current zoom */
		    if(zoom < zoom_min)
			zoom = zoom_min;

		    /* Translate */
		    xadj->value += (gfloat)xadj->step_increment / zoom;

		    ImgViewRealizeChange(iv);
		    ImgViewQueueDrawView(iv);
		}
		SIGNAL_STOP_EMIT
		status = TRUE;
	    }
	    /* Zoom To Fit */
	    else if((keyval == GDK_End) || (keyval == GDK_KP_End))
	    {
		if(keypress)
		{
		    ImgViewZoomToFitCB(widget, iv);
		}
		status = TRUE;
	    }
	    /* Zoom One To One */
	    else if((keyval == GDK_Home) || (keyval == GDK_KP_Home))
	    {
		if(keypress)
		{
		    ImgViewZoomOneToOneCB(widget, iv);
		}
		status = TRUE;
	    }
	    /* Zoom in to units of 100%'s with respect to number keys,
	     * where 2 = 200%, 4 = 400%, etc... keys 1-9 are supported
	     */
	    else if((keyval >= '1') && (keyval <= '9'))
	    {
		if(keypress)
		{
		    /* Record current zoom and set new zoom */
		    iv->last_view_x = GTK_ADJUSTMENT_GET_VALUE(xadj);
		    iv->last_view_y = GTK_ADJUSTMENT_GET_VALUE(yadj);
		    iv->view_last_zoom = iv->view_zoom;
		    if(keymods & GDK_SHIFT_MASK)
			iv->view_zoom = 1.0f /
			    MAX((gfloat)(keyval - '1') + 1.0f, 1.0f);
		    else
			iv->view_zoom =
			    MAX((gfloat)(keyval - '1') + 1.0f, 1.0f);
		    ImgViewRealizeChange(iv);
		    ImgViewQueueDrawView(iv);
		}
		status = TRUE;
	    }

	    /* Update cursor based on current modifier */
	    if(status)
	    {
		/* Color Probe: CTRL + ALT */
		if((keymods & GDK_MOD1_MASK) &&
		   (keymods & GDK_CONTROL_MASK)
		)
		    cur = iv->color_probe_cur;
		/* Crop Rectangular: CTRL + SHIFT (and crop allowed) */
		else if((keymods & GDK_CONTROL_MASK) &&
		        (keymods & GDK_SHIFT_MASK) &&
		        (iv->crop_flags & IMGVIEW_CROP_ALLOWED)
		)
		    cur = iv->crop_cur;
		/* Zoom: ALT */
		else if(keymods & GDK_MOD1_MASK)
		    cur = iv->zoom_cur;
		/* Zoom Rectangular: SHIFT */
		else if(keymods & GDK_SHIFT_MASK)
		    cur = iv->zoom_rectangle_cur;
		/* Translate: CTRL */
		else if(keymods & GDK_CONTROL_MASK)
		    cur = iv->translate_cur;

		/* Update cursor only if not dragging */
		if((window != NULL) &&
		   (iv->drag_mode == IMGVIEW_DRAG_MODE_NONE)
		)
		    gdk_window_set_cursor(window, cur);
	    }
#undef SIGNAL_STOP_EMIT
	    break;

	  case GDK_BUTTON_PRESS:
	    button = (GdkEventButton *)event;
	    keymods = button->state;
	    if(!GTK_WIDGET_HAS_FOCUS(widget))
		gtk_widget_grab_focus(widget);
	    switch(button->button)
	    {
	      case GDK_BUTTON1:
		/* Color Probe? */
		if((keymods & GDK_MOD1_MASK) &&
		   (keymods & GDK_CONTROL_MASK)
		)
		{
		    iv->drag_mode = IMGVIEW_DRAG_MODE_COLOR_PROBE;
		    cur = iv->color_probe_cur;
		    /* Set color probe coordinates */
		    iv->color_probe_x = ImgViewConvertUnitViewToOrigX(
			iv, (gint)button->x
		    );
		    iv->color_probe_y = ImgViewConvertUnitViewToOrigY(
			iv, (gint)button->y
		    );
		    /* Need to redraw view and info label */
		    ImgViewQueueDrawView(iv);
		    ImgViewInfoLabelDraw(iv);
		}
		/* Zoom? */
		else if(keymods & GDK_MOD1_MASK)
		{
		    iv->drag_mode = IMGVIEW_DRAG_MODE_ZOOM;
		    cur = iv->zoom_cur;
		    need_grab_pointer = TRUE;
		    /* Need to redraw view and info label */
		    ImgViewQueueDrawView(iv);
		    ImgViewInfoLabelDraw(iv);
		}
		/* Crop Rectangular? */
		else if((keymods & GDK_CONTROL_MASK) &&
			(keymods & GDK_SHIFT_MASK) &&
			(iv->crop_flags & IMGVIEW_CROP_ALLOWED)
		)
		{
		    iv->drag_mode = IMGVIEW_DRAG_MODE_CROP_RECTANGLE;
		    cur = iv->crop_cur;
		    need_grab_pointer = TRUE;
		    /* Set crop rectangle as defined (but not
		     * finalized) at the start of the crop rectangle
		     * defination and set crop rectangle coordinates
		     */
		    iv->crop_flags |= IMGVIEW_CROP_DEFINED;
		    iv->crop_rectangle_start_x =
			iv->crop_rectangle_cur_x = ImgViewConvertUnitViewToOrigX(
			    iv, (gint)button->x
			);
		    iv->crop_rectangle_start_y =
			iv->crop_rectangle_cur_y = ImgViewConvertUnitViewToOrigY(
			    iv, (gint)button->y
			);
		    /* Need to redraw view and info label */
		    ImgViewQueueDrawView(iv);
		    ImgViewInfoLabelDraw(iv);
		}
		/* Zoom Rectangular? */
		else if(keymods & GDK_SHIFT_MASK)
		{
		    iv->drag_mode = IMGVIEW_DRAG_MODE_ZOOM_RECTANGLE;
		    cur = iv->zoom_rectangle_cur;
		    need_grab_pointer = TRUE;
		    /* Reset drag rectangle position */
		    iv->drag_zoom_rectangle_start_x =
			iv->drag_zoom_rectangle_cur_x = (gint)button->x;
		    iv->drag_zoom_rectangle_start_y =
			iv->drag_zoom_rectangle_cur_y = (gint)button->y;
		    /* Need to redraw view and info label */
		    ImgViewQueueDrawView(iv);
		    ImgViewInfoLabelDraw(iv);
		}
		/* Translate */
		else
		{
		    iv->drag_mode = IMGVIEW_DRAG_MODE_TRANSLATE;
		    cur = iv->translate_cur;
		    need_grab_pointer = TRUE;
		}
		status = TRUE;
		break;

	      case GDK_BUTTON2:
		if(keymods & GDK_MOD1_MASK)
		{
		    /* Zoom 1:1 */
		    ImgViewZoomOneToOneCB(widget, data);
		    /* Still need to set cursor to zoom */
		    cur = iv->zoom_cur;
		}
		else if(keymods & GDK_SHIFT_MASK)
		{
		    /* Zoom to fit */
		    ImgViewZoomToFitCB(widget, data);
		    /* Still need to set cursor to zoom rectangular */
		    cur = iv->zoom_rectangle_cur;
		}
		else
		{
		    /* Drag zoom */
		    iv->drag_mode = IMGVIEW_DRAG_MODE_ZOOM;
		    cur = iv->zoom_cur;
		    need_grab_pointer = TRUE;

		    /* Need to redraw view and info label */
		    ImgViewQueueDrawView(iv);
		    ImgViewInfoLabelDraw(iv);
		}
		status = TRUE;
		break;

	      case GDK_BUTTON3:
		if(iv->menu != NULL)
		{
		    GtkMenu *menu = GTK_MENU(iv->menu);
		    gtk_menu_popup(
			menu,
			NULL, NULL, NULL, NULL,
			button->button, button->time
		    );
		}
		status = TRUE;
		break;

	      case GDK_BUTTON4:
		/* Zoom out */
/*
		ImgViewZoomIterate(
		    iv, (gint)(1.5f * IMGVIEW_ZOOM_ITERATION_PIXELS)
		);
 */
		ImgViewZoomIterateCoeff(iv, -0.25f);
		ImgViewRealizeChange(iv);
		ImgViewQueueDrawView(iv);
		status = TRUE;
		break;

	      case GDK_BUTTON5:
		/* Zoom in */
/*
		ImgViewZoomIterate(
		    iv, (gint)(-1.5f * IMGVIEW_ZOOM_ITERATION_PIXELS)
		);
 */
		ImgViewZoomIterateCoeff(iv, 0.25f);
		ImgViewRealizeChange(iv);
		ImgViewQueueDrawView(iv);
		status = TRUE;
		break;
	    }
	    /* Set cursor */
	    if(window != NULL)
		gdk_window_set_cursor(window, cur);

	    /* Reset last drag positions on any button press */
	    iv->drag_last_x = (gint)button->x;
	    iv->drag_last_y = (gint)button->y;

	    /* Grab pointer? */
	    if(need_grab_pointer && (window != NULL))
		gdk_pointer_grab(
		    window,
		    FALSE,
		    GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
		    NULL,
		    NULL,
		    button->time
		);
	    break;

	  case GDK_BUTTON_RELEASE:
	    button = (GdkEventButton *)event;
	    keymods = button->state;

	    /* Ungrab pointer */
	    if(gdk_pointer_is_grabbed())
		gdk_pointer_ungrab(button->time);

	    /* Post drag handling */
	    switch(iv->drag_mode)
	    {
	      case IMGVIEW_DRAG_MODE_NONE:
		break;

	      case IMGVIEW_DRAG_MODE_TRANSLATE:
		break;

	      case IMGVIEW_DRAG_MODE_ZOOM:
		/* Must reset drag mode before redraw */
		iv->drag_mode = IMGVIEW_DRAG_MODE_NONE;
		/* Redraw */
		ImgViewQueueDrawView(iv);
		break;

	      case IMGVIEW_DRAG_MODE_ZOOM_RECTANGLE:
		/* Must reset drag mode before processing and redraw */
		iv->drag_mode = IMGVIEW_DRAG_MODE_NONE;
		/* Zoom to rectangle */
		ImgViewZoomRectangular(
		    iv,
		    (gint)button->x, iv->drag_zoom_rectangle_start_x,
		    (gint)button->y, iv->drag_zoom_rectangle_start_y
		);
		/* Clip bounds, update adjustments, and redraw */
		ImgViewRealizeChange(iv); 
		ImgViewQueueDrawView(iv);
		break;

	      case IMGVIEW_DRAG_MODE_CROP_RECTANGLE:
		/* Must reset drag mode before processing and redraw */
		iv->drag_mode = IMGVIEW_DRAG_MODE_NONE;
		if(iv->crop_flags & IMGVIEW_CROP_ALLOWED)
		{
		    /* Crop rectangle should now be defined and in its
		     * final geometry, now map the Crop Dialog
		     */
		    imgview_image_struct *img = ImgViewGetImage(iv);
		    imgview_cropdlg_struct *d = iv->crop_dialog;

		    /* Update current crop rectangle end bounds and
		     * mark the crop rectangle as finalized
		     */
		    iv->crop_rectangle_cur_x = ImgViewConvertUnitViewToOrigX(
			iv, (gint)button->x
		    );
		    iv->crop_rectangle_cur_y = ImgViewConvertUnitViewToOrigY(
			iv, (gint)button->y
		    );
		    iv->crop_flags |= IMGVIEW_CROP_FINALIZED;

		    /* Create the Crop Dialog as needed */
		    if(d == NULL)
			iv->crop_dialog = d = ImgViewCropDlgNew(iv);

		    /* Map the Crop Dialog with the new crop values */
		    if((img != NULL) && (d != NULL))
			ImgViewCropDlgMapValues(
			    d,
			    iv->crop_rectangle_cur_x,
			    iv->crop_rectangle_start_x,
			    iv->crop_rectangle_cur_y,
			    iv->crop_rectangle_start_y,
			    img->width, img->height
			);
		}
		/* Redraw */
		ImgViewQueueDrawView(iv);
		break;

	      case IMGVIEW_DRAG_MODE_COLOR_PROBE:
		break;
	    }

	    /* Cancel any drag modes whenever a button is released */
	    iv->drag_mode = IMGVIEW_DRAG_MODE_NONE;

	    /* Reset last drag positions on any button release */
	    iv->drag_last_x = (gint)button->x;
	    iv->drag_last_y = (gint)button->y;

	    /* Reset last drag rectangle position */
	    iv->drag_zoom_rectangle_start_x =
		iv->drag_zoom_rectangle_cur_x = (gint)button->x;
	    iv->drag_zoom_rectangle_start_y =
		iv->drag_zoom_rectangle_cur_y = (gint)button->y;

	    /* Do not reset last crop rectangle position because it
	     * usually needs to be displayed after this button release
	     * event
	     */

	    /* No key modifiers are held? */
	    if(!(keymods & GDK_CONTROL_MASK) &&
	       !(keymods & GDK_MOD1_MASK) &&
	       !(keymods & GDK_SHIFT_MASK)
	    )
	    {
		if(window != NULL)
		    gdk_window_set_cursor(window, NULL);
	    }

	    status = TRUE;
	    break;

	  case GDK_MOTION_NOTIFY:
	    motion = (GdkEventMotion *)event;
	    if(motion->is_hint)
	    {
		if(iv->drag_mode != IMGVIEW_DRAG_MODE_NONE)
		    gdk_window_get_pointer(
			motion->window, &x, &y, &mask
		    );
	    }
	    else
	    {
		x = (gint)motion->x;
		y = (gint)motion->y;
		mask = motion->state;
	    }

	    /* Handle by drag mode */
	    switch(iv->drag_mode)
	    {
	      case IMGVIEW_DRAG_MODE_NONE:
		break;

	      case IMGVIEW_DRAG_MODE_TRANSLATE:
		if((xadj != NULL) && (yadj != NULL))
		{
		    gint	dx = iv->drag_last_x - x,
				dy = iv->drag_last_y - y;
		    gfloat zoom = iv->view_zoom;
		    const gfloat zoom_min = IMGVIEW_ZOOM_MIN;


		    /* Sanitize current zoom */
		    if(zoom < zoom_min)
			zoom = zoom_min;

		    /* Translate */
		    xadj->value += (gfloat)dx / zoom;
		    yadj->value += (gfloat)dy / zoom;

		    /* Clip bounds and update adjustments */
		    ImgViewRealizeChange(iv);
		    ImgViewQueueDrawView(iv);
		}
		status = TRUE;
		break;

	      case IMGVIEW_DRAG_MODE_ZOOM:
		if((xadj != NULL) && (yadj != NULL) &&
		   (tar_img != NULL) && (src_img != NULL)
		)
		{
		    /* Zoom */
/*
		    ImgViewZoomIterate(iv, iv->drag_last_y - y);
 */
		    if(tar_img->height > 0)
		    {
			const gint dy = iv->drag_last_y - y;
			ImgViewZoomIterateCoeff(
			    iv,
			    -(gfloat)dy / (gfloat)tar_img->height
			);
		    }

		    /* Clip bounds, update adjustments and redraw */
		    ImgViewRealizeChange(iv);
		    ImgViewQueueDrawView(iv);
		}
		status = TRUE;
		break;

	      case IMGVIEW_DRAG_MODE_ZOOM_RECTANGLE:
		if((tar_img != NULL) && (src_img != NULL))
		{
		    /* Update current rectangle bounds */
		    iv->drag_zoom_rectangle_cur_x = x;
		    iv->drag_zoom_rectangle_cur_y = y;

		    /* Draw only the rectangle and send the view
		     * image to the GdkDrawable
		     */
		    ImgViewQueueSendView(iv);
		}
		status = TRUE;
		break;

	      case IMGVIEW_DRAG_MODE_CROP_RECTANGLE:
		if((tar_img != NULL) && (src_img != NULL) &&
		   (iv->crop_flags & IMGVIEW_CROP_ALLOWED)
		)
		{
		    /* Update current rectangle bounds */
		    iv->crop_rectangle_cur_x = ImgViewConvertUnitViewToOrigX(
			iv, (gint)motion->x
		    );
		    iv->crop_rectangle_cur_y = ImgViewConvertUnitViewToOrigY(
			iv, (gint)motion->y
		    );

		    /* Draw only the rectangle and send the view  
		     * image to the GdkDrawable  
		     */
		    ImgViewQueueSendView(iv);
		}
		status = TRUE;
		break;

	      case IMGVIEW_DRAG_MODE_COLOR_PROBE:
		/* Color probing has no interest in motion events */
		break;
	    }
	    /* Reset last drag positions on any motion */
	    iv->drag_last_x = x;
	    iv->drag_last_y = y;
	    break;
	}

	return(status);
}

/*
 *	ImgView view draw idle callback.
 *
 *	Used to call ImgViewDrawView() when idle.
 */
static gint ImgViewDrawViewIdleCB(gpointer data)
{
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return(FALSE);

	/* Redraw */
	ImgViewDrawView(iv, TRUE, NULL);

	/* Reset draw idle to indicate it has been handled */
	iv->view_draw_idle_id = 0;

	return(FALSE);
}

/*
 *	Next frame timeout callback.
 */
static gint ImgViewNextFrameTOCB(gpointer data)
{
	gulong delay;
	imgview_frame_struct *frame;
	imgview_image_struct *img;
	imgview_struct *iv = IMGVIEW(data);
	if(iv == NULL)
	    return(FALSE);

	if(iv->freeze_count > 0)
	    return(TRUE);

	img = iv->orig_img;
	if(img == NULL)
	{
	    iv->next_frame_toid = 0;
	    return(FALSE);
	}

	/* Go to next frame */
	iv->orig_img_frame_num++;

	/* Cycled? */
	if(iv->orig_img_frame_num >= img->nframes)
	{
	    iv->orig_img_frame_num = 0;
	}

	/* Get next frame */
	frame = ImgViewImageGetFrame(img, iv->orig_img_frame_num);
	delay = (frame != NULL) ? frame->delay : 100l;

	/* Queue draw of new frame */
	ImgViewQueueDrawView(iv);

	/* Schedual next frame timeout */
	iv->next_frame_toid = gtk_timeout_add(
	    delay,
	    ImgViewNextFrameTOCB, iv
	);

	return(FALSE);
}


/*
 *	Redraws the ImgView's info label GtkDrawingArea.
 *
 *	This function should be called whenever the adjustments change
 *	(image geometry changed) or when the info label is exposed.
 */
static void ImgViewInfoLabelDraw(imgview_struct *iv)
{
	const gint border_minor = 2;
	gint width, height;
	GdkFont *font;
	GdkWindow *window;
	GdkDrawable *drawable;
	GtkStateType state;
	GtkAdjustment *xadj, *yadj;
	GtkStyle *style;
	GtkWidget *w = (iv != NULL) ? iv->info_label : NULL;
	const imgview_image_struct *src_img;
	if(w == NULL)
	    return;

	if((iv->freeze_count > 0) || !iv->map_state)
	    return;

	if(!GTK_WIDGET_VISIBLE(w))
	    return;

	window = w->window;
	state = GTK_WIDGET_STATE(w);
	style = gtk_widget_get_style(w);
	if((window == NULL) || (style == NULL))
	    return;

	drawable = window;

	gdk_window_get_size(drawable, &width, &height);
	font = style->font;


	/* Begin drawing */

	/* Draw background */
	gtk_style_apply_default_background(
	    style, drawable, FALSE, state,
	    NULL,
	    0, 0, width, height
	);

	/* No image loaded? */
	xadj = iv->view_x_adj;
	yadj = iv->view_y_adj;
	src_img = iv->orig_img;
	if((xadj == NULL) || (yadj == NULL) || (src_img == NULL))
	    return;


	/* Draw info label */
	if((font != NULL) && iv->show_values)
	{
	    GdkTextBounds b;
	    GdkGC *gc = style->text_gc[state];
	    gchar buf[80];

	    /* Format info label message */
	    g_snprintf(
		buf, sizeof(buf),
		"%ix%i%s%i%s%i",
		(gint)(xadj->upper - xadj->lower),
		(gint)(yadj->upper - yadj->lower),
		((gint)xadj->value < 0) ? "" : "+",
		(gint)xadj->value,
		((gint)yadj->value < 0) ? "" : "+",
		(gint)yadj->value
	    );
	    gdk_string_bounds(font, buf, &b);

	    /* Draw info label message */
	    gdk_draw_string(
		drawable, font, gc,
		border_minor - b.lbearing,
		(height / 2) - ((font->ascent + font->descent) / 2) +
		    font->ascent,
		buf
	    );
	}
}


/*
 *	Redraws the ImgView's View GtkDrawingArea.
 *
 *	If blit_from_original is TRUE then the original image will be
 *	blitted to the view image before the view image is sent to
 *	the GtkDrawingArea.
 */
static void ImgViewDrawView(
	imgview_struct *iv,
	gboolean blit_from_original,
	const GdkRectangle *area
)
{
	GdkWindow *window;
	GdkDrawable *drawable;
	GtkStateType state;
	GtkStyle *style;
	imgview_image_struct *img;
	GtkWidget *w = (iv != NULL) ? iv->view_da : NULL;
	if(w == NULL)
	    return;

	/* Blit original image data to target image data? */
	if(blit_from_original)
	    ImgViewBlitView(iv);

	/* Do not continue drawing after blitting the original image
	 * if the ImgView is frozen, not mapped, or the view widget
	 * is not visible
	 */
	if((iv->freeze_count > 0) || !iv->map_state)
	    return;

	if(!GTK_WIDGET_VISIBLE(w))
	    return;

	state = GTK_WIDGET_STATE(w);
	window = w->window;
	style = gtk_widget_get_style(w);
	img = iv->view_img;
	if((window == NULL) || (style == NULL) || (img == NULL))
	    return;

	drawable = window;


	/* Begin updating view image to onscreen window */

	/* Put blitted view image to view drawing area widget's window */
	if(area != NULL)
	    ImgViewImageSendRectangle(
		img, 0, drawable, style->fg_gc[GTK_STATE_NORMAL],
		iv->quality, area
	    );
	else
	    ImgViewImageSend(
		img, 0, drawable, style->fg_gc[GTK_STATE_NORMAL],
		iv->quality
	    );

	/* Check drag mode and draw superimposed graphics as needed */
	/* Zoom? */
	if(iv->drag_mode == IMGVIEW_DRAG_MODE_ZOOM)
	{
	    const gfloat zoom = iv->view_zoom;
	    GdkFont *font = style->font;
	    GdkGC *gc = iv->view_selection_gc;

	    /* Draw zoom value string? */
	    if(iv->show_values && (font != NULL) && (gc != NULL) &&
	       (zoom > 0.0f)
	    )
	    {
		GdkTextBounds b;
		gchar s[80];

		/* Format zoom value string */
		g_snprintf(
		    s, sizeof(s),
		    "%.0f%%",
		    zoom * 100
		);
		gdk_string_bounds(font, s, &b);

		/* Draw zoom value string */
		gdk_draw_string(
		    drawable, font, gc,
		    ((img->width - b.width) / 2) - b.lbearing,
		    (img->height / 2) -
			((font->ascent + font->descent) / 2) +
			font->ascent,
		    s
		);
	    }
	}
	/* Zoom Rectangular? */
	else if(iv->drag_mode == IMGVIEW_DRAG_MODE_ZOOM_RECTANGLE)
	{
	    const gfloat zoom = iv->view_zoom;
	    gint xw[2], yw[2], rww, rhw;
	    GdkGC *gc = iv->view_selection_gc;

	    /* Get zoom rectangle in window coordinates */
	    xw[0] = MIN(
		iv->drag_zoom_rectangle_start_x,
		iv->drag_zoom_rectangle_cur_x
	    );
	    xw[1] = MAX(
		iv->drag_zoom_rectangle_start_x,
		iv->drag_zoom_rectangle_cur_x   
	    );
	    yw[0] = MIN(
		iv->drag_zoom_rectangle_start_y,
		iv->drag_zoom_rectangle_cur_y
	    );
	    yw[1] = MAX(
		iv->drag_zoom_rectangle_start_y,
		iv->drag_zoom_rectangle_cur_y
	    );
	    rww = xw[1] - xw[0];
	    rhw = yw[1] - yw[0];

	    /* Enough data to draw rectangle? */
	    if((rww > 0) && (rhw > 0) && (gc != NULL) && (zoom > 0.0f))
	    {
		GdkFont *font = style->font;

		/* Draw zoom rectangle */
		gdk_draw_rectangle(
		    drawable, gc, FALSE,
		    xw[0], yw[0], rww, rhw
		);

		/* Draw zoom rectangle geometry value string? */
		if(iv->show_values && (font != NULL))
		{
		    GdkTextBounds b;
		    gchar s[80];

		    /* Format rectangle geometry value string */
		    g_snprintf(
			s, sizeof(s),
			"%ix%i",
			(gint)((rww + 1) / zoom),
			(gint)((rhw + 1) / zoom)
		    );
		    gdk_string_bounds(font, s, &b);

		    /* Draw rectangle geometry value string */
		    if((b.width < rww) &&
		       ((font->ascent + font->descent + 2) < rhw)
		    )
			gdk_draw_string(
			    drawable, font, gc,
			    (xw[0] + ((rww - b.width) / 2)) - b.lbearing,
			    (yw[0] + 2) + font->ascent,
			    s
			);
		}
	    }
	}
	/* Crop Rectangle? */
	else if(iv->crop_flags & IMGVIEW_CROP_DEFINED)
	{
	    GdkGC *gc = iv->view_selection_gc;
	    gint xw[2], yw[2], rww, rhw, xd[2], yd[2], rwd, rhd;

	    /* Get crop rectangle in image data coordinates */
	    xd[0] = MIN(iv->crop_rectangle_start_x, iv->crop_rectangle_cur_x);
	    xd[1] = MAX(iv->crop_rectangle_start_x, iv->crop_rectangle_cur_x);
	    yd[0] = MIN(iv->crop_rectangle_start_y, iv->crop_rectangle_cur_y);
	    yd[1] = MAX(iv->crop_rectangle_start_y, iv->crop_rectangle_cur_y);
	    rwd = xd[1] - xd[0];
	    rhd = yd[1] - yd[0];

	    /* Get crop rectangle in window coordinates */
	    xw[0] = ImgViewConvertUnitOrigToViewX(iv, xd[0]);
	    xw[1] = ImgViewConvertUnitOrigToViewX(iv, xd[1]);
	    yw[0] = ImgViewConvertUnitOrigToViewY(iv, yd[0]);
	    yw[1] = ImgViewConvertUnitOrigToViewY(iv, yd[1]);
	    rww = xw[1] - xw[0];
	    rhw = yw[1] - yw[0];

	    /* Crop rectangle has positive size? */
	    if((gc != NULL) && (rww > 0) && (rhw > 0))
	    {
		GdkFont *font = style->font;

		/* If the crop rectangle has been finalized then draw a
		 * selected out area of the cropped out area,
		 * otherwise draw an outline rectangle
		 */
		if(iv->crop_flags & IMGVIEW_CROP_FINALIZED)
		{
		    gdk_draw_rectangle(
			drawable, gc, TRUE,
			0, 0, img->width, img->height
		    );
		    gdk_draw_rectangle(
			drawable, gc, TRUE,
			xw[0], yw[0], rww + 1, rhw + 1
		    );
		}
		else
		{
		    gdk_draw_rectangle(
			drawable, gc, FALSE,
			xw[0], yw[0], rww, rhw
		    );
		}

		/* Draw crop geometry value string? */
		if(iv->show_values && (font != NULL))
		{
		    GdkTextBounds b;
		    gchar s[128];

		    /* Format crop geometry value string */
		    g_snprintf(
			s, sizeof(s),
			"%ix%i%s%i%s%i",
			rwd + 1, rhd + 1,
			(xd[0] >= 0) ? "+" : "", xd[0],
			(yd[0] >= 0) ? "+" : "", yd[0]
		    );
		    gdk_string_bounds(font, s, &b);

		    /* Draw crop geometry value string */
		    if((b.width < rww) &&
		       ((font->ascent + font->descent + 2) < rhw)
		    )
			gdk_draw_string(
			    drawable, font, gc,
			    (xw[0] + ((rww - b.width) / 2)) - b.lbearing,
			    (yw[0] + 2) + font->ascent,
			    s
			);
	        }
	    }
	}
	/* Color Probe? */
	else if(iv->drag_mode == IMGVIEW_DRAG_MODE_COLOR_PROBE)
	{
	    const gint frame_num = iv->orig_img_frame_num;
	    imgview_image_struct *src_img = iv->orig_img;
	    imgview_frame_struct *src_frame = ImgViewImageGetFrame(
		src_img, frame_num
	    );

	    /* Get color probe spot in image data coordinates */
	    gint	xd = iv->color_probe_x,
			yd = iv->color_probe_y;

	    /* Get color probe spot in window coordinates */
	    gint	xw = ImgViewConvertUnitOrigToViewX(iv, xd),
			yw = ImgViewConvertUnitOrigToViewY(iv, yd);

	    if(src_frame != NULL)
	    {
		/* Color probe spot within image data bounds? */
		if((xd >= 0) && (xd < src_img->width) &&
		   (yd >= 0) && (yd < src_img->height)
		)
		{
		    GdkFont *font = style->font;
		    GdkGC *gc = iv->view_selection_gc;
		    const guint8 *ptr = src_frame->buf +
			(yd * src_img->bpl) + (xd * src_img->bpp);

		    if((font != NULL) && (gc != NULL))
		    {
			const gint font_height = font->ascent + font->descent;
			GdkTextBounds b;
			gchar s[128];

			/* Format crop geometry value string */
			switch(src_img->bpp)
			{
		          case 4:
			    g_snprintf(
				s, sizeof(s),
				"RGB=%.2X%.2X%.2X A=%.2X",
				ptr[0], ptr[1], ptr[2], ptr[3]
			    );
			    break;
			  case 3:
			    g_snprintf(
				s, sizeof(s),
				"RGB=%.2X%.2X%.2X",
				ptr[0], ptr[1], ptr[2]
			    );
			    break;
		          case 2:
			    g_snprintf(
				s, sizeof(s),
				"G=%.2X A=%.2X",
				ptr[0], ptr[1]
			    );
			    break;
		          case 1:
			    g_snprintf(
				s, sizeof(s),
			        "G=%.2X",
			        ptr[0]
			    );
			    break;
		          default:
			    *s = '\0';
			    break;
		        }
		        if(*s != '\0')
			{
			    gint tx, ty;
			    gdk_string_bounds(font, s, &b);
			    tx = (xw < (img->width / 2)) ?
				(xw + 15) : (xw - b.width - 15);
			    ty = (yw < (img->height / 2)) ?
				(yw + 15) : (yw - font_height - 15);
			    if((tx + b.width) >= img->width)
			        tx = img->width - b.width;
			    if(tx < 0)
			        tx = 0;
			    if((ty + font_height) >= img->height)
				ty = img->height - font_height;
			    if(ty < 0)
				ty = 0;
			    gdk_draw_string(
				drawable, font, gc,
				tx - b.lbearing, ty + font->ascent,
				s
			     );
			}
		    }
		}
	    }
	}
}

/*
 *	Rounds off the given value to the nearest int.
 */
static gint ImgViewRInt(gfloat x)
{
	return((gint)(x + 0.5f));
}

/*
 *	Converts the specified window coordinate to the zoomed
 *	image data coordinate.
 */
gint ImgViewConvertUnitViewToOrigX(imgview_struct *iv, gint x)
{
	gint sw;
	gfloat view_zoom;
	GtkAdjustment *adj;
	imgview_image_struct *src_img, *tar_img;

	if(iv == NULL)
	    return(x);

	src_img = iv->orig_img;
	tar_img = iv->view_img;
	adj = iv->view_x_adj;
	if((src_img == NULL) || (tar_img == NULL) || (adj == NULL))
	    return(x);

	view_zoom = iv->view_zoom;
	if(view_zoom <= 0.0f)
	    return(x);

	sw = (gint)(src_img->width * view_zoom);
	if(sw < tar_img->width)
	    x -= (tar_img->width / 2) - (sw / 2);

	return((gint)(adj->value + (x / view_zoom)));
}

/*
 *	Converts the specified window coordinate to the zoomed
 *	image data coordinate.
 */
gint ImgViewConvertUnitViewToOrigY(imgview_struct *iv, gint y)
{
	gint sh;
	gfloat view_zoom;
	GtkAdjustment *adj;
	imgview_image_struct *src_img, *tar_img;

	if(iv == NULL)
	    return(y);

	src_img = iv->orig_img;
	tar_img = iv->view_img;
	adj = iv->view_y_adj;
	if((src_img == NULL) || (tar_img == NULL) || (adj == NULL))
	    return(y);

	view_zoom = iv->view_zoom;
	if(view_zoom <= 0.0f)
	    return(y);

	sh = (gint)(src_img->height * view_zoom);
	if(sh < tar_img->height)
	    y -= (tar_img->height / 2) - (sh / 2);

	return((gint)(adj->value + (y / view_zoom)));
}

/*
 *	Converts the specified image data coordinate to the window
 *	coordinate.
 */
gint ImgViewConvertUnitOrigToViewX(imgview_struct *iv, gint x)
{
	gint x_offset, sw;
	gfloat view_zoom;
	GtkAdjustment *adj;
	imgview_image_struct *src_img, *tar_img;

	if(iv == NULL)
	    return(x);

	src_img = iv->orig_img;
	tar_img = iv->view_img;
	adj = iv->view_x_adj;
	if((src_img == NULL) || (tar_img == NULL) || (adj == NULL))
	    return(x);

	view_zoom = iv->view_zoom;
	if(view_zoom <= 0.0f)
	    return(x);

	sw = (gint)(src_img->width * view_zoom);
	if(sw < tar_img->width)
	    x_offset = (tar_img->width / 2) - (sw / 2);
	else
	    x_offset = 0;

	return(
	    (gint)((x - adj->value) * view_zoom) + x_offset
	);
}

/*
 *	Converts the specified image data coordinate to the window
 *	coordinate.
 */
gint ImgViewConvertUnitOrigToViewY(imgview_struct *iv, gint y)
{
	gint y_offset, sh;
	gfloat view_zoom;
	GtkAdjustment *adj;
	imgview_image_struct *src_img, *tar_img;

	if(iv == NULL)
	    return(y);

	src_img = iv->orig_img;
	tar_img = iv->view_img;
	adj = iv->view_y_adj;
	if((src_img == NULL) || (tar_img == NULL) || (adj == NULL))
	    return(y);

	view_zoom = iv->view_zoom;
	if(view_zoom <= 0.0f)
	    return(y);

	sh = (gint)(src_img->height * view_zoom);
	if(sh < tar_img->height)
	    y_offset = (tar_img->height / 2) - (sh / 2);
	else
	    y_offset = 0;

	return(
	    (gint)((y - adj->value) * iv->view_zoom) + y_offset
	);
}


/*
 *	Clips the ImgView's translate bounds, updates the adjustments,
 *	and emits a "changed" signal for the adjustments.
 *
 *	This function should be called whenever changes are made to
 *	the view, ie when the view's translation or zoom has changed or
 *	when a new image has been loaded.
 */
static void ImgViewRealizeChange(imgview_struct *iv)
{
	gfloat zoom;
	gint src_vw, src_vh;
	imgview_image_struct *tar_img, *src_img;
	GtkAdjustment *xadj, *yadj;

	if(iv == NULL)
	    return;

	tar_img = iv->view_img;
	src_img = iv->orig_img;
	zoom = iv->view_zoom;
	xadj = iv->view_x_adj;
	yadj = iv->view_y_adj;

	/* Must have adjustments */
	if((xadj == NULL) || (yadj == NULL))
	    return;

	if((tar_img == NULL) || (zoom <= 0.0f))
	{
	    xadj->value = 0.0f;
	    yadj->value = 0.0f;
	    return;
	}

	/* If no source image, then set adjustments to size of
	 * target image
	 */
	if(src_img == NULL)
	{
	    xadj->value = 0.0f;
	    xadj->lower = 0.0f;
	    xadj->upper = (gfloat)tar_img->width;
	    xadj->page_increment = 0.0f;
	    xadj->page_size = (gfloat)tar_img->width;

	    yadj->value = 0.0f;
	    yadj->lower = 0.0f;
	    yadj->upper = (gfloat)tar_img->height;
	    yadj->page_increment = 0.0f;
	    yadj->page_size = (gfloat)tar_img->height;
	}
	else
	{
	    /* Calculate the size of the viewed area on the source image */
	    src_vw = (gint)(tar_img->width / zoom);
	    src_vh = (gint)(tar_img->height / zoom);

	    /* Set bounds for adjustments to match source image size */
	    xadj->lower = 0.0f;
	    xadj->upper = (gfloat)src_img->width;
	    yadj->lower = 0.0f;
	    yadj->upper = (gfloat)src_img->height;

	    /* Width of viewed source smaller than target width? */
	    if((src_img->width * zoom) < tar_img->width)
	    {
		xadj->value = 0.0f;
		xadj->page_size = (gfloat)src_img->width;
	    }
	    else
	    {
		if(xadj->value > (gfloat)(src_img->width - src_vw))
		    xadj->value = (gfloat)(src_img->width - src_vw);
		if(xadj->value < 0.0f)
		    xadj->value = 0.0f;
		xadj->page_size = (gfloat)src_vw;
		xadj->page_increment = (gfloat)src_vw * xadj->page_size;
	    }

	    /* Height of viewed source smaller than target height? */
	    if((src_img->height * zoom) < tar_img->height)
	    {
		yadj->value = 0.0f;
		yadj->page_size = (gfloat)src_img->height;
	    }
	    else
	    {
		if(yadj->value > (gfloat)(src_img->height - src_vh))
		    yadj->value = (gfloat)(src_img->height - src_vh);
		if(yadj->value < 0.0f)
		    yadj->value = 0.0f;
		yadj->page_size = (gfloat)src_vh;
		yadj->page_increment = (gfloat)src_vh * yadj->page_size;
	    }
	}

	/* Draw info label */
	ImgViewInfoLabelDraw(iv);

	/* Send "changed" signal to adjustments so that the
	 * scrollbars are updated
	 */
	gtk_signal_emit_by_name(GTK_OBJECT(xadj), "changed");
	gtk_signal_emit_by_name(GTK_OBJECT(yadj), "changed");
}

#if 0
/*
 *	Zoom iteration.
 *
 *	Zooms in or out depending on the value of dz in units
 *	of pixels. Positive dz will zoom out while negative dz will
 *	zoom in.
 *
 *	No bounds will be cliped and view is not redrawn, the calling 
 *	function is responsible for that.
 */
static void ImgViewZoomIterate(
	imgview_struct *iv, const gint dz
)
{
	const gfloat	zoom_min = IMGVIEW_ZOOM_MIN,
			zoom_max = IMGVIEW_ZOOM_MAX,
			zoom_rate = IMGVIEW_ZOOM_RATE;
	imgview_image_struct *tar_img;
	GtkAdjustment *xadj, *yadj;
	gint p_vw, p_vh, n_vw, n_vh;
	gfloat	new_zoom,
		prev_zoom = iv->view_zoom;

	if((iv == NULL) || (dz == 0))
	    return;

	xadj = iv->view_x_adj;
	yadj = iv->view_y_adj;
	tar_img = iv->view_img;
	if((xadj == NULL) || (yadj == NULL) || (tar_img == NULL))
	    return;

	/* Record current zoom and set new zoom */
	iv->last_view_x = GTK_ADJUSTMENT_GET_VALUE(xadj);
	iv->last_view_y = GTK_ADJUSTMENT_GET_VALUE(yadj);
	iv->view_last_zoom = iv->view_zoom;
	iv->view_zoom -= (gfloat)dz * zoom_rate;
	if(iv->view_zoom < zoom_min)
	    iv->view_zoom = zoom_min;
	new_zoom = iv->view_zoom;

	/* Sanitize previous zoom */
	if(prev_zoom > zoom_max)
	    prev_zoom = zoom_max;
	else if(prev_zoom < zoom_min)
	    prev_zoom = zoom_min;

	/* Calculate the previous and new viewable dimensions on the
	 * source image
	 */
	p_vw = (gint)(tar_img->width / prev_zoom);
	p_vh = (gint)(tar_img->height / prev_zoom);
	n_vw = (gint)(tar_img->width / new_zoom);
	n_vh = (gint)(tar_img->height / new_zoom);

	/* Adjust translation due to zooming */
	xadj->value += (p_vw / 2) - (n_vw / 2);
	yadj->value += (p_vh / 2) - (n_vh / 2);
}
#endif

/*
 *	Zoom iteration by zoom coefficient delta.
 *
 *	Zooms in or out depending on the value of the delta coefficient
 *	dc. A positive dc will zoom out while a negative dc will
 *	zoom in.
 *
 *	No bounds will be cliped and view is not redrawn, the calling 
 *	function is responsible for that.
 */
static void ImgViewZoomIterateCoeff(
	imgview_struct *iv, const gfloat dc
)
{
	const gfloat	zoom_min = IMGVIEW_ZOOM_MIN,
			zoom_max = IMGVIEW_ZOOM_MAX;
	imgview_image_struct *tar_img;
	GtkAdjustment *xadj, *yadj;
	gint p_vw, p_vh, n_vw, n_vh;
	gfloat	new_zoom,
		prev_zoom = iv->view_zoom;

	if((iv == NULL) || (dc == 0.0f))
	    return;

	xadj = iv->view_x_adj;
	yadj = iv->view_y_adj;
	tar_img = iv->view_img;
	if((xadj == NULL) || (yadj == NULL) || (tar_img == NULL))
	    return;

	/* Record the current zoom and set the new zoom */
	iv->last_view_x = GTK_ADJUSTMENT_GET_VALUE(xadj);
	iv->last_view_y = GTK_ADJUSTMENT_GET_VALUE(yadj);
	iv->view_last_zoom = iv->view_zoom;
/*	iv->view_zoom -= (gfloat)dz * zoom_rate; */
	iv->view_zoom = iv->view_zoom + (iv->view_zoom * dc);
	if(iv->view_zoom < zoom_min)
	    iv->view_zoom = zoom_min;
	new_zoom = iv->view_zoom;

	/* Sanitize previous zoom */
	if(prev_zoom > zoom_max)
	    prev_zoom = zoom_max;
	else if(prev_zoom < zoom_min)
	    prev_zoom = zoom_min;

	/* Calculate the previous and new viewable dimensions on the
	 * source image
	 */
	p_vw = (gint)(tar_img->width / prev_zoom);
	p_vh = (gint)(tar_img->height / prev_zoom);
	n_vw = (gint)(tar_img->width / new_zoom);
	n_vh = (gint)(tar_img->height / new_zoom);

	/* Adjust translation due to zooming */
	xadj->value += (p_vw / 2) - (n_vw / 2);
	yadj->value += (p_vh / 2) - (n_vh / 2);
}

/*
 *	Rectangular zoom.
 *
 *	Zooms into the defined rectangle from the given inputs.
 *
 *	No bounds will be cliped and view is not redrawn, the calling
 *	function is responsible for that.
 */
static void ImgViewZoomRectangular(
	imgview_struct *iv,
	gint x0, gint x1,
	gint y0, gint y1
)
{
	gint view_width, view_height;
	gint src_width, src_height;
	gint rw, rh, rcx, rcy;
	imgview_image_struct *tar_img, *src_img;
	GtkAdjustment *xadj, *yadj;
	const gfloat	zoom_min = IMGVIEW_ZOOM_MIN,
			zoom_max = IMGVIEW_ZOOM_MAX;
	gfloat	new_zoom,
		prev_zoom = iv->view_zoom;

	if((iv == NULL) || (x0 == x1) || (y0 == y1) || (prev_zoom <= 0.0f))
	    return;

	xadj = iv->view_x_adj;
	yadj = iv->view_y_adj;
	tar_img = iv->view_img;
	src_img = iv->orig_img;
	if((xadj == NULL) || (yadj == NULL) ||
	   (tar_img == NULL) || (src_img == NULL)
	)
	    return;

	if((tar_img->width <= 0) || (tar_img->height <= 0))
	    return;

	/* Flip rectangle coordinates as needed */
	if(x0 > x1)
	{
	    gint t = x1;
	    x1 = x0;
	    x0 = t;
	}
	if(y0 > y1)
	{
	    gint t = y1;
	    y1 = y0;
	    y0 = t;
	}

	/* Calculate zoomed rectangle size */
	rw = (gint)((x1 - x0) / prev_zoom);
	rh = (gint)((y1 - y0) / prev_zoom);
	if((rw <= 0) || (rh <= 0))
	    return;

	/* Get size of view in window coordinates */
	view_width = tar_img->width;
	view_height = tar_img->height;

	/* Get size of original image in window coordinates */
	src_width = src_img->width;
	src_height = src_img->height;

	/* Remove centering offset from rectangle coordinates */
	x0 += (gint)MIN(((src_width * prev_zoom) - view_width) / 2, 0);
	x1 += (gint)MIN(((src_width * prev_zoom) - view_width) / 2, 0);
	y0 += (gint)MIN(((src_height * prev_zoom) - view_height) / 2, 0);
	y1 += (gint)MIN(((src_height * prev_zoom) - view_height) / 2, 0);

	/* Calculate zoomed rectangle center (be sure to discard offset
	 * used to center image on view if source image is smaller).
	 */
	rcx = (gint)(xadj->value + ((gfloat)x0 / prev_zoom) + ((gfloat)rw / 2));
	rcy = (gint)(yadj->value + ((gfloat)y0 / prev_zoom) + ((gfloat)rh / 2));

	/* Calculate new zoom coefficient */
	new_zoom = (gfloat)view_width / (gfloat)rw;
	if(((gfloat)view_height / new_zoom) < rh)
	    new_zoom = (gfloat)view_height / (gfloat)rh;

	/* Check if new zoom is out of bounds, if it is then skip */
	if((new_zoom < zoom_min) || (new_zoom > zoom_max))
	    return;

	/* Record current zoom and set new zoom */
	iv->last_view_x = GTK_ADJUSTMENT_GET_VALUE(iv->view_x_adj);
	iv->last_view_y = GTK_ADJUSTMENT_GET_VALUE(iv->view_y_adj);
	iv->view_last_zoom = iv->view_zoom;
	iv->view_zoom = new_zoom;

	/* Update translation */
	xadj->value = (gfloat)(rcx - ((gfloat)view_width / new_zoom / 2));
	yadj->value = (gfloat)(rcy - ((gfloat)view_height / new_zoom / 2));
}


/*
 *	Blits the ImgView's orig_img to view_img using the given image
 *	viewer's translation and zoom.
 */
static void ImgViewBlitView(imgview_struct *iv)
{
	gint frame_num;
	GtkAdjustment *xadj, *yadj;
	GtkStyle *style;
	GtkWidget *w = (iv != NULL) ? iv->view_da : NULL;
	imgview_frame_struct *src_frame, *tar_frame;
	imgview_image_struct *src_img, *tar_img;
	imgview_alpha_flags alpha_flags;
	guint32 bg_pix32 = 0xffffffff;
	gint bpp;

	gint	src_sk_col,		/* Skips, in * 256 */
		src_sk_row;
	gint	src_cx, src_cy,		/* Cur pos, in * 256 */
		src_x, src_y,		/* Restart pos, in * 256 */
		src_w, src_h,
		src_tw, src_th;
	gint	tar_cx, tar_cy,		/* Cur pos, in * 256 */
		tar_x, tar_y,		/* Restart pos, in * 256 */
		tar_w, tar_h,
		tar_tw, tar_th;
	gfloat zoom;


	if(w == NULL)
	    return;

	xadj = iv->view_x_adj;
	yadj = iv->view_y_adj;
	if((xadj == NULL) || (yadj == NULL))
	    return;

	alpha_flags = iv->alpha_flags;

	/* Get the background pixel */
	style = gtk_widget_get_style(w);
	if(style != NULL)
	{
	    const GdkColor *c = &style->base[GTK_STATE_NORMAL];
	    bg_pix32 = (
		((guint32)0xff000000) |
		(((guint32)c->blue & 0x0000ff00) << 8) |
		(((guint32)c->green & 0x0000ff00)) |
		(((guint32)c->red & 0x0000ff00) >> 8)
	    );
	}

	/* Get the target image as the view's buffer image */
	tar_img = iv->view_img;
	tar_frame = ImgViewImageGetFrame(tar_img, 0);
	if(tar_frame == NULL)
	    return;

	/* Get the bytes per pixel of the target image */
	bpp = tar_img->bpp;
	if(bpp <= 0)
	    return;

	/* If there is no source image then just clear the target image */
	frame_num = iv->orig_img_frame_num;
	src_img = iv->orig_img;
	src_frame = ImgViewImageGetFrame(src_img, frame_num);
	if(src_frame == NULL)
	{
	    ImgViewImageClear(tar_img, 0, bg_pix32);
	    return;
	}

	/* Target image and source image depths not the same? */
	if(bpp != src_img->bpp)
	    return;

	/* Make sure both images have allocated image buffers */
	if((src_frame->buf == NULL) || (tar_frame->buf == NULL))
	    return;

	/* Get zoom */
	zoom = iv->view_zoom;
	if(zoom <= 0.0f)
	    return;

	/* Column and row skips in units of 256 */
	src_sk_col = (gint)ImgViewRInt(256.0f / zoom);
	src_sk_row = (gint)ImgViewRInt(256.0f / zoom);

	/* Calculate source and target allocation bounds, in units of
	 * 256
	 */
	src_tw = (gint)src_img->width * 256;
	src_th = (gint)src_img->height * 256;
	tar_tw = (gint)tar_img->width * 256;
	tar_th = (gint)tar_img->height * 256;

	/* Calculate visible source bounds based on the target bounds
	 * with zoom applied, in units of 256
	 */
	src_w = (gint)(tar_tw / zoom);
	src_h = (gint)(tar_th / zoom);
	/* Visible source bounds cannot exceed allocation size in units
	 * of 256.
	 */
	if(src_w > src_tw)
	    src_w = src_tw;
	if(src_h > src_th)
	    src_h = src_th;

	/* Prepare target starting points and bounds */
	tar_w = (gint)(((src_tw * zoom) < tar_tw) ? src_tw * zoom : tar_tw);
	tar_h = (gint)(((src_th * zoom) < tar_th) ? src_th * zoom : tar_th);

	tar_x = (tar_w < tar_tw) ? (tar_tw / 2) - (tar_w / 2) : 0;
	if(tar_x < 0)
	    tar_x = 0;
	tar_y = (tar_h < tar_th) ? (tar_th / 2) - (tar_h / 2) : 0;
	if(tar_y < 0)
	    tar_y = 0;

	tar_w += tar_x;		/* Offset target iteration bounds */
	tar_h += tar_y;


	/* Prepare source starting points and bounds */
	src_x = (gint)(xadj->value * 256.0);
	src_x = ((src_x + src_w) > src_tw) ? src_tw - src_w : src_x;
	if(src_x < 0)
	    src_x = 0;
	src_y = (gint)(yadj->value * 256.0);
	src_y = ((src_y + src_h) > src_th) ? src_th - src_h : src_y;
	if(src_y < 0)
	    src_y = 0;

	src_w += src_x;		/* Offset source iteration bounds */
	src_h += src_y;


	/* Set source and target starting positions in units of 256 */
	src_cx = src_x;
	src_cy = src_y;
	tar_cx = tar_x;
	tar_cy = tar_y;


	/* Need to clear the target image if zoom causes source image to
	 * be displayed smaller than the target image's size
	 */
	if(((src_tw * zoom) < tar_tw) || ((src_th * zoom) < tar_th))
	    ImgViewImageClear(tar_img, 0, bg_pix32);

#define DEBUG_CHECK_BOUNDS	{		\
 if((src_cx < 0) || ((src_cx >> 8) >= src_img->width)) \
  printf("Source segfault X %i(%i)\n", (src_cx >> 8), src_img->width); \
 if((src_cy < 0) || ((src_cy >> 8) >= src_img->height)) \
  printf("Source segfault Y %i(%i)\n", (src_cy >> 8), src_img->height); \
 if((tar_cx < 0) || ((tar_cx >> 8) >= tar_img->width)) \
  printf("Target segfault X %i(%i)\n", (tar_cx >> 8), tar_img->width); \
 if((tar_cy < 0) || ((tar_cy >> 8) >= tar_img->height)) \
  printf("Target segfault Y %i(%i)\n", (tar_cy >> 8), src_img->height); \
}

	/* 8 bits */
	if(bpp == 1)
	{
	    gint		src_bpp = src_img->bpp,
				tar_bpp = tar_img->bpp;
	    gint		src_bpl = src_img->bpl,
				tar_bpl = tar_img->bpl;
	    const guint8	*src_ptr,
				*src_buf = (const guint8 *)src_frame->buf;
	    guint8		*tar_ptr,
				*tar_buf = (guint8 *)tar_frame->buf;


	    while((src_cy < src_h) &&
		  (tar_cy < tar_h)
	    )
	    {
		src_ptr = src_buf +
		    ((src_cy >> 8) * src_bpl) +
		    ((src_cx >> 8) * src_bpp);
		tar_ptr = tar_buf +
		    ((tar_cy >> 8) * tar_bpl) +
		    ((tar_cx >> 8) * tar_bpp);

		*tar_ptr = *src_ptr;

		/* Increment target x colum */
		tar_cx += 256;
		/* Go to next target line? */
		if(tar_cx >= tar_w)
		{
		    tar_cy += 256;
		    tar_cx = tar_x;
		    src_cy += src_sk_row;
		    src_cx = src_x;
		    continue;
		}

		/* Increment source x column */
		src_cx += src_sk_col;
		/* Go to next source line? */
		if(src_cx >= src_w)
		{
		    tar_cy += 256;
		    tar_cx = tar_x;
		    src_cy += src_sk_row;
		    src_cx = src_x;
		    continue;
		}
	    }
	}
	/* 15 or 16 bits */
	else if(bpp == 2)
	{
	    gint		src_bpp = src_img->bpp,
				tar_bpp = tar_img->bpp;
	    gint		src_bpl = src_img->bpl,
				tar_bpl = tar_img->bpl;
	    const guint8	*src_ptr,
				*src_buf = (const guint8 *)src_frame->buf;
	    guint8              *tar_ptr,
				*tar_buf = (guint8 *)tar_frame->buf;


	    while((src_cy < src_h) &&
		  (tar_cy < tar_h)
	    )
	    {
		src_ptr = src_buf +
		    ((src_cy >> 8) * src_bpl) +
		    ((src_cx >> 8) * src_bpp);
		tar_ptr = tar_buf +
		    ((tar_cy >> 8) * tar_bpl) +
		    ((tar_cx >> 8) * tar_bpp);

		*(guint16 *)tar_ptr = *(guint16 *)src_ptr;

		/* Increment target x column */
		tar_cx += 256;
		/* Go to next target line? */
		if(tar_cx >= tar_w)
		{
		    tar_cy += 256;
		    tar_cx = tar_x;
		    src_cy += src_sk_row;
		    src_cx = src_x;
		    continue;
		}

		/* Increment source x column */
		src_cx += src_sk_col;
		/* Go to next source line? */
		if(src_cx >= src_w)
		{
		    tar_cy += 256;
		    tar_cx = tar_x;
		    src_cy += src_sk_row;
		    src_cx = src_x;
		    continue;
		}
	    }
	}
	/* 24 bits */
	else if(bpp == 3)
	{
	    gint                src_bpp = src_img->bpp,
				tar_bpp = tar_img->bpp;
	    gint                src_bpl = src_img->bpl,
				tar_bpl = tar_img->bpl;
	    const guint8	*src_ptr,
				*src_buf = (const guint8 *)src_frame->buf;
	    guint8		*tar_ptr,
				*tar_buf = (guint8 *)tar_frame->buf;

	    while((src_cy < src_h) &&
		  (tar_cy < tar_h)
	    )
	    {
		src_ptr = src_buf +
		    ((src_cy >> 8) * src_bpl) +
		    ((src_cx >> 8) * src_bpp);
		tar_ptr = tar_buf +
		    ((tar_cy >> 8) * tar_bpl) +
		    ((tar_cx >> 8) * tar_bpp);

		*tar_ptr++ = *src_ptr++;
		*tar_ptr++ = *src_ptr++;
		*tar_ptr = *src_ptr;

		/* Increment target x column */
		tar_cx += 256;
		/* Go to next target line? */
		if(tar_cx >= tar_w)
		{
		    tar_cy += 256;
		    tar_cx = tar_x;
		    src_cy += src_sk_row;
		    src_cx = src_x;
		    continue;
		}

		/* Increment source x column */
		src_cx += src_sk_col;
		/* Go to next source line? */
		if(src_cx >= src_w)
		{
		    tar_cy += 256;
		    tar_cx = tar_x;
		    src_cy += src_sk_row;
		    src_cx = src_x;
		    continue;
		}
	    }
	}
	/* 32 bits */
	else if(bpp == 4)
	{
	    gint		src_bpp = src_img->bpp,
				tar_bpp = tar_img->bpp;
	    gint                src_bpl = src_img->bpl,
				tar_bpl = tar_img->bpl;
	    const guint8	*src_ptr,
				*src_buf = (const guint8 *)src_frame->buf;
	    guint8		*tar_ptr,
				*tar_buf = (guint8 *)tar_frame->buf;

	    /* Has alpha channel with non-uniform values? */
	    if(alpha_flags & IMGVIEW_ALPHA_DEFINED)
	    {
		guint src_alpha, tar_alpha;
		const guint8 *bg_ptr;

		while((src_cy < src_h) &&
		      (tar_cy < tar_h)
		)
		{
		    src_ptr = src_buf +
			((src_cy >> 8) * src_bpl) +
			((src_cx >> 8) * src_bpp);
		    tar_ptr = tar_buf +
			((tar_cy >> 8) * tar_bpl) +
			((tar_cx >> 8) * tar_bpp);

#ifdef USE_BIG_ENDIAN
#define SWAB16(a)	( (((gushort)(a) << 8) & 0xFF00) |	\
			  (((gushort)(a) >> 8) & 0x00FF)	\
			)
#define SWAB32(a)	( (SWAB16(((a) >> 16) & 0xFFFF) << 0) |	\
			  (SWAB16((a) & 0xFFFF) << 16)		\
			)

		    *(guint32 *)src_ptr = SWAB32(*(guint32 *)src_ptr);
#endif	/* USE_BIG_ENDIAN */

		    /* Calculate alpha channel value coefficient */
		    if(alpha_flags & IMGVIEW_ALPHA_INVERTED)
			src_alpha = 0xff - src_ptr[3];
		    else
			src_alpha = src_ptr[3];

#ifdef USE_BIG_ENDIAN
		    *(guint32 *)src_ptr = SWAB32(*(guint32 *)src_ptr);
#endif	/* USE_BIG_ENDIAN */

		    if(src_alpha == 0xff)
		    {
			*(guint32 *)tar_ptr = *(guint32 *)src_ptr;
		    }
		    else if(src_alpha > 0x00)
		    {
			tar_alpha = 0xff - src_alpha;
			bg_ptr = (const guint8 *)&bg_pix32;

			*tar_ptr++ = (guint8)(
			    ((guint)*bg_ptr++ * tar_alpha / 0xff) +
			    ((guint)*src_ptr++ * src_alpha / 0xff)
			);
			*tar_ptr++ = (guint8)(
			    ((guint)*bg_ptr++ * tar_alpha / 0xff) +
			    ((guint)*src_ptr++ * src_alpha / 0xff)
			);
			*tar_ptr++ = (guint8)(
			    ((guint)*bg_ptr++ * tar_alpha / 0xff) +
			    ((guint)*src_ptr++ * src_alpha / 0xff)
			);
			*tar_ptr = *bg_ptr;
		    }
		    else
		    {
			*(guint32 *)tar_ptr = bg_pix32;
		    }

		    /* Increment target x column */
		    tar_cx += 256;
		    /* Go to next target line? */
		    if(tar_cx >= tar_w)
		    {
		        tar_cy += 256;
			tar_cx = tar_x;
			src_cy += src_sk_row;
			src_cx = src_x;
			continue;
		    }

		    /* Increment source x column */
		    src_cx += src_sk_col;
		    /* Go to next source line? */
		    if(src_cx >= src_w)
		    {
		        tar_cy += 256;
		        tar_cx = tar_x;
		        src_cy += src_sk_row;
			src_cx = src_x;
			continue;
		    }
		}
	    }
	    else
	    {
		while((src_cy < src_h) &&
		      (tar_cy < tar_h)
		)
		{
		    src_ptr = src_buf +
			((src_cy >> 8) * src_bpl) +
			((src_cx >> 8) * src_bpp);
		    tar_ptr = tar_buf +
			((tar_cy >> 8) * tar_bpl) +
			((tar_cx >> 8) * tar_bpp);
		    *(guint32 *)tar_ptr = *(guint32 *)src_ptr;

		    /* Increment target x column */
		    tar_cx += 256;
		    /* Go to next target line? */
		    if(tar_cx >= tar_w)
		    {
			tar_cy += 256;
			tar_cx = tar_x;
			src_cy += src_sk_row;
			src_cx = src_x;
			continue;
		    }

		    /* Increment source x column */
		    src_cx += src_sk_col;
		    /* Go to next source line? */
		    if(src_cx >= src_w)
		    {
		        tar_cy += 256;
		        tar_cx = tar_x;
		        src_cy += src_sk_row;
		        src_cx = src_x;
		        continue;
		    }
		}
	    }	/* Has alpha channel? */
	}

#undef DEBUG_CHECK_BOUNDS
}

/*
 *	Updates the contents of the WM icon (if any) on the given
 *	ImgView. If the ImgView's toplevel_is_window is FALSE
 *	then there will be no WM icon to update.
 *
 *	If iv->show_image_on_wm_icon is FALSE then this call has no
 *	affect.
 */
static void ImgViewWMIconUpdate(imgview_struct *iv)
{
	const gint	width = IMGVIEW_WM_ICON_WIDTH,
			height = IMGVIEW_WM_ICON_HEIGHT;
	gint bpp = 4, frame_num;
	GdkWindow *window;
	GdkBitmap *mask = NULL;
	GdkPixmap *pixmap = NULL;
	GtkWidget *w;
	GtkStyle *style;
	imgview_frame_struct *frame;
	imgview_image_struct *src_img, *tar_img;

	if(iv == NULL)
	    return;

	/* Skip if ImgView's toplevel widget is not a GtkWindow or
	 * we do not want to show image on wm icon
	 */
	if(!iv->toplevel_is_window || !iv->show_image_on_wm_icon)
	    return;

	/* Icon size must be positive */
	if((width <= 0) || (height <= 0))
	    return;

	/* Get toplevel widget's GdkWindow */
	w = iv->toplevel;
	window = (w != NULL) ? w->window : NULL;
	if(window == NULL)
	    return;

	style = gtk_widget_get_style(w);
	if(style == NULL)
	    return;

	/* Get the original image as the source image (if any) */
	src_img = iv->orig_img;
	if(src_img != NULL)
	    bpp = src_img->bpp;

	/* Create target image the size of the icon */
	tar_img = ImgViewImageNew(
	    width, height,
	    bpp, 0
	);
	if(tar_img == NULL)
	    return;

	frame_num = iv->orig_img_frame_num;
	frame = ImgViewImageGetFrame(src_img, frame_num);
	if(frame != NULL)
	{
	    guint8 *tar_buf = (guint8 *)g_malloc(
		tar_img->bpl * tar_img->height
	    );
	    GUIImageBufferResize(
		bpp,
		frame->buf,
		src_img->width, src_img->height, src_img->bpl,
		tar_buf,
		tar_img->width, tar_img->height, tar_img->bpl
	    );
	    ImgViewImageAppendFrame(tar_img, tar_buf, frame->delay);
	}
	else
	{
	    /* No source image, so just clear the target image */
	    const GdkColor *c = &style->base[GTK_STATE_NORMAL];
	    guint32 bg_pix32 = ((guint32)0xff000000) +
		(((guint32)c->blue & 0x0000ff00) << 8) +
		(((guint32)c->green & 0x0000ff00)) +
		(((guint32)c->red & 0x0000ff00) >> 8)
	    ;
	    guint8 *tar_buf = (guint8 *)g_malloc(
		tar_img->bpl * tar_img->height
	    );
	    ImgViewImageAppendFrame(tar_img, tar_buf, 0l);
	    ImgViewImageClear(tar_img, 0, bg_pix32);
	}

	/* Create mask */
	frame = ImgViewImageGetFrame(tar_img, 0);
	if(frame != NULL)
	{
	    gint x, y;
	    const gint	width = tar_img->width,
			height = tar_img->height,
			bpp = tar_img->bpp,
			bpl = tar_img->bpl,
			bm_width = (width / 8) + ((width % 8) ? 1 : 0);
	    const guint8 *buf = frame->buf, *tar_ptr, *tar_line;
	    guint8 *bm_data = (guint8 *)g_malloc0(
		bm_width * height * sizeof(guint8)
	    );

	    /* Copy target image data alpha channel to bitmap data */
	    for(y = 0; y < height; y++)
	    {
		tar_line = buf + (y * bpl);
		for(x = 0; x < width; x++)
		{
		    tar_ptr = tar_line + (x * bpp);
		    if(tar_ptr[3] >= 0x80)
			bm_data[(y * bm_width) + (x / 8)] |=
			    (1 << (x % 8));
		}
	    }

	    /* Create mask from the bitmap data */
	    mask = gdk_bitmap_create_from_data(
		window, (const gchar *)bm_data, width, height
	    );

	    g_free(bm_data);
	}

	/* Create pixmap */
	pixmap = gdk_pixmap_new(
	    window, width, height, -1
	);

	/* Put the target image to the pixmap */
	ImgViewImageSend(
	    tar_img, 0, pixmap,
	    style->fg_gc[GTK_STATE_NORMAL],
	    iv->quality
	);

	/* Delete the target image */
	ImgViewImageDelete(tar_img);

	/* Update WM icon for our toplevel window's GdkWindow */
	if(pixmap != NULL)
	    gdk_window_set_icon(
		window,
		NULL,		/* WM icon GdkWindow */
		pixmap,		/* WM icon GdkPixmap */
		mask		/* WM icon GdkBitmap */
	    );

	/* Unref old icon mask and pixmap */
	GDK_PIXMAP_UNREF(iv->wm_icon_pixmap)
	GDK_BITMAP_UNREF(iv->wm_icon_mask)

	/* Record new mask and pixmap */
	iv->wm_icon_mask = mask;
	iv->wm_icon_pixmap = pixmap;
}


/*
 *	Recreates the ImgView's view image to match the current size
 *	of the view GtkDrawingArea.
 *
 *	This function should be called when the view GtkDrawingArea
 *	receives a "configure_event" signal.
 */
static void ImgViewBufferRecreate(imgview_struct *iv)
{
	gint width, height, bpp = 4;
	GdkWindow *window;
	GtkWidget *w;

	if(iv == NULL)
	    return;

	w = iv->view_da;
	window = (w != NULL) ? w->window : NULL;
	if(window == NULL)
	    return;

	width = w->allocation.width;
	height = w->allocation.height;
	if((width <= 0) || (height <= 0))
	    return;

	/* Delete the view image if there is a change in size */
	if(iv->view_img != NULL)
	{
	    imgview_image_struct *img = iv->view_img;

	    bpp = img->bpp;	/* Get the view image's original bpp */

	    /* Change in size? */
	    if((img->width != width) || (img->height != height))
	    {
		ImgViewImageDelete(img);
		iv->view_img = NULL;
	    }
	}

	/* Create a new ImgView image for the view based on the new
	 * size if there was a change in size
	 */
	if(iv->view_img == NULL)
	{
	    guint8 *buf;
	    GtkStyle *style;
	    imgview_image_struct *img = ImgViewImageNew(
		width, height, bpp, 0
	    );
	    if(img == NULL)
		return;

	    /* Set the new view image */
	    iv->view_img = img;

	    /* Append one frame */
	    buf = (guint8 *)g_malloc(img->bpl * img->height);
	    ImgViewImageAppendFrame(img, buf, 0l);

	    /* Clear new image to match background color, this is
	     * needed since the next redraw will be async and we don't
	     * want an unsightly black background
	     */
	    style = gtk_widget_get_style(w);
	    if(style != NULL)
	    {
		const GdkColor *c = &style->base[GTK_STATE_NORMAL];
		guint32 bg_pix32 =
		    ((guint32)0xff000000) +
		    (((guint32)c->blue & 0x0000ff00) << 8) +
		    (((guint32)c->green & 0x0000ff00)) +
		    (((guint32)c->red & 0x0000ff00) >> 8)
		;
		ImgViewImageClear(img, 0, bg_pix32);
	    }
	}
}

/*
 *	Returns the GtkAccelGroup for the ImgView if available.
 */
GtkAccelGroup *ImgViewGetAccelGroup(imgview_struct *iv)
{
	return((iv != NULL) ? iv->accelgrp : NULL);
}

/*
 *	Returns TRUE if the ImgView's toplevel widget is a
 *	GtkWindow otherwise it is a GtkVBox.
 */
gboolean ImgViewToplevelIsWindow(imgview_struct *iv)
{
	return((iv != NULL) ? iv->toplevel_is_window : FALSE);
}

/*
 *	Returns the toplevel widget, this may be either a GtkVBox or
 *	GtkWindow depending on what iv->toplevel_is_window says.
 */
GtkWidget *ImgViewGetToplevelWidget(imgview_struct *iv)
{
	return((iv != NULL) ? iv->toplevel : NULL);
}

/*
 *	Returns the view drawing area widget.
 */
GtkDrawingArea *ImgViewGetViewWidget(imgview_struct *iv)
{
	return((GtkDrawingArea *)((iv != NULL) ? iv->view_da : NULL));
}

/*
 *	Returns the menu widget.
 */
GtkMenu *ImgViewGetMenuWidget(imgview_struct *iv)
{
	return((GtkMenu *)((iv != NULL) ? iv->menu : NULL));
}

/*
 *	Returns TRUE if there is an image loaded on the given image
 *	viewer.
 */
gboolean ImgViewIsLoaded(imgview_struct *iv)
{
	if(iv == NULL)
	    return(FALSE);

	return((iv->orig_img != NULL) ? TRUE : FALSE);
}

/*
 *	Returns the loaded image, can return NULL if there is no
 *	image loaded.
 */
imgview_image_struct *ImgViewGetImage(imgview_struct *iv)
{
	return((iv != NULL) ? iv->orig_img : NULL);
}

/*
 *	Returns the pointer to the ImgView's loaded image data.
 *
 *	The format is always returned as IMGVIEW_FORMAT_RGBA.
 *
 *	Can return NULL if no image is currently loaded.
 */
guint8 *ImgViewGetImageData(
	imgview_struct *iv, gint frame_num,
	gint *width, gint *height, gint *bpp, gint *bpl,
	imgview_format *format
)
{
	imgview_frame_struct *frame;
	imgview_image_struct *img;

	if(width != NULL)
	    *width = 0;
	if(height != NULL)
	    *height = 0;
	if(bpp != NULL)
	    *bpp = 0;
	if(bpl != NULL)
	    *bpl = 0;
	if(format != NULL)
	    *format = IMGVIEW_FORMAT_RGBA;

	if(iv == NULL)
	    return(NULL);

	img = iv->orig_img;
	frame = ImgViewImageGetFrame(img, frame_num);
	if(frame == NULL)
	    return(NULL);

	if(width != NULL)
	    *width = img->width;
	if(height != NULL)
	    *height = img->height;
	if(bpp != NULL)
	    *bpp = img->bpp;
	if(bpl != NULL)
	    *bpl = img->bpl;

	return(frame->buf);
}

/*
 *	Returns the current frame number.
 */
gint ImgViewGetCurrentFrame(imgview_struct *iv)
{
	return((iv != NULL) ? iv->orig_img_frame_num : -1);
}

/*
 *	Returns the total number of frames.
 */
gint ImgViewGetTotalFrames(imgview_struct *iv)
{
	imgview_image_struct *img;

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

	img = iv->orig_img;
	if(img == NULL)
	    return(-1);

	return(img->nframes);
}


/*
 *	Deletes the orig_img on the ImgView and redraws.
 *
 *	This basically flushes GDK, unloads the image, resets zoom and 
 *	translations, and resets alpha channel flags.
 *
 *	WM icon will be updated, menus will be updated, and view will
 *	be redrawn.
 */
void ImgViewClear(imgview_struct *iv)
{
	gboolean image_unloaded = FALSE;
	GtkAdjustment *adj;

	if(iv == NULL)
	    return;

	/* Stop playing */
	GTK_TIMEOUT_REMOVE(iv->next_frame_toid);
	iv->next_frame_toid = 0;

	/* Is there an image currently loaded? */
	if(iv->orig_img != NULL)
	{
	    ImgViewImageDelete(iv->orig_img);
	    iv->orig_img = NULL;
	    iv->orig_img_frame_num = 0;

	    /* Mark that the image was actually unloaded */
	    image_unloaded = TRUE;
	}

	/* If an image was actually unloaded, then reset values */
	if(image_unloaded)
	{
	    /* Reset alpha channel flags */
	    iv->alpha_flags = (imgview_alpha_flags)0;
	    iv->alpha_threshold = 0x80;

	    /* Record current zoom and reset zoom */
	    iv->last_view_x = GTK_ADJUSTMENT_GET_VALUE(iv->view_x_adj);
	    iv->last_view_y = GTK_ADJUSTMENT_GET_VALUE(iv->view_y_adj);
	    iv->view_last_zoom = iv->view_zoom;
	    iv->view_zoom = 1.0f;

	    /* Reset translations */
	    adj = iv->view_x_adj;
	    if(adj != NULL)
	    {
		adj->value = 0.0f;
	    }
	    adj = iv->view_y_adj;
	    {
		adj->value = 0.0f;
	    }

	    /* Update WM icon as needed */
	    ImgViewWMIconUpdate(iv);

	    /* Clip bounds, update adjustments, and redraw */
	    ImgViewRealizeChange(iv);
	    ImgViewUpdateMenus(iv);
	    ImgViewQueueDrawView(iv);
	}
}

/*
 *	Transfered the specified ImgView image to the ImgView.
 *
 *	The image must not be referenced again after this call.
 *
 *	If an image is already loaded then it will be unloaded first.
 *
 *	Returns non-zero on error.
 */
static gint ImgViewSetImageNexus(
	imgview_struct *iv,
	imgview_image_struct *img,	/* Transfered */
	gboolean zoom_to_fit
)
{
	gboolean has_alpha = FALSE;
	gint width, height, bpp, bpl;
	GList *glist;
	imgview_frame_struct *frame;
	GtkWidget *w = (iv != NULL) ? iv->view_da : NULL;
	if(w == NULL)
	{
	    ImgViewImageDelete(img);
	    return(-1);
	}

	/* Clear existing image (if any) first
	 *
	 * If (and only if) an image was loaded then translations will   
	 * be reset, view redrawn, and menus updated
	 */
	ImgViewClear(iv);

	/* Image was not unloaded? */
	if(iv->orig_img != NULL)
	{
	    g_printerr(
"ImgViewSetImageNexus(): Internal error:\
 Image was not unloaded after calling ImgViewClear().\n"
	    );
	    ImgViewImageDelete(img);
	    return(-3);
	}

	/* Do not continue if a new image was not specified */
	if(img == NULL)
	    return(0);

	/* Transfer image */
	iv->orig_img = img;

	/* Get values from new image and make sure that they are
	 * valid
	 */
	width = img->width;
	height = img->height;
	bpp = img->bpp;
	if((width <= 0) || (height <= 0) || (bpp <= 0))
	{
	    iv->orig_img = NULL;
	    ImgViewImageDelete(img);
	    return(-1);
	}
	if(img->bpl <= 0)
	    img->bpl = bpp * width;
	bpl = img->bpl;


	/* Scan image data to gather values */
	for(glist = img->frames_list;
	    glist != NULL;
	    glist = g_list_next(glist)
	)
	{
	    frame = IMGVIEW_FRAME(glist->data);
	    if(frame == NULL)
		continue;

	    if(frame->buf != NULL)
	    {
		const guint8 default_alpha_byte = 
		    (iv->alpha_flags & IMGVIEW_ALPHA_INVERTED) ?
			0x00 : 0xff;
		gint x, y;
		const guint8 *buf = frame->buf, *line, *pixel;

		for(y = 0; y < height; y++)
		{
		    line = buf + (y * bpl);

		    for(x = 0; x < width; x++)
		    {
		        pixel = line + (x * bpp);

			/* Check if alpha value dosen't match the
			 * default alpha value which would hint that
			 * there is an alpha channel
			 */
			if((bpp >= 4) && !has_alpha)
		        {
			    if(
 (guint8)((*(guint32 *)pixel & 0xff000000) >> 24) != default_alpha_byte
			    )
			        has_alpha = TRUE;
		        }
		    }
	        }
	    }
	}

	/* Update alpha channel flag on ImgView */
	if(has_alpha)
	    iv->alpha_flags |= IMGVIEW_ALPHA_DEFINED;
	else
	    iv->alpha_flags &= ~IMGVIEW_ALPHA_DEFINED;


	/* Update WM icon as needed */
	ImgViewWMIconUpdate(iv);

	/* Zoom to fit after loading? */
	if(zoom_to_fit)
	{
	    /* Zoom the image to fit, this will also realize changes and
	     * redraw
	     */
	    const gint	wwidth = w->allocation.width,
			wheight = w->allocation.height;
	    gfloat zoom = (gfloat)wwidth / (gfloat)width;
	    if(zoom > 0.0f)
	    {
		if((wheight / zoom) < height)
		    zoom = (gfloat)wheight / (gfloat)height;
	    }
	    if(zoom > 0.0f)
	    {
		/* Record current zoom and set new zoom */
		iv->last_view_x = GTK_ADJUSTMENT_GET_VALUE(iv->view_x_adj);
		iv->last_view_y = GTK_ADJUSTMENT_GET_VALUE(iv->view_y_adj);
		iv->view_last_zoom = iv->view_zoom;
		iv->view_zoom = zoom;
	    }
	}

	/* Clip bounds, update adjustments, and redraw */
	ImgViewRealizeChange(iv);
	ImgViewUpdateMenus(iv);
	ImgViewQueueDrawView(iv);

	return(0);
}

gint ImgViewSetImage(
	imgview_struct *iv,
	imgview_image_struct *img       /* Transfered */
)
{
	return(ImgViewSetImageNexus(iv, img, FALSE));
}

gint ImgViewSetImageToFit(
	imgview_struct *iv,
	imgview_image_struct *img       /* Transfered */
)
{
	return(ImgViewSetImageNexus(iv, img, TRUE));
}


/*
 *	Loads the specified image data to the ImgView.
 *
 *	The specified image data format must match the specified format.
 *
 *	If an image is already loaded then it will be unloaded first.
 *
 *	Returns non-zero on error.
 */
static gint ImgViewLoadNexus(
	imgview_struct *iv,
	gint width, gint height,
	gint bytes_per_line,		/* Set 0 to auto calculate */
	imgview_format format,
	const guint8 *data,
	gboolean zoom_to_fit
)
{
	gint frame_num;
	GdkColormap *colormap;
	GdkWindow *window;
	GtkWidget *w;
	imgview_frame_struct *frame;
	imgview_image_struct *img;

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

	/* Clear existing image (if any) first
	 *
	 * If (and only if) an image was loaded then translations will
	 * be reset, view redrawn, and menus updated
	 */
	ImgViewClear(iv);

	/* Image was not unloaded? */
	if(iv->orig_img != NULL)
	{
	    g_printerr(
"ImgViewLoadNexus(): Internal error:\
 Image was not unloaded after calling ImgViewClear().\n"
	    );
	    return(-3);
	}

	/* No image data given? */
	if((data == NULL) || (width <= 0) || (height <= 0))
	    return(-1);

	/* Get view GtkDrawingArea */
	w = iv->view_da;
	window = (w != NULL) ? w->window : NULL;
	if(window == NULL)
	    return(-1);

	/* Get colormap, visual, and depth of the GdkWindow */
	colormap = gdk_window_get_colormap(window);
	if(colormap == NULL)
	    return(-1);

	/* Create new ImgView Image */
	iv->orig_img = img = ImgViewImageNew(
	    width, height, 4, 0
	);
	if(img == NULL)
	    return(-3);

	frame_num = ImgViewImageAppendFrameNew(img);
	frame = ImgViewImageGetFrame(img, frame_num);

	/* Copy given data to target image if target image's buffer
	 * is allocated. Both source image data and target image data
	 * must be of the same width and height but may differ in
	 * bytes per line and/or bytes per pixel
	 */
	if((frame != NULL) ? (frame->buf != NULL) : FALSE)
	{
	    gboolean is_color, has_alpha = FALSE;
	    gint x, y, bc, bc_min;
	    gint src_bpp, src_bpl;
	    gint	tar_bpp = img->bpp,
			tar_bpl = img->bpl;
	    const guint8	*src_line, *src_ptr,
				*src_buf = data;
	    guint8		*tar_line, *tar_ptr, *tar_base_ptr,
				*tar_buf = (guint8 *)frame->buf;

	    /* Check source image data format, to calculate source image
	     * data bytes per line and bytes per pixel units. This will
	     * determine the correct src_bpp and is_color value.
	     */
	    switch(format)
	    {
	      case IMGVIEW_FORMAT_RGBA:
		src_bpp = 4;
		is_color = TRUE;
		break;
	      case IMGVIEW_FORMAT_RGB:
		src_bpp = 3;
		is_color = TRUE;
		break;
	      case IMGVIEW_FORMAT_GREYSCALEA32:
		src_bpp = 4;
		is_color = FALSE;
		break;
	      case IMGVIEW_FORMAT_GREYSCALE32:
		src_bpp = 4;
		is_color = FALSE;
		break;
	      case IMGVIEW_FORMAT_GREYSCALE16:
		src_bpp = 2;
		is_color = FALSE;
		break;
	      case IMGVIEW_FORMAT_GREYSCALE8:
		src_bpp = 1;
		is_color = FALSE;
		break;
	      default:
		/* Unsupported source image format, safest is to assume
		 * 1 byte per pixel
		 */
		src_bpp = 1;
		is_color = FALSE;
		break;
	    }
	    /* Calculate source image bytes per line unit, if given
	     * bytes_per_line is 0 then that means they want us to 
	     * calculate it
	     */
	    src_bpl = (bytes_per_line <= 0) ?
		(width * src_bpp) : bytes_per_line;

	    /* Calculate byte count minimum, the smaller of the two
	     * bytes per pixel units
	     */
	    bc_min = MIN(src_bpp, tar_bpp);

	    /* Color blitting? */
	    if(is_color)
	    {
		/* Color blitting */
		guint8 default_alpha_byte =
		    (iv->alpha_flags & IMGVIEW_ALPHA_INVERTED) ?
			0x00 : 0xff;

		/* Iterate through each row */
		for(y = 0; y < height; y++)
		{
		    src_line = src_buf + (y * src_bpl);
		    tar_line = tar_buf + (y * tar_bpl);

		    for(x = 0; x < width; x++)
		    {
			src_ptr = src_line + (x * src_bpp);
			tar_base_ptr = tar_ptr = tar_line + (x * tar_bpp);

			/* Copy bytes from current source pixel to current
			 * target pixel, incrementing each pointer one byte
			 * after each byte is copied
			 */
			for(bc = 0; bc < bc_min; bc++)
			    *tar_ptr++ = *src_ptr++;

			/* Clear any remaining bytes on the current
			 * target pixel. Remaining bytes should be
			 * the alpha channel
			 */
			for(; bc < tar_bpp; bc++)
			    *tar_ptr++ = default_alpha_byte;

			/* Check if alpha value dosen't match
			 * default_alpha_byte which would hint that there
			 * is an alpha channel
			 */
			if((tar_bpp >= 4) && !has_alpha)
			{
			    if(tar_base_ptr[3] != default_alpha_byte)
				has_alpha = TRUE;
			}
		    }
		}	/* Iterate through each row */
	    }
	    else
	    {
		/* Greyscale blitting */
		guint8 first_grey_byte;

		/* Iterate through each row */
		for(y = 0; y < height; y++)
		{
		    src_line = src_buf + (y * src_bpl);
		    tar_line = tar_buf + (y * tar_bpl);

		    for(x = 0; x < width; x++)
		    {
			src_ptr = src_line + (x * src_bpp);
			tar_ptr = tar_line + (x * tar_bpp);

			/* Copy first byte */
			*tar_ptr++ = first_grey_byte = *src_ptr++;

			/* Copy bytes from current source pixel to
			 * current target pixel, incrementing each
			 * pointer one byte after each byte is copied
			 *
			 * Start at 1 since first byte is already
			 * coppied
			 */
			for(bc = 1; bc < bc_min; bc++)
			    *tar_ptr++ = *src_ptr++;

			/* Clear any remaining bytes on the target
			 * pixel
			 */
			for(; bc < tar_bpp; bc++)
			    *tar_ptr++ = first_grey_byte;
		    }
		}
	    }

	    /* Update alpha channel flag on ImgView */
	    if(has_alpha)
		iv->alpha_flags |= IMGVIEW_ALPHA_DEFINED;
	    else
		iv->alpha_flags &= ~IMGVIEW_ALPHA_DEFINED;
	}

	/* Update WM icon as needed */
	ImgViewWMIconUpdate(iv);

	/* Zoom to fit after loading? */
	if(zoom_to_fit)
	{
	    /* Zoom the image to fit, this will also realize changes and
	     * redraw.
	     */
	    gint	wwidth = w->allocation.width,
			wheight = w->allocation.height;
	    gfloat zoom = (gfloat)wwidth / (gfloat)width;
	    if(zoom > 0.0f)
	    {
		if((wheight / zoom) < height)
		    zoom = (gfloat)wheight / (gfloat)height;
	    }
	    if(zoom > 0.0f)
	    {
		/* Record current zoom and set new zoom */
		iv->last_view_x = GTK_ADJUSTMENT_GET_VALUE(iv->view_x_adj);
		iv->last_view_y = GTK_ADJUSTMENT_GET_VALUE(iv->view_y_adj);
		iv->view_last_zoom = iv->view_zoom;
		iv->view_zoom = zoom;
	    }
	}

	/* Clip bounds, update adjustments, and redraw */
	ImgViewRealizeChange(iv);
	ImgViewUpdateMenus(iv);
	ImgViewQueueDrawView(iv);

	return(0);
}

gint ImgViewLoad(
	imgview_struct *iv,
	gint width, gint height,
	gint bytes_per_line,	/* Can be 0 to auto calculate */
	imgview_format format,	/* One of IMGVIEW_FORMAT_* */
	const guint8 *data
)
{
	return(ImgViewLoadNexus(
	    iv, width, height, bytes_per_line, format, data, FALSE
	));
}

gint ImgViewLoadToFit(
	imgview_struct *iv,
	gint width, gint height,
	gint bytes_per_line,	/* Can be 0 to auto calculate */
	imgview_format format,	/* One of IMGVIEW_FORMAT_* */
	const guint8 *data
)
{
	return(ImgViewLoadNexus(
	    iv, width, height, bytes_per_line, format, data, TRUE
	));
}


/*
 *	Starts playing.
 */
void ImgViewPlay(imgview_struct *iv)
{
	gulong delay = 0l;
	imgview_frame_struct *frame;

	if(iv == NULL)
	    return;

	/* Already playing or no image loaded? */
	if((iv->next_frame_toid != 0) || (iv->orig_img == NULL))
	    return;

	frame = ImgViewImageGetFrame(
	   iv->orig_img, iv->orig_img_frame_num
	);
	if(frame != NULL)
	    delay = frame->delay;

	iv->next_frame_toid = gtk_timeout_add(
	    delay,
	    ImgViewNextFrameTOCB, iv
	);
}

/*
 *	Pause.
 */
void ImgViewPause(imgview_struct *iv)
{
	if(iv == NULL)   
	    return;   

	GTK_TIMEOUT_REMOVE(iv->next_frame_toid);
	iv->next_frame_toid = 0;
}

/*
 *	Seek to the frame specified by frame_num and redraw.
 */
void ImgViewSeek(imgview_struct *iv, gint frame_num)
{
	imgview_image_struct *img;

	if(iv == NULL)
	    return;

	/* No image loaded? */
	img = iv->orig_img;
	if(img == NULL)
	    return;

	/* Last frame? */
	if(frame_num < 0)
	    frame_num = img->nframes - 1;

	/* Invalid frame? */
	if((frame_num < 0) || (frame_num >= img->nframes))
	    return;

	/* No change in frame? */
	if(frame_num == iv->orig_img_frame_num)
	    return;

	/* Seek to next frame */
	iv->orig_img_frame_num = frame_num;

	/* Queue draw */
	ImgViewQueueDrawView(iv);
}

/*
 *	Checks if the ImgView is currently playing.
 */
gboolean ImgViewIsPlaying(imgview_struct *iv)
{
	return((iv != NULL) ? (iv->next_frame_toid != 0) : FALSE);
}


/*
 *	Creates a new ImgView.
 */
imgview_struct *ImgViewNew(
	gboolean show_toolbar,
	gboolean show_values,
	gboolean show_statusbar,
	gboolean show_image_on_wm_icon,
	gint quality,		/* From 0 to 2 (2 being best/slowest) */
	gboolean toplevel_is_window,
	GtkWidget **toplevel_rtn
)
{
	const gint border_minor = 2;
	GdkFont *font;
	GdkColormap *colormap;
	GdkWindow *window = (GdkWindow *)GDK_ROOT_PARENT();
	GtkAdjustment *adj;
	GtkRcStyle *rcstyle;
	GtkStyle *style;
	GtkWidget *w, *parent, *parent2, *parent3, *parent4, *parent5;
	GtkAccelGroup *accelgrp;
	imgview_struct *iv = IMGVIEW(g_malloc0(
	    sizeof(imgview_struct)
	));

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

	/* Reset values */
	iv->map_state = FALSE;
	iv->freeze_count = 0;
	iv->busy_count = 0;
	iv->toplevel_is_window = toplevel_is_window;
	iv->show_values = show_values;
	iv->show_image_on_wm_icon = show_image_on_wm_icon;
	iv->quality = quality;
	iv->last_view_x = 0.0f;
	iv->last_view_y = 0.0f;
	iv->view_zoom = 1.0f;
	iv->view_last_zoom = iv->view_zoom;
	iv->view_zoom_idle = 0;
	iv->view_zoom_toid = 0;
	iv->view_draw_idle_id = 0;
	iv->next_frame_toid = 0;
	iv->alpha_flags = (imgview_alpha_flags)0;
	iv->alpha_threshold = 0x80;
	iv->crop_dialog = NULL;

	/* Cursors */
	iv->busy_cur = gdk_cursor_new(GDK_WATCH);
	iv->translate_cur = gdk_cursor_new(GDK_FLEUR);
	iv->zoom_cur = gdk_cursor_new(GDK_SIZING);
	iv->zoom_rectangle_cur = gdk_cursor_new(GDK_TCROSS);
	iv->crop_cur = NULL;
	iv->color_probe_cur = NULL;

	/* Keyboard Accel Group */
	iv->accelgrp = accelgrp = gtk_accel_group_new();

	/* View GtkAdjustments */
	iv->view_x_adj = adj = (GtkAdjustment *)gtk_adjustment_new(
	    0.0f,		/* Current value */
	    0.0f, 0.0f,		/* Lower & upper */
	    10.0f, 50.0f,	/* Step & page increment */
	    0.0f		/* Page size */
	);
	gtk_signal_connect(
	    GTK_OBJECT(adj), "value_changed",
	    GTK_SIGNAL_FUNC(ImgViewAdjustmentValueChangedCB), iv
	);

	iv->view_y_adj = adj = (GtkAdjustment *)gtk_adjustment_new(
	    0.0f,		/* Current value */
	    0.0f, 0.0f,		/* Lower & upper */
	    10.0f, 50.0f,	/* Step & page increment */
	    0.0f		/* Page size */
	);
	gtk_signal_connect(
	    GTK_OBJECT(adj), "value_changed",
	    GTK_SIGNAL_FUNC(ImgViewAdjustmentValueChangedCB), iv
	);


	/* Check if toplevel is to be created as a GtkWindow */
	if(toplevel_is_window)
	{
	    /* Create a toplevel GtkWindow with RGB buffers */
	    gtk_widget_push_visual(gdk_rgb_get_visual());
	    gtk_widget_push_colormap(gdk_rgb_get_cmap());
	    iv->toplevel = w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	    gtk_widget_pop_visual();
	    gtk_widget_pop_colormap();
	    gtk_window_set_policy(
		GTK_WINDOW(w), TRUE, TRUE, TRUE
	    );
#if 0
/* Do not set usize, so calling function can set view_da's usize
 * for better image fitting
 */
	    gtk_widget_set_usize(w, 320, 240);
#endif
	    gtk_window_set_title(GTK_WINDOW(w), IMGVIEW_TITLE);
#ifdef PROG_NAME
	    gtk_window_set_wmclass(
		GTK_WINDOW(w), "imgview", PROG_NAME
	    );
#endif
	    gtk_widget_realize(w);
	    window = w->window;
	    if(window != NULL)
	    {
		GdkGeometry geo;

		geo.min_width = IMGVIEW_MIN_WIDTH;
		geo.min_height = IMGVIEW_MIN_HEIGHT;
		geo.base_width = 0;
		geo.base_height = 0;
		geo.width_inc = 1;
		geo.height_inc = 1;
		gdk_window_set_geometry_hints(
		    window,
		    &geo,
		    GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE |
		    /* GDK_HINT_ASPECT | */
		    GDK_HINT_RESIZE_INC
		);

		if(!show_image_on_wm_icon)
		    GUISetWMIcon(
			window,
			(guint8 **)icon_iv_48x48_xpm
		    );
	    }
	    gtk_signal_connect(
		GTK_OBJECT(w), "delete_event",
		GTK_SIGNAL_FUNC(ImgViewDeleteEventCB), iv
	    );
	    gtk_window_add_accel_group(GTK_WINDOW(w), accelgrp);
	    /* Update toplevel return */
	    if(toplevel_rtn != NULL)
		*toplevel_rtn = w;
	    parent = w;

	    /* Main vbox in window */
	    iv->main_vbox = w = gtk_vbox_new(FALSE, border_minor);
	    gtk_container_add(GTK_CONTAINER(parent), w);
	    gtk_widget_show(w);
	    parent = w;
	}
	else
	{
	    /* Create a vbox as the toplevel, which will be parented by
	     * the calling function
	     */
	    iv->toplevel = w = gtk_vbox_new(FALSE, border_minor);
	    gtk_accel_group_attach(accelgrp, GTK_OBJECT(w));
	    parent = w;

	    iv->main_vbox = NULL;

	    /* Update toplevel return */
	    if(toplevel_rtn != NULL)
		*toplevel_rtn = w;
	}

	/* Get values from the toplevel widget
	 *
	 * Note that the window may be the toplevel widget's GdkWindow
	 * if it is a GtkWindow or the root GdkWindow if the toplevel
	 * widget is a GtkBox
	 */
	w = iv->toplevel;

	colormap = gdk_window_get_colormap(window);
	if(colormap == NULL)
	    colormap = gdk_colormap_get_system();

	style = gtk_widget_get_style(w);
	if(style == NULL)
	    style = gtk_widget_get_default_style();
	font = (style != NULL) ? style->font : NULL;

	/* Toolbar */
	iv->toolbar_map_state = show_toolbar;
	/* Toolbar toplevel hbox */
	iv->toolbar_toplevel = w = gtk_hbox_new(FALSE, border_minor);
	gtk_widget_set_usize(w, -1, IMGVIEW_TOOLBAR_HEIGHT);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	if(show_toolbar)
	    gtk_widget_show(w);
	parent3 = w;

	/* Toolbar depressed frame */
	w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_box_pack_start(GTK_BOX(parent3), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent4 = w;
	/* Hbox inside toolbar's depressed frame */
	w = gtk_hbox_new(FALSE, border_minor);
	gtk_container_add(GTK_CONTAINER(parent4), w);
	gtk_widget_show(w);
	parent4 = w;

	/* Vbox for info label */
	w = gtk_vbox_new(TRUE, 0);
	gtk_box_pack_start(GTK_BOX(parent4), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent5 = w;

	/* Info label GtkDrawingArea, the info label is really a 
	 * GtkDrawingArea because otherwise it would resize the image
	 * viewer annoyingly each time the label is changed (which is
	 * quite often)
	 */
	iv->info_label = w = gtk_drawing_area_new();
	gtk_widget_set_usize(w, -1, 16);
	gtk_widget_add_events(
	    w,
	    GDK_EXPOSURE_MASK | GDK_ENTER_NOTIFY_MASK |
	    GDK_LEAVE_NOTIFY_MASK
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(ImgViewInfoLabelExposeCB), iv
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "enter_notify_event",
	    GTK_SIGNAL_FUNC(ImgViewInfoLabelCrossingCB), iv
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "leave_notify_event",
	    GTK_SIGNAL_FUNC(ImgViewInfoLabelCrossingCB), iv
	);
	gtk_box_pack_start(GTK_BOX(parent5), w, FALSE, FALSE, 0);
	gtk_widget_show(w);

	/* Toolbar buttons */
	iv->zoom_in_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_zoom_in_16x16_xpm
	);
	gtk_widget_set_usize(w, 16, 16);
	gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "pressed",
	    GTK_SIGNAL_FUNC(ImgViewZoomInPressedCB), iv
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "released",
	    GTK_SIGNAL_FUNC(ImgViewZoomReleasedCB), iv
	);
	gtk_widget_show(w);

	iv->zoom_out_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_zoom_out_16x16_xpm
	);
	gtk_widget_set_usize(w, 16, 16);
	gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "pressed",
	    GTK_SIGNAL_FUNC(ImgViewZoomOutPressedCB), iv
	);
	gtk_signal_connect(
	    GTK_OBJECT(w), "released",
	    GTK_SIGNAL_FUNC(ImgViewZoomReleasedCB), iv
	);
	gtk_widget_show(w);

	iv->zoom_onetoone_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_zoom_onetoone_16x16_xpm
	);
	gtk_widget_set_usize(w, 16, 16);
	gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(ImgViewZoomOneToOneCB), iv
	);
	gtk_widget_show(w);

	iv->zoom_tofit_btn = w = GUIButtonPixmap(
	    (guint8 **)icon_zoom_tofit_16x16_xpm
	);
	gtk_widget_set_usize(w, 16, 16);
	gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
	gtk_box_pack_start(GTK_BOX(parent4), w, FALSE, FALSE, 0);
	gtk_signal_connect(
	    GTK_OBJECT(w), "clicked",
	    GTK_SIGNAL_FUNC(ImgViewZoomToFitCB),
	    (gpointer)iv
	);
	gtk_widget_show(w);

	/* Show toolbar? */
	if(show_toolbar)
	    gtk_widget_show(iv->toolbar_toplevel);

	/* Table to hold drawing area and scrollbars */
	w = gtk_table_new(2, 2, FALSE);
	gtk_table_set_row_spacing(GTK_TABLE(w), 0, border_minor);
	gtk_table_set_col_spacing(GTK_TABLE(w), 0, border_minor);
	gtk_box_pack_start(GTK_BOX(parent), w, TRUE, TRUE, 0);
	gtk_widget_show(w);
	parent2 = w;

	/* Frame for drawing area */
	w = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_IN);
	gtk_table_attach(
	    GTK_TABLE(parent2), w,
	    0, 1, 0, 1,
	    GTK_EXPAND | GTK_SHRINK | GTK_FILL,
	    GTK_EXPAND | GTK_SHRINK | GTK_FILL,
	    0, 0
	);
	gtk_widget_show(w);
	parent3 = w;

	/* View GtkDrawingArea (with RGB buffers if toplevel is not a
	 * GtkWindow)
	 */
	if(!toplevel_is_window)
	{
	    gtk_widget_push_visual(gdk_rgb_get_visual());
	    gtk_widget_push_colormap(gdk_rgb_get_cmap());
	}
	iv->view_da = w = gtk_drawing_area_new();
	if(!toplevel_is_window)
	{
	    gtk_widget_pop_visual();
	    gtk_widget_pop_colormap();
	}
	/* Set view style */
	rcstyle = gtk_rc_style_new();
	rcstyle->color_flags[GTK_STATE_NORMAL] = GTK_RC_BASE;
	rcstyle->color_flags[GTK_STATE_SELECTED] = GTK_RC_BASE;
	rcstyle->color_flags[GTK_STATE_INSENSITIVE] = GTK_RC_BASE;
	GDK_COLOR_SET_COEFF(
	    &rcstyle->base[GTK_STATE_NORMAL],
	    1.0f, 1.0f, 1.0f
	)
	GDK_COLOR_SET_COEFF(
	    &rcstyle->base[GTK_STATE_SELECTED],
	    0.0f, 0.0f, 0.61f
	)
	GDK_COLOR_SET_COEFF(
	    &rcstyle->base[GTK_STATE_INSENSITIVE],
	    1.0f, 1.0f, 1.0f
	)
	gtk_widget_modify_style(w, rcstyle);
	GTK_RC_STYLE_UNREF(rcstyle)
	/* Needs to accept focus for keyboard modifier events */
	GTK_WIDGET_SET_FLAGS(w, GTK_CAN_FOCUS | GTK_CAN_DEFAULT);
	gtk_widget_add_events(
	    w,
	    GDK_STRUCTURE_MASK | GDK_EXPOSURE_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 |
	    GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
	);
	/* Signals must be connected after so calling functions can
	 * stop their emittion if they need to take control of the
	 * ImgView */
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "configure_event",
	    GTK_SIGNAL_FUNC(ImgViewViewEventCB), iv
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "expose_event",
	    GTK_SIGNAL_FUNC(ImgViewViewEventCB), iv
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "key_press_event",
	    GTK_SIGNAL_FUNC(ImgViewViewEventCB), iv
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "key_release_event",
	    GTK_SIGNAL_FUNC(ImgViewViewEventCB), iv
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "button_press_event",
	    GTK_SIGNAL_FUNC(ImgViewViewEventCB), iv
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "button_release_event",
	    GTK_SIGNAL_FUNC(ImgViewViewEventCB), iv
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "motion_notify_event",
	    GTK_SIGNAL_FUNC(ImgViewViewEventCB), iv
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "enter_notify_event",
	    GTK_SIGNAL_FUNC(ImgViewViewEventCB), iv
	);
	gtk_signal_connect_after(
	    GTK_OBJECT(w), "leave_notify_event",
	    GTK_SIGNAL_FUNC(ImgViewViewEventCB), iv
	);
	gtk_container_add(GTK_CONTAINER(parent3), w);
	gtk_widget_show(w);

	/* Reset view image, it will be created when
	 * ImgViewBufferRecreate() is called for the first time
	 */
	iv->view_img = NULL;


	/* Begin creating resources for the view */

	/* Create the GC for drawing view selections and text */
	if((window != NULL) && (colormap != NULL))
	{
	    GdkColor *cf, *cb;
	    GdkGCValues gcv;
	    GdkGCValuesMask gcv_mask = (
		GDK_GC_FOREGROUND | GDK_GC_BACKGROUND | GDK_GC_FONT |
		GDK_GC_FUNCTION | GDK_GC_FILL | GDK_GC_LINE_WIDTH |
		GDK_GC_LINE_STYLE | GDK_GC_CAP_STYLE | GDK_GC_JOIN_STYLE
	    );
	    cf = &gcv.foreground;
	    GDK_COLOR_SET_COEFF(cf, 0.0f, 0.0f, 0.0f)
	    GDK_COLORMAP_ALLOC_COLOR(colormap, cf)
	    cb = &gcv.background;
	    GDK_COLOR_SET_COEFF(cb, 1.0f, 1.0f, 1.0f)
	    GDK_COLORMAP_ALLOC_COLOR(colormap, cb)
	    if(font != NULL)
		gdk_font_ref(font);
	    gcv.font = font;
	    gcv.function = GDK_INVERT;
	    gcv.fill = GDK_SOLID;
	    gcv.line_width = 1;
	    gcv.line_style = GDK_LINE_SOLID;
	    gcv.cap_style = GDK_CAP_NOT_LAST;
	    gcv.join_style = GDK_JOIN_MITER;
	    iv->view_selection_gc = gdk_gc_new_with_values(
		window, &gcv, gcv_mask
	    );
	    GDK_COLORMAP_FREE_COLOR(colormap, cf)
	    GDK_COLORMAP_FREE_COLOR(colormap, cb)
	    GDK_FONT_UNREF(font)
	}

	/* Bitmaps */
#if 0
	iv->scissor_bm = gdk_bitmap_create_from_data(
	    window,
	    (const gchar *)scissor_16x16_bm,
	    16, 16
	);
#else
	iv->scissor_bm = NULL;
#endif

	/* Other cursors */
	if((window != NULL) && (colormap != NULL))
	{
	    gint width = 16, height = 16;
	    GdkColor fg, bg;
	    GdkBitmap *mask;
	    GdkPixmap *pixmap;

	    GDK_COLOR_SET_COEFF(&fg, 0.0f, 0.0f, 0.0f);
	    GDK_COLORMAP_ALLOC_COLOR(colormap, &fg);
	    GDK_COLOR_SET_COEFF(&bg, 1.0f, 1.0f, 1.0f);
	    GDK_COLORMAP_ALLOC_COLOR(colormap, &bg);

	    mask = gdk_bitmap_create_from_data(
		window,
		(const gchar *)exactoknife_mask_16x16_bm,
		width, height
	    );
	    pixmap = gdk_pixmap_create_from_data(
		window,
		(const gchar *)exactoknife_16x16_bm,
		width, height, 1,
		&fg, &bg
	    );
	    if(pixmap != NULL)
	    {
		GDK_CURSOR_DESTROY(iv->crop_cur);
		iv->crop_cur = gdk_cursor_new_from_pixmap(
		    pixmap, mask, &fg, &bg,
		    0, height - 1
		);
	    }
	    GDK_PIXMAP_UNREF(pixmap);
	    GDK_BITMAP_UNREF(mask);

	    mask = gdk_bitmap_create_from_data(
		window,
		(const gchar *)color_probe_mask_16x16_bm,
		width, height
	    );
	    pixmap = gdk_pixmap_create_from_data(
		window,
		(const gchar *)color_probe_16x16_bm,
		width, height, 1,
		&fg, &bg
	    );
	    if(pixmap != NULL)
	    {
		GDK_CURSOR_DESTROY(iv->color_probe_cur);
		iv->color_probe_cur = gdk_cursor_new_from_pixmap(
		    pixmap, mask, &fg, &bg,
		    0, height - 1
		);
	    }
	    GDK_PIXMAP_UNREF(pixmap);
	    GDK_BITMAP_UNREF(mask);

	    GDK_COLORMAP_FREE_COLOR(colormap, &fg);
	    GDK_COLORMAP_FREE_COLOR(colormap, &fg);
	}
	if(iv->crop_cur == NULL)
	    iv->crop_cur = gdk_cursor_new(GDK_TCROSS);



	/* Create scroll bars, make sure that we increase the
	 * ref count for the adjustment since it will be unref'ed
	 * when the scroll bar is destroyed
	 */
	gtk_object_ref(GTK_OBJECT(iv->view_x_adj));
	iv->hscrollbar = w = gtk_hscrollbar_new(iv->view_x_adj);
	gtk_table_attach(
	    GTK_TABLE(parent2), w,
	    0, 1, 1, 2,
	    GTK_EXPAND | GTK_SHRINK | GTK_FILL,
	    GTK_FILL,
	    0, 0
	);
	gtk_widget_show(w);

	gtk_object_ref(GTK_OBJECT(iv->view_y_adj));
	iv->vscrollbar = w = gtk_vscrollbar_new(iv->view_y_adj);
	gtk_table_attach(
	    GTK_TABLE(parent2), w,
	    1, 2, 0, 1,
	    GTK_FILL,
	    GTK_EXPAND | GTK_SHRINK | GTK_FILL,
	    0, 0
	);
	gtk_widget_show(w);


	/* Status bar */
	iv->statusbar_map_state = show_statusbar;
#if 0
/* Frame does not look pretty for a simple status bar with only a
 * progress bar
 */
	/* Status bar toplevel hbox */
	iv->statusbar_toplevel = w = gtk_frame_new(NULL);
	gtk_widget_set_usize(w, -1, IMGVIEW_STATUSBAR_HEIGHT);
	gtk_frame_set_shadow_type(GTK_FRAME(w), GTK_SHADOW_OUT);
	gtk_container_border_width(GTK_CONTAINER(w), 0);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	if(show_statusbar)
	    gtk_widget_show(w);
	parent2 = w;
#endif
	/* Hbox to stretch progress bar */
	iv->statusbar_toplevel = w = gtk_hbox_new(FALSE, 0);
	gtk_widget_set_usize(w, -1, IMGVIEW_STATUSBAR_HEIGHT);
	gtk_box_pack_start(GTK_BOX(parent), w, FALSE, FALSE, 0);
	if(show_statusbar)
	    gtk_widget_show(w);
	parent2 = w;
/* We need to use a GtkTable here instead if we want a label or other
 * widgets on the status bar
 */
	/* Status bar progress bar */
	adj = (GtkAdjustment *)gtk_adjustment_new(0, 1, 100, 0, 0, 0);
	iv->progress_bar = w = gtk_progress_bar_new_with_adjustment(adj);
/*	gtk_widget_set_usize(w, 100, -1); */
	gtk_progress_bar_set_orientation(
	    GTK_PROGRESS_BAR(w), GTK_PROGRESS_LEFT_TO_RIGHT
	);
	gtk_progress_bar_set_bar_style(
	    GTK_PROGRESS_BAR(w), GTK_PROGRESS_CONTINUOUS
	);
	gtk_progress_set_activity_mode(
	    GTK_PROGRESS(w), FALSE
	);
	gtk_box_pack_start(GTK_BOX(parent2), w, TRUE, TRUE, 0);
	gtk_widget_show(w);






	/* Right-click menu */
	iv->menu = w = (GtkWidget *)GUIMenuCreate();
	if(w != NULL)
	{
	    GtkWidget *submenu, *menu = w;
	    guint accel_key, accel_mods;
	    GtkAccelGroup *accelgrp = NULL;
	    guint8 **icon;
	    const gchar *label;
	    gpointer mclient_data = iv;
	    void (*func_cb)(GtkWidget *w, gpointer) = NULL;

#define DO_ADD_MENU_ITEM_LABEL	{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_LABEL, accelgrp,	\
  icon, label, accel_key, accel_mods, NULL,	\
  mclient_data, func_cb				\
 );						\
}
#define DO_ADD_MENU_ITEM_CHECK	{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_CHECK, accelgrp,	\
  icon, label, accel_key, accel_mods, NULL,	\
  mclient_data, func_cb				\
 );						\
}
#define DO_ADD_MENU_ITEM_SUBMENU	{	\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_SUBMENU, accelgrp,	\
  icon, label, accel_key, accel_mods, NULL,	\
  mclient_data, func_cb				\
 );						\
 GUIMenuItemSetSubMenu(w, submenu);		\
}
#define DO_ADD_MENU_SEP		{		\
 w = GUIMenuItemCreate(				\
  menu, GUI_MENU_ITEM_TYPE_SEPARATOR, NULL,	\
  NULL, NULL, 0, 0, NULL,			\
  NULL, NULL					\
 );						\
}
#if 0
	    icon = (guint8 **)icon_zoom_in_20x20_xpm;
	    label = "Zoom In";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ImgViewZoomInCB;
	    DO_ADD_MENU_ITEM_LABEL
	    iv->zoom_in_mi = w;

	    icon = (guint8 **)icon_zoom_out_20x20_xpm;
	    label = "Zoom Out";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ImgViewZoomOutCB;
	    DO_ADD_MENU_ITEM_LABEL
	    iv->zoom_out_mi = w;
#endif
	    icon = (guint8 **)icon_zoom_onetoone_20x20_xpm;
	    label = "Zoom 1:1";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ImgViewZoomOneToOneCB;
	    DO_ADD_MENU_ITEM_LABEL
	    iv->zoom_onetoone_mi = w;

	    icon = (guint8 **)icon_zoom_tofit_20x20_xpm;
	    label = "Zoom To Fit";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = ImgViewZoomToFitCB;
	    DO_ADD_MENU_ITEM_LABEL
	    iv->zoom_tofit_mi = w;

	    /* Rotate submenu */
	    iv->rotate_transform_menu = submenu = GUIMenuCreate();
	    if(submenu != NULL)
	    {
		GtkWidget *menu;	/* Overload */

		menu = submenu;

		icon = (guint8 **)icon_rotate_cw_20x20_xpm;
		label = "90 Clockwise";
		accel_key = 0;
		accel_mods = 0;
		func_cb = ImgViewRotateCW90CB;
		DO_ADD_MENU_ITEM_LABEL
		iv->rotate_cw_90_mi = w;

		icon = (guint8 **)icon_rotate_ccw_20x20_xpm;
		label = "90 Counter Clockwise";
		accel_key = 0;
		accel_mods = 0;
		func_cb = ImgViewRotateCCW90CB;
		DO_ADD_MENU_ITEM_LABEL
		iv->rotate_ccw_90_mi = w;

		icon = (guint8 **)icon_rotate_cw_20x20_xpm;
		label = "180 Clockwise";
		accel_key = 0;
		accel_mods = 0;
		func_cb = ImgViewRotateCW180CB;
		DO_ADD_MENU_ITEM_LABEL
		iv->rotate_cw_180_mi = w;

		DO_ADD_MENU_SEP

		icon = (guint8 **)icon_mirror_horizontal_20x20_xpm;
		label = "Mirror Horizontal";
		accel_key = 0;
		accel_mods = 0;
		func_cb = ImgViewMirrorHorizontalCB;
		DO_ADD_MENU_ITEM_LABEL
		iv->mirror_horizontal_mi = w;

		icon = (guint8 **)icon_mirror_vertical_20x20_xpm;
		label = "Mirror Vertical";
		accel_key = 0;
		accel_mods = 0;
		func_cb = ImgViewMirrorVerticalCB;
		DO_ADD_MENU_ITEM_LABEL
		iv->mirror_vertical_mi = w;
	    }
	    icon = NULL;
	    label = "Rotate/Transform";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = NULL;
	    DO_ADD_MENU_ITEM_SUBMENU
	    iv->rotate_transform_submenu_mi = w;

	    DO_ADD_MENU_SEP

	    /* Show submenu */
	    iv->show_menu = submenu = GUIMenuCreate();
	    if(submenu != NULL)
	    {
		GtkWidget *menu;	/* Overload */

		menu = submenu;

		icon = NULL;
		label = "Tool Bar";
	        accel_key = 0;
	        accel_mods = 0;
	        func_cb = ImgViewToolBarToggleCB;
	        DO_ADD_MENU_ITEM_CHECK
	        iv->show_toolbar_micheck = w;
		GUIMenuItemSetCheck(w, iv->toolbar_map_state, FALSE);

	        icon = NULL;
	        label = "Values";
	        accel_key = 0;
	        accel_mods = 0;
	        func_cb = ImgViewValuesToggleCB;
	        DO_ADD_MENU_ITEM_CHECK
	        iv->show_values_micheck = w;
	        GUIMenuItemSetCheck(w, iv->show_values, FALSE);

	        icon = NULL;
		label = "Status Bar";
	        accel_key = 0;
	        accel_mods = 0;
	        func_cb = ImgViewStatusBarToggleCB;
	        DO_ADD_MENU_ITEM_CHECK
	        iv->show_statusbar_micheck = w;
	        GUIMenuItemSetCheck(w, iv->statusbar_map_state, FALSE);
	    }
	    icon = NULL;
	    label = "Show";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = NULL;
	    DO_ADD_MENU_ITEM_SUBMENU
	    iv->show_submenu_mi = w;

	    /* Quality submenu */
	    iv->quality_menu = submenu = GUIMenuCreate();
	    if(submenu != NULL)
	    {
		GtkWidget *menu;	/* Overload */

		menu = submenu;

		icon = NULL;
		label = "Poor/Fastest";
		accel_key = 0;
		accel_mods = 0;
		func_cb = ImgViewQualityPoorCB;
		DO_ADD_MENU_ITEM_CHECK
		iv->quality_poor_mi = w;
		GUIMenuItemSetCheck(w, (quality == 0) ? TRUE : FALSE, FALSE);

		icon = NULL;
		label = "Optimal";
		accel_key = 0;
		accel_mods = 0;
		func_cb = ImgViewQualityOptimalCB;
		DO_ADD_MENU_ITEM_CHECK
		iv->quality_optimal_mi = w;
		GUIMenuItemSetCheck(w, (quality == 1) ? TRUE : FALSE, FALSE);

		icon = NULL;
		label = "Best/Slowest";
		accel_key = 0;
		accel_mods = 0;
		func_cb = ImgViewQualityBestCB;
		DO_ADD_MENU_ITEM_CHECK
		iv->quality_best_mi = w;
		GUIMenuItemSetCheck(w, (quality == 2) ? TRUE : FALSE, FALSE);
	    }
	    icon = NULL;
	    label = "Quality";
	    accel_key = 0;
	    accel_mods = 0;
	    func_cb = NULL;
	    DO_ADD_MENU_ITEM_SUBMENU
	    iv->quality_submenu_mi = w;

#undef DO_ADD_MENU_ITEM_LABEL
#undef DO_ADD_MENU_ITEM_CHECK
#undef DO_ADD_MENU_ITEM_SUBMENU
#undef DO_ADD_MENU_SEP

	}

	/* Reset and update menus */
	ImgViewReset(iv, FALSE);

	return(iv);
}

/*
 *	Sets the ImgView's image changed callback function.
 */
void ImgViewSetChangedCB(
	imgview_struct *iv,
	void (*changed_cb)(
		imgview_struct *,
		imgview_image_struct *,
		gpointer
	),
	gpointer data
)
{
	if(iv == NULL)
	    return;

	iv->changed_cb = changed_cb;
	iv->changed_data = data;
}

/*
 *	Resets alpha values.
 *
 *	Resets translate and zoom positions.
 *
 *	Deletes the ImgView's dialogs.
 *
 *	Removes all idle and timeout callbacks.
 *
 *	Unloads the image and stops animations by calling
 *	ImgViewClear().
 *
 *	Updates menus.
 *
 *	If need_unmap is TRUE then the ImgView will be unmapped.
 */
void ImgViewReset(imgview_struct *iv, gboolean need_unmap)
{
	if(iv == NULL)
	    return;

	/* Reset alpha channel flags */
	iv->alpha_flags = (imgview_alpha_flags)0;
	iv->alpha_threshold = 0x80;

	/* Reset zoom */
	iv->last_view_x = 0.0f;
	iv->last_view_y = 0.0f;
	iv->view_last_zoom = iv->view_zoom = 1.0f;

	/* Reset the drag mode */
	iv->drag_mode = IMGVIEW_DRAG_MODE_NONE;

	/* Reset last drag values */
	iv->drag_last_x = 0;
	iv->drag_last_y = 0;

	/* Reset the Drag Rectangular values */
	iv->drag_zoom_rectangle_start_x =
	    iv->drag_zoom_rectangle_cur_x = 0;
	iv->drag_zoom_rectangle_start_y =
	    iv->drag_zoom_rectangle_cur_y = 0;

	/* Reset the Crop Rectangle value */
	iv->crop_flags &= ~IMGVIEW_CROP_DEFINED;
	iv->crop_flags &= ~IMGVIEW_CROP_FINALIZED;
	iv->crop_rectangle_start_x =
	    iv->crop_rectangle_start_y = 0;
	iv->crop_rectangle_cur_x =
	    iv->crop_rectangle_cur_y = 0;

	/* Delete the Crop Dialog */
	ImgViewCropDlgDelete(iv->crop_dialog);
	iv->crop_dialog = NULL;

	/* Reset the Color Probe */
	iv->color_probe_x = 0;
	iv->color_probe_y = 0;


	/* Unload the image (if any) */
	ImgViewClear(iv);

	/* Realize changes and update menus */
	ImgViewRealizeChange(iv);
	ImgViewUpdateMenus(iv);

	if(need_unmap)
	    ImgViewUnmap(iv);

	/* Flush output for any pending async image sends */
/*	gdk_flush(); */

	/* Remove timeouts and idles
	 *
	 * These need to be done after the function calls above which
	 * may restablish them
	 */
	GTK_IDLE_REMOVE(iv->view_zoom_idle);
	iv->view_zoom_idle = 0;
	GTK_TIMEOUT_REMOVE(iv->view_zoom_toid);
	iv->view_zoom_toid = 0;
	GTK_IDLE_REMOVE(iv->view_draw_idle_id);
	iv->view_draw_idle_id = 0;
	GTK_TIMEOUT_REMOVE(iv->next_frame_toid);
	iv->next_frame_toid = 0;
}

/*
 *	Queues a redraw for the ImgView's view.
 */
void ImgViewQueueDrawView(imgview_struct *iv)
{
	if(iv == NULL)
	    return;

	/* Already queued? */
	if(iv->view_draw_idle_id > 0)
	    return;

	/* Queue redraw by adding a idle callback to the draw idle
	 * callback
	 */
	iv->view_draw_idle_id = gtk_idle_add_priority(
	    GTK_PRIORITY_REDRAW,
	    ImgViewDrawViewIdleCB, iv
	);
}

/*
 *	Queues a send image for the ImgView's view.
 *
 *	This is the same as ImgViewQueueDrawView() except that the
 *	original image will not be blitted to the view's image,
 *	suitable for only when you need to send the view image to its
 *	GdkDrawable and draw any rectangular markings.
 */
void ImgViewQueueSendView(imgview_struct *iv)
{
	GtkWidget *w;

	if(iv == NULL)
	    return;

	w = iv->view_da;
	if(w != NULL)
	    gtk_widget_queue_draw(w);
}

/*
 *	Redraws the ImgView.
 */
void ImgViewDraw(imgview_struct *iv)
{
	if(iv == NULL)
	    return;

	ImgViewInfoLabelDraw(iv);
	ImgViewDrawView(iv, TRUE, NULL);
}

/*
 *	Updates the ImgView's widgets to reflect current
 *	values.
 */
void ImgViewUpdateMenus(imgview_struct *iv)
{
	gboolean sensitive, state;
	GtkWidget *w;
	gint quality;
	imgview_image_struct *src_img;
	if(iv == NULL)
	    return;

	quality = iv->quality;
	src_img = iv->orig_img;

#define DO_SET_CHECK_MENU_ITEM	{		\
 iv->freeze_count++;				\
 GUIMenuItemSetCheck(w, state, TRUE);		\
 iv->freeze_count--;				\
}

	/* Update toolbar widgets */
	sensitive = (src_img != NULL) ? TRUE : FALSE;
	GTK_WIDGET_SET_SENSITIVE(iv->zoom_in_btn, sensitive);
	GTK_WIDGET_SET_SENSITIVE(iv->zoom_out_btn, sensitive);
	GTK_WIDGET_SET_SENSITIVE(iv->zoom_onetoone_btn, sensitive);
	GTK_WIDGET_SET_SENSITIVE(iv->zoom_tofit_btn, sensitive);

	/* Update menu items */
	sensitive = (src_img != NULL) ? TRUE : FALSE;
	GTK_WIDGET_SET_SENSITIVE(iv->zoom_in_mi, sensitive);
	GTK_WIDGET_SET_SENSITIVE(iv->zoom_out_mi, sensitive);
	GTK_WIDGET_SET_SENSITIVE(iv->zoom_onetoone_mi, sensitive);
	GTK_WIDGET_SET_SENSITIVE(iv->zoom_tofit_mi, sensitive);
	GTK_WIDGET_SET_SENSITIVE(iv->rotate_cw_90_mi, sensitive);
	GTK_WIDGET_SET_SENSITIVE(iv->rotate_ccw_90_mi, sensitive);
	GTK_WIDGET_SET_SENSITIVE(iv->rotate_cw_180_mi, sensitive);
	GTK_WIDGET_SET_SENSITIVE(iv->mirror_horizontal_mi, sensitive);
	GTK_WIDGET_SET_SENSITIVE(iv->mirror_vertical_mi, sensitive);

	w = iv->show_toolbar_micheck;
	state = iv->toolbar_map_state;
	DO_SET_CHECK_MENU_ITEM
	w = iv->show_values_micheck;
	state = iv->show_values;
	DO_SET_CHECK_MENU_ITEM
	w = iv->show_statusbar_micheck;
	state = iv->statusbar_map_state;
	DO_SET_CHECK_MENU_ITEM

	/* Update quality menu items */
	w = iv->quality_poor_mi;
	state = (quality == 0) ? TRUE : FALSE;
	DO_SET_CHECK_MENU_ITEM
	w = iv->quality_optimal_mi;
	state = (quality == 1) ? TRUE : FALSE;
	DO_SET_CHECK_MENU_ITEM
	w = iv->quality_best_mi;
	state = (quality == 2) ? TRUE : FALSE;
	DO_SET_CHECK_MENU_ITEM


#undef DO_SET_CHECK_MENU_ITEM
}


/*
 *	Shows or hides the ImgView's Tool Bar.
 */
void ImgViewShowToolBar(imgview_struct *iv, gboolean show)
{
	if(iv == NULL)
	    return;

	if(show)
	{
	    /* Show toolbar */
	    if(!iv->toolbar_map_state)
	    {
		GtkWidget *w = iv->toolbar_toplevel;
		if(w != NULL)
		    gtk_widget_show(w);
		iv->toolbar_map_state = TRUE;
		ImgViewUpdateMenus(iv);
	    }
	}
	else
	{
	    /* Hide toolbar */
	    if(iv->toolbar_map_state)
	    {
		GtkWidget *w = iv->toolbar_toplevel;
		if(w != NULL)
		    gtk_widget_hide(w);
		iv->toolbar_map_state = FALSE;
		ImgViewUpdateMenus(iv);
	    }
	}
}

/*
 *	Shows or hides the ImgView's Status Bar.
 */
void ImgViewShowStatusBar(imgview_struct *iv, gboolean show)
{
	if(iv == NULL)
	    return;

	if(show)
	{
	    /* Show status bar */
	    if(!iv->statusbar_map_state)
	    {
		GtkWidget *w = iv->statusbar_toplevel;
		if(w != NULL)
		    gtk_widget_show(w);
		iv->statusbar_map_state = TRUE;
		ImgViewUpdateMenus(iv);
	    }
	}
	else
	{
	    /* Hide status bar */
	    if(iv->statusbar_map_state)
	    {
		GtkWidget *w = iv->statusbar_toplevel;
		if(w != NULL)
		    gtk_widget_hide(w);
		iv->statusbar_map_state = FALSE;
		ImgViewUpdateMenus(iv);
	    }
	}
}

/*
 *	Sets the ImgView's background color.
 *
 *	The specified color does not need to be allocated.
 *
 *	The colors in the array must be 5 to match with each enumeration
 *	of GTK_STATE_*.
 */
void ImgViewSetViewBG(
	imgview_struct *iv,
	GdkColor *c		/* 5 colors */
)
{
	gint i;
	GtkWidget *w = (GtkWidget *)ImgViewGetViewWidget(iv);
	GtkRcStyle *rcstyle;
	GdkColor *ct, *cs;
	if((w == NULL) || (c == NULL))
	    return;

	rcstyle = gtk_rc_style_new();
	for(i = 0; i < 5; i++)
	{
	    rcstyle->color_flags[i] = GTK_RC_BASE;

	    ct = &rcstyle->base[i];
	    cs = &c[i];
	    ct->red = cs->red;
	    ct->green = cs->green;
	    ct->blue = cs->blue;
	}
	gtk_widget_modify_style(w, rcstyle);
	GTK_RC_STYLE_UNREF(rcstyle)

	/* Update menus and redraw view to reflect the new background
	 * color
	 */
	ImgViewUpdateMenus(iv);
	ImgViewQueueDrawView(iv);
}


/*
 *	Zoom in one step.
 */
void ImgViewZoomIn(imgview_struct *iv)
{
	if(iv == NULL)
	    return;

	/* Zoom in */
/*
	ImgViewZoomIterate(iv, -IMGVIEW_ZOOM_ITERATION_PIXELS);
 */
	ImgViewZoomIterateCoeff(iv, 0.1f);

	/* Clip bounds, update adjustments and redraw */
	ImgViewRealizeChange(iv);
	ImgViewQueueDrawView(iv);
}

/*
 *	Zoom out one step.
 */
void ImgViewZoomOut(imgview_struct *iv)
{
	if(iv == NULL)
	    return;

	/* Zoom out */
/*
	ImgViewZoomIterate(iv, IMGVIEW_ZOOM_ITERATION_PIXELS);
 */
	ImgViewZoomIterateCoeff(iv, -0.1f);

	/* Clip bounds, update adjustments and redraw */
	ImgViewRealizeChange(iv);
	ImgViewQueueDrawView(iv);
}

/*
 *	Zoom one to one.
 */
void ImgViewZoomOneToOne(imgview_struct *iv)
{
	ImgViewZoomOneToOneCB(NULL, iv);
}

/*
 *	Zoom to fit.
 */
void ImgViewZoomToFit(imgview_struct *iv)
{
	ImgViewZoomToFitCB(NULL, iv);
}

/*
 *	Restore previous zoom.
 */
void ImgViewZoomPrev(imgview_struct *iv)
{
	ImgViewZoomPrevCB(NULL, iv);
}


/*
 *	Sets the given ImgView to allow user cropping or not.
 */
void ImgViewAllowCrop(imgview_struct *iv, gboolean allow_crop)
{
	if(iv == NULL)
	    return;

	if(allow_crop)
	    iv->crop_flags |= IMGVIEW_CROP_ALLOWED;
	else
	    iv->crop_flags &= ~IMGVIEW_CROP_ALLOWED;

	ImgViewUpdateMenus(iv);
}

/*
 *	Marks the ImgView as busy or ready.
 */
void ImgViewSetBusy(imgview_struct *iv, gboolean busy)
{
	GdkCursor *cur;
	GtkWidget *w = (iv != NULL) ? iv->toplevel : NULL;
	if(w == NULL)
	    return;

	if(busy)
	{
	    iv->busy_count++;
	    if(iv->busy_count > 1)
		return;
	    cur = iv->busy_cur;
	}
	else
	{
	    iv->busy_count--;
	    if(iv->busy_count < 0)
		iv->busy_count = 0;
	    if(iv->busy_count > 0)
		return;  
	    cur = NULL;			/* Use default cursor */
	}

	if(w->window != NULL)
	{
	    gdk_window_set_cursor(w->window, cur);
	    gdk_flush();
	}
}

/*
 *	Updates the progress bar.
 */
void ImgViewProgressUpdate(
	imgview_struct *iv, gfloat position, gboolean allow_gtk_iteration
)
{
	GtkProgressBar *pb;
	GtkWidget *w = (iv != NULL) ? iv->progress_bar : NULL;
	if(w == NULL)
	    return;

	pb = GTK_PROGRESS_BAR(w);

	if(position > 1.0f)
	    position = 1.0f;
	else if(position < 0.0f)
	    position = 0.0f;

	gtk_progress_bar_update(pb, position);

	if(allow_gtk_iteration)
	{
	    while(gtk_events_pending() > 0)
		gtk_main_iteration();
	}
}

/*
 *	Maps the ImgView.
 */
void ImgViewMap(imgview_struct *iv)
{
	GtkWidget *w = (iv != NULL) ? iv->toplevel : NULL;
	if(w == NULL)
	    return;

	gtk_widget_show_raise(w);
	iv->map_state = TRUE;
}

/*
 *	Unmaps the ImgView.
 */
void ImgViewUnmap(imgview_struct *iv)
{
	GtkWidget *w = (iv != NULL) ? iv->toplevel : NULL; 
	if(w == NULL) 
	    return; 

	gtk_widget_hide(w);
	iv->map_state = FALSE;
}

/*
 *	Deletes the ImgView.
 */
void ImgViewDelete(imgview_struct *iv)
{
	if(iv == NULL)
	    return;

	iv->freeze_count++;

	/* Delete the Crop Dialog */
	ImgViewCropDlgDelete(iv->crop_dialog);
	iv->crop_dialog = NULL;

	/* Remove all timeouts and idles */
	GTK_IDLE_REMOVE(iv->view_zoom_idle);
	iv->view_zoom_idle = 0;
	GTK_TIMEOUT_REMOVE(iv->view_zoom_toid);
	iv->view_zoom_toid = 0;
	GTK_IDLE_REMOVE(iv->view_draw_idle_id);
	iv->view_draw_idle_id = 0;
	GTK_TIMEOUT_REMOVE(iv->next_frame_toid);
	iv->next_frame_toid = 0;   

	/* Destroy all widgets */
	GTK_WIDGET_DESTROY(iv->rotate_transform_menu);
	GTK_WIDGET_DESTROY(iv->show_menu);
	GTK_WIDGET_DESTROY(iv->quality_menu);
	GTK_WIDGET_DESTROY(iv->menu);
	GTK_WIDGET_DESTROY(iv->view_da);
	GTK_WIDGET_DESTROY(iv->toplevel);

	/* Unref the icon pixmap & mask */
	GDK_PIXMAP_UNREF(iv->wm_icon_pixmap);
	GDK_BITMAP_UNREF(iv->wm_icon_mask);

	GTK_ACCEL_GROUP_UNREF(iv->accelgrp);

	/* Delete all ImgView images */
	ImgViewImageDelete(iv->view_img);
	ImgViewImageDelete(iv->orig_img);

	/* Unref all GtkAdjustments */
	GTK_OBJECT_UNREF(iv->view_x_adj);
	GTK_OBJECT_UNREF(iv->view_y_adj);

	/* Destroy all cursors */
	GDK_CURSOR_DESTROY(iv->busy_cur);
	GDK_CURSOR_DESTROY(iv->translate_cur);
	GDK_CURSOR_DESTROY(iv->zoom_cur);
	GDK_CURSOR_DESTROY(iv->zoom_rectangle_cur);
	GDK_CURSOR_DESTROY(iv->crop_cur);
	GDK_CURSOR_DESTROY(iv->color_probe_cur);

	GDK_BITMAP_UNREF(iv->scissor_bm);

	GDK_GC_UNREF(iv->view_selection_gc);

	iv->freeze_count--;

	g_free(iv);
}
