Fixed reversevideo
[rodin/chimara.git] / libchimara / input.c
index 8400998f22f057b0bc59fb2ce5bdb0007d279988..6a050005e9a555094bd09351e00e2269ef64ced8 100644 (file)
@@ -1,10 +1,50 @@
 #include "charset.h"
 #include "magic.h"
 #include "input.h"
+#include "chimara-glk-private.h"
+
+extern GPrivate *glk_data_key;
 
 /* Forward declarations */
-static int flush_text_buffer(winid_t win);
-static int flush_text_grid(winid_t win);
+static int finish_text_buffer_line_input(winid_t win, gboolean emit_signal);
+static int finish_text_grid_line_input(winid_t win, gboolean emit_signal);
+
+/* Internal function: code common to both flavors of char event request */
+void
+request_char_event_common(winid_t win, gboolean unicode)
+{
+       VALID_WINDOW(win, return);
+       g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
+       g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
+
+       flush_window_buffer(win);
+       
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+
+       win->input_request_type = unicode? INPUT_REQUEST_CHARACTER_UNICODE : INPUT_REQUEST_CHARACTER;
+       g_signal_handler_unblock( G_OBJECT(win->widget), win->char_input_keypress_handler );
+
+       gdk_threads_enter();
+       
+       /* If the request is in a text buffer window, scroll to the end of the
+        text buffer. TODO: This may scroll text off the top of the window that the
+        user hasn't read yet. We need to implement a paging mechanism. */
+       if(win->type == wintype_TextBuffer)
+       {
+               GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
+               GtkTextIter iter;
+               gtk_text_buffer_get_end_iter(buffer, &iter);
+               gtk_text_buffer_place_cursor(buffer, &iter);
+               gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), gtk_text_buffer_get_insert(buffer));
+               /* Why doesn't this always work?? */
+       }
+       
+       gtk_widget_grab_focus( GTK_WIDGET(win->widget) );
+       gdk_threads_leave();
+       
+       /* Emit the "waiting" signal to let listeners know we are ready for input */
+       g_signal_emit_by_name(glk_data->self, "waiting");
+}
 
 /** 
  * glk_request_char_event:
@@ -19,12 +59,7 @@ static int flush_text_grid(winid_t win);
 void
 glk_request_char_event(winid_t win)
 {
-       VALID_WINDOW(win, return);
-       g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
-       g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
-
-       win->input_request_type = INPUT_REQUEST_CHARACTER;
-       g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
+       request_char_event_common(win, FALSE);
 }
 
 /** 
@@ -37,12 +72,7 @@ glk_request_char_event(winid_t win)
 void
 glk_request_char_event_uni(winid_t win)
 {
-       VALID_WINDOW(win, return);
-       g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
-       g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
-
-       win->input_request_type = INPUT_REQUEST_CHARACTER_UNICODE;
-       g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
+       request_char_event_common(win, TRUE);
 }
 
 /**
@@ -63,7 +93,7 @@ glk_cancel_char_event(winid_t win)
        if(win->input_request_type == INPUT_REQUEST_CHARACTER || win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE)
        {
                win->input_request_type = INPUT_REQUEST_NONE;
-               g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
+               g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
        }
 }
 
@@ -123,11 +153,15 @@ text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert,
     gtk_widget_modify_base(win->input_entry, GTK_STATE_NORMAL, &background);
     
     g_signal_connect(win->input_entry, "activate", G_CALLBACK(on_input_entry_activate), win);
+    g_signal_connect(win->input_entry, "key-press-event", G_CALLBACK(on_input_entry_key_press_event), win);
+    win->line_input_entry_changed = g_signal_connect(win->input_entry, "changed", G_CALLBACK(on_input_entry_changed), win);
     
     gtk_widget_show(win->input_entry);
     gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(win->widget), win->input_entry, win->input_anchor);
        
-       g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
+       g_signal_handler_unblock( G_OBJECT(win->widget), win->char_input_keypress_handler );
+
+       gtk_widget_grab_focus(win->input_entry);
        
        gdk_threads_leave();
 }
@@ -136,6 +170,8 @@ text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert,
 static void
 text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
 {
+       flush_window_buffer(win);
+
        gdk_threads_enter();
        
        GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
@@ -154,14 +190,23 @@ text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean inser
     gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
     
     /* Insert pre-entered text if needed */
-    if(insert)
+    if(insert) {
         gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1);
+               gtk_text_buffer_get_end_iter(buffer, &end_iter); /* update after text insertion */
+       }
     
     /* Scroll to input point */
     gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position);
+
+       /* Apply the correct style to the input prompt */
+       GtkTextIter input_iter;
+    gtk_text_buffer_get_iter_at_mark(buffer, &input_iter, input_position);
+    gtk_text_buffer_apply_tag_by_name(buffer, "input", &input_iter, &end_iter);
     
     gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
     g_signal_handler_unblock(buffer, win->insert_text_handler);
+
+       gtk_widget_grab_focus(win->widget);
        
        gdk_threads_leave();
 }
@@ -192,7 +237,7 @@ text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean inser
  * it is illegal to change the contents of the buffer yourself. 
  */
 void
-glk_request_line_event(winid_t win, charbuf, glui32 maxlen, glui32 initlen)
+glk_request_line_event(winid_t win, char *buf, glui32 maxlen, glui32 initlen)
 {
        VALID_WINDOW(win, return);
        g_return_if_fail(buf);
@@ -200,6 +245,12 @@ glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen)
        g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
        g_return_if_fail(initlen <= maxlen);
 
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       
+       /* Register the buffer */
+       if(glk_data->register_arr)
+        win->buffer_rock = (*glk_data->register_arr)(buf, maxlen, "&+#!Cn");
+       
        win->input_request_type = INPUT_REQUEST_LINE;
        win->line_input_buffer = buf;
        win->line_input_buffer_max_len = maxlen;
@@ -215,6 +266,9 @@ glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen)
                break;
     }
        g_free(inserttext);
+       
+       /* Emit the "waiting" signal to let listeners know we are ready for input */
+       g_signal_emit_by_name(glk_data->self, "waiting");
 }
 
 /**
@@ -232,8 +286,8 @@ glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen)
  * The result will be in Unicode Normalization Form C. This basically means that
  * composite characters will be single characters where possible, instead of
  * sequences of base and combining marks. See 
- * <ulink url="http://www.unicode.org/reports/tr15/">Unicode Standard Annex #15
- * </ulink> for the details.
+ * <ulink url="http://www.unicode.org/reports/tr15/">Unicode Standard Annex 
+ * #15</ulink> for the details.
  */
 void
 glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen)
@@ -244,6 +298,12 @@ glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initl
        g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
        g_return_if_fail(initlen <= maxlen);
 
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       
+       /* Register the buffer */
+       if(glk_data->register_arr)
+        win->buffer_rock = (*glk_data->register_arr)(buf, maxlen, "&+#!Iu");
+
        win->input_request_type = INPUT_REQUEST_LINE_UNICODE;
        win->line_input_buffer_unicode = buf;
        win->line_input_buffer_max_len = maxlen;
@@ -267,6 +327,9 @@ glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initl
                break;
     }          
        g_free(utf8);
+       
+       /* Emit the "waiting" signal to let listeners know we are ready for input */
+       g_signal_emit_by_name(glk_data->self, "waiting");
 }
 
 /**
@@ -277,9 +340,9 @@ glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initl
  * This cancels a pending request for line input. (Either Latin-1 or Unicode.)
  *
  * The event pointed to by the event argument will be filled in as if the
- * player had hit <keycap>enter</keycap>, and the input composed so far will be stored in the
- * buffer; see below. If you do not care about this information, pass %NULL as
- * the @event argument. (The buffer will still be filled.) 
+ * player had hit <keycap>enter</keycap>, and the input composed so far will be 
+ * stored in the buffer; see below. If you do not care about this information, 
+ * pass %NULL as the @event argument. (The buffer will still be filled.) 
  *
  * For convenience, it is legal to call glk_cancel_line_event() even if there
  * is no line input request on that window. The event type will be set to
@@ -301,19 +364,28 @@ glk_cancel_line_event(winid_t win, event_t *event)
        if(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE)
                return;
 
-       g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
+       g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
 
        int chars_written = 0;
 
        if(win->type == wintype_TextGrid) {
-               g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
-               chars_written = flush_text_grid(win);
+               g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
+               chars_written = finish_text_grid_line_input(win, FALSE);
        } else if(win->type == wintype_TextBuffer) {
                GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
                g_signal_handler_block(window_buffer, win->insert_text_handler);
-               chars_written = flush_text_buffer(win);
+               chars_written = finish_text_buffer_line_input(win, FALSE);
        }
 
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       if(glk_data->unregister_arr) 
+       {
+               if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
+                       (*glk_data->unregister_arr)(win->line_input_buffer_unicode, win->line_input_buffer_max_len, "&+#!Iu", win->buffer_rock);
+               else
+               (*glk_data->unregister_arr)(win->line_input_buffer, win->line_input_buffer_max_len, "&+#!Cn", win->buffer_rock);
+    }
+       
        if(event != NULL && chars_written > 0) {
                event->type = evtype_LineInput;
                event->val1 = chars_written;
@@ -322,93 +394,97 @@ glk_cancel_line_event(winid_t win, event_t *event)
 
 /* Internal function: General callback for signal key-press-event on a text buffer or text grid window. Used in character input on both text buffers and grids, and also in line input on grids, to redirect keystrokes to the line input field. Blocked when not in use. */
 gboolean
-on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
+on_char_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
 {
-       /* If this is a text grid window, and line input is active, then redirect the key press to the line input GtkEntry */
-       if( win->type == wintype_TextGrid && (win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) )
-       {
-               if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
-                   || event->keyval == GDK_Down || event->keyval == GDK_KP_Down
-                   || event->keyval == GDK_Left || event->keyval == GDK_KP_Left
-                   || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
-                   || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab
-                   || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up
-                   || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down
-                   || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
-                   || event->keyval == GDK_End || event->keyval == GDK_KP_End)
-                       return FALSE; /* Don't redirect these keys */
-               gtk_widget_grab_focus(win->input_entry);
-               gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1);
-               gboolean retval = TRUE;
-               g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval);
-               return retval; /* Block this key event if the entry handled it */
-       }
-       if(win->input_request_type != INPUT_REQUEST_CHARACTER && 
-               win->input_request_type != INPUT_REQUEST_CHARACTER_UNICODE)
-               return FALSE;
+       glui32 keycode = keyval_to_glk_keycode(event->keyval, win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE);
 
-       int keycode;
+       ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(widget, CHIMARA_TYPE_GLK));
+       g_assert(glk);
+       event_throw(glk, evtype_CharInput, win, keycode, 0);
+       g_signal_emit_by_name(glk, "char-input", win->rock, event->keyval);
 
-       switch(event->keyval) {
-               case GDK_Up:
-               case GDK_KP_Up: keycode = keycode_Up; break;
-               case GDK_Down: 
-               case GDK_KP_Down: keycode = keycode_Down; break;
-               case GDK_Left:
-               case GDK_KP_Left: keycode = keycode_Left; break;
-               case GDK_Right:
-               case GDK_KP_Right: keycode = keycode_Right; break;
-               case GDK_Linefeed:
-               case GDK_Return:
-               case GDK_KP_Enter: keycode = keycode_Return; break;
-               case GDK_Delete:
-               case GDK_BackSpace:
-               case GDK_KP_Delete: keycode = keycode_Delete; break;
-               case GDK_Escape: keycode = keycode_Escape; break;
-               case GDK_Tab: 
-               case GDK_KP_Tab: keycode = keycode_Tab; break;
-               case GDK_Page_Up:
-               case GDK_KP_Page_Up: keycode = keycode_PageUp; break;
-               case GDK_Page_Down:
-               case GDK_KP_Page_Down: keycode = keycode_PageDown; break;
-               case GDK_Home:
-               case GDK_KP_Home: keycode = keycode_Home; break;
-               case GDK_End:
-               case GDK_KP_End: keycode = keycode_End; break;
-               case GDK_F1: 
-               case GDK_KP_F1: keycode = keycode_Func1; break;
-               case GDK_F2: 
-               case GDK_KP_F2: keycode = keycode_Func2; break;
-               case GDK_F3: 
-               case GDK_KP_F3: keycode = keycode_Func3; break;
-               case GDK_F4: 
-               case GDK_KP_F4: keycode = keycode_Func4; break;
-               case GDK_F5: keycode = keycode_Func5; break;
-               case GDK_F6: keycode = keycode_Func6; break;
-               case GDK_F7: keycode = keycode_Func7; break;
-               case GDK_F8: keycode = keycode_Func8; break;
-               case GDK_F9: keycode = keycode_Func9; break;
-               case GDK_F10: keycode = keycode_Func10; break;
-               case GDK_F11: keycode = keycode_Func11; break;
-               case GDK_F12: keycode = keycode_Func12; break;
-               default:
-                       keycode = gdk_keyval_to_unicode(event->keyval);
-                       /* If keycode is 0, then keyval was not recognized; also return
-                       unknown if Latin-1 input was requested and the character is not in
-                       Latin-1 */
-                       if(keycode == 0 || (win->input_request_type == INPUT_REQUEST_CHARACTER && keycode > 255))
-                               keycode = keycode_Unknown;      
-       }
-
-       event_throw(evtype_CharInput, win, keycode, 0);
-       
        /* Only one keypress will be handled */
        win->input_request_type = INPUT_REQUEST_NONE;
-       g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
+       g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
 
        return TRUE;
 }
 
+gboolean
+on_line_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
+{
+       switch(win->type)
+       {
+               case wintype_TextBuffer:
+                       if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
+                               || event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
+                       {
+                               /* Prevent falling off the end of the history list */
+                               if( (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
+                                       && win->history_pos && win->history_pos->next == NULL)
+                                       return TRUE;
+                               if( (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
+                                       && (win->history_pos == NULL || win->history_pos->prev == NULL) )
+                                       return TRUE;
+                       
+                               GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(win->widget));
+                               GtkTextIter start, end;
+                               /* Erase any text that was already typed */
+                               GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
+                               gtk_text_buffer_get_iter_at_mark(buffer, &start, input_position);
+                               gtk_text_buffer_get_end_iter(buffer, &end);
+                               
+                               if(win->history_pos == NULL) {
+                                       gchar *current_input = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
+                                       win->history = g_list_prepend(win->history, current_input);
+                                       win->history_pos = win->history;
+                               }
+                               
+                               gtk_text_buffer_delete(buffer, &start, &end);
+               
+                               if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
+                               {
+                                       if(win->history_pos)
+                                               win->history_pos = g_list_next(win->history_pos);
+                                       else
+                                               win->history_pos = win->history;
+                               }
+                               else /* down */
+                                       win->history_pos = g_list_previous(win->history_pos);
+               
+                               /* Insert the history item into the window */
+                               gtk_text_buffer_get_end_iter(buffer, &end);
+                               
+                               g_signal_handler_block(buffer, win->insert_text_handler);
+                               gtk_text_buffer_insert_with_tags_by_name(buffer, &end, win->history_pos->data, -1, "input", NULL);
+                               g_signal_handler_unblock(buffer, win->insert_text_handler);
+                               return TRUE;
+                       }
+                       return FALSE;
+
+               /* If this is a text grid window, then redirect the key press to the line input GtkEntry */
+               case wintype_TextGrid:
+               {
+                       if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
+                               || event->keyval == GDK_Down || event->keyval == GDK_KP_Down
+                               || event->keyval == GDK_Left || event->keyval == GDK_KP_Left
+                               || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
+                               || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab
+                               || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up
+                               || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down
+                               || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
+                               || event->keyval == GDK_End || event->keyval == GDK_KP_End)
+                               return FALSE; /* Don't redirect these keys */
+                       gtk_widget_grab_focus(win->input_entry);
+                       gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1);
+                       gboolean retval = TRUE;
+                       g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval);
+                       return retval; /* Block this key event if the entry handled it */
+               }
+       }
+       return FALSE;
+}
+
 /* Internal function: finish handling a line input request, for both text grid and text buffer windows. */
 static int
 write_to_window_buffer(winid_t win, const gchar *inserted_text)
@@ -460,7 +536,7 @@ write_to_window_buffer(winid_t win, const gchar *inserted_text)
 /* Internal function: Retrieves the input of a TextBuffer window and stores it in the window buffer.
  * Returns the number of characters written, suitable for inclusion in a line input event. */
 static int
-flush_text_buffer(winid_t win)
+finish_text_buffer_line_input(winid_t win, gboolean emit_signal)
 {
        VALID_WINDOW(win, return 0);
        g_return_val_if_fail(win->type == wintype_TextBuffer, 0);
@@ -482,6 +558,24 @@ flush_text_buffer(winid_t win)
        gchar* inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
 
        int chars_written = write_to_window_buffer(win, inserted_text);
+       if(emit_signal)
+       {
+               ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
+               g_assert(glk);
+               g_signal_emit_by_name(glk, "line-input", win->rock, inserted_text);
+       }
+       
+       /* Add the text to the window input history */
+       if(win->history_pos != NULL)
+       {
+               g_free(win->history->data);
+               win->history = g_list_delete_link(win->history, win->history);
+       }
+       if(*inserted_text != 0)
+               win->history = g_list_prepend(win->history, g_strdup(inserted_text));   
+       
+       win->history_pos = NULL;
+       
        g_free(inserted_text);
 
        return chars_written;
@@ -490,10 +584,10 @@ flush_text_buffer(winid_t win)
 /* Internal function: Retrieves the input of a TextGrid window and stores it in the window buffer.
  * Returns the number of characters written, suitable for inclusion in a line input event. */
 static int
-flush_text_grid(winid_t win)
+finish_text_grid_line_input(winid_t win, gboolean emit_signal)
 {
        VALID_WINDOW(win, return 0);
-       g_return_val_if_fail(win->type == wintype_TextBuffer, 0);
+       g_return_val_if_fail(win->type == wintype_TextGrid, 0);
 
        GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
        
@@ -519,8 +613,24 @@ flush_text_grid(winid_t win)
     g_free(text_to_insert);
     
     int chars_written = write_to_window_buffer(win, text);
+    if(emit_signal)
+    {
+               ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
+               g_assert(glk);
+               g_signal_emit_by_name(glk, "line-input", win->rock, text);
+    }
+       
+       /* Add the text to the window input history */
+       if(win->history_pos != NULL)
+       {
+               g_free(win->history->data);
+               win->history = g_list_delete_link(win->history, win->history);
+       }
+       if(*text != 0)
+               win->history = g_list_prepend(win->history, g_strdup(text));    
+       win->history_pos = NULL;
+       
        g_free(text);
-
        return chars_written;
 }
 
@@ -532,18 +642,35 @@ pasted into the window. */
 void
 after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win) 
 {
+       GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
+
+       /* Set the history position to NULL and erase the text we were already editing */
+       if(win->history_pos != NULL)
+       {
+               g_free(win->history->data);
+               win->history = g_list_delete_link(win->history, win->history);
+               win->history_pos = NULL;
+       }
        if( strchr(text, '\n') != NULL ) 
        {
                /* Remove signal handlers */
-               GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
                g_signal_handler_block(window_buffer, win->insert_text_handler);
                
                /* Make the window uneditable again and retrieve the text that was input */
         gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
 
-        int chars_written = flush_text_buffer(win);
-               event_throw(evtype_LineInput, win, chars_written, 0);
+        int chars_written = finish_text_buffer_line_input(win, TRUE);
+        ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
+               event_throw(glk, evtype_LineInput, win, chars_written, 0);
        }
+
+       /* Apply the 'input' style to the text that was entered */
+       GtkTextIter end_iter;
+       gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
+       GtkTextIter input_iter;
+       GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
+       gtk_text_buffer_get_iter_at_mark(window_buffer, &input_iter, input_position);
+       gtk_text_buffer_apply_tag_by_name(window_buffer, "input", &input_iter, &end_iter);
 }
 
 /* Internal function: Callback for signal activate on the line input GtkEntry
@@ -551,9 +678,188 @@ in a text grid window. */
 void
 on_input_entry_activate(GtkEntry *input_entry, winid_t win)
 {
-       g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
+       g_signal_handler_block(win->widget, win->char_input_keypress_handler);
+
+       int chars_written = finish_text_grid_line_input(win, TRUE);
+       ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
+       event_throw(glk, evtype_LineInput, win, chars_written, 0);
+}
+
+/* Internal function: Callback for signal key-press-event on the line input 
+GtkEntry in a text grid window. */
+gboolean
+on_input_entry_key_press_event(GtkEntry *input_entry, GdkEventKey *event, winid_t win)
+{
+       if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
+               || event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
+       {
+               /* Prevent falling off the end of the history list */
+               if( (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
+                       && win->history_pos && win->history_pos->next == NULL)
+                       return TRUE;
+               if( (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
+                       && (win->history_pos == NULL || win->history_pos->prev == NULL) )
+                       return TRUE;
+       
+               if(win->history_pos == NULL) 
+               {
+                       gchar *current_input = gtk_entry_get_text(input_entry);
+                       win->history = g_list_prepend(win->history, current_input);
+                       win->history_pos = win->history;
+               }
+
+               if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
+               {
+                       if(win->history_pos)
+                               win->history_pos = g_list_next(win->history_pos);
+                       else
+                               win->history_pos = win->history;
+               }
+               else /* down */
+                       win->history_pos = g_list_previous(win->history_pos);
+
+               /* Insert the history item into the window */
+               g_signal_handler_block(input_entry, win->line_input_entry_changed);
+               gtk_entry_set_text(input_entry, win->history_pos->data);
+               g_signal_handler_unblock(input_entry, win->line_input_entry_changed);
+               return TRUE;
+       }
+       return FALSE;
+}
+
+void
+on_input_entry_changed(GtkEditable *editable, winid_t win)
+{
+       /* Set the history position to NULL and erase the text we were already editing */
+       if(win->history_pos != NULL)
+       {
+               g_free(win->history->data);
+               win->history = g_list_delete_link(win->history, win->history);
+               win->history_pos = NULL;
+       }
+}
+
+glui32
+keyval_to_glk_keycode(guint keyval, gboolean unicode)
+{
+       glui32 keycode;
+       switch(keyval) {
+               case GDK_Up:
+               case GDK_KP_Up: return keycode_Up;
+               case GDK_Down: 
+               case GDK_KP_Down: return keycode_Down;
+               case GDK_Left:
+               case GDK_KP_Left: return keycode_Left; 
+               case GDK_Right:
+               case GDK_KP_Right: return keycode_Right; 
+               case GDK_Linefeed:
+               case GDK_Return:
+               case GDK_KP_Enter: return keycode_Return; 
+               case GDK_Delete:
+               case GDK_BackSpace:
+               case GDK_KP_Delete: return keycode_Delete; 
+               case GDK_Escape: return keycode_Escape; 
+               case GDK_Tab: 
+               case GDK_KP_Tab: return keycode_Tab; 
+               case GDK_Page_Up:
+               case GDK_KP_Page_Up: return keycode_PageUp; 
+               case GDK_Page_Down:
+               case GDK_KP_Page_Down: return keycode_PageDown; 
+               case GDK_Home:
+               case GDK_KP_Home: return keycode_Home; 
+               case GDK_End:
+               case GDK_KP_End: return keycode_End; 
+               case GDK_F1: 
+               case GDK_KP_F1: return keycode_Func1; 
+               case GDK_F2: 
+               case GDK_KP_F2: return keycode_Func2; 
+               case GDK_F3: 
+               case GDK_KP_F3: return keycode_Func3; 
+               case GDK_F4: 
+               case GDK_KP_F4: return keycode_Func4; 
+               case GDK_F5: return keycode_Func5; 
+               case GDK_F6: return keycode_Func6; 
+               case GDK_F7: return keycode_Func7; 
+               case GDK_F8: return keycode_Func8; 
+               case GDK_F9: return keycode_Func9; 
+               case GDK_F10: return keycode_Func10; 
+               case GDK_F11: return keycode_Func11; 
+               case GDK_F12: return keycode_Func12; 
+               default:
+                       keycode = gdk_keyval_to_unicode(keyval);
+                       /* If keycode is 0, then keyval was not recognized; also return
+                       unknown if Latin-1 input was requested and the character is not in
+                       Latin-1 */
+                       if(keycode == 0 || (!unicode && keycode > 255))
+                               return keycode_Unknown;
+                       return keycode;
+       }
+}
 
-       int chars_written = flush_text_grid(win);
-       event_throw(evtype_LineInput, win, chars_written, 0);
+void
+force_char_input_from_queue(winid_t win, event_t *event)
+{
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       guint keyval = GPOINTER_TO_UINT(g_async_queue_pop(glk_data->char_input_queue));
+       
+       glk_cancel_char_event(win);
+       
+       gdk_threads_enter();
+       ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
+       g_assert(glk);
+       g_signal_emit_by_name(glk, "char-input", win->rock, keyval);
+       gdk_threads_leave();
+       
+       event->type = evtype_CharInput;
+       event->win = win;
+       event->val1 = keyval_to_glk_keycode(keyval, win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE);
+       event->val2 = 0;
 }
 
+void
+force_line_input_from_queue(winid_t win, event_t *event)
+{
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       const gchar *text = g_async_queue_pop(glk_data->line_input_queue);
+       glui32 chars_written = 0;
+       
+       gdk_threads_enter();
+       if(win->type == wintype_TextBuffer)
+       {
+               GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
+               GtkTextIter start, end;
+               
+               /* Remove signal handlers so the line input doesn't get picked up again */
+               g_signal_handler_block(buffer, win->insert_text_handler);
+               
+               /* Erase any text that was already typed */
+               GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
+               gtk_text_buffer_get_iter_at_mark(buffer, &start, input_position);
+               gtk_text_buffer_get_end_iter(buffer, &end);
+               gtk_text_buffer_delete(buffer, &start, &end);
+               
+               /* Make the window uneditable again */
+               gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
+               
+               /* Insert the forced input into the window */
+               gtk_text_buffer_get_end_iter(buffer, &end);
+               gchar *text_to_insert = g_strconcat(text, "\n", NULL);
+               gtk_text_buffer_insert_with_tags_by_name(buffer, &end, text_to_insert, -1, "input", NULL);
+               chars_written = finish_text_buffer_line_input(win, TRUE);               
+       }
+       else if(win->type == wintype_TextGrid)
+       {
+               /* Remove signal handlers so the line input doesn't get picked up again */
+               g_signal_handler_block(win->widget, win->char_input_keypress_handler);
+               
+               /* Insert the forced input into the window */
+               gtk_entry_set_text(GTK_ENTRY(win->input_entry), text);
+               chars_written = finish_text_grid_line_input(win, TRUE);
+       }
+       gdk_threads_leave();
+       
+       event->type = evtype_LineInput;
+       event->win = win;
+       event->val1 = chars_written;
+       event->val2 = 0;
+}