/*************************************************************************
 *
 *	GTK Euler : the notebook widget
 *
 *************************************************************************/

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include <gdk/gdkkeysyms.h>

#include "term.h"
#include "sysdep.h"
#include "stack.h"
#include "builtin.h"
#include "command.h"
#include "edit.h"
#include "rc.h"

#define TERM_MINWIDTH_DEFAULT		80
#define TERM_MINHEIGHT_DEFAULT		24


int linelength = E_TWIDTH_DEFAULT;


static char deadkey;	/* for deadkey handling (^ and ¨) */

#define PADDING 2


enum {
	GTK_TERM_CHANGED,
	GTK_TERM_SAVED,
	GTK_TERM_EDITING,
	GTK_TERM_INTERPRETING,
	LAST_SIGNAL
};

static guint gtk_term_signals[LAST_SIGNAL] = {0,0,0,0};

static GtkWidgetClass *parent_class = NULL;


static void gtk_term_class_init          (GtkTermClass *klass);
static void gtk_term_init                (GtkTerm      *term);

/* GtkObject and GtkWidget virtual methods */
static void gtk_term_destroy (GtkObject *object);
static void gtk_term_realize (GtkWidget *widget);
static void gtk_term_unrealize (GtkWidget *widget);

static void gtk_term_size_request (GtkWidget *widget, GtkRequisition *requisition);
static void gtk_term_size_allocate (GtkWidget *widget, GtkAllocation *allocation);

static gint gtk_term_expose (GtkWidget *widget, GdkEventExpose *event);
static void gtk_term_map (GtkWidget *widget);
static gint gtk_term_focus_in(GtkWidget *widget, GdkEventFocus *event);
static gint gtk_term_focus_out(GtkWidget *widget, GdkEventFocus *event);
static gint gtk_term_key_press (GtkWidget *widget, GdkEventKey *event);
static gint gtk_term_button_press (GtkWidget *widget, GdkEventButton *event);
static gint gtk_term_button_release (GtkWidget *widget, GdkEventButton *event);
static gint gtk_term_motion_notify (GtkWidget *widget, GdkEventMotion *event);

static gint gtk_term_selection_clear(GtkWidget *widget, GdkEventSelection *event);
static void gtk_term_selection_get(GtkWidget *widget, GtkSelectionData  *selection_data, guint info, guint time);
static void gtk_term_selection_received(GtkWidget *widget, GtkSelectionData *selection_data, guint time);

/* private methods */
static void gtk_term_adjustment_safe_set_value(GtkTerm *term, gfloat value);
static void gtk_term_scrollbar_moved (GtkAdjustment *adj, GtkWidget *widget);
static void gtk_term_scroll(GtkWidget* widget, GdkEventScroll* event, gpointer user_data);
static void gtk_term_cursor_up(GtkTerm *term);
static void gtk_term_cursor_down(GtkTerm *term);
static void gtk_term_update_caret(GtkTerm *term);
static gint gtk_term_blink_caret(gpointer data);
static void gtk_term_draw_caret(GtkTerm *term, guint state);
static void gtk_term_scroll_to_pos(GtkTerm *term);
static void gtk_term_draw_text(GtkTerm *term);
static void gtk_term_redraw_current(GtkTerm *term);
static void gtk_term_redraw_below(GtkTerm *term, int index);

GType gtk_term_get_type()
{
	static GType term_type = 0;

	if (!term_type)
	{
		GTypeInfo term_info =
		{
			sizeof (GtkTermClass),
			NULL,
			NULL,
			(GClassInitFunc) gtk_term_class_init, 
			NULL,
			NULL,
			sizeof (GtkTerm),
			0,
			(GInstanceInitFunc) gtk_term_init
		};

		term_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkTerm",
                                         	&term_info, 0);
	}

	return term_type;
}

/*
 *	init of new signals and virtual methods
 */
static void gtk_term_class_init(GtkTermClass *klass)
{
	GtkObjectClass *object_class = (GtkObjectClass*)klass;
	GtkWidgetClass *widget_class = (GtkWidgetClass*)klass;
	
	/*
	 *	save a copy of parent class structure to keep access to
	 *	its methods
	 */
	parent_class = gtk_type_class (gtk_widget_get_type ());

	/*
	 *	define new signals
	 */
	
	gtk_term_signals[GTK_TERM_CHANGED] = g_signal_new ("term_changed",
                                                       G_TYPE_FROM_CLASS (klass),
                                                       G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
                                                       G_STRUCT_OFFSET (GtkTermClass, changed),
                                                       NULL, 
                                                       NULL,                
                                                       g_cclosure_marshal_VOID__VOID,
                                                       G_TYPE_NONE, 0);
													
	gtk_term_signals[GTK_TERM_SAVED] = g_signal_new ("term_saved",
													 G_TYPE_FROM_CLASS (klass),
                                                     G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
                                                     G_STRUCT_OFFSET (GtkTermClass, saved),
                                                     NULL, 
                                                     NULL,                
                                                     g_cclosure_marshal_VOID__VOID,
                                                     G_TYPE_NONE, 0);
													
	
	gtk_term_signals[GTK_TERM_EDITING] = g_signal_new("term_editing",
													  G_TYPE_FROM_CLASS (klass),
                                                      G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
                                                      G_STRUCT_OFFSET (GtkTermClass, editing),
                                                      NULL, 
                                                      NULL,                
                                                      g_cclosure_marshal_VOID__VOID,
                                                      G_TYPE_NONE, 0);
													  
	gtk_term_signals[GTK_TERM_INTERPRETING] = g_signal_new("term_interpreting",
													       G_TYPE_FROM_CLASS (klass),
                                                           G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
                                                           G_STRUCT_OFFSET (GtkTermClass, interpreting),
                                                           NULL, 
                                                           NULL,                
                                                           g_cclosure_marshal_VOID__VOID,
                                                           G_TYPE_NONE, 0);

	/*
	 *	define virtual methods
	 */
	object_class->destroy = gtk_term_destroy;

	widget_class->realize               = gtk_term_realize;
	widget_class->unrealize             = gtk_term_unrealize;
	widget_class->size_request          = gtk_term_size_request;
	widget_class->size_allocate         = gtk_term_size_allocate;
	widget_class->expose_event          = gtk_term_expose;
	widget_class->map                   = gtk_term_map;
	widget_class->button_press_event    = gtk_term_button_press;
	widget_class->button_release_event  = gtk_term_button_release;
	widget_class->motion_notify_event   = gtk_term_motion_notify;
	widget_class->focus_in_event        = gtk_term_focus_in;
	widget_class->focus_out_event       = gtk_term_focus_out;
	widget_class->key_press_event       = gtk_term_key_press;	
	widget_class->selection_clear_event = gtk_term_selection_clear;
	widget_class->selection_received    = gtk_term_selection_received;
	widget_class->selection_get         = gtk_term_selection_get;
}

/*
 *	init of object parameters, create subwidgets
 */
static void gtk_term_init(GtkTerm *term)
{
	GTK_WIDGET_SET_FLAGS(term, GTK_CAN_FOCUS);

	term->name = g_string_new("");
	
	term->a = e_new();
	e_append(term->a,"",E_OUTPUT);
	
	term->top = term->cur = term->pos = term->epos = 0;
	term->promptlen	= 1;
	term->xoff		= 0;
	term->xcaret	= term->ycaret = 0;
	
	term->twidth	= 0;
	term->theight 	= 0;
	
	term->tfont		= g_string_new("");
	term->font		= NULL;
	term->cwidth	= 0;
	term->cheight	= 0;

	term->cursor	= NULL;
	
	term->scan		= 0;
	term->code		= 0;

	term->commentGC = term->outputGC = term->promptGC = term->udfGC = term->highlightGC = NULL;

	/* scrollbar set up */
	term->v_adj = GTK_ADJUSTMENT(gtk_adjustment_new (0.0, 0.0, 0.0, 1.0, (gfloat)term->theight -1.0, (gfloat)term->theight));
	gtk_object_ref ((gpointer)(term->v_adj));
	gtk_object_sink (GTK_OBJECT (term->v_adj));
	
	g_signal_connect ((gpointer) (term->v_adj),"value_changed",
					 G_CALLBACK (gtk_term_scrollbar_moved),term);
	g_signal_connect ((gpointer) (term), "scroll_event",
					  G_CALLBACK (gtk_term_scroll), NULL);
	
	/* caret timer */
	term->timeout_id	= -1;
	term->caret_blink_state = 0;
	
	/* command history set up */
	term->history = e_new();
	e_append(term->history,"",0);
	term->max_history = 32;
	term->hist = 0;
	
	/* clipboard */
	term->clipboard = g_string_new("");
	
	/* flags */
	
	term->editing = 0;
	term->initializing = 1;
	term->selecting = 0;
	term->changed = 0;
	
	term->menu = NULL;
	
	gtk_selection_add_target(GTK_WIDGET(term),GDK_SELECTION_PRIMARY,GDK_SELECTION_TYPE_STRING,1);
}

/************************************************************************
 *	PUBLIC INTERFACE
 ************************************************************************/

/*
 * create a term widget with cols columns and rows rows
 */
GtkWidget* gtk_term_new(guint cols, guint rows, char *font)
{
	GtkTerm *term = gtk_type_new (gtk_term_get_type ());

	g_string_assign(term->tfont,font);
	term->twidth = cols;
	term->theight = rows;
	
	return GTK_WIDGET (term);
}

/*
 *	load a notebook
 */
gint gtk_term_load(GtkWidget *widget, gchar *filename)
{
	GtkTerm	*	term;
	int i;
	
	g_return_val_if_fail (widget != NULL,0);
	g_return_val_if_fail (GTK_IS_TERM (widget),0);

	term = GTK_TERM (widget);
	
	if (!e_load(term->a,filename)) return 0;
	
	term->v_adj->value = 0.0;
	term->v_adj->lower = 0.0;
	term->v_adj->upper = (gfloat)(e_get_length(term->a));
	term->v_adj->step_increment = 1.0;
	term->v_adj->page_increment = (gfloat)(term->theight-1);
	term->v_adj->page_size = (gfloat)term->theight;
	gtk_adjustment_changed(term->v_adj);
	
	term->top = 0;
	
	/* find out for the first command */
	for (i=0 ; i<e_get_length(term->a) ; i++)
		if (e_get_type(term->a,i)==E_PROMPT) {
			term->cur = i;
			term->pos = term->epos = term->promptlen;
			term->xoff = 0;
			gtk_term_update_caret(term);
			break;
		}
	
	gdk_window_clear(term->text);
	gtk_term_draw_text(term);
	
	g_string_assign(term->name,filename);
	term->changed = 0;
	g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_SAVED],0);
	
	return 1;
}

/*
 *	save the terminal to a notebook
 */
gint gtk_term_save(GtkWidget *widget, gchar *filename)
{
	GtkTerm	*term;
	gchar *file;

	g_return_val_if_fail (widget != NULL,0);
	g_return_val_if_fail (GTK_IS_TERM (widget),0);

	term = GTK_TERM (widget);
	
	if (filename)
		file = filename;
	else
		file = term->name->str;
	
	if (!e_save(term->a,file)) return 0;
	
	if (filename) g_string_assign(term->name,filename);
	term->changed = 0;
	g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_SAVED],0);
	
	return 1;
}

/*
 *	clear the terminal
 */
void gtk_term_clear(GtkWidget *widget)
{
	GtkTerm	*term;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);

	e_clear(term->a);
	e_append(term->a,"",0);
	term->cur = term->top = 0;
	
	term->v_adj->value = 0.0;
	term->v_adj->lower = 0.0;
	term->v_adj->upper = (gfloat)(e_get_length(term->a));
	term->v_adj->step_increment = 1.0;
	term->v_adj->page_increment = (gfloat)(term->theight-1);
	term->v_adj->page_size = (gfloat)term->theight;
	gtk_adjustment_changed(term->v_adj);

	gdk_window_clear(term->text);
	
	if (!term->changed) {
		g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
		term->changed = 1;
	}
}

void gtk_term_clear_new(GtkWidget *widget)
{
	GtkTerm	*term;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);

	gtk_term_draw_caret(term,0);
	
	e_clear(term->a);
	e_append(term->a,">",E_PROMPT);
	term->cur = term->top = 0;
	term->pos = term->epos = term->promptlen;
	gtk_term_update_caret(term);
	
	term->v_adj->value = 0.0;
	term->v_adj->lower = 0.0;
	term->v_adj->upper = (gfloat)(e_get_length(term->a));
	term->v_adj->step_increment = 1.0;
	term->v_adj->page_increment = (gfloat)(term->theight-1);
	term->v_adj->page_size = (gfloat)term->theight;
	gtk_adjustment_changed(term->v_adj);

	gdk_window_clear(term->text);
	gtk_term_draw_text(term);
	
	term->changed = 0;
	g_string_assign(term->name,"");
	g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_SAVED],0);
	
	gtk_term_draw_caret(term,1);
}

/*	gtk_term_copy
 *		copy the current selection to the clipboard
 */
void gtk_term_copy(GtkWidget *widget)
{
	GtkTerm	*term;
	gchar *s;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);
	
	if (term->epos!=term->pos) {
		if(gtk_selection_owner_set(widget,GDK_SELECTION_PRIMARY,GDK_CURRENT_TIME)){
			if (term->epos>term->pos)
				s = g_strndup(&((e_get_text(term->a,term->cur))[term->pos]),term->epos-term->pos);
			else
				s = g_strndup(&((e_get_text(term->a,term->cur))[term->epos]),term->pos-term->epos);
			
			g_string_assign(term->clipboard,s);
			g_free(s);
		} else {
			g_print("euler could not own the selection");
		}
	}
	//else g_string_assign(term->clipboard,"");
}

/*	gtk_term_cut
 *	cut the current selection to the clipboard
 */
void gtk_term_cut(GtkWidget *widget)
{
	GtkTerm	*term;
	gchar *s;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);
	
	if (term->epos!=term->pos) {
		if(gtk_selection_owner_set(widget,GDK_SELECTION_PRIMARY,GDK_CURRENT_TIME)) {
			if (term->epos>term->pos) {
				s = g_strndup(&((e_get_text(term->a,term->cur))[term->pos]),term->epos-term->pos);
				e_remove_text(term->a,term->cur,term->pos,term->epos-term->pos);
				term->epos = term->pos;
			} else {
				s = g_strndup(&((e_get_text(term->a,term->cur))[term->epos]),term->pos-term->epos);
				e_remove_text(term->a,term->cur,term->epos,term->pos-term->epos);
				term->pos = term->epos;
			}
			g_string_assign(term->clipboard,s);
			g_free(s);
			gtk_term_update_caret(term);
			gtk_term_scroll_to_pos(term);
			if (!term->changed) {
				g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
				term->changed = 1;
			}
		} else {
			g_print("euler could not own the selection\n");
		}
	}
}

/*	gtk_term_paste
 *	paste the clipboard content to the current line
 */
void gtk_term_paste(GtkWidget *widget)
{
	GtkTerm	*term;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);
	
	gtk_selection_convert(widget,GDK_SELECTION_PRIMARY,GDK_TARGET_STRING,GDK_CURRENT_TIME);
}

gchar * gtk_term_get_comment(GtkWidget *widget)
{
	GtkTerm	*term;
	gchar *buffer, *next;
	int k, i, buffersize = 0;

	g_return_val_if_fail (widget != NULL,0);
	g_return_val_if_fail (GTK_IS_TERM (widget),0);

	term = GTK_TERM (widget);
	
	/* calculate the size of the comment */
	k=term->cur;
	while (k-1>=0 && e_get_type(term->a,k-1)==E_COMMENT) {
		k--;
		buffersize += e_get_text_length(term->a,k)-1;
	}
	
	if (!buffersize) return NULL;
	
	next = buffer = g_malloc(buffersize);

	for (i=k;e_get_type(term->a,i)==E_COMMENT;i++) {
		strcpy(next,e_get_text(term->a,i)+2);
		next += e_get_text_length(term->a,i)-1;
		*(next-1)='\n';
	}
	*(next-1)=0;
	
	return buffer;
}

void gtk_term_set_comment(GtkWidget *widget, gchar *cmt)
{
	GtkTerm	*term;
	char *s = cmt;
	int old_cur, dy;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);
	
	gtk_term_draw_caret(term,0);
	
	/* delete the current comment */
	while (term->cur-1>=0 && e_get_type(term->a,term->cur-1)==E_COMMENT) {
		e_remove(term->a,term->cur-1);
		term->cur--;
	}

	old_cur=term->cur;

	/* insert the new one if any */
	if (strlen(cmt)) {
		e_insert(term->a,term->cur,"% ",E_COMMENT);
		term->cur++;

		while (*s)
		{   switch (*s)
			{   case '\n' :
					e_insert(term->a,term->cur,"% ",E_COMMENT);
					term->cur++;
					break;
				default :
					e_append_char(term->a,term->cur-1,*s);
			}
			s++;
		}
	}
	if (!term->changed) {
		g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
		term->changed = 1;
	}
	
	
	/* redraw what is needed */
	term->v_adj->value = (gfloat)term->top;
	term->v_adj->lower = 0.0;
	term->v_adj->upper = (gfloat)(e_get_length(term->a));
	term->v_adj->step_increment = 1.0;
	term->v_adj->page_increment = (gfloat)(term->theight-1);
	term->v_adj->page_size = (gfloat)term->theight;
	gtk_adjustment_changed(term->v_adj);
	
	gtk_term_update_caret(term);
	gtk_term_redraw_below(term,old_cur);

	dy = old_cur - term->top;
	
	if (dy>=term->theight)
		gtk_term_adjustment_safe_set_value(term, (gfloat)(old_cur-term->theight+1));
	else if (dy<0)
		gtk_term_adjustment_safe_set_value(term, (gfloat)old_cur);

	gtk_term_draw_caret(term,1);
}

gchar* gtk_term_get_name(GtkWidget *widget)
{
	GtkTerm	*term;

	g_return_val_if_fail (widget != NULL,0);
	g_return_val_if_fail (GTK_IS_TERM (widget),0);

	term = GTK_TERM (widget);
	
	return term->name->str;
}

gint gtk_term_is_named(GtkWidget *widget)
{
	GtkTerm	*term;

	g_return_val_if_fail (widget != NULL,0);
	g_return_val_if_fail (GTK_IS_TERM (widget),0);

	term = GTK_TERM (widget);

	return (term->name->len!=0);
}

gint gtk_term_is_changed(GtkWidget *widget)
{
	GtkTerm	*term;

	g_return_val_if_fail (widget != NULL,0);
	g_return_val_if_fail (GTK_IS_TERM (widget),0);

	term = GTK_TERM (widget);

	return term->changed;
}

gint gtk_term_is_initialized(GtkWidget *widget)
{
	GtkTerm	*term;

	g_return_val_if_fail (widget != NULL,0);
	g_return_val_if_fail (GTK_IS_TERM (widget),0);

	term = GTK_TERM (widget);

	return !term->initializing;
}

gint gtk_term_is_editing(GtkWidget *widget)
{
	GtkTerm	*term;

	g_return_val_if_fail (widget != NULL,0);
	g_return_val_if_fail (GTK_IS_TERM (widget),0);

	term = GTK_TERM (widget);

	return term->editing;
}

void gtk_term_print(GtkWidget *widget, gchar *s)
{
	GtkTerm	*	term;
	int i,n;
	
	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));
	g_return_if_fail (s != NULL);

	term = GTK_TERM (widget);

	while (*s)
	{   switch (*s)
		{   case '\n' :
				term->pos = term->epos = 0;
				gtk_term_update_caret(term);
				gtk_term_redraw_current(term);
				e_insert(term->a,term->cur+1,"",E_OUTPUT);
				term->cur++;

				term->v_adj->value = (gfloat)term->top;
				term->v_adj->lower = 0.0;
				term->v_adj->upper = (gfloat)(e_get_length(term->a));
				term->v_adj->step_increment = 1.0;
				term->v_adj->page_increment = (gfloat)(term->theight-1);
				term->v_adj->page_size = (gfloat)term->theight;
				gtk_adjustment_changed(term->v_adj);
	
				gtk_term_scroll_to_pos(term);

				break;
			case 9 :
				n=4-(term->pos%4);
				for (i=0; i<n; i++)
				{
					e_append_char(term->a,term->cur,' ');
					term->pos++;
					term->epos++;
				}
				break;
			default :
				e_append_char(term->a,term->cur,*s);
//				gtk_term_update_caret(term);
//				gtk_term_redraw_current(term);
				term->pos++;
				term->epos++;
		}
		s++;
	}
	if (!term->changed && !term->initializing) {
		g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
		term->changed = 1;
	}
}

void gtk_term_edit_on(GtkWidget *widget)
{
	GtkTerm	*	term;
	char ch;
	int i, type = E_OUTPUT, oldcur;;
	
	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);

	ch =(e_get_text(term->a,term->cur))[term->pos-1];
	
	if (ch=='>') {
		term->promptlen = term->pos;
		if (term->promptlen==1) {
			e_set_type(term->a,term->cur,E_PROMPT);
			type = E_PROMPT;
		}
	} else if (ch=='$') {
		e_set_type(term->a,term->cur,E_UDF);
		type = E_UDF;
	}

	oldcur = term->cur;
	
	for (i=term->cur+1;i<e_get_length(term->a);i++) {
		if (e_get_type(term->a,i)==type) {
			e_remove(term->a,term->cur);
			term->cur=i-1;
			break;
		}
	}
	
	term->v_adj->value = (gfloat)term->top;
	term->v_adj->lower = 0.0;
	term->v_adj->upper = (gfloat)(e_get_length(term->a));
	term->v_adj->step_increment = 1.0;
	term->v_adj->page_increment = (gfloat)(term->theight-1);
	term->v_adj->page_size = (gfloat)term->theight;
	gtk_adjustment_changed(term->v_adj);
	
	gtk_term_scroll_to_pos(term);
	gtk_term_redraw_below(term,oldcur-1>=0? oldcur:0);
	
	term->editing = 1;

	if (term->timeout_id == -1 && term->editing && GTK_WIDGET_HAS_FOCUS(widget)) {
		term->timeout_id = gtk_timeout_add (500, gtk_term_blink_caret, term);
		gtk_term_draw_caret(term,1);
	}

	g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_EDITING],0);
	if (term->initializing) term->initializing = 0;
}

void gtk_term_edit_off(GtkWidget *widget)
{
	GtkTerm	*	term;
	int type;
	
	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);
	
	g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_INTERPRETING],0);

	term->editing = 0;
	if (term->timeout_id != -1)
	{
		g_source_remove(term->timeout_id);
		term->timeout_id = -1;
	}
	
	type = e_get_type(term->a,term->cur);
	if (type==E_COMMENT ||
	   (!strncmp(e_get_text(term->a,term->cur),">function",9) &&
	    term->cur+1<e_get_length(term->a) &&
	    e_get_type(term->a,term->cur+1)==E_UDF) ||
	   (type==E_UDF &&
	    strncmp(e_get_text(term->a,term->cur),"$endfunction",12))) return;

	/*	the line will be evaluated : remove the old output results */
	while (term->cur+1<e_get_length(term->a)) {
		type = e_get_type(term->a,term->cur+1);
		if (type==E_PROMPT || type==E_COMMENT) break;
		e_remove(term->a,term->cur+1);
	}

	term->v_adj->value = (gfloat)term->top;
	term->v_adj->lower = 0.0;
	term->v_adj->upper = (gfloat)(e_get_length(term->a));
	term->v_adj->step_increment = 1.0;
	term->v_adj->page_increment = (gfloat)(term->theight-1);
	term->v_adj->page_size = (gfloat)term->theight;
	gtk_adjustment_changed(term->v_adj);
}

void gtk_term_insert_command(GtkWidget *widget, char *text)
{
	int			type;
	GtkTerm	*	term;
	
	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);
	
	type = e_get_type(term->a,term->cur);
	
	if (type!=E_PROMPT && type!=E_UDF) return;

	gtk_term_draw_caret(term,0);

	if (type==E_PROMPT) {
		int i = term->cur;
		while (i-1>=0 && e_get_type(term->a,i-1)==E_COMMENT) i--;
		term->cur = i;
		e_insert(term->a,term->cur,">",E_PROMPT);
	} else {
		e_insert(term->a,term->cur,"$",E_UDF);
	}
	term->pos = term->epos = term->promptlen;
	term->v_adj->upper = (gfloat)(e_get_length(term->a));
	gtk_adjustment_changed(term->v_adj);
	if (term->cur < term->top) {
		gtk_term_scroll_to_pos(term);
	} else {
		gtk_term_update_caret(term);
		gtk_term_redraw_below(term,term->cur);
	}
	
	gtk_term_update_caret(term);
	gtk_term_draw_caret(term,1);
	
	if (!term->changed) {
		g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
		term->changed = 1;
	}
}

void gtk_term_delete_command(GtkWidget *widget)
{
	GtkTerm	*	term;
	int i;
	
	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);
	
	if (e_get_type(term->a,term->cur)==E_UDF)
	{
		if (term->cur==e_get_length(term->a)-1 || e_get_type(term->a,term->cur+1)!=E_UDF)
			return;
		
		gtk_term_draw_caret(term,0);
		e_remove(term->a,term->cur);		
		term->pos = term->epos = term->promptlen;
		
//		term->v_adj->value = 0.0;
		term->v_adj->lower = 0.0;
		term->v_adj->upper = (gfloat)(e_get_length(term->a));
		term->v_adj->step_increment = 1.0;
		term->v_adj->page_increment = (gfloat)(term->theight-1);
		term->v_adj->page_size = (gfloat)term->theight;
		gtk_adjustment_changed(term->v_adj);

		gtk_term_update_caret(term);
		gtk_term_redraw_below(term,term->cur);

		gtk_term_draw_caret(term,1);
		return;
	}

	/*	is there another command line after this one (don't delete
	 *	the last line) ?
	 */
	for (i=term->cur+1;i<e_get_length(term->a);i++)
		if (e_get_type(term->a,i)==E_PROMPT) break;
	if (i==e_get_length(term->a)) return;
	
	gtk_term_draw_caret(term,0);

	/* delete the comment lines if any */
	while(term->cur && e_get_type(term->a,term->cur-1)==E_COMMENT) {
		e_remove(term->a,term->cur-1);
		term->cur--;
	}
	
	/* delete the command line */
	e_remove(term->a,term->cur);
	
	/* delete the output and user function definition lines if any */
	while (e_get_type(term->a,term->cur)==E_OUTPUT || e_get_type(term->a,term->cur)==E_UDF)
		e_remove(term->a,term->cur);
		
	/* set up the new current line (the next prompt) */
	while (term->cur<e_get_length(term->a) && e_get_type(term->a,term->cur)!=E_PROMPT)
		term->cur++;
	
	term->pos = term->epos = term->promptlen;

//	term->v_adj->value = 0.0;
	term->v_adj->lower = 0.0;
	term->v_adj->upper = (gfloat)(e_get_length(term->a));
	term->v_adj->step_increment = 1.0;
	term->v_adj->page_increment = (gfloat)(term->theight-1);
	term->v_adj->page_size = (gfloat)term->theight;
	gtk_adjustment_changed(term->v_adj);

	gtk_term_scroll_to_pos(term);
	
	gdk_window_clear(term->text);
	gtk_term_draw_text(term);

	gtk_term_draw_caret(term,1);
	
	if (!term->changed) {
		g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
		term->changed = 1;
	}
}

void gtk_term_delete_current_output(GtkWidget *widget)
{
	GtkTerm	*	term;
	int i, changed=0;
	
	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);
	
	if (term->editing) {
		i = term->cur+1;
		while (i<e_get_length(term->a) && e_get_type(term->a,i)==E_OUTPUT) {
			e_remove(term->a,i);
			changed = 1;
		}
		if(!changed) return;
			
//		term->v_adj->value = 0.0;
		term->v_adj->lower = 0.0;
		term->v_adj->upper = (gfloat)(e_get_length(term->a));
		term->v_adj->step_increment = 1.0;
		term->v_adj->page_increment = (gfloat)(term->theight-1);
		term->v_adj->page_size = (gfloat)term->theight;
		gtk_adjustment_changed(term->v_adj);
		
		gtk_term_scroll_to_pos(term);
		gdk_window_clear(term->text);
		gtk_term_draw_text(term);

		gtk_term_draw_caret(term,1);
	
		if (!term->changed && changed) {
			g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
			term->changed = 1;
		}
	}
}

void gtk_term_delete_outputs(GtkWidget *widget)
{
	GtkTerm	*	term;
	int i, changed=0;
	
	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);
	
	if (term->editing) {
		i=0;
		while (i<e_get_length(term->a)) {
			if (e_get_type(term->a,i)==E_OUTPUT) {
				e_remove(term->a,i);
				if (i<term->cur) term->cur--;
				changed = 1;
			}
			else
				i++;
		}
		if (!changed) return;
			
//		term->v_adj->value = 0.0;
		term->v_adj->lower = 0.0;
		term->v_adj->upper = (gfloat)(e_get_length(term->a));
		term->v_adj->step_increment = 1.0;
		term->v_adj->page_increment = (gfloat)(term->theight-1);
		term->v_adj->page_size = (gfloat)term->theight;
		gtk_adjustment_changed(term->v_adj);

		gtk_term_scroll_to_pos(term);
		gdk_window_clear(term->text);
		gtk_term_draw_text(term);
		
		gtk_term_draw_caret(term,1);
		
		if (!term->changed && changed) {
			g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
			term->changed = 1;
		}
	}
}

void gtk_term_set_popup(GtkWidget *widget, GtkMenu *menu)
{
	GtkTerm	*term;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));
	g_return_if_fail (menu!=NULL);

	term = GTK_TERM (widget);
	term->menu = menu;			
}

void gtk_term_set_scrollbar(GtkWidget *widget, GtkWidget *scroll)
{
	GtkTerm	*term;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));
	g_return_if_fail (scroll!=NULL);

	term = GTK_TERM (widget);
	term->p_scroll = scroll;
}

void gtk_term_set_colors(GtkWidget *widget, GdkColor *cmdColor,
											GdkColor *outColor,
											GdkColor *cmtColor,
											GdkColor *udfColor)
{
	GtkTerm	*term;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);
			
	gdk_gc_set_foreground(term->commentGC,cmtColor);
	gdk_gc_set_foreground(term->outputGC,outColor);
	gdk_gc_set_foreground(term->promptGC,cmdColor);
	gdk_gc_set_foreground(term->udfGC,udfColor);
}


/************************************************************************
 *	PRIVATE METHODS
 ************************************************************************/

static void gtk_term_cursor_up(GtkTerm *term)
{
	int i;
	
	if (e_get_type(term->a,term->cur)==E_UDF) {
		if (e_get_type(term->a,term->cur-1)==E_UDF) {
			gtk_term_draw_caret(term,0);
			term->pos = term->epos = term->promptlen;
			gtk_term_update_caret(term);
			gtk_term_redraw_current(term);
			term->cur--;
			gtk_term_scroll_to_pos(term);
			gtk_term_draw_caret(term,1);
		}
		return;
	}
	
	if (e_get_type(term->a,term->cur)!=E_PROMPT) return;
	
	for (i=term->cur-1 ; i>=0 ; i--)
		if (e_get_type(term->a,i)==E_PROMPT) {
			gtk_term_draw_caret(term,0);
			term->pos = term->epos = term->promptlen;
			gtk_term_update_caret(term);
			gtk_term_redraw_current(term);
			term->cur = i;
			gtk_term_scroll_to_pos(term);
			gtk_term_draw_caret(term,1);
			break;
		}
}

static void gtk_term_cursor_down(GtkTerm *term)
{
	int i;
	
	if (e_get_type(term->a,term->cur)==E_UDF) {
		if (e_get_type(term->a,term->cur+1)==E_UDF) {
			gtk_term_draw_caret(term,0);
			term->pos = term->epos = term->promptlen;
			gtk_term_update_caret(term);
			gtk_term_redraw_current(term);
			term->cur++;
			gtk_term_scroll_to_pos(term);
			gtk_term_draw_caret(term,1);
		}
		return;
	}

	if (e_get_type(term->a,term->cur)!=E_PROMPT) return;

	for (i=term->cur+1 ; i<e_get_length(term->a) ; i++)
		if (e_get_type(term->a,i)==E_PROMPT) {
			gtk_term_draw_caret(term,0);
			term->pos = term->epos = term->promptlen;
			gtk_term_update_caret(term);
			gtk_term_redraw_current(term);
			term->cur = i;
			gtk_term_scroll_to_pos(term);
			gtk_term_draw_caret(term,1);
			break;
		}
}

static void gtk_term_scroll_to_pos(GtkTerm *term)
{
	gint w,h,dy;

	dy = term->cur - term->top;
	
	if (dy>=term->theight)
		gtk_term_adjustment_safe_set_value(term, (gfloat)(term->cur-term->theight+1));
	else if (dy<0)
		gtk_term_adjustment_safe_set_value(term, (gfloat)term->cur);
	else{
		gtk_term_adjustment_safe_set_value(term, (gfloat)term->v_adj->value);
		gtk_term_update_caret(term);
	}
	gdk_drawable_get_size(term->text,&w,&h);
	
	term->xcaret = term->xoff+term->pos*term->cwidth+PADDING;
	if (term->xcaret>w-40){
		while (term->xcaret>w-40) {
			term->xoff-=40;
			term->xcaret = term->xoff+term->pos*term->cwidth+PADDING;
		}
		gdk_window_clear(term->text);
		gtk_term_draw_text(term);
	} else if (term->xcaret<PADDING) {
		while(term->xcaret<PADDING) {
			term->xoff+=40;
			term->xcaret = term->xoff+term->pos*term->cwidth+PADDING;
		}
		gdk_window_clear(term->text);
		gtk_term_draw_text(term);
	} else
		gtk_term_redraw_current(term);
}

static void gtk_term_update_caret(GtkTerm *term)
{
	term->xcaret = term->xoff+term->pos*term->cwidth+PADDING;
	term->ycaret = (term->cur-term->top)*term->cheight+PADDING;
}

static void gtk_term_draw_caret(GtkTerm *term, guint state)
{
	GdkGC *gc;
	
	if (term->pos==term->epos && term->cur >= term->top && term->cur < (term->top + term->theight)) {
		if (state && !term->caret_blink_state) {
			gc = ((GtkWidget*)term)->style->black_gc;
			gdk_draw_line(term->text,gc,term->xcaret,term->ycaret,term->xcaret,term->ycaret+term->cheight);
		} else if (!state && term->caret_blink_state) {
			gc = ((GtkWidget*)term)->style->white_gc;
			gdk_draw_line(term->text,gc,term->xcaret,term->ycaret,term->xcaret,term->ycaret+term->cheight);
			gtk_term_redraw_current(term);
		}
		term->caret_blink_state = state;
	}
}

static gint gtk_term_blink_caret(gpointer data)
{
	GtkTerm *term = (GtkTerm*)data;

	g_return_val_if_fail (data != NULL, FALSE);

	gtk_term_draw_caret(term,!term->caret_blink_state);

	return TRUE;
}

/************************************************************************
 *	GtkObject and GtkWidget VIRTUAL METHODS DEFINITION
 ************************************************************************/
/*
 *	GtkObject destroy method
 */
static void gtk_term_destroy (GtkObject *object)
{
	GtkTerm *term;

	g_return_if_fail (object != NULL);
	g_return_if_fail (GTK_IS_TERM (object));

	term = GTK_TERM (object);

	if (term->timeout_id != -1)
		g_source_remove(term->timeout_id);

	e_free(term->history);
	e_free(term->a);
	
	if (term->cursor) {
		gdk_cursor_destroy(term->cursor);
		term->cursor = NULL;
	}
	
	if (term->font)
	{
		gdk_font_unref (term->font);
		term->font = NULL;
	}

	if (term->v_adj)
	{
		gtk_signal_disconnect_by_data (GTK_OBJECT (term->v_adj), term);
		gtk_object_unref (GTK_OBJECT (term->v_adj));
		term->v_adj = NULL;
	}
	
	g_string_free(term->name,TRUE);
	g_string_free(term->clipboard,TRUE);
	
	/*
	 *	call widget class destroy method
	 */
	if (GTK_OBJECT_CLASS (parent_class)->destroy)
		(* GTK_OBJECT_CLASS (parent_class)->destroy)(object);
}

/*
 *	realize / unrealize : set up or destroy the gdk_window
 */
static void gtk_term_realize (GtkWidget *widget)
{
	GtkTerm *term;
	GdkWindowAttr attributes;
	gint attributes_mask;
	GdkColormap *cmap;
	GdkColor c;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
	term = GTK_TERM (widget);

	attributes.x = widget->allocation.x;
	attributes.y = widget->allocation.y;
	attributes.width = widget->allocation.width;
	attributes.height = widget->allocation.height;
	attributes.wclass = GDK_INPUT_OUTPUT;
	attributes.window_type = GDK_WINDOW_CHILD;
	attributes.event_mask = gtk_widget_get_events (widget)
			| GDK_EXPOSURE_MASK
			| GDK_BUTTON_PRESS_MASK
			| GDK_BUTTON_RELEASE_MASK
			| GDK_BUTTON_MOTION_MASK
			| GDK_KEY_PRESS_MASK;
	attributes.visual = gtk_widget_get_visual (widget);
	attributes.colormap = cmap = gtk_widget_get_colormap (widget);

	attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

	/* main window */
	widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
				   &attributes, attributes_mask);
	widget->style = gtk_style_attach (widget->style, widget->window);
	gdk_window_set_user_data (widget->window, widget);
	gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);

	attributes.x = widget->style->xthickness;
	attributes.y = widget->style->ythickness;
	attributes.width = MAX (1, (gint)widget->allocation.width - (gint)attributes.x * 2);
	attributes.height = MAX (1, (gint)widget->allocation.height - (gint)attributes.y * 2);
	attributes.cursor = term->cursor = gdk_cursor_new(GDK_XTERM);
	attributes_mask |= GDK_WA_CURSOR;
	term->text = gdk_window_new (widget->window, &attributes, attributes_mask);
	gdk_window_set_user_data (term->text, term);
	gdk_window_set_background (term->text, &widget->style->white);
	gdk_window_show(term->text);
	
	term->font		= gdk_font_load (term->tfont->str);
	if (!term->font) {
		fprintf(stderr,"The default font could not be found. Please change it by editing the \"tfont\" symbol in ~/euler/eulerrc\n");
		exit(1);
	}
	term->cwidth	= gdk_char_width(term->font,'m');
	term->cheight	= 2+gdk_string_height(term->font,"abcdefghijklmnopqrstuvwxyz0123456789");

	term->commentGC	= gdk_gc_new(term->text);
	c.red = 0;
	c.green = 100<<8;
	c.blue = 0;
	if (gdk_color_alloc(cmap,&c))
		gdk_gc_set_foreground(term->commentGC,&c);
	term->outputGC	= gdk_gc_new(term->text);
	term->udfGC		= gdk_gc_new(term->text);
	c.red = 0;
	c.green = 0;
	c.blue = 100<<8;
	if (gdk_color_alloc(cmap,&c))
		gdk_gc_set_foreground(term->udfGC,&c);
	term->promptGC	= gdk_gc_new(term->text);
	c.red = 100<<8;
	c.green = 0;
	c.blue = 0;
	if (gdk_color_alloc(cmap,&c))
		gdk_gc_set_foreground(term->promptGC,&c);
	term->highlightGC = gdk_gc_new(term->text);
	c.red = 255<<8;
	c.green = 153<<8;
	c.blue = 41<<8;
	if (gdk_color_alloc(cmap,&c))
		gdk_gc_set_foreground(term->highlightGC,&c);

	term->v_adj->value = 0.0;
	term->v_adj->lower = 0.0;
	term->v_adj->upper = (gfloat)(e_get_length(term->a));
	term->v_adj->step_increment = 1.0;
	term->v_adj->page_increment = (gfloat)(term->theight-1);
	term->v_adj->page_size = (gfloat)term->theight;
	gtk_adjustment_changed(term->v_adj);
}

static void gtk_term_unrealize (GtkWidget *widget)
{
	GtkTerm *term;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	GTK_WIDGET_UNSET_FLAGS (widget, GTK_REALIZED);
	
	term = GTK_TERM (widget);
	
	gdk_window_set_user_data (term->text, NULL);
	gdk_window_destroy (term->text);
	term->text = NULL;

	gdk_gc_destroy(term->commentGC);
	gdk_gc_destroy(term->outputGC);
	gdk_gc_destroy(term->udfGC);
	gdk_gc_destroy(term->promptGC);
	gdk_gc_destroy(term->highlightGC);
	
	if (GTK_WIDGET_CLASS (parent_class)->unrealize)
		(*GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
}

/*
 *	selection handling
 */
static gint gtk_term_selection_clear(GtkWidget *widget, GdkEventSelection *event)
{
	GtkTerm *term;

	g_return_val_if_fail (widget != NULL,FALSE);
	g_return_val_if_fail (GTK_IS_TERM(widget),FALSE);

	term = GTK_TERM(widget);

  /* Let the selection handling code know that the selection
   * has been changed, since we've overriden the default handler */
	if (!gtk_selection_clear (widget, event))
		return FALSE;
	
	if (term->clipboard->len!=0)
		g_string_assign(term->clipboard,"");
		
	return TRUE;
}

static void gtk_term_selection_get(GtkWidget *widget, GtkSelectionData  *selection_data, guint info, guint time)
{
	GtkTerm *term;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM(widget));

	term = GTK_TERM(widget);

	gtk_selection_data_set(selection_data,GDK_SELECTION_TYPE_STRING,8,term->clipboard->str,term->clipboard->len);
}

static void gtk_term_selection_received(GtkWidget *widget, GtkSelectionData *selection_data, guint time)
{
	GtkTerm *term;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM(widget));

	term = GTK_TERM(widget);

	if (selection_data->length<0) return;
	
	if (selection_data->type != GDK_SELECTION_TYPE_STRING) return;

	/* if a piece of text is currently selected : delete it */
	if (term->editing && (term->pos != term->epos))
    {
    	if (term->epos>term->pos) {
			e_remove_text(term->a,term->cur,term->pos,term->epos-term->pos);
			term->epos = term->pos;
		} else {
			e_remove_text(term->a,term->cur,term->epos,term->pos-term->epos);
			term->pos = term->epos;
		}
    }

	selection_data->data[selection_data->length] = 0;
	e_insert_text(term->a,term->cur,term->pos,(gchar *)selection_data->data);
	term->pos += strlen ((gchar *)selection_data->data);
	term->epos = term->pos;
	gtk_term_update_caret(term);
	gtk_term_scroll_to_pos(term);
	if (!term->changed) {
		g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
		term->changed = 1;
	}
}

/*
 *	size_request : set the minimum widget size : 80 chars * 24 lines
 */
static void gtk_term_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
	GtkTerm *term;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));
	g_return_if_fail (requisition != NULL);

	term = GTK_TERM (widget);

	requisition->width = (term->twidth * term->cwidth) + (widget->style->xthickness * 2) + 2*PADDING;
	requisition->height = (term->theight * term->cheight) + (widget->style->ythickness * 2) + 2*PADDING;
}

/*
 *	size_allocate : set the actual widget size
 */
static void gtk_term_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
	GtkTerm *term;
	static short count = 1;
	
	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));
	g_return_if_fail (allocation != NULL);

	widget->allocation = *allocation;

	if (GTK_WIDGET_REALIZED(widget))
	{
		term = GTK_TERM(widget);
		
		if (count == 2) {
			int w = (TERM_MINWIDTH_DEFAULT * term->cwidth) + (widget->style->xthickness * 2) + 2*PADDING;
			int h = (TERM_MINHEIGHT_DEFAULT * term->cheight) + (widget->style->ythickness * 2) + 2*PADDING;
			gtk_widget_set_size_request(widget, w, h);
			count = 0;
		}
		
		if (count == 1) count = 2;
			
		gdk_window_move_resize(widget->window,
							allocation->x,
							allocation->y,
							allocation->width,
							allocation->height);

		gdk_window_move_resize(term->text,
							widget->style->xthickness,
							widget->style->ythickness,
							allocation->width-2*widget->style->xthickness,
							allocation->height-2*widget->style->ythickness);

		linelength = term->twidth = (allocation->width - (2 * widget->style->xthickness) - 2 * PADDING)/term->cwidth;
		term->theight = (allocation->height - (2 * widget->style->ythickness)- 2 * PADDING)/term->cheight;

		/*
		 * resize the scrollbar
		 */
		term->v_adj->page_increment = (gfloat)(term->theight-1);
		term->v_adj->page_size = (gfloat)term->theight;
		gtk_adjustment_changed(term->v_adj);
		gtk_term_scroll_to_pos(term);
	}
}

static void gtk_term_redraw_current(GtkTerm *term)
{
	int			offy;
	GdkGC *		gc = NULL;
		
	offy = PADDING+(term->cur-term->top)*term->cheight;
	
	gdk_window_clear_area(term->text,0,offy,2*PADDING+term->twidth*term->cwidth,term->cheight);
		
	if (term->pos!=term->epos) {
		if (term->epos>term->pos)
			gdk_draw_rectangle(term->text,term->highlightGC,TRUE,term->xcaret,offy,(term->epos-term->pos)*term->cwidth,term->cheight);
		else
			gdk_draw_rectangle(term->text,term->highlightGC,TRUE,term->xcaret-(term->pos-term->epos)*term->cwidth,offy,(term->pos-term->epos)*term->cwidth,term->cheight);
	}
	
	switch (e_get_type(term->a,term->cur))
	{
		case E_OUTPUT:
			gc = term->outputGC;
			break;
		case E_PROMPT:
			gc = term->promptGC;
			break;
		case E_UDF:
			gc = term->udfGC;
			break;
		case E_COMMENT:
			gc = term->commentGC;
	}
	gdk_draw_string(term->text,term->font,gc,PADDING+term->xoff,offy+term->cheight-term->font->descent-1,e_get_text(term->a,term->cur));
}

static void gtk_term_redraw_below(GtkTerm *term, int index)
{
	int			offy;
	GdkGC *		gc = NULL;
	int			i, last;
		
	offy = PADDING+(index-term->top)*term->cheight;
		
	gdk_window_clear_area(term->text,
		0,
		offy,
		2*PADDING+term->twidth*term->cwidth,
		(term->theight-index+term->top)*term->cheight);

	last = MIN(term->top+term->theight,e_get_length(term->a));

	for (i=index ; i<last ; i++)
	{
		if (i==term->cur && term->epos!=term->pos) {
			if (term->epos>term->pos)
				gdk_draw_rectangle(term->text,term->highlightGC,TRUE,term->xcaret,offy,(term->epos-term->pos)*term->cwidth,term->cheight);
			else
				gdk_draw_rectangle(term->text,term->highlightGC,TRUE,term->xcaret-(term->pos-term->epos)*term->cwidth,offy,(term->pos-term->epos)*term->cwidth,term->cheight);
		}

		offy+=term->cheight;

		switch (e_get_type(term->a,i))
		{
			case E_OUTPUT:
				gc = term->outputGC;
				break;
			case E_PROMPT:
				gc = term->promptGC;
				break;
			case E_UDF:
				gc = term->udfGC;
				break;
			case E_COMMENT:
				gc = term->commentGC;
		}
		gdk_draw_string(term->text,term->font,gc,
			PADDING+term->xoff,
			offy-term->font->descent-1,
			e_get_text(term->a,i));
	}
}

static void gtk_term_draw_text(GtkTerm *term)
{
	int			offx, offy;
	GdkGC *		gc = NULL;
	int			i, last;
		
	if (term->editing) gtk_term_draw_caret(term,0);
	
	offx = PADDING+term->xoff;
	offy = PADDING;
	
	last = MIN(term->top+term->theight,e_get_length(term->a));
	
	for (i=term->top ; i<last ; i++)
	{
		if (i==term->cur && term->epos!=term->pos) {
			if (term->epos>term->pos)
				gdk_draw_rectangle(term->text,term->highlightGC,TRUE,term->xcaret,offy,(term->epos-term->pos)*term->cwidth,term->cheight);
			else
				gdk_draw_rectangle(term->text,term->highlightGC,TRUE,term->xcaret-(term->pos-term->epos)*term->cwidth,offy,(term->pos-term->epos)*term->cwidth,term->cheight);
		}

		offy+=term->cheight;
		switch (e_get_type(term->a,i))
		{
			case E_OUTPUT:
				gc = term->outputGC;
				break;
			case E_PROMPT:
				gc = term->promptGC;
				break;
			case E_UDF:
				gc = term->udfGC;
				break;
			case E_COMMENT:
				gc = term->commentGC;
		}
//		gdk_draw_rectangle(term->text,gc,FALSE,offx,offy-term->cheight,widget->allocation.width-2*(PADDING+widget->style->klass->ythickness),term->cheight);
		gdk_draw_string(term->text,term->font,gc,offx,offy-term->font->descent-1,e_get_text(term->a,i));
	}
			
	if (term->editing) gtk_term_draw_caret(term,1);
}

/*
 *	draw / expose : draw or refresh the widget
 */
static gint gtk_term_expose (GtkWidget *widget, GdkEventExpose *event)
{
	GtkTerm *	term;

	g_return_val_if_fail (widget != NULL,FALSE);
	g_return_val_if_fail (GTK_IS_TERM (widget),FALSE);
	g_return_val_if_fail (event!=NULL,FALSE);

	if (GTK_WIDGET_DRAWABLE (widget))
	{
		term = GTK_TERM (widget);

		if (event->window==term->text) 
			gtk_term_draw_text(term);
		else
			gtk_draw_shadow (widget->style,widget->window,GTK_STATE_NORMAL,GTK_SHADOW_IN,0,0,widget->allocation.width,widget->allocation.height);
	}
	return FALSE;
}

static void gtk_term_adjustment_safe_set_value(GtkTerm *term, gfloat value)
{
	if (value < term->v_adj->lower || term->v_adj->upper < term->v_adj->page_size)
		gtk_adjustment_set_value(term->v_adj, term->v_adj->lower);
	else if (value > term->v_adj->upper - term->v_adj->page_size)
		gtk_adjustment_set_value(term->v_adj, term->v_adj->upper - term->v_adj->page_size);
	else
		gtk_adjustment_set_value(term->v_adj, value);
}

/*
 * Callback for when the adjustment changes - i.e., the scrollbar
 * moves.
 */
static void gtk_term_scrollbar_moved (GtkAdjustment *adj, GtkWidget *widget)
{
	GtkTerm	*term;
	gint	dy;
	
	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);

	dy = (gint)adj->value - term->top;
	
	if (dy)
	{
		if (term->editing) gtk_term_draw_caret(term,0);
		
		if (dy>0)
		{
			gint offx, offy, i;
			GdkGC *gc = NULL;
			
			offx = PADDING+term->xoff;
			offy = PADDING;

			gdk_window_copy_area(term->text,term->outputGC,offx,offy,
				term->text,
				offx,offy+dy*term->cheight,
				term->twidth*term->cwidth+PADDING,
				(term->theight-dy)*term->cheight);
			
			gdk_draw_rectangle(term->text,widget->style->white_gc,TRUE,
				offx,offy+(term->theight-dy)*term->cheight,
				term->twidth*term->cwidth+PADDING,
				dy*term->cheight);
				
			for (i=term->top+term->theight; i<term->top+term->theight+dy; i++) {
				if (i==term->cur && term->epos!=term->pos) {
					if (term->epos>term->pos)
						gdk_draw_rectangle(term->text,term->highlightGC,TRUE,term->xcaret,offy+(i-term->top-dy)*term->cheight,(term->epos-term->pos)*term->cwidth+PADDING,term->cheight);
					else
						gdk_draw_rectangle(term->text,term->highlightGC,TRUE,term->xcaret-(term->pos-term->epos)*term->cwidth,offy+(i-term->top-dy)*term->cheight,(term->pos-term->epos)*term->cwidth+PADDING,term->cheight);
				}

				switch (e_get_type(term->a,i))
				{
					case E_OUTPUT:
						gc = term->outputGC;
						break;
					case E_PROMPT:
						gc = term->promptGC;
						break;
					case E_UDF:
						gc = term->udfGC;
						break;
					case E_COMMENT:
						gc = term->commentGC;
				}
				if(e_get_text(term->a,i))
				gdk_draw_string(term->text,term->font,gc,
					offx,
					offy+(i-term->top-dy+1)*term->cheight-term->font->descent-1,
					e_get_text(term->a,i));	
			}
			term->top += dy;
			
		}
		else
		{
			gint offx, offy, i;
			GdkGC *gc = NULL;
			
			dy = -dy;
			
			offx = PADDING+term->xoff;
			offy = PADDING;
			
			gdk_window_copy_area(term->text,term->outputGC,offx,offy+dy*term->cheight,
				term->text,
				offx,offy,
				term->twidth*term->cwidth+PADDING,
				(term->theight-dy)*term->cheight);
			
			gdk_draw_rectangle(term->text,widget->style->white_gc,TRUE,
				offx,offy,
				term->twidth*term->cwidth+PADDING,
				dy*term->cheight);
			
			for (i=term->top-dy; i<term->top; i++) {
				if (i==term->cur && term->epos!=term->pos) {
					if (term->epos>term->pos)
						gdk_draw_rectangle(term->text,term->highlightGC,TRUE,term->xcaret,offy+(i-term->top+dy)*term->cheight,(term->epos-term->pos)*term->cwidth+PADDING,term->cheight);
					else
						gdk_draw_rectangle(term->text,term->highlightGC,TRUE,term->xcaret-(term->pos-term->epos)*term->cwidth,offy+(i-term->top+dy)*term->cheight,(term->pos-term->epos)*term->cwidth+PADDING,term->cheight);
				}
	
				switch (e_get_type(term->a,i))
				{
					case E_OUTPUT:
						gc = term->outputGC;
						break;
					case E_PROMPT:
						gc = term->promptGC;
						break;
					case E_UDF:
						gc = term->udfGC;
						break;
					case E_COMMENT:
						gc = term->commentGC;
				}
				if(e_get_text(term->a,i))
					gdk_draw_string(term->text,term->font,gc,offx,
									offy+(i-term->top+dy+1)*term->cheight-term->font->descent-1,
									e_get_text(term->a,i));	
			}
			term->top -= dy;
		}
		
		gtk_term_update_caret(term);
		if (term->editing) gtk_term_draw_caret(term,1);
	}
}

static void gtk_term_scroll(GtkWidget* widget, GdkEventScroll* event, gpointer user_data)
{
	GtkAdjustment*  sb_state;
	gdouble lower;
	gdouble upper;
	gdouble far;
	
	GtkTerm *term;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);

	sb_state = gtk_range_get_adjustment(GTK_RANGE(term->p_scroll));
		
	lower = sb_state->lower;
	upper = sb_state->upper;
	
	if (sb_state->page_size >= upper)
		return;
		
	switch (event->direction) {
	case GDK_SCROLL_UP:
		if (sb_state->value <= lower) 
			return;	
		if ((sb_state->value - lower) < sb_state->step_increment)
			far = sb_state->value - lower;
		else
			far = sb_state->step_increment;
		gtk_adjustment_set_value(sb_state, sb_state->value - far);
		break;
	case GDK_SCROLL_DOWN:
		if (sb_state->value >= (upper - sb_state->page_size))
			return;
		if (((upper - sb_state->page_size) - sb_state->value) < sb_state->step_increment)
			far = (upper - sb_state->page_size) - sb_state->value;
		else
			far = sb_state->step_increment;
		gtk_adjustment_set_value(sb_state, sb_state->value + far);
		break;
	default:{}; 
	}
}

static void gtk_term_map (GtkWidget *widget)
{
	GtkTerm *term;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_TERM (widget));

	term = GTK_TERM (widget);

	if (!GTK_WIDGET_MAPPED (widget))
	{
		GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);

		gdk_window_show (widget->window);

		if (!GTK_WIDGET_HAS_FOCUS (widget))
			gtk_widget_grab_focus (widget);
	}
}

static gint gtk_term_button_press (GtkWidget *widget, GdkEventButton *event)
{
	GtkTerm *	term;
	int			n, p;

	g_return_val_if_fail (widget != NULL,FALSE);
	g_return_val_if_fail (GTK_IS_TERM (widget),FALSE);
	g_return_val_if_fail (event!=NULL,FALSE);
	
	if (event->button==1) {
		term = GTK_TERM (widget);
	
		if (!term->editing) return FALSE;
	
		n = term->top + (event->y - widget->style->ythickness - PADDING)/term->cheight;
		p = (event->x - term->xoff - widget->style->xthickness - PADDING)/term->cwidth;	

		if (n>=e_get_length(term->a) || e_get_type(term->a,n)==E_OUTPUT || e_get_type(term->a,n)==E_COMMENT)
			return FALSE;
	
		if (((e_get_type(term->a,term->cur)==E_UDF && term->cur==n) || e_get_type(term->a,n)==E_PROMPT) && !nojump) {
			gtk_term_draw_caret(term,0);
			term->pos = term->epos = term->promptlen;
			gtk_term_update_caret(term);
			gtk_term_redraw_current(term);
			if (p<term->promptlen) p=term->promptlen;
			if (p>e_get_text_length(term->a,n)) p=e_get_text_length(term->a,n);
			term->cur = n;
			term->pos = term->epos = p;
			gtk_term_update_caret(term);
			gtk_term_scroll_to_pos(term);
			term->selecting = 1;
		}
	} else if (event->button==3) {
		term = GTK_TERM (widget);
	
		if (!term->editing) return FALSE;
	
		n = term->top + (event->y - widget->style->ythickness - PADDING)/term->cheight;
		p = (event->x - term->xoff - widget->style->xthickness - PADDING)/term->cwidth;	

		if (n>=e_get_length(term->a) || e_get_type(term->a,n)==E_OUTPUT || e_get_type(term->a,n)==E_COMMENT)
			return FALSE;
		if (term->cur!=n)
		if (((e_get_type(term->a,term->cur)==E_UDF && term->cur==n) || e_get_type(term->a,n)==E_PROMPT) && !nojump) {
			gtk_term_draw_caret(term,0);
			term->pos = term->epos = term->promptlen;
			gtk_term_update_caret(term);
			gtk_term_redraw_current(term);
			if (p<term->promptlen) p=term->promptlen;
			if (p>e_get_text_length(term->a,n)) p=e_get_text_length(term->a,n);
			term->cur = n;
			term->pos = term->epos = p;
			gtk_term_update_caret(term);
			gtk_term_scroll_to_pos(term);
		}
		
		gtk_menu_popup(term->menu,NULL,NULL,NULL,NULL,event->button,event->time);
	}
	return FALSE;
}

static void gtk_term_scroll_to_epos(GtkTerm *term)
{
	gint w,h,eposx;
	
	gdk_drawable_get_size(term->text,&w,&h);
	
	eposx = term->xoff+term->epos*term->cwidth+PADDING;
	if (eposx>w-40){
		term->xoff-=40;
		term->xcaret = term->xoff+term->pos*term->cwidth+PADDING;
		gdk_window_clear(term->text);
		gtk_term_draw_text(term);
	} else if (eposx<PADDING) {
		term->xoff+=40;
		term->xcaret = term->xoff+term->pos*term->cwidth+PADDING;
		gdk_window_clear(term->text);
		gtk_term_draw_text(term);
	} else
		gtk_term_redraw_current(term);
}

static gint gtk_term_motion_notify (GtkWidget *widget, GdkEventMotion *event)
{
	GtkTerm *	term;
	int			p;
	
	g_return_val_if_fail (widget != NULL,FALSE);
	g_return_val_if_fail (GTK_IS_TERM (widget),FALSE);
	g_return_val_if_fail (event!=NULL,FALSE);
	
	term = GTK_TERM (widget);
	if (!term->editing || !term->selecting) return FALSE;
	
	p = (event->x - term->xoff - widget->style->xthickness - PADDING)/term->cwidth;
	if (p<term->promptlen) p=term->promptlen;
	if (p>e_get_text_length(term->a,term->cur)) p=e_get_text_length(term->a,term->cur);
	term->epos = p;
	gtk_term_scroll_to_epos(term);

	return FALSE;
}

static gint gtk_term_button_release (GtkWidget *widget, GdkEventButton *event)
{
	GtkTerm *	term;

	g_return_val_if_fail (widget != NULL,FALSE);
	g_return_val_if_fail (GTK_IS_TERM (widget),FALSE);
	g_return_val_if_fail (event!=NULL,FALSE);
	
	term = GTK_TERM (widget);
	
	switch (event->button) {
		case 1:
			term->selecting = 0;
			gtk_term_redraw_current(term);
			gtk_term_draw_caret(term,1);
			break;
		case 4:
			gtk_term_adjustment_safe_set_value(term, term->v_adj->value-term->theight+1);	
			break;
		case 5:
			gtk_term_adjustment_safe_set_value(term, (gfloat)MIN(term->top+term->theight-1,e_get_length(term->a)-term->theight));
			break;
	}
	return FALSE;
}

static gint gtk_term_focus_in(GtkWidget *widget, GdkEventFocus *event)
{
	GtkTerm *term;

	g_return_val_if_fail (widget != NULL, FALSE);
	g_return_val_if_fail (GTK_IS_TERM (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	term = GTK_TERM (widget);
	GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);

	if (term->timeout_id == -1 && term->editing) {
		term->timeout_id = gtk_timeout_add (500, gtk_term_blink_caret, term);
		gtk_term_draw_caret(term,1);
	}
	return FALSE;
}


static gint gtk_term_focus_out(GtkWidget *widget, GdkEventFocus *event)
{
	GtkTerm *term;

	g_return_val_if_fail (widget != NULL, FALSE);
	g_return_val_if_fail (GTK_IS_TERM (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	term = GTK_TERM (widget);
	GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);

	if (term->timeout_id != -1)
	{
		g_source_remove(term->timeout_id);
		term->timeout_id = -1;
	}

	gtk_term_draw_caret(term,0);
	
	return FALSE;
}


/* dohelp
 *	Extend a start string in up to 16 ways to a command or function.
 *	This function is called from the line editor, whenever the HELP
 *	key is pressed.
 */
static char helpstart[256];
static char helpextend[16][16];
static int helpn=0,helpnext=0,helphist=-1;
static char *p;
extern commandtyp command_list[];

static int dohelp (char start[256], char extend[16][16])
{	
	int count=0,ln,l;
	header *hd=(header *)ramstart;
	builtintyp *b=builtin_list;
	commandtyp *c=command_list;
	ln=(int)strlen(start);
	while (b->name)
	{	if (!strncmp(start,b->name,ln))
		{	l=(int)strlen(b->name)-ln;
			if (l>0 && l<16)
			{	strcpy(extend[count],b->name+ln);
				count++;
			}
			if (count>=16) return count;
		}
		b++;
	}
	while (hd<(header *)udfend)
	{	if (!strncmp(start,hd->name,ln))
		{	l=(int)strlen(hd->name)-ln;
			if (l>0 && l<16)
			{	strcpy(extend[count],hd->name+ln);
				count++;
			}
			if (count>=16) return count;
		}
		hd=nextof(hd);
	}
	while (c->name)
	{	if (!strncmp(start,c->name,ln))
		{	l=(int)strlen(c->name)-ln;
			if (l>0 && l<16)
			{	strcpy(extend[count],c->name+ln);
				count++;
			}
			if (count>=16) return count;
		}
		c++;
	}
	return count;
}

/* extend the command at cursor position */
static void gtk_term_edithelp (GtkTerm *term)
{	
	char *start,*end,*p;
	int i,l;
	
	/* search history */
	l=e_get_text_length(term->a,term->cur);
	helphist=-1;
	for (i=e_get_length(term->history)-1; i>=0; i--)
		if (!strncmp(e_get_text(term->history,i),e_get_text(term->a,term->cur),l))
		{	helphist=i;
			break;
		}
	
	/* get the begining of the command to extend */
	start=end=p=e_get_text(term->a,term->cur)+term->pos;
	
	while (start>e_get_text(term->a,term->cur) && (isalpha(*(start-1)) || isdigit(*(start-1))))
		start--;
	while (isdigit(*start)) start++;
	while (isalpha(*end) || isdigit(*end)) end++;
	if (start>p || start>=end) return;
	while (p<end) { term->pos++; p++; }
	memmove(helpstart,start,end-start);
	helpstart[end-start]=0;
	helpn=dohelp(helpstart,helpextend);
	helpnext=0;
}

static void gtk_term_fkinsert (GtkTerm *term, int i)
{
	char *p = fktext[i];
	e_insert_text(term->a,term->cur,term->pos,p);
	gtk_term_redraw_current(term);
	term->pos += (int)strlen(p);
	gtk_term_scroll_to_pos(term);
	if (!term->changed) {
		g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
		term->changed = 1;
	}
}

static void get_scan(GdkEventKey *event, GtkTerm *term)
{
	switch (event->keyval) {
		case GDK_Escape:
			term->scan = escape;
			break;
			
		case GDK_KP_Up:
		case GDK_Up:
			term->scan = cursor_up;
			break;
				
		case GDK_KP_Down:
		case GDK_Down:
			term->scan = cursor_down;
			break;
			
		case GDK_KP_Right:
		case GDK_Right:
			if (event->state & GDK_SHIFT_MASK) {
				term->scan = word_right;
			} else {
				term->scan = cursor_right;
			}
			break;
			
		case GDK_KP_Left:
		case GDK_Left:
			if (event->state & GDK_SHIFT_MASK) {
				term->scan = word_left;
			} else {
				term->scan = cursor_left;
			}
			break;
			
		case GDK_BackSpace:
			term->scan = backspace;
			break;
			
		case GDK_Delete:
		case GDK_KP_Delete:
			term->scan = deletekey;
			break;
			
		case GDK_KP_Insert:
		case GDK_Insert:
			break;
			
		case GDK_KP_Home:
		case GDK_Home:
			term->scan = line_start;
			break;
			
		case GDK_KP_End:
		case GDK_End:
			term->scan = line_end;
			break;
		
		case GDK_KP_F1:
		case GDK_F1:
			term->scan = fk1;
			break;
			
		case GDK_KP_F2:
		case GDK_F2:
			term->scan = fk2;
			break;
			
		case GDK_KP_F3:
		case GDK_F3:
			term->scan = fk3;
			break;
			
		case GDK_KP_F4:
		case GDK_F4:
			term->scan = fk4;
			break;

		case GDK_F5:
			term->scan = fk5;
			break;
				
		case GDK_F6:
			term->scan = fk6;
			break;
			
		case GDK_F7:
			term->scan = fk7;
			break;
			
		case GDK_F8:
			term->scan = fk8;
			break;
			
		case GDK_F9:
			term->scan = fk9;
			break;
			
		case GDK_F10:
			term->scan = fk10;
			break;
			
		case GDK_F11:
		case GDK_F12:
		case GDK_F13:
		case GDK_F14:
		case GDK_F15:
		case GDK_F16:
		case GDK_F17:
		case GDK_F18:
		case GDK_F19:
		case GDK_F20:
			break;
	
		case GDK_KP_Page_Up:
		case GDK_Page_Up:
			break;
			
		case GDK_KP_Page_Down:
		case GDK_Page_Down:
			break;
			
		case GDK_Tab:
			term->scan = switch_screen;
			break;

		case GDK_KP_Enter:
		case GDK_Return:
			term->scan = enter;
			term->code = 13;
			break;

		default:
			if (event->string[0] && !(event->state & event->state & GDK_CONTROL_MASK || event->state & GDK_MOD1_MASK)) {
				char ch = event->string[0];
			 	if (deadkey) {
			 		switch (deadkey) {
	 					case '^':
							switch (ch) {
								case ' ':
									ch = '^';
									break;
								case 'a':
									ch = 'â';
									break;
								case 'e':
									ch = 'ê';
									break;
								case 'i':
									ch = 'î';
									break;
								case 'o':
									ch = 'ô';
									break;
								case 'u':
									ch = 'û';
									break;
								case 'A':
									ch = 'Â';
									break;
								case 'E':
									ch = 'Ê';
									break;
								case 'I':
									ch = 'Î';
									break;
								case 'O':
									ch = 'Ô';
									break;
								case 'U':
									ch = 'Û';
									break;
								default:
									{};
							}
							deadkey=0;
							break;
					
						case '¨':
							switch (ch) {
								case ' ':
									ch = '¨';
									break;
								case 'a':
									ch = 'ä';
									break;
								case 'e':
									ch = 'ë';
									break;
								case 'i':
									ch = 'ï';
									break;
								case 'o':
									ch = 'ö';
									break;
								case 'u':
									ch = 'ü';
									break;
								case 'y':
									ch = 'ÿ';
									break;
								case 'A':
									ch = 'Ä';
									break;
								case 'E':
									ch = 'Ë';
									break;
								case 'I':
									ch = 'Ï';
									break;
								case 'O':
									ch = 'Ö';
									break;
								case 'U':
									ch = 'Ü';
									break;
								default:
									{};
							}
							deadkey=0;
							break;
					}
				}
				else
					if (ch=='^' || ch=='¨') deadkey=ch;
					
				if (!deadkey) {
					if (ch==' ') term->scan = space;
					term->code = ch;
				}
			}
			break;
	}
}

static gint gtk_term_key_press (GtkWidget *widget, GdkEventKey *event)
{
	GtkTerm *term;
	
	g_return_val_if_fail (widget != NULL, FALSE);
	g_return_val_if_fail (GTK_IS_TERM (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	term = GTK_TERM (widget);

	term->code = 0; term->scan = 0;

	if (term->editing) {	
		if (event->keyval!=GDK_KP_Insert && event->keyval!=GDK_Insert) helpn=0;
	
		gtk_term_draw_caret(term,0);
	
		switch (event->keyval)
		{
			case GDK_Escape:
				term->scan = escape;
				e_remove_text(term->a,term->cur,term->promptlen,e_get_text_length(term->a,term->cur)-term->promptlen);
				term->pos = term->epos = term->promptlen;
				if (e_get_type(term->a,term->cur)==E_UDF) {
					e_append_text(term->a,term->cur,"endfunction");
					term->pos += 11;
					term->epos = term->pos;
				}
				gtk_term_update_caret(term);
				gtk_term_scroll_to_pos(term);
				if (!term->changed) {
					g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
					term->changed = 1;
				}
				break;
			
			case GDK_KP_Up:
			case GDK_Up:
				if (event->state & GDK_SHIFT_MASK) {
					term->scan = cursor_up;
					if (term->hist>0) {
						term->hist--;
						e_remove_text(term->a,term->cur,term->promptlen,e_get_text_length(term->a,term->cur)-term->promptlen);
						e_append_text(term->a,term->cur,e_get_text(term->history,term->hist));
						term->pos = term->epos = term->promptlen;
						gtk_term_update_caret(term);
						gtk_term_redraw_current(term);
						term->pos = term->epos = e_get_text_length(term->a,term->cur);
						gtk_term_scroll_to_pos(term);
						if (!term->changed) {
							g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
							term->changed = 1;
						}
					}
				} else {
					gtk_term_cursor_up(term);
				}
				break;
				
			case GDK_KP_Down:
			case GDK_Down:
				if (event->state & GDK_SHIFT_MASK) {
					term->scan = cursor_down;
					if (term->hist<e_get_length(term->history)-1) {
						term->hist++;
						e_remove_text(term->a,term->cur,term->promptlen,e_get_text_length(term->a,term->cur)-term->promptlen);
						e_append_text(term->a,term->cur,e_get_text(term->history,term->hist));
						term->pos = term->epos = term->promptlen;
						gtk_term_update_caret(term);
						gtk_term_redraw_current(term);
						term->pos = term->epos = e_get_text_length(term->a,term->cur);
						gtk_term_scroll_to_pos(term);
						if (!term->changed) {
							g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
							term->changed = 1;
						}
					}
				} else {
					gtk_term_cursor_down(term);
				}
				break;
			
			case GDK_KP_Right:
			case GDK_Right:
				if (event->state & GDK_SHIFT_MASK) {
					term->scan = word_right;
					while ((e_get_text(term->a,term->cur))[term->pos] &&
						   (e_get_text(term->a,term->cur))[term->pos]!=' ') term->pos++;
					while ((e_get_text(term->a,term->cur))[term->pos]==' ') term->pos++;
					gtk_term_scroll_to_pos(term);
				term->epos = term->pos;
				} else {
					term->scan = cursor_right;
					if (term->epos==term->pos) {
						if ((e_get_text(term->a,term->cur))[term->pos]) {
							term->pos++;term->epos++;
						}
					} else {
						if (term->epos>term->pos)
							term->pos = term->epos;
						else
							term->epos = term->pos;
					}
					gtk_term_scroll_to_pos(term);
				}
				break;
			
			case GDK_KP_Left:
			case GDK_Left:
				if (event->state & GDK_SHIFT_MASK) {
					term->scan = word_left;
					while (term->pos>term->promptlen && (e_get_text(term->a,term->cur))[term->pos]==' ')
						term->pos--;
					while (term->pos>term->promptlen && (e_get_text(term->a,term->cur))[term->pos]!=' ')
						term->pos--;
					gtk_term_scroll_to_pos(term);
					term->epos = term->pos;
				} else {
					term->scan = cursor_left;
					if (term->epos==term->pos) {
						if (term->pos>term->promptlen) {		
							term->pos--;term->epos--;
						}
					} else {
						if (term->epos>term->pos)
							term->epos=term->pos;
						else
							term->pos=term->epos;
					}
					gtk_term_scroll_to_pos(term);
				}
				break;
			
			case GDK_BackSpace:
				term->scan = backspace;
				if (term->pos>term->promptlen) {
					if (term->pos==term->epos) {
						term->pos--;term->epos--;
						gtk_term_update_caret(term);
						e_remove_text(term->a,term->cur,term->pos,1);
					} else {
						if (term->epos>term->pos) {
							e_remove_text(term->a,term->cur,term->pos,term->epos-term->pos);
							term->epos = term->pos;
						} else {
							e_remove_text(term->a,term->cur,term->epos,term->pos-term->epos);
							term->pos = term->epos;
						}
					}
					gtk_term_scroll_to_pos(term);
					if (!term->changed) {
						g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
						term->changed = 1;
					}
				}
				break;
			
			case GDK_Delete:
			case GDK_KP_Delete:
				term->scan = deletekey;
				if (term->pos<e_get_text_length(term->a,term->cur)) {
					if (term->pos==term->epos) {
						e_remove_text(term->a,term->cur,term->pos,1);
					} else {
						if (term->epos>term->pos) {
							e_remove_text(term->a,term->cur,term->pos,term->epos-term->pos);
							term->epos = term->pos;
						} else {
							e_remove_text(term->a,term->cur,term->epos,term->pos-term->epos);
							term->pos = term->epos;
						}
					}
					gtk_term_scroll_to_pos(term);
					if (!term->changed) {
						g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
						term->changed = 1;
					}
				}
				break;
			
			case GDK_KP_Insert:
			case GDK_Insert:
	    		if (!helpn && helphist<0)
	    		{	gtk_term_edithelp(term);
	    			p=0;
		    	}
		    	if (helphist>=0)
				{
					e_remove_text(term->a,term->cur,term->promptlen,e_get_text_length(term->a,term->cur)-term->promptlen);
					e_append_text(term->a,term->cur,e_get_text(term->history,helphist));
					term->pos = term->epos = term->promptlen;
					gtk_term_update_caret(term);
					gtk_term_redraw_current(term);
					term->pos = term->epos = e_get_text_length(term->a,term->cur);
					gtk_term_scroll_to_pos(term);
		    	}
		    	else if (helpnext<helpn)
	    		{
					if (p)
	    			{	
	    				int l=strlen(p);
	    				term->epos-=l;
		    			e_remove_text(term->a,term->cur,term->pos,l);
		    			gtk_term_update_caret(term);
	    			}
	    			p=helpextend[helpnext++];
	    			e_insert_text(term->a,term->cur,term->pos,p);
		    		term->epos+=strlen(p);
					gtk_term_scroll_to_pos(term);
	    		}
	    		if (!term->changed) {
					g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
					term->changed = 1;
				}
				break;
			
			case GDK_KP_Home:
			case GDK_Home:
				term->scan = line_start;
				term->pos = term->epos = term->promptlen;
				gtk_term_scroll_to_pos(term);
				break;
			
			case GDK_KP_End:
			case GDK_End:
				term->scan = line_end;
				term->pos = term->epos = e_get_text_length(term->a,term->cur);
				gtk_term_scroll_to_pos(term);
				break;
			
			case GDK_KP_F1:
			case GDK_F1:
				term->scan = fk1;
				gtk_term_fkinsert(term,0);
				break;
			
			case GDK_KP_F2:
			case GDK_F2:
				term->scan = fk2;
				gtk_term_fkinsert(term,1);
				break;
			
			case GDK_KP_F3:
			case GDK_F3:
				term->scan = fk3;
				gtk_term_fkinsert(term,2);
				break;
			
			case GDK_KP_F4:
			case GDK_F4:
				term->scan = fk4;
				gtk_term_fkinsert(term,3);
				break;

			case GDK_F5:
				term->scan = fk5;
				gtk_term_fkinsert(term,4);
				break;
				
			case GDK_F6:
				term->scan = fk6;
				gtk_term_fkinsert(term,5);
				break;
			
			case GDK_F7:
				term->scan = fk7;
				gtk_term_fkinsert(term,6);
				break;
			
			case GDK_F8:
				term->scan = fk8;
				gtk_term_fkinsert(term,7);
				break;
			
			case GDK_F9:
				term->scan = fk9;
				gtk_term_fkinsert(term,8);
				break;
			
			case GDK_F10:
				term->scan = fk10;
				gtk_term_fkinsert(term,9);
				break;
			
			case GDK_F11:
			case GDK_F12:
			case GDK_F13:
			case GDK_F14:
			case GDK_F15:
			case GDK_F16:
			case GDK_F17:
			case GDK_F18:
			case GDK_F19:
			case GDK_F20:
				break;
		
			case GDK_KP_Page_Up:
			case GDK_Page_Up:
				/* scroll the view a page up */
				gtk_term_adjustment_safe_set_value(term, term->v_adj->value-term->theight+1);
				break;
			
			case GDK_KP_Page_Down:
			case GDK_Page_Down:
				gtk_term_adjustment_safe_set_value(term, (gfloat)MIN(term->top+term->theight-1,e_get_length(term->a)-term->theight));
				/* scroll the view a page down */
				break;
			
			case GDK_Tab:
				term->scan = switch_screen;
				break;

			case GDK_KP_Enter:
			case GDK_Return:
				term->scan = enter;
				e_insert(term->history,e_get_length(term->history)-1,e_get_text(term->a,term->cur)+term->promptlen,0);
				if (e_get_length(term->history) > term->max_history)
					e_remove(term->history,0);
				term->hist=e_get_length(term->history)-1;
				set_editline(e_get_text(term->a,term->cur)+term->promptlen);
				return TRUE;

			default:
				if (event->string[0] && !(event->state & event->state & GDK_CONTROL_MASK || event->state & GDK_MOD1_MASK)) {
					char ch = event->string[0];
				 	if (deadkey) {
				 		switch (deadkey) {
		 					case '^':
								switch (ch) {
									case ' ':
										ch = '^';
										break;
									case 'a':
										ch = 'â';
										break;
									case 'e':
										ch = 'ê';
										break;
									case 'i':
										ch = 'î';
										break;
									case 'o':
										ch = 'ô';
										break;
									case 'u':
										ch = 'û';
										break;
									case 'A':
										ch = 'Â';
										break;
									case 'E':
										ch = 'Ê';
										break;
									case 'I':
										ch = 'Î';
										break;
									case 'O':
										ch = 'Ô';
										break;
									case 'U':
										ch = 'Û';
										break;
									default:
										{};
								}
								deadkey=0;
								break;
 					
							case '¨':
								switch (ch) {
									case ' ':
										ch = '¨';
										break;
									case 'a':
										ch = 'ä';
										break;
									case 'e':
										ch = 'ë';
										break;
									case 'i':
										ch = 'ï';
										break;
									case 'o':
										ch = 'ö';
										break;
									case 'u':
										ch = 'ü';
										break;
									case 'y':
										ch = 'ÿ';
										break;
									case 'A':
										ch = 'Ä';
										break;
									case 'E':
										ch = 'Ë';
										break;
									case 'I':
										ch = 'Ï';
										break;
									case 'O':
										ch = 'Ö';
										break;
									case 'U':
										ch = 'Ü';
										break;
									default:
										{};
								}
								deadkey=0;
								break;
						}
					}
					else
						if (ch=='^' || ch=='¨') deadkey=ch;
						
					if (!deadkey) {
						if (ch==' ') term->scan = space;
						term->code = ch;
						if (term->pos!=term->epos) {
							if (term->epos>term->pos) {
								e_remove_text(term->a,term->cur,term->pos,term->epos-term->pos);
								term->epos = term->pos;
							} else {
								e_remove_text(term->a,term->cur,term->epos,term->pos-term->epos);
								term->pos = term->epos;
							}
						}
						e_insert_char(term->a,term->cur,term->pos,ch);
						term->pos++;term->epos = term->pos;
						gtk_term_scroll_to_pos(term);
						if (!term->changed) {
							g_signal_emit(GTK_OBJECT(term),gtk_term_signals[GTK_TERM_CHANGED],0);
							term->changed = 1;
						}
					}
				}
				break;
		}
	
		gtk_term_draw_caret(term,1);
	}	
	else get_scan(event,term);

	if (event->state & GDK_CONTROL_MASK || event->state & GDK_MOD1_MASK)
		return FALSE;
	return TRUE;
}

gint gtk_term_redraw(GtkWidget *widget)
{
	GtkTerm *term;

	g_return_val_if_fail (widget != NULL, FALSE);
	g_return_val_if_fail (GTK_IS_TERM (widget), FALSE);

	term = GTK_TERM (widget);

	gtk_term_redraw_current(term);
	
	return FALSE;
}
