#include <string.h>
#include <ctype.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include "guiutils.h"
#include "hview.h"
#include "hviewcb.h"


static guint8 HViewHexConvert(const gchar *buf);

static gint HViewScrollUpTOCB(gpointer data);
static gint HViewScrollDownTOCB(gpointer data);

gint HViewDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
);

gint HViewConfigureCB(
	GtkWidget *widget, GdkEventConfigure *configure, gpointer data
);
gint HViewExposeCB(
	GtkWidget *widget, GdkEventExpose *expose, gpointer data
);
gint HViewKeyCB(
	GtkWidget *widget, GdkEventKey *key, gpointer data
);
gint HViewButtonCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
);
gint HViewMotionCB(
	GtkWidget *widget, GdkEventMotion *motion, gpointer data
);
void HViewValueChangedCB(GtkAdjustment *adj, gpointer data);

void HViewInsertCB(GtkWidget *w, gpointer data);
void HViewDeleteCB(GtkWidget *w, gpointer data);
void HViewCutCB(GtkWidget *w, gpointer data);
void HViewCopyCB(GtkWidget *w, gpointer data);
void HViewPasteCB(GtkWidget *w, gpointer data);
void HViewSelectAllCB(GtkWidget *w, gpointer data);
void HViewUnselectAllCB(GtkWidget *w, gpointer data);
void HViewEditModeHexCB(GtkWidget *w, gpointer data);
void HViewEditModeASCIICB(GtkWidget *w, gpointer data);


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


/*
 *	Converts the given hex string (without the 0x prefix) into
 *	an guint8 value.
 */
static guint8 HViewHexConvert(const gchar *buf)
{
	gchar a = buf[0];
	guint8 v = 0x00;

	if((a >= 'A') && (a <= 'F'))
	    v += (guint8)(a - 'A' + 10) * 16;
	else
	    v += (guint8)(a - '0') * 16;

	a = buf[1];

	if((a >= 'A') && (a <= 'F'))
	    v += (guint8)(a - 'A' + 10);
	else
	    v += (guint8)(a - '0');

	return(v);
}

/*
 *	Scroll up timeout callback.
 */
static gint HViewScrollUpTOCB(gpointer data)
{
	gint x, y;
	GdkModifierType mask;
	GdkWindow *window;
	GdkEventMotion ev;
	GtkWidget *w;
	hview_struct *hv = HVIEW(data);
	if(hv == NULL)
	    return(FALSE);

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

	gdk_window_get_pointer(window, &x, &y, &mask);

	memset(&ev, 0x00, sizeof(GdkEventMotion));
	ev.type = GDK_MOTION_NOTIFY;
	ev.window = window;
	ev.send_event = TRUE;
	ev.time = GDK_CURRENT_TIME;
	ev.x = (gdouble)x;
	ev.y = (gdouble)y;
	ev.state = mask;
	ev.is_hint = FALSE;

	return(HViewMotionCB(w, &ev, data));
}

/*
 *	Scroll down timeout callback.
 */
static gint HViewScrollDownTOCB(gpointer data)
{
	return(HViewScrollUpTOCB(data));
}



/*
 *	Hex View toplevel GtkWindow "delete_event" signal callback.
 */
gint HViewDeleteEventCB(
	GtkWidget *widget, GdkEvent *event, gpointer data
)
{
	HViewUnmap(HVIEW(data));
	return(TRUE);
}

/*
 *	Hex View view GtkDrawingaArea "configure_event" signal callback.
 */
gint HViewConfigureCB(
	GtkWidget *widget, GdkEventConfigure *configure, gpointer data
)
{
	gint status = FALSE;
	gint width, height;
	GtkAdjustment *adj;
	GtkWidget *w;
	hview_struct *hv = HVIEW(data);
	if((widget == NULL) || (configure == NULL) || (hv == NULL))
	    return(status);

	w = hv->view_da;
	if(w == NULL)
	    return(status);

	width = configure->width;
	height = configure->height;

	/* Recreate view pixmap */
	if(hv->view_pm != NULL)
	    gdk_pixmap_unref(hv->view_pm);
	if((width > 0) && (height > 0))
	    hv->view_pm = GDK_PIXMAP_NEW(width, height);
	else
	    hv->view_pm = NULL;

	/* Update adjustments */
	adj = hv->vadj;
	if(adj != NULL)
	{
	    adj->lower = 0.0f;
	    adj->upper = (gfloat)(
		(hv->buf_len / hv->cells_per_row * hv->cell_height) +
		((hv->buf_len % hv->cells_per_row) ? hv->cell_height : 0)
	    );
	    adj->page_size = (gfloat)height;
	    adj->step_increment = (gfloat)hv->cell_height;
	    adj->page_increment = adj->page_size / 2.0f;
	    gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");

	    if(adj->value > (adj->upper - adj->page_size))
	    {
		adj->value = adj->upper - adj->page_size;
		if(adj->value < adj->lower)
		    adj->value = adj->lower;
		gtk_signal_emit_by_name(GTK_OBJECT(adj), "value_changed");
	    }
	}

	status = TRUE;

	return(status);
}

/*
 *	Hex View view GtkDrawingArea "expose_event" callback.
 */
gint HViewExposeCB(
	GtkWidget *widget, GdkEventExpose *expose, gpointer data
)
{
	gint status = FALSE;
	const guint8 *buf;
	gint buf_pos, buf_len;
	gint width, height;
	gint cell_width, cell_height, cells_per_row;
	GdkWindow *window;
	GdkPixmap *pixmap;
	GdkDrawable *drawable;
	GdkGC *gc;
	GtkAdjustment *adj;
	GtkStateType state;
	GtkStyle *style;
	GtkWidget *w;
	hview_struct *hv = HVIEW(data);
	if(hv == NULL)
	    return(status);

	buf = hv->buf;
	buf_pos = hv->buf_pos;
	buf_len = hv->buf_len;

	cell_width = hv->cell_width;
	cell_height = hv->cell_height;
	cells_per_row = hv->cells_per_row;

	if((cell_width <= 0) || (cell_height <= 0) || (cells_per_row <= 0))
	    return(status);

	gc = hv->gc;
	adj = hv->vadj;
	w = hv->view_da;
	if((gc == NULL) || (adj == NULL) || (w == NULL))
	    return(status);

	if(!GTK_WIDGET_VISIBLE(w))
	    return(status);

	gdk_gc_set_function(gc, GDK_COPY);
	gdk_gc_set_fill(gc, GDK_SOLID);

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

	/* Create view pixmap as needed */
	if(hv->view_pm == NULL)
	    hv->view_pm = pixmap = GDK_PIXMAP_NEW(
		w->allocation.width, w->allocation.height
	    );
	else
	    pixmap = hv->view_pm;
	if(pixmap == NULL)
	    return(status);

	gdk_window_get_size(pixmap, &width, &height);
	if((width <= 0) || (height <= 0))
	    return(status);

	drawable = pixmap;

	/* Draw base */
	gdk_draw_rectangle(
	    drawable, style->base_gc[state], TRUE,
	    0, 0, width, height
	);

	/* Draw text */
	if((buf != NULL) && (hv->font != NULL))
	{
	    gboolean is_selected;
	    const gint	border = 0,
			edit_buf_pos = hv->edit_buf_i;
	    const gchar *edit_buf = hv->edit_buf;
	    gint i, j, n, x, y;
	    GdkFont *font = hv->font;
	    gint buf_start = (gint)adj->value / cell_height * cells_per_row;
	    gint y_start = border - ((gint)adj->value % cell_height);
	    gint	sel_start = hv->buf_sel_start,
			sel_end = hv->buf_sel_end;
	    gchar tmp_buf[80];

	    /* Set up gc for drawing text */
	    gdk_gc_set_font(gc, font);

	    /* Sanitize the selection bounds */
	    if((sel_start > -1) && (sel_end > -1))
	    {
		if(sel_end < sel_start)
		{
		    const gint t = sel_start;
		    sel_start = sel_end;
		    sel_end = t;
		}
	    }

	    /* Set the starting positions */
	    i = buf_start;		/* Buffer index */
	    if(i < 0)
		i = 0;
	    y = y_start;		/* Line */

	    /* Draw each line */
	    while((i < buf_len) && (y < height))
	    {
		/* Draw the address */
		x = hv->address_x;
		g_snprintf(
		    tmp_buf, sizeof(tmp_buf),
		    "0x%.8X", i
		);
		gdk_gc_set_foreground(gc, &style->text[state]);
		gdk_draw_string(
		    drawable, font, gc,
		    x, y + font->ascent, tmp_buf
		);

		/* Draw each hex value
		 *
		 * n is the number of hex values drawn for this line
		 * j is the buffer index
		 * x is the x coordinate position in window coordinates
		 */
		for(n = 0, j = i, x = hv->hex_x;
		    n < cells_per_row;
		    n++, j++, x += (3 * cell_width)
		)
		{
		    if(j >= buf_len)
			break;

		    /* Is this hex value selected? */
		    if((j >= sel_start) && (j <= sel_end))
		    {
			/* Draw the selected background */
			gdk_draw_rectangle(
			    drawable,
			    style->bg_gc[GTK_STATE_SELECTED],
			    TRUE, x - 1, y,
			    (2 * cell_width) + 2, cell_height
			);
			gdk_gc_set_foreground(
			    gc, &style->text[GTK_STATE_SELECTED]
			);
			is_selected = TRUE;
		    }
		    else
		    {
			gdk_gc_set_foreground(gc, &style->text[state]);
			is_selected = FALSE;
		    }

		    /* Is a value being edited? */
		    if((edit_buf_pos > 0) ?
			(is_selected ? TRUE : (j == hv->buf_pos)) : FALSE
		    )
		    {
			/* Draw the hex value from the edit buffer */
			const gint m = MIN(edit_buf_pos, sizeof(hv->edit_buf));
			gint i;

			for(i = 0; i < m; i++)
			    tmp_buf[i] = edit_buf[i];
			tmp_buf[i] = '_';
			tmp_buf[i + 1] = '\0';

			gdk_draw_string(
			    drawable, font, gc,
			    x, y + font->ascent,
			    tmp_buf
			);
		    }
		    else
		    {
			/* Draw the hex value */
			g_snprintf(
			    tmp_buf, sizeof(tmp_buf),
			    "%.2X", buf[j]
			);
			gdk_draw_string(
			    drawable, font, gc,
			    x, y + font->ascent,
			    tmp_buf
			);
		    }

		    /* Is the cursor over this value? */
		    if(j == hv->buf_pos)
		    {
			/* Draw the cursor */
			GdkGCValues gcv;
			gdk_gc_get_values(
			    style->text_gc[GTK_STATE_SELECTED], &gcv
			);
			gdk_gc_set_function(
			    style->text_gc[GTK_STATE_SELECTED], GDK_INVERT
			);
			gdk_draw_rectangle(
			    drawable,
			    style->text_gc[GTK_STATE_SELECTED],
			    FALSE, x - 1, y,
			    (2 * cell_width) + 1, cell_height - 1
			);
			gdk_gc_set_function(
			    style->text_gc[GTK_STATE_SELECTED],
			    gcv.function
			);
		    }
		}

		/* Draw each ASCII value
		 *
		 * n is the number of hex values drawn for this line
		 * j is the buffer index
		 * x is the x coordinate position in window coordinates
		 */
		for(n = 0, j = i, x = hv->ascii_x;
		    n < cells_per_row;
		    n++, j++, x += cell_width
		)
		{
		    if(j >= buf_len)
			break;

		    /* Draw selected color background if this cell falls
		     * within the selection bounds?  Also set text color
		     * for upcomming string drawing.
		     */
		    if((j >= sel_start) && (j <= sel_end))
		    {
			gdk_draw_rectangle(
			    drawable,
			    style->bg_gc[GTK_STATE_SELECTED],
			    TRUE, x, y, cell_width, cell_height
			);
			gdk_gc_set_foreground(
			    gc, &style->text[GTK_STATE_SELECTED]
			);
		    }
		    else
		    {
			gdk_gc_set_foreground(gc, &style->text[state]);
		    }
		    g_snprintf(
			tmp_buf, sizeof(tmp_buf),
			"%c", (gchar)buf[j]
		    );
		    gdk_draw_string(
			drawable, font, gc,
			x, y + font->ascent, tmp_buf
		    );

		    /* Cursor rectangle */
		    if(j == hv->buf_pos)
		    {
			GdkGCValues gcv;
			gdk_gc_get_values(
			    style->text_gc[GTK_STATE_SELECTED], &gcv
			);
			gdk_gc_set_function(
			    style->text_gc[GTK_STATE_SELECTED], GDK_INVERT
			);
			gdk_draw_rectangle(
			    drawable,
			    style->text_gc[GTK_STATE_SELECTED],
			    FALSE, x, y, cell_width - 1, cell_height - 1
			);
			gdk_gc_set_function(
			    style->text_gc[GTK_STATE_SELECTED],
			    gcv.function
			);
		    }
		}

		i += cells_per_row;
		y += cell_height;
	    }

	}

	/* Put the drawn pixmap to the window */
	if(window != drawable)
	    gdk_draw_pixmap(
		window, gc, drawable,
		0, 0, 0, 0, width, height
	    );

	status = TRUE;

	return(status);
}

/*
 *	Hex View view GtkDrawingArea "key_press_event" or
 *	"key_release_event" callback.
 */
gint HViewKeyCB(
	GtkWidget *widget, GdkEventKey *key, gpointer data
)
{
	gint status = FALSE;
	gint etype;
	gboolean key_press;
	GtkAdjustment *adj;
	hview_struct *hv = HVIEW(data);
	if((widget == NULL) || (key == NULL) || (hv == NULL))
	    return(status);

#define STOP_SIGNAL_EMIT	{			\
 gtk_signal_emit_stop_by_name(				\
  GTK_OBJECT(widget),					\
  (etype == GDK_KEY_PRESS) ?				\
   "key_press_event" : "key_release_event"		\
 );							\
}
	adj = hv->vadj;
	etype = key->type;
	key_press = (etype == GDK_KEY_PRESS) ? TRUE : FALSE;
	switch(key->keyval)
	{
	  case GDK_Escape:
	    if(key_press)
	    {
		/* Cancel user editing of current hex value */
		if(hv->edit_buf_i > 0)
		{
		    hv->edit_buf_i = 0;
		    *hv->edit_buf = '\0';
		    HViewQueueDraw(hv);
		    HViewSetStatusMessage(hv, NULL, FALSE);
		}
	    }
	    status = TRUE;
	    break;

	  case GDK_BackSpace:
	    if(key_press)
	    {
		if(hv->edit_buf_i > 0)
		    hv->edit_buf_i--;
		if(hv->edit_buf_i < sizeof(hv->edit_buf))
		    hv->edit_buf[hv->edit_buf_i] = '\0';
		HViewQueueDraw(hv);
		HViewSetStatusMessage(hv, NULL, FALSE);
	    }
	    status = TRUE;
	    break;

	  case GDK_space:
	    if(key_press)
	    {
		/* Select region? */
		if((key->state & GDK_CONTROL_MASK) ||
		   (key->state & GDK_SHIFT_MASK)
		)
		{
		    hv->buf_sel_end = hv->buf_pos;
		}
		else
		{
		    /* Toggle select */
		    if(HVIEW_IS_BUF_SELECTED(hv))
			hv->buf_sel_start = hv->buf_sel_end = -1;
		    else
			hv->buf_sel_start = hv->buf_sel_end = hv->buf_pos;
		}
		HViewQueueDraw(hv);
		HViewUpdate(hv);
		HViewSetStatusMessage(hv, NULL, FALSE);
		if(hv->select_cb != NULL)
		    hv->select_cb(
			hv,
			hv->buf_sel_start, hv->buf_sel_end,
			hv->select_data
		    );
	    }
	    status = TRUE;
	    break;

	  case GDK_Return:
	  case GDK_KP_Enter:
	  case GDK_ISO_Enter:
	    if(key_press && !hv->read_only)
	    {
		gboolean changed = FALSE;;

		/* In hex edit mode? */
		if(hv->edit_mode == HVIEW_EDIT_MODE_HEX)
		{
		    guint8 v;

		    /* If the user was in the middle of editing the hex
		     * value, then finish it by assuming the second
		     * character is a '0'
		     */
		    if((hv->edit_buf_i == 1) && (sizeof(hv->edit_buf) >= 3))
		    {
			hv->edit_buf[1] = '0';
			hv->edit_buf[2] = '\0';
		    }
		    /* Process new value only if one or more characters
		     * in hex edit buffer, this is so that if this key
		     * is pressed while nothing is edit then nothing is
		     * edited
		     */
		    if(hv->edit_buf_i > 0)
		    {
			v = HViewHexConvert(hv->edit_buf);

			/* If selected, then set selection.  Otherwise
			 * just set the cursor position.
			 */
			if(HVIEW_IS_BUF_SELECTED(hv))
			    HViewSetSelected(hv, v);
			else if(HVIEW_IS_BUF_POS_VALID(hv, hv->buf_pos))
			    hv->buf[hv->buf_pos] = v;
		    }

		    /* Clear hex edit buffer */
		    hv->edit_buf_i = 0;
		    *hv->edit_buf = '\0';

		    hv->modified = changed = TRUE;
		}

		/* Seek cursor past selection and update selection to
		 * cursor's position
		 */
		if(hv->buf_sel_end > -1)
		{
		    if(hv->buf_sel_end > hv->buf_sel_start)
			hv->buf_pos = hv->buf_sel_end + 1;
		    else
			hv->buf_pos = hv->buf_sel_start + 1;
		}
		else
		{
		    hv->buf_pos++;
		}
		if(hv->buf_pos >= hv->buf_len)
		    hv->buf_pos = hv->buf_len - 1;
		if(hv->buf_pos < 0)
		    hv->buf_pos = 0;

		/* Unselect */
		hv->buf_sel_start = hv->buf_sel_end = -1;

		HViewQueueDraw(hv);
		HViewUpdate(hv);
		HViewSetStatusMessage(hv, NULL, FALSE);
		if(HViewIsBufferPositionVisible(hv, hv->buf_pos) !=
		    GTK_VISIBILITY_FULL
		)
		    HViewScrollTo(hv, hv->buf_pos, 0.5f);

		if(changed && (hv->changed_cb != NULL))
		    hv->changed_cb(hv, hv->changed_data);

		if(hv->select_cb != NULL)
		    hv->select_cb(
			hv,
			hv->buf_sel_start, hv->buf_sel_end,
			hv->select_data
		    );
	    }
	    status = TRUE;
	    break;

	  case GDK_Insert:
	    if(key_press && !hv->read_only)
	    {
		if(key->state & GDK_CONTROL_MASK)
		    HViewCopyCB(widget, hv);
		else if(key->state & GDK_SHIFT_MASK)
		    HViewPasteCB(widget, hv);
		else
		    HViewInsertCB(widget, hv);
	    }
	    status = TRUE;
	    break;

	  case GDK_Delete:
	    if(key_press && !hv->read_only)
	    {
		if((key->state & GDK_CONTROL_MASK) ||
		   (key->state & GDK_SHIFT_MASK)
		)
		    HViewCutCB(widget, hv);
		else
		    HViewDeleteCB(widget, hv);
	    }
	    status = TRUE;
	    break;


	  case GDK_Up:
	    if(key_press)
	    {
		/* Cancel editing of current hex value */
		if(hv->edit_buf_i > 0)
		{
		    hv->edit_buf_i = 0;
		    *hv->edit_buf = '\0';
		}

	        if((key->state & GDK_CONTROL_MASK) &&
	           (adj != NULL)
		)
		{
		    adj->value -= adj->step_increment;
		    if(adj->value < adj->lower)
			adj->value = adj->lower;
		    gtk_signal_emit_by_name(
			GTK_OBJECT(adj), "value_changed"
		    );
		}
		else
		{
		    gint last_pos = hv->buf_pos;
		    hv->buf_pos -= hv->cells_per_row;
		    if(hv->buf_pos < 0)
		        hv->buf_pos = 0;
		    if(key->state & GDK_SHIFT_MASK)
		    {
			if(HVIEW_IS_BUF_SELECTED(hv))
			{
			    hv->buf_sel_end = hv->buf_pos;
			}
			else
			{
			    hv->buf_sel_start = last_pos;
			    hv->buf_sel_end = hv->buf_pos;
			}
			HViewUpdate(hv);
			if(hv->select_cb != NULL)
			    hv->select_cb(
				hv,
				hv->buf_sel_start, hv->buf_sel_end,
				hv->select_data
			    );
		    }
		    HViewQueueDraw(hv);
		    HViewSetStatusMessage(hv, NULL, FALSE);
		    if(HViewIsBufferPositionVisible(hv, hv->buf_pos) !=
			GTK_VISIBILITY_FULL
		    )
			HViewScrollTo(hv, hv->buf_pos, 0.0f);
		}
	    }
	    STOP_SIGNAL_EMIT
	    status = TRUE;
	    break;

	  case GDK_Down:
	    if(key_press)
	    {
		/* Cancel editing of current hex value */
		if(hv->edit_buf_i > 0)
		{
		    hv->edit_buf_i = 0;
		    *hv->edit_buf = '\0';
		}

		if((key->state & GDK_CONTROL_MASK) &&
		   (adj != NULL)
		)
		{
		    adj->value += adj->step_increment;
		    if((adj->value + adj->page_size) > adj->upper)
			adj->value = adj->upper - adj->page_size;
		    gtk_signal_emit_by_name(
			GTK_OBJECT(adj), "value_changed"
		    );
		}
		else
		{
		    gint last_pos = hv->buf_pos;
		    hv->buf_pos += hv->cells_per_row;
		    if(hv->buf_pos >= hv->buf_len)
			hv->buf_pos = hv->buf_len - 1;
		    if(key->state & GDK_SHIFT_MASK)
		    {
			if(HVIEW_IS_BUF_SELECTED(hv))
			    hv->buf_sel_end = hv->buf_pos;
			else
			{
			    hv->buf_sel_start = last_pos;
			    hv->buf_sel_end = hv->buf_pos;
			}
			HViewUpdate(hv);
			if(hv->select_cb != NULL)
			    hv->select_cb(
				hv,
				hv->buf_sel_start, hv->buf_sel_end,
				hv->select_data
			    );
		    }
		    HViewQueueDraw(hv);
		    HViewSetStatusMessage(hv, NULL, FALSE);
		    if(HViewIsBufferPositionVisible(hv, hv->buf_pos) !=
			GTK_VISIBILITY_FULL
		    )
			HViewScrollTo(hv, hv->buf_pos, 1.0f);
		}
	    }
	    STOP_SIGNAL_EMIT
	    status = TRUE;
	    break;

	  case GDK_Left:
	    if(key_press)
	    {
		/* Cancel editing of current hex value */
		if(hv->edit_buf_i > 0)
		{
		    hv->edit_buf_i = 0;
		    *hv->edit_buf = '\0';
		}

	        if((key->state & GDK_CONTROL_MASK) &&
	           (adj != NULL)
		)
		{

		}
		else
		{
		    gint last_pos = hv->buf_pos;
		    hv->buf_pos--;
		    if(hv->buf_pos < 0)
			hv->buf_pos = 0;
		    if(key->state & GDK_SHIFT_MASK)
		    {
			if(HVIEW_IS_BUF_SELECTED(hv))
			    hv->buf_sel_end = hv->buf_pos;
			else
			{
			    hv->buf_sel_start = last_pos;
			    hv->buf_sel_end = hv->buf_pos;
			}
			HViewUpdate(hv);
			if(hv->select_cb != NULL)
			    hv->select_cb(
				hv,
				hv->buf_sel_start, hv->buf_sel_end,
				hv->select_data
			    );
		    }
		    HViewQueueDraw(hv);
		    HViewSetStatusMessage(hv, NULL, FALSE);
		    if(HViewIsBufferPositionVisible(hv, hv->buf_pos) !=
			GTK_VISIBILITY_FULL
		    )
			HViewScrollTo(hv, hv->buf_pos, 0.0f);
		}
	    }
	    STOP_SIGNAL_EMIT
	    status = TRUE;
	    break;

	  case GDK_Right:
	    if(key_press)
	    {
		/* Cancel editing of current hex value */
		if(hv->edit_buf_i > 0)
		{
		    hv->edit_buf_i = 0;
		    *hv->edit_buf = '\0';
		}

		if((key->state & GDK_CONTROL_MASK) &&
		   (adj != NULL)
		)
		{

		}
		else
		{
		    gint last_pos = hv->buf_pos;
		    hv->buf_pos++;
		    if(hv->buf_pos >= hv->buf_len)
			hv->buf_pos = hv->buf_len - 1;
		    if(key->state & GDK_SHIFT_MASK)
		    {
			if(HVIEW_IS_BUF_SELECTED(hv))
			    hv->buf_sel_end = hv->buf_pos;
			else
			{
			    hv->buf_sel_start = last_pos;
			    hv->buf_sel_end = hv->buf_pos;
			}
			HViewUpdate(hv);
			if(hv->select_cb != NULL)
			    hv->select_cb(
				hv,
				hv->buf_sel_start, hv->buf_sel_end,
				hv->select_data
			    );
		    }
		    HViewQueueDraw(hv);
		    HViewSetStatusMessage(hv, NULL, FALSE);
		    if(HViewIsBufferPositionVisible(hv, hv->buf_pos) !=
			GTK_VISIBILITY_FULL
		    )
			HViewScrollTo(hv, hv->buf_pos, 1.0f);
		}
	    }
	    STOP_SIGNAL_EMIT
	    status = TRUE;
	    break;

	  case GDK_Page_Up:
	  case GDK_KP_Page_Up:
	    if(key_press)
	    {
		/* Cancel editing of current hex value */
		if(hv->edit_buf_i > 0)
		{
		    hv->edit_buf_i = 0;
		    *hv->edit_buf = '\0';
		}

		if((key->state & GDK_CONTROL_MASK) && (adj != NULL))
		{
		    gfloat v = adj->value - adj->page_increment;
		    if(v < adj->lower)
			v = adj->lower;
		    gtk_adjustment_set_value(adj, v);
		}
		else if((adj != NULL) && (hv->cell_height > 0))
		{
		    const gint	last_pos = hv->buf_pos,
				rows_visible = (gint)(adj->page_size / hv->cell_height);

		    hv->buf_pos -= (rows_visible / 2) * hv->cells_per_row;
		    if(hv->buf_pos < 0)
			hv->buf_pos = 0;

		    if(key->state & GDK_SHIFT_MASK)
		    {
			if(HVIEW_IS_BUF_SELECTED(hv))
			    hv->buf_sel_end = hv->buf_pos;
			else
			{   
			    hv->buf_sel_start = last_pos;
			    hv->buf_sel_end = hv->buf_pos;
			}
			HViewUpdate(hv);
			if(hv->select_cb != NULL)
			    hv->select_cb(
				hv,
				hv->buf_sel_start, hv->buf_sel_end,
				hv->select_data
			    );
		    }
		    HViewQueueDraw(hv);
		    HViewSetStatusMessage(hv, NULL, FALSE);

		    if(HViewIsBufferPositionVisible(hv, hv->buf_pos) !=
			GTK_VISIBILITY_FULL
		    )
			HViewScrollTo(hv, hv->buf_pos, 0.0f);
		}
	    }
	    STOP_SIGNAL_EMIT
	    status = TRUE;
	    break;

	  case GDK_Page_Down:
	  case GDK_KP_Page_Down:
	    if(key_press)
	    {
		/* Cancel editing of current hex value */
		if(hv->edit_buf_i > 0)
		{
		    hv->edit_buf_i = 0;
		    *hv->edit_buf = '\0';
		}

		if((key->state & GDK_CONTROL_MASK) && (adj != NULL))
		{
		    gfloat v = adj->value + adj->page_increment;
		    if(v > (adj->upper - adj->page_size))
			v = adj->upper - adj->page_size;
		    if(v < adj->lower)
			v = adj->lower;
		    gtk_adjustment_set_value(adj, v);
		}
		else if((adj != NULL) && (hv->cell_height > 0))
		{   
		    const gint	last_pos = hv->buf_pos,
				rows_visible = (gint)(adj->page_size / hv->cell_height);
		    hv->buf_pos += (rows_visible / 2) * hv->cells_per_row;
		    if(hv->buf_pos >= hv->buf_len)
			hv->buf_pos = hv->buf_len - 1;

		    if(key->state & GDK_SHIFT_MASK)
		    {
			if(HVIEW_IS_BUF_SELECTED(hv))
			    hv->buf_sel_end = hv->buf_pos;
			else
			{   
			    hv->buf_sel_start = last_pos;
			    hv->buf_sel_end = hv->buf_pos;
			}
			HViewUpdate(hv);
			if(hv->select_cb != NULL)
			    hv->select_cb(
				hv,
				hv->buf_sel_start, hv->buf_sel_end,
				hv->select_data
			    );
		    }
		    HViewQueueDraw(hv);
		    HViewSetStatusMessage(hv, NULL, FALSE);

		    if(HViewIsBufferPositionVisible(hv, hv->buf_pos) !=
			GTK_VISIBILITY_FULL
		    )
			HViewScrollTo(hv, hv->buf_pos, 1.0f);
		}
	    }
	    STOP_SIGNAL_EMIT
	    status = TRUE;
	    break;

	  case GDK_Home:
	  case GDK_KP_Home:
	    if(key_press)
	    {
		/* Cancel editing of current hex value */
		if(hv->edit_buf_i > 0)
		{
		    hv->edit_buf_i = 0;
		    *hv->edit_buf = '\0';
		}

		if((key->state & GDK_CONTROL_MASK) && (adj != NULL))
		{
		    gfloat v = adj->lower;
		    gtk_adjustment_set_value(adj, v);
		}
		else if(adj != NULL)
		{
		    gint last_pos = hv->buf_pos;
		    hv->buf_pos = 0;

		    if(key->state & GDK_SHIFT_MASK)
		    {
			if(HVIEW_IS_BUF_SELECTED(hv))
			{
			    hv->buf_sel_end = hv->buf_pos;
			}
			else
			{   
			    hv->buf_sel_start = last_pos;
			    hv->buf_sel_end = hv->buf_pos;
			}
			HViewUpdate(hv);
			if(hv->select_cb != NULL)
			    hv->select_cb(
				hv,
				hv->buf_sel_start, hv->buf_sel_end,
				hv->select_data
			    );
		    }
		    HViewQueueDraw(hv);
		    HViewSetStatusMessage(hv, NULL, FALSE);

		    if(HViewIsBufferPositionVisible(hv, hv->buf_pos) !=
			GTK_VISIBILITY_FULL
		    )
			HViewScrollTo(hv, hv->buf_pos, 0.0f);
		}
	    }
	    STOP_SIGNAL_EMIT
	    status = TRUE;
	    break;

	  case GDK_End:
	  case GDK_KP_End:
	    if(key_press)
	    {
		/* Cancel editing of current hex value */
		if(hv->edit_buf_i > 0)
		{
		    hv->edit_buf_i = 0;
		    *hv->edit_buf = '\0';
		}

		if((key->state & GDK_CONTROL_MASK) && (adj != NULL))
		{
		    gfloat v = adj->upper - adj->page_size;
		    if(v < adj->lower)
			v = adj->lower;
		    gtk_adjustment_set_value(adj, v);
		}
		else if(adj != NULL)
		{
		    gint last_pos = hv->buf_pos;
		    hv->buf_pos = hv->buf_len - 1;

		    if(key->state & GDK_SHIFT_MASK)
		    {
			if(HVIEW_IS_BUF_SELECTED(hv))
			{
			    hv->buf_sel_end = hv->buf_pos;
			}
			else
			{   
			    hv->buf_sel_start = last_pos;
			    hv->buf_sel_end = hv->buf_pos;
			}
			HViewUpdate(hv);
			if(hv->select_cb != NULL)
			    hv->select_cb(
				hv,
				hv->buf_sel_start, hv->buf_sel_end,
				hv->select_data
			    );
		    }
		    HViewQueueDraw(hv);
		    HViewSetStatusMessage(hv, NULL, FALSE);

		    if(HViewIsBufferPositionVisible(hv, hv->buf_pos) !=
			GTK_VISIBILITY_FULL
		    )
			HViewScrollTo(hv, hv->buf_pos, 1.0f);
		}
	    }
	    STOP_SIGNAL_EMIT
	    status = TRUE;
	    break;

	  default:
	    /* Hex edit mode? */
	    if((hv->edit_mode == HVIEW_EDIT_MODE_HEX) &&
	       !hv->read_only &&
	       isxdigit((int)key->keyval) &&
	       !(0xFFFFFF00 & key->keyval) &&
	       ((key->state == 0x00000000) ||
		(key->state == GDK_SHIFT_MASK)
	       )
	    )
	    {
		if(key_press)
		{
		    const gint i = hv->edit_buf_i;

		    /* Add new character to the tempory hex edit
		     * buffer and increment hex edit buffer position.
		     * Do this only if the current hex edit buffer
		     * position is in bounds.
		     */
		    if((i >= 0) && (i <= 1) &&
		       (sizeof(hv->edit_buf) >= (i + 1))
		    )
		    {
			hv->edit_buf[i] =
			    toupper((gchar)key->keyval);
			hv->edit_buf[i + 1] = '\0';
			hv->edit_buf_i++;
		    }

		    /* Entered two characters for the hex value? */
		    if(hv->edit_buf_i >= 2)
		    {
			const guint8 v = HViewHexConvert(hv->edit_buf);

			/* If selected then set all values in the
			 * selection, otherwise just set the value
			 * at the cursor position
			 */
			if(HVIEW_IS_BUF_SELECTED(hv))
			    HViewSetSelected(hv, v);
			else if(HVIEW_IS_BUF_POS_VALID(hv, hv->buf_pos))
			    hv->buf[hv->buf_pos] = v;

			/* Clear hex edit buffer */
			hv->edit_buf_i = 0;
			*hv->edit_buf = '\0';

			hv->modified = TRUE;

			/* Seek the cursor past selection and update
			 * the selection to the cursor's position
			 */
			if(hv->buf_sel_end > -1)
			{
			    if(hv->buf_sel_end > hv->buf_sel_start)
				hv->buf_pos = hv->buf_sel_end + 1;
			    else
				hv->buf_pos = hv->buf_sel_start + 1;
			}
			else
			{
			    hv->buf_pos++;
			}
			if(hv->buf_pos >= hv->buf_len)
			    hv->buf_pos = hv->buf_len - 1;
			if(hv->buf_pos < 0)
			    hv->buf_pos = 0;

			/* Unselect */
			hv->buf_sel_start = hv->buf_sel_end = -1;

			HViewQueueDraw(hv);
			HViewUpdate(hv);
			if(HViewIsBufferPositionVisible(hv, hv->buf_pos) !=
			    GTK_VISIBILITY_FULL
			)
			    HViewScrollTo(hv, hv->buf_pos, 0.5f);

			if(hv->changed_cb != NULL)
			    hv->changed_cb(hv, hv->changed_data);

			if(hv->select_cb != NULL)
			    hv->select_cb(
				hv,
				hv->buf_sel_start, hv->buf_sel_end,
				hv->select_data
			    );
		    }

		    HViewQueueDraw(hv);
		    HViewSetStatusMessage(hv, NULL, FALSE);
		}
		status = TRUE;
	    }
	    /* aSCII edit mode? */
	    else if((hv->edit_mode == HVIEW_EDIT_MODE_ASCII) &&
		    isascii((int)key->keyval) &&
		    ((key->state == 0x00000000) ||
		     (key->state == GDK_SHIFT_MASK)
		    )
	    )
	    {
		if(key_press)
		{
		    if(HVIEW_IS_BUF_SELECTED(hv))
			HViewSetSelected(hv, (guint8 )key->keyval);
		    else if(HVIEW_IS_BUF_POS_VALID(hv, hv->buf_pos))
			hv->buf[hv->buf_pos] = (guint8 )key->keyval;

		    /* Seek the cursor past the selection and set the
		     * selection to the cursor's position
		     */
		    if(HVIEW_IS_BUF_SELECTED(hv))
		    {
			if(hv->buf_sel_end > hv->buf_sel_start)
			    hv->buf_pos = hv->buf_sel_end + 1;
			else
			    hv->buf_pos = hv->buf_sel_start + 1;
		    }
		    else
		    {
			hv->buf_pos++;
		    }
		    if(hv->buf_pos >= hv->buf_len)
			hv->buf_pos = hv->buf_len - 1;
		    if(hv->buf_pos < 0)
			hv->buf_pos = 0;

		    /* Unselect */
		    hv->buf_sel_start = hv->buf_sel_end = -1;

		    hv->modified = TRUE;

		    HViewQueueDraw(hv);
		    HViewUpdate(hv);
		    HViewSetStatusMessage(hv, NULL, FALSE);
		    if(HViewIsBufferPositionVisible(hv, hv->buf_pos) !=
			GTK_VISIBILITY_FULL
		    )
			HViewScrollTo(hv, hv->buf_pos, 0.5f);

		    if(hv->changed_cb != NULL)
			hv->changed_cb(hv, hv->changed_data);

		    if(hv->select_cb != NULL)
			hv->select_cb(
			    hv,
			    hv->buf_sel_start, hv->buf_sel_end,
			    hv->select_data
			);
		}
		status = TRUE;
	    }
	    /* Cut? */
	    else if((key->keyval == 'x') &&  (key->state == GDK_CONTROL_MASK))
	    {
		if(key_press)
		    HViewCutCB(NULL, hv);
		status = TRUE;
	    }
	    /* Copy? */
	    else if((key->keyval == 'c') &&  (key->state == GDK_CONTROL_MASK))
	    {
		if(key_press)
		    HViewCopyCB(NULL, hv);
		status = TRUE;
	    }
	    /* Paste? */
	    else if((key->keyval == 'v') &&  (key->state == GDK_CONTROL_MASK))
	    {
		if(key_press)
		    HViewPasteCB(NULL, hv);
		status = TRUE;
	    }
	    /* Toggle edit mode? */
	    else if((key->keyval == 'm') &&  (key->state == GDK_CONTROL_MASK))
	    {
		if(key_press)
		    HViewSetEditMode(
			hv,
			(hv->edit_mode == HVIEW_EDIT_MODE_HEX) ?
			    HVIEW_EDIT_MODE_ASCII : HVIEW_EDIT_MODE_HEX
		    );
		status = TRUE;
	    }
	    break;
	}

	return(status);
#undef STOP_SIGNAL_EMIT
}

/*
 *	Hex View view GtkDrawingArea "button_press_event" or
 *	"button_release_event" callback.
 */
gint HViewButtonCB(
	GtkWidget *widget, GdkEventButton *button, gpointer data
)
{
	gint status = FALSE;
	gint etype;
	gboolean button_press;
	hview_struct *hv = HVIEW(data);
	if((button == NULL) || (hv == NULL))
	    return(status);

	etype = button->type;
	button_press = (etype == GDK_BUTTON_PRESS) ? TRUE : FALSE;
	if(button_press)
	    gtk_widget_grab_focus(widget);

	switch(button->button)
	{
	  case GDK_BUTTON1:
	    /* Position cursor or select */
	    if(button_press)
	    {
		const gint	x = (gint)button->x,
				y = (gint)button->y;
		/* Focus cell, set selection, and mark dragging */
		hv->dragging_selection = TRUE;

		/* Select range? */
		if(button->state & GDK_SHIFT_MASK)
		{
		    /* Is there a selection? */
		    if((hv->buf_sel_start > -1) &&
		       (hv->buf_sel_end > -1)
		    )
		    {
			const gint i = HViewPositionXYToBuffer(hv, x, y);
			if(i < hv->buf_pos)
			    hv->buf_sel_start = i;
			else
			    hv->buf_sel_end = i;
			hv->buf_pos = i;
		    }
		    /* Is the cursor set? */
		    else if(hv->buf_pos > -1)
		    {
			const gint i = HViewPositionXYToBuffer(hv, x, y);
			if(i < hv->buf_pos)
			{
			    hv->buf_sel_start = i;
			    hv->buf_sel_end = hv->buf_pos;
			}
			else
			{
			    hv->buf_sel_start = hv->buf_pos;
			    hv->buf_sel_end = i;
			}
			hv->buf_pos = i;
		    }
		    /* All else just set the cursor position and the
		     * start of the selection
		     */
		    else
		    {
			hv->buf_sel_start = hv->buf_pos =
			    HViewPositionXYToBuffer(hv, x, y);
			hv->buf_sel_end = -1;
		    }
		}
		else
		{
		    hv->buf_sel_start = hv->buf_pos =
			HViewPositionXYToBuffer(hv, x, y);
		    hv->buf_sel_end = -1;
		}

		HViewQueueDraw(hv);
		HViewSetStatusMessage(hv, NULL, FALSE);
		if(HViewIsBufferPositionVisible(hv, hv->buf_pos) !=
		    GTK_VISIBILITY_FULL
		)
		    HViewScrollTo(hv, hv->buf_pos, 0.5f);

		if(hv->select_cb != NULL)
		    hv->select_cb(
			hv,
			hv->buf_sel_start, hv->buf_sel_end,
			hv->select_data
		    );
	    }
	    else
	    {
		/* End dragging */
		hv->dragging_selection = FALSE;

		/* Any drag occured? */
		if(hv->buf_sel_end > -1)
		{
		    hv->buf_pos = HViewPositionXYToBuffer(
			hv, (gint)button->x, (gint)button->y
		    );
		}
		else
		{
		    /* No dragging occured, reset selection */
		    hv->buf_sel_start = -1;
		}

		if(hv->scroll_toid > 0)
		{
		    gtk_timeout_remove(hv->scroll_toid);
		    hv->scroll_toid = 0;
		}

		HViewQueueDraw(hv);
		HViewUpdate(hv);
		HViewSetStatusMessage(hv, NULL, FALSE);

		if(hv->select_cb != NULL)
		    hv->select_cb(
			hv,
			hv->buf_sel_start, hv->buf_sel_end,
			hv->select_data
		    );
	    }
	    status = TRUE;
	    break;

	  case GDK_BUTTON2:
	    /* Translate */
	    if(button_press)
	    {
		GtkWidget *w = hv->view_da;

		hv->dragging_scroll = TRUE;
		hv->last_motion_x = (gint)button->x;
		hv->last_motion_y = (gint)button->y;
		if(w != NULL)
		    gdk_window_set_cursor(w->window, hv->translate_cur);
	    }
	    else
	    {
		GtkWidget *w = hv->view_da;

		hv->dragging_scroll = FALSE;
		if(w != NULL)
		    gdk_window_set_cursor(w->window, NULL);
	    }
	    status = TRUE;
	    break;

	  case GDK_BUTTON3:
	    /* Popup menu */
	    if(button_press)
	    {
		/* Map right click menu */
		GtkMenu *menu = (GtkMenu *)hv->menu;
		if(menu != NULL)
		    gtk_menu_popup(
			menu, NULL, NULL,
			NULL, NULL,
			button->button, button->time
		    );
	    }
	    status = TRUE;
	    break;

	  case GDK_BUTTON4:
	    /* Scroll up */
	    if(button_press && (hv->vadj != NULL))
	    {
		GtkAdjustment *adj = hv->vadj;
		const gfloat inc = MAX(
		    (0.25f * adj->page_size),
		    adj->step_increment
		);
		gfloat v = adj->value - inc;
		if(v > (adj->upper - adj->page_size))
		    v = adj->upper - adj->page_size;
		if(v < adj->lower)
		    v = adj->lower;
		gtk_adjustment_set_value(adj, v);
	    }
	    status = TRUE;
	    break;

	  case GDK_BUTTON5:
	    /* Scroll down */
	    if(button_press && (hv->vadj != NULL))
	    {
		GtkAdjustment *adj = hv->vadj;
		const gfloat inc = MAX(
		    (0.25f * adj->page_size),
		    adj->step_increment
		);
		gfloat v = adj->value + inc;
		if(v > (adj->upper - adj->page_size))
		    v = adj->upper - adj->page_size;
		if(v < adj->lower)
		    v = adj->lower;
		gtk_adjustment_set_value(adj, v);
	    }
	    status = TRUE;
	    break;
	}

	return(status);
}

/*
 *	Hex View view GtkDrawingArea "motion_notify_event" callback.
 */
gint HViewMotionCB(
	GtkWidget *widget, GdkEventMotion *motion, gpointer data
)
{
	GtkAdjustment *adj;
	hview_struct *hv = HVIEW(data);
	if((motion == NULL) || (hv == NULL))
	    return(FALSE);

	adj = hv->vadj;
	if(adj == NULL)
	    return(FALSE);

	/* Request for further motion events if this is a hint and
	 * the pointer button 1 is pressed
	 */
	if(motion->is_hint &&
	   (hv->dragging_selection || hv->dragging_scroll)
	)
	{
	    gint x, y;
	    GdkModifierType mask;
	    gdk_window_get_pointer(
		motion->window, &x, &y, &mask
	    );
	}

	/* Dragging selection? */
	if(hv->dragging_selection)
	{
	    gint	x = (gint)motion->x,
			y = (gint)motion->y;
	    gint width, height;
	    GdkWindow *window = motion->window;

	    gdk_window_get_size(window, &width, &height);

	    hv->buf_sel_end = hv->buf_pos = HViewPositionXYToBuffer(
		hv, x, y
	    );
	    HViewQueueDraw(hv);
	    HViewSetStatusMessage(hv, NULL, FALSE);
	    if(HViewIsBufferPositionVisible(hv, hv->buf_pos) !=
	        GTK_VISIBILITY_FULL
	    )
		HViewScrollTo(
		    hv, hv->buf_pos,
		    (gfloat)((y < (height / 2)) ? 0.0 : 1.0)
		);


	    /* Check if motion y has moved up or down */

	    /* Above window? */
	    if(y < 0)
	    {
		if(hv->scroll_toid == 0)
		    hv->scroll_toid = gtk_timeout_add(
			180,
			(GtkFunction)HViewScrollUpTOCB,
			hv
		    );
	    }
	    /* Below window? */
	    else if(y >= height)
	    {
		if(hv->scroll_toid == 0)
		    hv->scroll_toid = gtk_timeout_add(
			180,
			(GtkFunction)HViewScrollDownTOCB,
			hv
		    );
	    }
	    /* Inside window */
	    else
	    {
		if(hv->scroll_toid > 0)
		{
		    gtk_timeout_remove(hv->scroll_toid);
		    hv->scroll_toid = 0;
		}
	    }
	}
	/* Dragging scroll? */
	else if(hv->dragging_scroll)
	{
	    gint dy = (gint)motion->y - hv->last_motion_y;
	    GtkAdjustment *adj = hv->vadj;

	    if(adj != NULL)
	    {
		adj->value -= (gfloat)dy;
		if((adj->value + adj->page_size) > adj->upper)
		    adj->value = adj->upper - adj->page_size;
		if(adj->value < adj->lower)
		    adj->value = adj->lower;
		gtk_signal_emit_by_name(
		    GTK_OBJECT(adj), "value_changed"
		);
	    }
	}

	/* Update last coordinates */
	hv->last_motion_x = (gint)motion->x;
	hv->last_motion_y = (gint)motion->y;

	return(TRUE);
}

/*
 *	Hex View GtkAdjustment "value_changed" callback.
 */
void HViewValueChangedCB(GtkAdjustment *adj, gpointer data)
{
	HViewQueueDraw(HVIEW(data));
}

/*
 *	Insert callback.
 */
void HViewInsertCB(GtkWidget *w, gpointer data)
{
	gint i, j, n;
	GtkAdjustment *adj;
	hview_struct *hv = HVIEW(data);
	if(hv == NULL)
	    return;

	if(hv->read_only)
	    return;

	/* Get insert position (where cursor is) */
	i = hv->buf_pos;
	if(i >= hv->buf_len)
	    i = hv->buf_len - 1;
	if(i < 0)
	    i = 0;

	n = 1;		/* Bytes to insert */

	/* Increase buffer allocation */
	if(hv->buf_len < 0)
	    hv->buf_len = 0;
	hv->buf_len += n;
	hv->buf = (guint8 *)g_realloc(
	    hv->buf, hv->buf_len * sizeof(guint8)
	);
	if(hv->buf == NULL)
	{
	    hv->buf_len = 0;
	    hv->buf_pos = 0;
	    return;
	}

	/* Shift buffer */
	for(j = hv->buf_len - 1; j >= (i + n); j--)
	    hv->buf[j] = hv->buf[j - n];

	/* Reset new inserted bytes */
	for(j = i; j < (i + n); j++)
	    hv->buf[j] = 0x00;

	if(hv->buf_pos < 0)
	    hv->buf_pos = 0;

	hv->modified = TRUE;

	/* Update adjustment */
	w = hv->view_da;
	adj = hv->vadj;
	if((adj != NULL) && (hv->cells_per_row > 0) && (hv->cell_height > 0))
	{
	    const gint height = w->allocation.height;

	    adj->lower = 0.0f;
	    adj->upper = (gfloat)(
		(hv->buf_len / hv->cells_per_row * hv->cell_height) +
		((hv->buf_len % hv->cells_per_row) ? hv->cell_height : 0)
	    );
	    adj->page_size = (gfloat)height;
	    adj->step_increment = (gfloat)hv->cell_height;
	    adj->page_increment = adj->page_size / 2.0f;

	    gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
	    gtk_signal_emit_by_name(GTK_OBJECT(adj), "value_changed");
	}
	HViewUpdate(hv);
	HViewSetStatusMessage(hv, NULL, FALSE);

	if(hv->changed_cb != NULL)
	    hv->changed_cb(hv, hv->changed_data);
}

/*
 *      Delete callback.
 */
void HViewDeleteCB(GtkWidget *w, gpointer data)
{
	gint i, j, n;
	GtkAdjustment *adj;
	hview_struct *hv = HVIEW(data);
	if(hv == NULL)
	    return;

	if(hv->read_only)
	    return;

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

	if(HVIEW_IS_BUF_SELECTED(hv))
	{
	    /* Get delete position and how many characters to
	     * delete.
	     */
	    if(hv->buf_sel_start < hv->buf_sel_end)
	    {
		i = hv->buf_sel_start;
		n = hv->buf_sel_end - hv->buf_sel_start + 1;
	    }
	    else
	    {
		i = hv->buf_sel_end;
		n = hv->buf_sel_start - hv->buf_sel_end + 1;
	    }
	}
	else
	{
	    i = hv->buf_pos;
	    n = 1;
	}

	/* Make sure length n at index i does not exceed the length of
	 * the buffer.
	 */
	if((i + n) > hv->buf_len)
	    n = hv->buf_len - i;
	if(n <= 0)
	    return;

	/* At this point i is the starting index of the buffer and
	 * n is the length.  All bounds have been checked.
	 */

	/* Shift buffer */
	for(j = i; (j + n) < hv->buf_len; j++)
	    hv->buf[j] = hv->buf[j + n];

	/* Reallocate buffer */
	hv->buf_len -= n;
	if(hv->buf_len <= 0)
	{
	    g_free(hv->buf);
	    hv->buf = NULL;
	    hv->buf_len = 0;
	    hv->buf_pos = 0;
	}
	else
	{
	    hv->buf = (guint8 *)g_realloc(
		hv->buf, hv->buf_len * sizeof(guint8)
	    );
	    if(hv->buf == NULL)
	    {
		hv->buf_len = 0;
		hv->buf_pos = 0;
		return;
	    }
	}

	/* Unselect */
	hv->buf_sel_start = -1;
	hv->buf_sel_end = -1;

	if(hv->buf_pos > i)
	{
	    if(hv->buf_pos >= (i + n))
		hv->buf_pos -= n;
	    else
		hv->buf_pos -= (hv->buf_pos - i);
	}
	if(hv->buf_pos >= hv->buf_len)
	    hv->buf_pos = hv->buf_len - 1;
	if(hv->buf_pos < 0)
	    hv->buf_pos = 0;

	hv->modified = TRUE;

	/* Update adjustment */
	adj = hv->vadj;
	w = hv->view_da;
	if((adj != NULL) && (hv->cells_per_row > 0) && (hv->cell_height > 0))
	{
	    const gint height = w->allocation.height;

	    adj->lower = 0.0f;
	    adj->upper = (gfloat)(
		(hv->buf_len / hv->cells_per_row * hv->cell_height) +
		((hv->buf_len % hv->cells_per_row) ? hv->cell_height : 0)
	    );
	    adj->page_size = (gfloat)height;
	    adj->step_increment = (gfloat)hv->cell_height;
	    adj->page_increment = adj->page_size / 2.0f;

	    if((adj->value + adj->page_size) > adj->upper)
	    {
		adj->value = adj->upper - adj->page_size;
		if(adj->value < adj->lower)
		    adj->value = adj->lower;
	    }

	    gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
	    gtk_signal_emit_by_name(GTK_OBJECT(adj), "value_changed");
	}
	HViewUpdate(hv);
	HViewSetStatusMessage(hv, NULL, FALSE);

	if(hv->changed_cb != NULL)
	    hv->changed_cb(hv, hv->changed_data);

	if(hv->select_cb != NULL)
	    hv->select_cb(
		hv,
		hv->buf_sel_start, hv->buf_sel_end,
		hv->select_data
	    );
}

/*
 *	Cut callback.
 */
void HViewCutCB(GtkWidget *w, gpointer data)
{
	HViewCopyCB(w, data);
	HViewDeleteCB(w, data);
}

/*
 *	Copy callback.
 */
void HViewCopyCB(GtkWidget *w, gpointer data)
{
	gint i, n;
	hview_struct *hv = HVIEW(data);
	if(hv == NULL)
	    return;

	if((hv->buf_sel_start < 0) || (hv->buf_sel_end < 0))
	    return;

	/* Get delete position and how many characters to
	 * delete
	 */
	if(hv->buf_sel_start < hv->buf_sel_end)
	{
	    i = hv->buf_sel_start;
	    n = hv->buf_sel_end - hv->buf_sel_start + 1;
	}
	else
	{
	    i = hv->buf_sel_end;
	    n = hv->buf_sel_start - hv->buf_sel_end + 1;
	}

	/* Make sure length n at index i does not exceed the length of
	 * the buffer
	 */
	if((i + n) > hv->buf_len)
	    n = hv->buf_len - i;
	if(n <= 0)
	    return;

	/* At this point i is the starting index of the buffer and
	 * n is the length, all bounds have been checked
	 */

	/* Set DDE */
	GUIDDESetBinary(
	    hv->view_da,
	    GDK_SELECTION_PRIMARY,
	    GDK_CURRENT_TIME,
	    &hv->buf[i], n
	);
}

/*
 *	Paste callback.
 */
void HViewPasteCB(GtkWidget *w, gpointer data)
{
	gint i, j, n, dde_len;
	guint8 *dde;
	GtkAdjustment *adj;
	hview_struct *hv = HVIEW(data);
	if(hv == NULL)
	    return;

	if(hv->read_only)
	    return;

	/* Get DDE */
	dde = GUIDDEGetBinary(
	    hv->view_da,
	    GDK_SELECTION_PRIMARY,
	    GDK_CURRENT_TIME,
	    &dde_len
	);
	if((dde == NULL) || (dde_len <= 0))
	{
	    g_free(dde);
	    return;
	}

	/* Get insert position (where cursor is) */
	i = hv->buf_pos;
	if(i >= hv->buf_len)
	    i = hv->buf_len - 1;
	if(i < 0)
	    i = 0;

	n = dde_len;	/* Bytes to insert */

	/* Increase buffer allocation */
	if(hv->buf_len < 0)
	    hv->buf_len = 0;
	hv->buf_len += n;
	hv->buf = (guint8 *)g_realloc(
	    hv->buf, hv->buf_len * sizeof(guint8)
	);
	if(hv->buf == NULL)
	{
	    hv->buf_len = 0;
	    hv->buf_pos = 0;
	    return;
	}

	/* Shift buffer */
	for(j = hv->buf_len - 1; j >= (i + n); j--)
	    hv->buf[j] = hv->buf[j - n];

	/* Copy dde buffer to insert point */
	memcpy(&hv->buf[i], dde, n * sizeof(guint8));

	g_free(dde);
	dde = NULL;
	dde_len = 0;

	if(hv->buf_pos < 0)
	    hv->buf_pos = 0;

	hv->modified = TRUE;

	/* Update adjustment */
	w = hv->view_da;
	adj = hv->vadj;
	if((adj != NULL) && (hv->cells_per_row > 0) && (hv->cell_height > 0))
	{
	    const gint height = w->allocation.height;

	    adj->lower = 0.0f;
	    adj->upper = (gfloat)(
		(hv->buf_len / hv->cells_per_row * hv->cell_height) +
		((hv->buf_len % hv->cells_per_row) ? hv->cell_height : 0)
	    );
	    adj->page_size = (gfloat)height;
	    adj->step_increment = (gfloat)hv->cell_height;
	    adj->page_increment = adj->page_size / 2.0f;

	    gtk_signal_emit_by_name(GTK_OBJECT(adj), "changed");
	    gtk_signal_emit_by_name(GTK_OBJECT(adj), "value_changed");
	}
	HViewUpdate(hv);
	HViewSetStatusMessage(hv, NULL, FALSE);

	if(hv->changed_cb != NULL)
	    hv->changed_cb(hv, hv->changed_data);
}

/*
 *	Hex View Select All callback.
 */
void HViewSelectAllCB(GtkWidget *w, gpointer data)
{
	hview_struct *hv = HVIEW(data);
	if(hv == NULL)
	    return;

	if(hv->buf_len <= 0)
	    return;

	hv->buf_sel_start = 0;
	hv->buf_sel_end = hv->buf_len - 1;

	HViewQueueDraw(hv);
	HViewUpdate(hv);
	HViewSetStatusMessage(hv, NULL, FALSE);
	if(hv->select_cb != NULL)
	    hv->select_cb(
		hv,
		hv->buf_sel_start, hv->buf_sel_end,
		hv->select_data
	    );
}

/*
 *	Hex View Unselect All callback.
 */
void HViewUnselectAllCB(GtkWidget *w, gpointer data)
{
	hview_struct *hv = HVIEW(data);
	if(hv == NULL)
	    return;

	if(!HVIEW_IS_BUF_SELECTED(hv))
	    return;

	hv->buf_sel_start = -1;
	hv->buf_sel_end = -1;

	HViewQueueDraw(hv);
	HViewUpdate(hv);
	HViewSetStatusMessage(hv, NULL, FALSE);
	if(hv->select_cb != NULL)
	    hv->select_cb(
		hv,
		hv->buf_sel_start, hv->buf_sel_end,
		hv->select_data
	    );
}

/*
 *	Hex Edit Mode callback.
 */
void HViewEditModeHexCB(GtkWidget *w, gpointer data)
{
	hview_struct *hv = HVIEW(data);
	if(hv == NULL)
	    return;

	if(hv->freeze_count > 0)
	    return;

	hv->freeze_count++;
	HViewSetEditMode(hv, HVIEW_EDIT_MODE_HEX);
	hv->freeze_count--;
}

/*
 *	ASCII Edit Mode callback.
 */
void HViewEditModeASCIICB(GtkWidget *w, gpointer data)
{
	hview_struct *hv = HVIEW(data);
	if(hv == NULL)
	    return;

	if(hv->freeze_count > 0)
	    return;

	hv->freeze_count++;
	HViewSetEditMode(hv, HVIEW_EDIT_MODE_ASCII);	
	hv->freeze_count--;
}
