+/* 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;
+}
+
+/* Internal function: Callback for signal insert-text on a text buffer window.
+Runs after the default handler has already inserted the text.*/
+void
+after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win)
+{
+ 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 */
+ gchar *inserted_text;
+ GtkTextIter start_iter, end_iter;
+
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
+ 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);
+
+ inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
+
+ end_line_input_request(win, inserted_text);
+ g_free(inserted_text);