+ /* 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);
+
+ if(latin1 == NULL)
+ {
+ g_warning("Error during utf8->latin1 conversion: %s", error->message);
+ event_throw(evtype_LineInput, win, 0, 0);
+ return;
+ }
+
+ /* Place input in the echo stream */
+ 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);
+ 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);
+
+ 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);*/
+
+ /* Copy the string (but not the NULL at the end) */
+ int copycount = MIN(win->line_input_buffer_max_len, items_written);
+ memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar));
+ g_free(unicode);
+ event_throw(evtype_LineInput, win, copycount, 0);
+ }
+ else
+ g_warning("%s: Wrong input request type.", __func__);
+
+ win->input_request_type = INPUT_REQUEST_NONE;
+}
+
+/* Internal function: calculate the bounds of the line input field of a text grid window. Fill them into start_iter and end_iter */
+static void
+text_grid_get_line_input_bounds(winid_t win, GtkTextIter *start_iter, GtkTextIter *end_iter)
+{
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
+ /* "cursor_position" is the Glk output cursor position, which is also the position of the start of the line input buffer */
+ GtkTextMark *input_start = gtk_text_buffer_get_mark(buffer, "cursor_position");
+ gtk_text_buffer_get_iter_at_mark(buffer, start_iter, input_start);
+ gint startpos = gtk_text_iter_get_line_offset(start_iter);
+ gint input_length = MIN(win->width - startpos, win->line_input_buffer_max_len);
+ *end_iter = *start_iter;
+ gtk_text_iter_set_line_offset(end_iter, startpos + input_length);
+}
+
+/* Internal function: Retag the input field bounds */
+static void
+text_grid_retag_line_input_bounds(winid_t win)
+{
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
+ GtkTextIter start, end, input_start, input_end;
+ text_grid_get_line_input_bounds(win, &input_start, &input_end);
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ gtk_text_buffer_remove_tag_by_name(buffer, "input_field", &start, &end);
+ gtk_text_buffer_apply_tag_by_name(buffer, "input_field", &input_start, &input_end);
+}
+
+/* Internal function: Callback for signal key-press-event on a text grid window. Only unblocked during line input, so that we can catch all the keys that have different functions in text grid line input, before they mess up 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);
+
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
+
+ switch(event->keyval) {
+ /* Backspace deletes the character before the cursor, moves the characters in the rest of the input field back one, and inserts a space at the end */
+ case GDK_BackSpace:
+ {
+ GtkTextIter insert, bound, input_start, input_end;
+ /* Backspace acts like Delete if there is text selected */
+ if( gtk_text_buffer_get_selection_bounds(buffer, &insert, &bound) )
+ {
+ text_grid_get_line_input_bounds(win, &input_start, &input_end);
+
+ /* Determine whether part of the selected range is in the input field, and if so, what part */
+ gint insert_where = gtk_text_iter_compare(&insert, &input_start);
+ gint bound_where = gtk_text_iter_compare(&bound, &input_start);
+ if(insert_where < 0 && bound_where <= 0)
+ return FALSE;
+ if(insert_where < 0)
+ insert = input_start;
+
+ insert_where = gtk_text_iter_compare(&insert, &input_end);
+ bound_where = gtk_text_iter_compare(&bound, &input_end);
+ if(insert_where >= 0 && bound_where > 0)
+ return FALSE;
+ if(bound_where > 0)
+ bound = input_end;
+
+ /* insert and bound are now within the input field */
+ gsize num_spaces = gtk_text_iter_get_offset(&bound) - gtk_text_iter_get_offset(&insert);
+ gchar *spaces = g_strnfill(num_spaces, ' ');
+ /* Create a temporary mark with right gravity at the end of the input field */
+ GtkTextMark *input_end_mark = gtk_text_buffer_create_mark(buffer, NULL, &input_end, FALSE);
+ gtk_text_buffer_delete(buffer, &insert, &bound);
+ gtk_text_buffer_get_iter_at_mark(buffer, &input_end, input_end_mark);
+ gtk_text_buffer_delete_mark(buffer, input_end_mark);
+ gtk_text_buffer_insert(buffer, &input_end, spaces, -1);
+ g_free(spaces);
+ text_grid_retag_line_input_bounds(win);
+
+ return TRUE;
+ }
+
+ text_grid_get_line_input_bounds(win, &input_start, &input_end);
+ /* if cursor is outside input field, continue as default */
+ if( !gtk_text_iter_in_range(&insert, &input_start, &input_end) )
+ return FALSE;
+ /* if cursor is at start of field, do nothing */
+ if( gtk_text_iter_equal(&insert, &input_start) )
+ return TRUE;
+ gtk_text_iter_backward_cursor_position(&insert);
+ /* Create a temporary mark with right gravity at the end of the input field */
+ GtkTextMark *input_end_mark = gtk_text_buffer_create_mark(buffer, NULL, &input_end, FALSE);
+ gtk_text_buffer_delete(buffer, &insert, &bound);
+ /* Revalidate end iterator */
+ gtk_text_buffer_get_iter_at_mark(buffer, &input_end, input_end_mark);
+ gtk_text_buffer_delete_mark(buffer, input_end_mark);
+ gtk_text_buffer_insert(buffer, &input_end, " ", -1);
+ text_grid_retag_line_input_bounds(win);
+ }
+ return TRUE;
+
+ /* Various forms of Enter */
+ case GDK_Linefeed:
+ case GDK_Return:
+ case GDK_KP_Enter:
+ {
+ GtkTextIter input_start, input_end, start, end;
+ g_signal_handler_block(GTK_TEXT_VIEW(win->widget), win->insert_text_handler);
+
+ /* Get the input text */
+ text_grid_get_line_input_bounds(win, &input_start, &input_end);
+ gtk_text_iter_forward_char(&input_end);
+ gchar *inserted_text = g_strchomp( gtk_text_buffer_get_text(buffer, &input_start, &input_end, 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(buffer, &start);
+ gtk_text_buffer_get_end_iter(buffer, &end);
+ gtk_text_buffer_remove_tag_by_name(buffer, "input_field", &start, &end);
+
+ end_line_input_request(win, inserted_text);
+ g_free(inserted_text);
+
+ /* Block the enter key */
+ return TRUE;
+ }
+ }
+ return FALSE;
+}