Got Gtk-Doc working. Now all the fancy /** comments before the functions
[projects/chimara/chimara.git] / src / input.c
index bf15211267292148fd1f6b582582e6bf666a7317..c9775fc77623465c2172ff31b1347aa724375dad 100644 (file)
@@ -1,6 +1,8 @@
+#include "charset.h"
 #include "input.h"
 
-/** glk_request_char_event:
+/** 
+ * glk_request_char_event:
  * @win: A window to request char events from.
  *
  * Request input of a Latin-1 character or special key. A window cannot have 
@@ -20,7 +22,8 @@ glk_request_char_event(winid_t win)
        g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
 }
 
-/** glk_request_char_event_uni:
+/** 
+ * glk_request_char_event_uni:
  * @win: A window to request char events from.
  *
  * Request input of a Unicode character or special key. See 
@@ -37,74 +40,84 @@ glk_request_char_event_uni(winid_t win)
        g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
 }
 
-/* Internal function: Request either latin-1 or unicode line input. */
+/* Internal function: Request either latin-1 or unicode line input, in a text grid window. */
 void
-request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
+text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
 {
        GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
 
-    if(win->type == wintype_TextGrid)
-    {
-        GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
-        GtkTextIter start_iter, end_iter;
-        gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, cursor);
-        
-        /* Determine the maximum length of the line input */
-        gint cursorpos = gtk_text_iter_get_line_offset(&start_iter);
-        gint input_length = MIN(win->width - 1 - cursorpos, win->line_input_buffer_max_len);
-        end_iter = start_iter;
-        gtk_text_iter_set_line_offset(&end_iter, cursorpos + input_length);
-        
-        /* Insert pre-entered text if needed */
-        gchar *text;
-        if(!insert)
-            text = g_strnfill(input_length, ' ');
-        else
-        {
-            gchar *blanks = g_strnfill(input_length - g_utf8_strlen(inserttext, -1), ' ');
-            text = g_strconcat(inserttext, blanks, NULL);
-            g_free(blanks);
-        }            
-        
-        /* Erase the text currently in the input field and replace it with blanks or the insert text, with the editable "input_field" tag */
-        GtkTextIter text_start, text_end;
-        GtkTextTag *uneditable = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "uneditable");
-        gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
-        gtk_text_buffer_get_start_iter(buffer, &text_start);
-        gtk_text_buffer_apply_tag(buffer, uneditable, &text_start, &start_iter);
-        gtk_text_buffer_insert_with_tags_by_name(buffer, &start_iter, text, -1, "input_field", NULL);
-        g_free(text);
-        gtk_text_buffer_get_end_iter(buffer, &text_end);
-        gtk_text_buffer_apply_tag(buffer, uneditable, &start_iter, &text_end);
+    GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
+    GtkTextIter start_iter, end_iter;
+    gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, cursor);
+    
+    /* Determine the maximum length of the line input */
+    gint cursorpos = gtk_text_iter_get_line_offset(&start_iter);
+    /* Odd; the Glk spec says the maximum input length is
+    windowwidth - 1 - cursorposition. I say no, because if cursorposition is
+    zero, then the input should fill the whole line. FIXME??? */
+    win->input_length = MIN(win->width - cursorpos, win->line_input_buffer_max_len);
+    end_iter = start_iter;
+    gtk_text_iter_set_line_offset(&end_iter, cursorpos + win->input_length);
+    
+    /* Erase the text currently in the input field and replace it with a GtkEntry */
+    gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
+    win->input_anchor = gtk_text_buffer_create_child_anchor(buffer, &start_iter);
+    win->input_entry = gtk_entry_new();
+       /* Set the entry's font to match that of the window */
+    GtkRcStyle *style = gtk_widget_get_modifier_style(win->widget);    /* Don't free */
+       gtk_widget_modify_font(win->input_entry, style->font_desc);
+       /* Make the entry as small as possible to fit with the text */
+       gtk_entry_set_has_frame(GTK_ENTRY(win->input_entry), FALSE);
+       GtkBorder border = { 0, 0, 0, 0 };
+       gtk_entry_set_inner_border(GTK_ENTRY(win->input_entry), &border);
+    gtk_entry_set_max_length(GTK_ENTRY(win->input_entry), win->input_length);
+    gtk_entry_set_width_chars(GTK_ENTRY(win->input_entry), win->input_length);
+    
+    /* Insert pre-entered text if needed */
+    if(insert)
+       gtk_entry_set_text(GTK_ENTRY(win->input_entry), inserttext);
+    
+    /* Set background color of entry (TODO: implement as property) */
+    GdkColor background;
+       gdk_color_parse("grey", &background);
+    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);
+    
+    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 );
+}
+    
+/* Internal function: Request either latin-1 or unicode line input, in a text buffer window. */
+void
+text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
+{
+       GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
 
-        gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
-        g_signal_handler_unblock(GTK_TEXT_VIEW(win->widget), win->insert_text_handler);
-    }
-    else if(win->type == wintype_TextBuffer)
-    {
-        /* Move the input_position mark to the end of the window_buffer */
-        GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
-        GtkTextIter end_iter;
-        gtk_text_buffer_get_end_iter(buffer, &end_iter);
-        gtk_text_buffer_move_mark(buffer, input_position, &end_iter);
+    /* Move the input_position mark to the end of the window_buffer */
+    GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
+    GtkTextIter end_iter;
+    gtk_text_buffer_get_end_iter(buffer, &end_iter);
+    gtk_text_buffer_move_mark(buffer, input_position, &end_iter);
+
+    /* Set the entire contents of the window_buffer as uneditable
+     * (so input can only be entered at the end) */
+    GtkTextIter start_iter;
+    gtk_text_buffer_get_start_iter(buffer, &start_iter);
+    gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
+    gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
     
-        /* Set the entire contents of the window_buffer as uneditable
-         * (so input can only be entered at the end) */
-        GtkTextIter start_iter;
-        gtk_text_buffer_get_start_iter(buffer, &start_iter);
-        gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
-        gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
-        
-        /* Insert pre-entered text if needed */
-        if(insert)
-            gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1);
-        
-        /* Scroll to input point */
-        gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position);
-        
-        gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
-        g_signal_handler_unblock(buffer, win->insert_text_handler);
-    }
+    /* Insert pre-entered text if needed */
+    if(insert)
+        gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1);
+    
+    /* Scroll to input point */
+    gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position);
+    
+    gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
+    g_signal_handler_unblock(buffer, win->insert_text_handler);
 }
 
 /**
@@ -139,13 +152,24 @@ glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen)
        g_return_if_fail(buf);
        g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
        g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
+       g_return_if_fail(initlen <= maxlen);
 
        win->input_request_type = INPUT_REQUEST_LINE;
        win->line_input_buffer = buf;
        win->line_input_buffer_max_len = maxlen;
 
        gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup("");
-       request_line_event_common(win, maxlen, (initlen > 0), inserttext);
+       switch(win->type)
+       {
+           case wintype_TextBuffer:
+               text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
+               break;
+           case wintype_TextGrid:
+               text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
+               break;
+        default:
+            g_assert_not_reached();
+    }
        g_free(inserttext);
 }
 
@@ -174,6 +198,7 @@ glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initl
        g_return_if_fail(buf);
        g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
        g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
+       g_return_if_fail(initlen <= maxlen);
 
        win->input_request_type = INPUT_REQUEST_LINE_UNICODE;
        win->line_input_buffer_unicode = buf;
@@ -181,26 +206,50 @@ glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initl
 
        gchar *utf8;
        if(initlen > 0) {
-               GError *error = NULL;
-               utf8 = g_ucs4_to_utf8(buf, initlen, NULL, NULL, &error);
-                       
+               utf8 = convert_ucs4_to_utf8(buf, initlen);
                if(utf8 == NULL)
-               {
-                       g_warning("Error during unicode->utf8 conversion: %s", error->message);
                        return;
-               }
        }
        else
                utf8 = g_strdup("");
 
-       request_line_event_common(win, maxlen, (initlen > 0), utf8);            
+    switch(win->type)
+       {
+           case wintype_TextBuffer:
+               text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8);
+               break;
+           case wintype_TextGrid:
+               text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8);
+               break;
+        default:
+            g_assert_not_reached();
+    }          
        g_free(utf8);
 }
 
-/* Internal function: Callback for signal key-press-event on a text buffer or text grid window. */
+/* 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)
-{    
+{
+       /* 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;
@@ -269,19 +318,16 @@ on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
 
 /* Internal function: finish handling a line input request, for both text grid and text buffer windows. */
 static void
-end_line_input_request(winid_t win, gchar *inserted_text)
+end_line_input_request(winid_t win, const gchar *inserted_text)
 {
     /* Convert the string from UTF-8 to Latin-1 or Unicode */
     if(win->input_request_type == INPUT_REQUEST_LINE) 
     {
-        GError *error = NULL;
-        gchar *latin1;
         gsize bytes_written;
-        latin1 = g_convert_with_fallback(inserted_text, -1, "ISO-8859-1", "UTF-8", "?", NULL, &bytes_written, &error);
+        gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written);
         
         if(latin1 == NULL)
         {
-            g_warning("Error during utf8->latin1 conversion: %s", error->message);
             event_throw(evtype_LineInput, win, 0, 0);
             return;
         }
@@ -290,29 +336,26 @@ end_line_input_request(winid_t win, gchar *inserted_text)
         if(win->echo_stream != NULL) 
             glk_put_string_stream(win->echo_stream, latin1);
 
-        /* Copy the string (but not the NULL at the end) */
-        int copycount = MIN(win->line_input_buffer_max_len, bytes_written - 1);
+        /* Copy the string (bytes_written does not include the NULL at the end) */
+        int copycount = MIN(win->line_input_buffer_max_len, bytes_written);
         memcpy(win->line_input_buffer, latin1, copycount);
         g_free(latin1);
         event_throw(evtype_LineInput, win, copycount, 0);
     }
     else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE) 
     {
-        gunichar *unicode;
         glong items_written;
-        unicode = g_utf8_to_ucs4_fast(inserted_text, -1, &items_written);
+        gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written);
         
         if(unicode == NULL)
         {
-            g_warning("Error during utf8->unicode conversion");
             event_throw(evtype_LineInput, win, 0, 0);
             return;
         }
 
         /* Place input in the echo stream */
-        /* TODO: fixme
         if(win->echo_stream != NULL) 
-            glk_put_string_stream_uni(window->echo_stream, unicode);*/
+            glk_put_string_stream_uni(win->echo_stream, unicode);
 
         /* Copy the string (but not the NULL at the end) */
         int copycount = MIN(win->line_input_buffer_max_len, items_written);
@@ -326,49 +369,11 @@ end_line_input_request(winid_t win, gchar *inserted_text)
     win->input_request_type = INPUT_REQUEST_NONE;
 }
 
-
-/* Internal function: Callback for signal key-press-event on a text grid window. Only unblocked during line input, so that we can catch the enter key before it inserts an enter into the window. */
-gboolean
-on_text_grid_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
-{
-    g_return_val_if_fail(win->type == wintype_TextGrid, FALSE);
-    g_return_val_if_fail(win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE, FALSE);
-    
-    if(event->keyval == GDK_Linefeed || event->keyval == GDK_Return || event->keyval == GDK_KP_Enter)
-    {
-        GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
-        GtkTextIter start_iter, end_iter;
-        g_signal_handler_block(GTK_TEXT_VIEW(win->widget), win->insert_text_handler);
-    
-        /* Determine the bounds of the line input */
-        GtkTextMark *cursor = gtk_text_buffer_get_mark(window_buffer, "cursor_position");
-        gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, cursor);
-        gint cursorpos = gtk_text_iter_get_line_offset(&start_iter);
-        gint input_length = MIN(win->width - 1 - cursorpos, win->line_input_buffer_max_len);
-        end_iter = start_iter;
-        gtk_text_iter_set_line_offset(&end_iter, cursorpos + input_length);
-        
-        gchar *inserted_text = g_strchomp( gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE) );
-        
-        /* Make the buffer uneditable again and remove the special tags */
-        gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
-        gtk_text_buffer_get_start_iter(window_buffer, &start_iter);
-        gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
-        gtk_text_buffer_remove_tag_by_name(window_buffer, "uneditable", &start_iter, &end_iter);
-        gtk_text_buffer_remove_tag_by_name(window_buffer, "input_field", &start_iter, &end_iter);
-        
-        end_line_input_request(win, inserted_text);
-        g_free(inserted_text);
-        
-        /* Block the enter key */
-        return TRUE; 
-    }
-    
-    return FALSE;
-}
-
 /* Internal function: Callback for signal insert-text on a text buffer window.
-Runs after the default handler has already inserted the text.*/
+Runs after the default handler has already inserted the text.
+FIXME: This function assumes that newline was the last character typed into the
+window. That assumption is wrong if, for example, text containing a newline was
+pasted into the window. */
 void
 after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win) 
 {
@@ -386,6 +391,7 @@ after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar
         GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
         gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position);
         gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
+               gtk_text_iter_backward_cursor_position(&end_iter); /* don't include \n */
         
         inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
 
@@ -393,3 +399,38 @@ after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar
         g_free(inserted_text);
        }
 }
+
+/* Internal function: Callback for signal activate on the line input GtkEntry
+in a text grid window. */
+void
+on_input_entry_activate(GtkEntry *input_entry, winid_t win)
+{
+       GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
+       
+       gchar *text = g_strdup(gtk_entry_get_text(input_entry));
+       /* Move the focus back into the text view */
+       gtk_widget_grab_focus(win->widget);
+       /* Remove entry widget from text view */
+       /* Should be ok even though this is the widget's own signal handler */
+       gtk_container_remove( GTK_CONTAINER(win->widget), GTK_WIDGET(input_entry) );
+       win->input_entry = NULL;
+       /* Delete the child anchor */
+       GtkTextIter start, end;
+       gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor);
+       end = start;
+       gtk_text_iter_forward_char(&end); /* Point after the child anchor */
+       gtk_text_buffer_delete(buffer, &start, &end);
+       win->input_anchor = NULL;
+       
+    gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' ');
+    gchar *text_to_insert = g_strconcat(text, spaces, NULL);
+       g_free(spaces);
+    gtk_text_buffer_insert(buffer, &start, text_to_insert, -1);
+    g_free(text_to_insert);
+    
+       g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
+       
+    end_line_input_request(win, text);
+       g_free(text);
+}
+