4 #include "chimara-glk-private.h"
6 extern GPrivate *glk_data_key;
8 /* Forward declarations */
9 static int finish_text_buffer_line_input(winid_t win, gboolean emit_signal);
10 static int finish_text_grid_line_input(winid_t win, gboolean emit_signal);
12 /* Internal function: code common to both flavors of char event request */
14 request_char_event_common(winid_t win, gboolean unicode)
16 VALID_WINDOW(win, return);
17 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
18 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
20 flush_window_buffer(win);
22 ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
24 win->input_request_type = unicode? INPUT_REQUEST_CHARACTER_UNICODE : INPUT_REQUEST_CHARACTER;
25 g_signal_handler_unblock( G_OBJECT(win->widget), win->char_input_keypress_handler );
29 /* If the request is in a text buffer window, scroll to the end of the
30 text buffer. TODO: This may scroll text off the top of the window that the
31 user hasn't read yet. We need to implement a paging mechanism. */
32 if(win->type == wintype_TextBuffer)
34 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
36 gtk_text_buffer_get_end_iter(buffer, &iter);
37 gtk_text_buffer_place_cursor(buffer, &iter);
38 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), gtk_text_buffer_get_insert(buffer));
39 /* Why doesn't this always work?? */
42 gtk_widget_grab_focus( GTK_WIDGET(win->widget) );
45 /* Emit the "waiting" signal to let listeners know we are ready for input */
46 g_signal_emit_by_name(glk_data->self, "waiting");
50 * glk_request_char_event:
51 * @win: A window to request char events from.
53 * Request input of a Latin-1 character or special key. A window cannot have
54 * requests for both character and line input at the same time. Nor can it have
55 * requests for character input of both types (Latin-1 and Unicode). It is
56 * illegal to call glk_request_char_event() if the window already has a pending
57 * request for either character or line input.
60 glk_request_char_event(winid_t win)
62 request_char_event_common(win, FALSE);
66 * glk_request_char_event_uni:
67 * @win: A window to request char events from.
69 * Request input of a Unicode character or special key. See
70 * glk_request_char_event().
73 glk_request_char_event_uni(winid_t win)
75 request_char_event_common(win, TRUE);
79 * glk_cancel_char_event:
80 * @win: A window to cancel the latest char event request on.
82 * This cancels a pending request for character input. (Either Latin-1 or
83 * Unicode.) For convenience, it is legal to call glk_cancel_char_event() even
84 * if there is no charcter input request on that window. Glk will ignore the
88 glk_cancel_char_event(winid_t win)
90 VALID_WINDOW(win, return);
91 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
93 if(win->input_request_type == INPUT_REQUEST_CHARACTER || win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE)
95 win->input_request_type = INPUT_REQUEST_NONE;
96 g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
100 /* Internal function: Request either latin-1 or unicode line input, in a text grid window. */
102 text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
106 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
108 GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
109 GtkTextIter start_iter, end_iter;
110 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, cursor);
112 /* Determine the maximum length of the line input */
113 gint cursorpos = gtk_text_iter_get_line_offset(&start_iter);
114 /* Odd; the Glk spec says the maximum input length is
115 windowwidth - 1 - cursorposition. I say no, because if cursorposition is
116 zero, then the input should fill the whole line. FIXME??? */
117 win->input_length = MIN(win->width - cursorpos, win->line_input_buffer_max_len);
118 end_iter = start_iter;
119 gtk_text_iter_set_line_offset(&end_iter, cursorpos + win->input_length);
121 /* If the buffer currently has a selection with one bound in the middle of
122 the input field, then deselect it. Otherwise the input field gets trashed */
123 GtkTextIter start_sel, end_sel;
124 if( gtk_text_buffer_get_selection_bounds(buffer, &start_sel, &end_sel) )
126 if( gtk_text_iter_in_range(&start_sel, &start_iter, &end_iter) )
127 gtk_text_buffer_place_cursor(buffer, &end_sel);
128 if( gtk_text_iter_in_range(&end_sel, &start_iter, &end_iter) )
129 gtk_text_buffer_place_cursor(buffer, &start_sel);
132 /* Erase the text currently in the input field and replace it with a GtkEntry */
133 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
134 win->input_anchor = gtk_text_buffer_create_child_anchor(buffer, &start_iter);
135 win->input_entry = gtk_entry_new();
136 /* Set the entry's font to match that of the window */
137 GtkRcStyle *style = gtk_widget_get_modifier_style(win->widget); /* Don't free */
138 gtk_widget_modify_font(win->input_entry, style->font_desc);
139 /* Make the entry as small as possible to fit with the text */
140 gtk_entry_set_has_frame(GTK_ENTRY(win->input_entry), FALSE);
141 GtkBorder border = { 0, 0, 0, 0 };
142 gtk_entry_set_inner_border(GTK_ENTRY(win->input_entry), &border);
143 gtk_entry_set_max_length(GTK_ENTRY(win->input_entry), win->input_length);
144 gtk_entry_set_width_chars(GTK_ENTRY(win->input_entry), win->input_length);
146 /* Insert pre-entered text if needed */
148 gtk_entry_set_text(GTK_ENTRY(win->input_entry), inserttext);
150 /* Set background color of entry (TODO: implement as property) */
152 gdk_color_parse("grey", &background);
153 gtk_widget_modify_base(win->input_entry, GTK_STATE_NORMAL, &background);
155 g_signal_connect(win->input_entry, "activate", G_CALLBACK(on_input_entry_activate), win);
156 g_signal_connect(win->input_entry, "key-press-event", G_CALLBACK(on_input_entry_key_press_event), win);
157 win->line_input_entry_changed = g_signal_connect(win->input_entry, "changed", G_CALLBACK(on_input_entry_changed), win);
159 gtk_widget_show(win->input_entry);
160 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(win->widget), win->input_entry, win->input_anchor);
162 g_signal_handler_unblock( G_OBJECT(win->widget), win->char_input_keypress_handler );
164 gtk_widget_grab_focus(win->input_entry);
169 /* Internal function: Request either latin-1 or unicode line input, in a text buffer window. */
171 text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
173 flush_window_buffer(win);
177 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
179 /* Move the input_position mark to the end of the window_buffer */
180 GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
181 GtkTextIter end_iter;
182 gtk_text_buffer_get_end_iter(buffer, &end_iter);
183 gtk_text_buffer_move_mark(buffer, input_position, &end_iter);
185 /* Set the entire contents of the window_buffer as uneditable
186 * (so input can only be entered at the end) */
187 GtkTextIter start_iter;
188 gtk_text_buffer_get_start_iter(buffer, &start_iter);
189 gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
190 gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
192 /* Insert pre-entered text if needed */
194 gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1);
195 gtk_text_buffer_get_end_iter(buffer, &end_iter); /* update after text insertion */
198 /* Scroll to input point */
199 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position);
201 /* Apply the correct style to the input prompt */
202 GtkTextIter input_iter;
203 gtk_text_buffer_get_iter_at_mark(buffer, &input_iter, input_position);
204 gtk_text_buffer_apply_tag_by_name(buffer, "input", &input_iter, &end_iter);
206 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
207 g_signal_handler_unblock(buffer, win->insert_text_handler);
209 gtk_widget_grab_focus(win->widget);
215 * glk_request_line_event:
216 * @win: A text buffer or text grid window to request line input on.
217 * @buf: A buffer of at least @maxlen bytes.
218 * @maxlen: Length of the buffer.
219 * @initlen: The number of characters in @buf to pre-enter.
221 * Requests input of a line of Latin-1 characters. A window cannot have requests
222 * for both character and line input at the same time. Nor can it have requests
223 * for line input of both types (Latin-1 and Unicode). It is illegal to call
224 * glk_request_line_event() if the window already has a pending request for
225 * either character or line input.
227 * The @buf argument is a pointer to space where the line input will be stored.
228 * (This may not be %NULL.) @maxlen is the length of this space, in bytes; the
229 * library will not accept more characters than this. If @initlen is nonzero,
230 * then the first @initlen bytes of @buf will be entered as pre-existing input
231 * — just as if the player had typed them himself. (The player can continue
232 * composing after this pre-entered input, or delete it or edit as usual.)
234 * The contents of the buffer are undefined until the input is completed (either
235 * by a line input event, or glk_cancel_line_event(). The library may or may not
236 * fill in the buffer as the player composes, while the input is still pending;
237 * it is illegal to change the contents of the buffer yourself.
240 glk_request_line_event(winid_t win, char *buf, glui32 maxlen, glui32 initlen)
242 VALID_WINDOW(win, return);
243 g_return_if_fail(buf);
244 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
245 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
246 g_return_if_fail(initlen <= maxlen);
248 ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
250 /* Register the buffer */
251 if(glk_data->register_arr)
252 win->buffer_rock = (*glk_data->register_arr)(buf, maxlen, "&+#!Cn");
254 win->input_request_type = INPUT_REQUEST_LINE;
255 win->line_input_buffer = buf;
256 win->line_input_buffer_max_len = maxlen;
258 gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup("");
261 case wintype_TextBuffer:
262 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
264 case wintype_TextGrid:
265 text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
270 /* Emit the "waiting" signal to let listeners know we are ready for input */
271 g_signal_emit_by_name(glk_data->self, "waiting");
275 * glk_request_line_event_uni:
276 * @win: A text buffer or text grid window to request line input on.
277 * @buf: A buffer of at least @maxlen characters.
278 * @maxlen: Length of the buffer.
279 * @initlen: The number of characters in @buf to pre-enter.
281 * Request input of a line of Unicode characters. This works the same as
282 * glk_request_line_event(), except the result is stored in an array of
283 * <type>glui32</type> values instead of an array of characters, and the values
284 * may be any valid Unicode code points.
286 * The result will be in Unicode Normalization Form C. This basically means that
287 * composite characters will be single characters where possible, instead of
288 * sequences of base and combining marks. See
289 * <ulink url="http://www.unicode.org/reports/tr15/">Unicode Standard Annex
290 * #15</ulink> for the details.
293 glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen)
295 VALID_WINDOW(win, return);
296 g_return_if_fail(buf);
297 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
298 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
299 g_return_if_fail(initlen <= maxlen);
301 ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
303 /* Register the buffer */
304 if(glk_data->register_arr)
305 win->buffer_rock = (*glk_data->register_arr)(buf, maxlen, "&+#!Iu");
307 win->input_request_type = INPUT_REQUEST_LINE_UNICODE;
308 win->line_input_buffer_unicode = buf;
309 win->line_input_buffer_max_len = maxlen;
313 utf8 = convert_ucs4_to_utf8(buf, initlen);
322 case wintype_TextBuffer:
323 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8);
325 case wintype_TextGrid:
326 text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8);
331 /* Emit the "waiting" signal to let listeners know we are ready for input */
332 g_signal_emit_by_name(glk_data->self, "waiting");
336 * glk_cancel_line_event:
337 * @win: A text buffer or text grid window to cancel line input on.
338 * @event: Will be filled in if the user had already input something.
340 * This cancels a pending request for line input. (Either Latin-1 or Unicode.)
342 * The event pointed to by the event argument will be filled in as if the
343 * player had hit <keycap>enter</keycap>, and the input composed so far will be
344 * stored in the buffer; see below. If you do not care about this information,
345 * pass %NULL as the @event argument. (The buffer will still be filled.)
347 * For convenience, it is legal to call glk_cancel_line_event() even if there
348 * is no line input request on that window. The event type will be set to
349 * %evtype_None in this case.
352 glk_cancel_line_event(winid_t win, event_t *event)
354 VALID_WINDOW(win, return);
355 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
358 event->type = evtype_None;
364 if(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE)
367 g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
369 int chars_written = 0;
371 if(win->type == wintype_TextGrid) {
372 g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
373 chars_written = finish_text_grid_line_input(win, FALSE);
374 } else if(win->type == wintype_TextBuffer) {
375 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
376 g_signal_handler_block(window_buffer, win->insert_text_handler);
377 chars_written = finish_text_buffer_line_input(win, FALSE);
380 ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
381 if(glk_data->unregister_arr)
383 if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
384 (*glk_data->unregister_arr)(win->line_input_buffer_unicode, win->line_input_buffer_max_len, "&+#!Iu", win->buffer_rock);
386 (*glk_data->unregister_arr)(win->line_input_buffer, win->line_input_buffer_max_len, "&+#!Cn", win->buffer_rock);
389 if(event != NULL && chars_written > 0) {
390 event->type = evtype_LineInput;
391 event->val1 = chars_written;
395 /* 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. */
397 on_char_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
399 glui32 keycode = keyval_to_glk_keycode(event->keyval, win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE);
401 ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(widget, CHIMARA_TYPE_GLK));
403 event_throw(glk, evtype_CharInput, win, keycode, 0);
404 g_signal_emit_by_name(glk, "char-input", win->rock, event->keyval);
406 /* Only one keypress will be handled */
407 win->input_request_type = INPUT_REQUEST_NONE;
408 g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
414 on_line_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
418 case wintype_TextBuffer:
419 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
420 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
422 /* Prevent falling off the end of the history list */
423 if( (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
424 && win->history_pos && win->history_pos->next == NULL)
426 if( (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
427 && (win->history_pos == NULL || win->history_pos->prev == NULL) )
430 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(win->widget));
431 GtkTextIter start, end;
432 /* Erase any text that was already typed */
433 GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
434 gtk_text_buffer_get_iter_at_mark(buffer, &start, input_position);
435 gtk_text_buffer_get_end_iter(buffer, &end);
437 if(win->history_pos == NULL) {
438 gchar *current_input = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
439 win->history = g_list_prepend(win->history, current_input);
440 win->history_pos = win->history;
443 gtk_text_buffer_delete(buffer, &start, &end);
445 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
448 win->history_pos = g_list_next(win->history_pos);
450 win->history_pos = win->history;
453 win->history_pos = g_list_previous(win->history_pos);
455 /* Insert the history item into the window */
456 gtk_text_buffer_get_end_iter(buffer, &end);
458 g_signal_handler_block(buffer, win->insert_text_handler);
459 gtk_text_buffer_insert_with_tags_by_name(buffer, &end, win->history_pos->data, -1, "input", NULL);
460 g_signal_handler_unblock(buffer, win->insert_text_handler);
465 /* If this is a text grid window, then redirect the key press to the line input GtkEntry */
466 case wintype_TextGrid:
468 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
469 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down
470 || event->keyval == GDK_Left || event->keyval == GDK_KP_Left
471 || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
472 || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab
473 || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up
474 || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down
475 || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
476 || event->keyval == GDK_End || event->keyval == GDK_KP_End)
477 return FALSE; /* Don't redirect these keys */
478 gtk_widget_grab_focus(win->input_entry);
479 gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1);
480 gboolean retval = TRUE;
481 g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval);
482 return retval; /* Block this key event if the entry handled it */
488 /* Internal function: finish handling a line input request, for both text grid and text buffer windows. */
490 write_to_window_buffer(winid_t win, const gchar *inserted_text)
494 /* Convert the string from UTF-8 to Latin-1 or Unicode */
495 if(win->input_request_type == INPUT_REQUEST_LINE)
498 gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written);
503 /* Place input in the echo stream */
504 if(win->echo_stream != NULL)
505 glk_put_string_stream(win->echo_stream, latin1);
507 /* Copy the string (bytes_written does not include the NULL at the end) */
508 copycount = MIN(win->line_input_buffer_max_len, bytes_written);
509 memcpy(win->line_input_buffer, latin1, copycount);
512 else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
515 gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written);
520 /* Place input in the echo stream */
521 if(win->echo_stream != NULL)
522 glk_put_string_stream_uni(win->echo_stream, unicode);
524 /* Copy the string (but not the NULL at the end) */
525 copycount = MIN(win->line_input_buffer_max_len, items_written);
526 memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar));
530 WARNING("Wrong input request type");
532 win->input_request_type = INPUT_REQUEST_NONE;
536 /* Internal function: Retrieves the input of a TextBuffer window and stores it in the window buffer.
537 * Returns the number of characters written, suitable for inclusion in a line input event. */
539 finish_text_buffer_line_input(winid_t win, gboolean emit_signal)
541 VALID_WINDOW(win, return 0);
542 g_return_val_if_fail(win->type == wintype_TextBuffer, 0);
544 GtkTextIter start_iter, end_iter, last_character;
546 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
547 GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
548 gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position);
549 gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
550 gtk_text_buffer_get_end_iter(window_buffer, &last_character);
551 gtk_text_iter_backward_cursor_position(&last_character);
553 gchar* last_char = gtk_text_buffer_get_text(window_buffer, &last_character, &end_iter, FALSE);
555 if( strchr(last_char, '\n') != NULL )
556 gtk_text_iter_backward_cursor_position(&end_iter);
558 gchar* inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
560 int chars_written = write_to_window_buffer(win, inserted_text);
563 ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
565 g_signal_emit_by_name(glk, "line-input", win->rock, inserted_text);
568 /* Add the text to the window input history */
569 if(win->history_pos != NULL)
571 g_free(win->history->data);
572 win->history = g_list_delete_link(win->history, win->history);
574 if(*inserted_text != 0)
575 win->history = g_list_prepend(win->history, g_strdup(inserted_text));
577 win->history_pos = NULL;
579 g_free(inserted_text);
581 return chars_written;
584 /* Internal function: Retrieves the input of a TextGrid window and stores it in the window buffer.
585 * Returns the number of characters written, suitable for inclusion in a line input event. */
587 finish_text_grid_line_input(winid_t win, gboolean emit_signal)
589 VALID_WINDOW(win, return 0);
590 g_return_val_if_fail(win->type == wintype_TextGrid, 0);
592 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
594 gchar *text = g_strdup( gtk_entry_get_text(GTK_ENTRY(win->input_entry)) );
595 /* Move the focus back into the text view */
596 gtk_widget_grab_focus(win->widget);
597 /* Remove entry widget from text view */
598 /* Should be ok even though this is the widget's own signal handler */
599 gtk_container_remove( GTK_CONTAINER(win->widget), GTK_WIDGET(win->input_entry) );
600 win->input_entry = NULL;
601 /* Delete the child anchor */
602 GtkTextIter start, end;
603 gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor);
605 gtk_text_iter_forward_char(&end); /* Point after the child anchor */
606 gtk_text_buffer_delete(buffer, &start, &end);
607 win->input_anchor = NULL;
609 gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' ');
610 gchar *text_to_insert = g_strconcat(text, spaces, NULL);
612 gtk_text_buffer_insert(buffer, &start, text_to_insert, -1);
613 g_free(text_to_insert);
615 int chars_written = write_to_window_buffer(win, text);
618 ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
620 g_signal_emit_by_name(glk, "line-input", win->rock, text);
623 /* Add the text to the window input history */
624 if(win->history_pos != NULL)
626 g_free(win->history->data);
627 win->history = g_list_delete_link(win->history, win->history);
630 win->history = g_list_prepend(win->history, g_strdup(text));
631 win->history_pos = NULL;
634 return chars_written;
637 /* Internal function: Callback for signal insert-text on a text buffer window.
638 Runs after the default handler has already inserted the text.
639 FIXME: This function assumes that newline was the last character typed into the
640 window. That assumption is wrong if, for example, text containing a newline was
641 pasted into the window. */
643 after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win)
645 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
647 /* Set the history position to NULL and erase the text we were already editing */
648 if(win->history_pos != NULL)
650 g_free(win->history->data);
651 win->history = g_list_delete_link(win->history, win->history);
652 win->history_pos = NULL;
654 if( strchr(text, '\n') != NULL )
656 /* Remove signal handlers */
657 g_signal_handler_block(window_buffer, win->insert_text_handler);
659 /* Make the window uneditable again and retrieve the text that was input */
660 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
662 int chars_written = finish_text_buffer_line_input(win, TRUE);
663 ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
664 event_throw(glk, evtype_LineInput, win, chars_written, 0);
667 /* Apply the 'input' style to the text that was entered */
668 GtkTextIter end_iter;
669 gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
670 GtkTextIter input_iter;
671 GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
672 gtk_text_buffer_get_iter_at_mark(window_buffer, &input_iter, input_position);
673 gtk_text_buffer_apply_tag_by_name(window_buffer, "input", &input_iter, &end_iter);
676 /* Internal function: Callback for signal activate on the line input GtkEntry
677 in a text grid window. */
679 on_input_entry_activate(GtkEntry *input_entry, winid_t win)
681 g_signal_handler_block(win->widget, win->char_input_keypress_handler);
683 int chars_written = finish_text_grid_line_input(win, TRUE);
684 ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
685 event_throw(glk, evtype_LineInput, win, chars_written, 0);
688 /* Internal function: Callback for signal key-press-event on the line input
689 GtkEntry in a text grid window. */
691 on_input_entry_key_press_event(GtkEntry *input_entry, GdkEventKey *event, winid_t win)
693 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
694 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
696 /* Prevent falling off the end of the history list */
697 if( (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
698 && win->history_pos && win->history_pos->next == NULL)
700 if( (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
701 && (win->history_pos == NULL || win->history_pos->prev == NULL) )
704 if(win->history_pos == NULL)
706 gchar *current_input = gtk_entry_get_text(input_entry);
707 win->history = g_list_prepend(win->history, current_input);
708 win->history_pos = win->history;
711 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
714 win->history_pos = g_list_next(win->history_pos);
716 win->history_pos = win->history;
719 win->history_pos = g_list_previous(win->history_pos);
721 /* Insert the history item into the window */
722 g_signal_handler_block(input_entry, win->line_input_entry_changed);
723 gtk_entry_set_text(input_entry, win->history_pos->data);
724 g_signal_handler_unblock(input_entry, win->line_input_entry_changed);
731 on_input_entry_changed(GtkEditable *editable, winid_t win)
733 /* Set the history position to NULL and erase the text we were already editing */
734 if(win->history_pos != NULL)
736 g_free(win->history->data);
737 win->history = g_list_delete_link(win->history, win->history);
738 win->history_pos = NULL;
743 keyval_to_glk_keycode(guint keyval, gboolean unicode)
748 case GDK_KP_Up: return keycode_Up;
750 case GDK_KP_Down: return keycode_Down;
752 case GDK_KP_Left: return keycode_Left;
754 case GDK_KP_Right: return keycode_Right;
757 case GDK_KP_Enter: return keycode_Return;
760 case GDK_KP_Delete: return keycode_Delete;
761 case GDK_Escape: return keycode_Escape;
763 case GDK_KP_Tab: return keycode_Tab;
765 case GDK_KP_Page_Up: return keycode_PageUp;
767 case GDK_KP_Page_Down: return keycode_PageDown;
769 case GDK_KP_Home: return keycode_Home;
771 case GDK_KP_End: return keycode_End;
773 case GDK_KP_F1: return keycode_Func1;
775 case GDK_KP_F2: return keycode_Func2;
777 case GDK_KP_F3: return keycode_Func3;
779 case GDK_KP_F4: return keycode_Func4;
780 case GDK_F5: return keycode_Func5;
781 case GDK_F6: return keycode_Func6;
782 case GDK_F7: return keycode_Func7;
783 case GDK_F8: return keycode_Func8;
784 case GDK_F9: return keycode_Func9;
785 case GDK_F10: return keycode_Func10;
786 case GDK_F11: return keycode_Func11;
787 case GDK_F12: return keycode_Func12;
789 keycode = gdk_keyval_to_unicode(keyval);
790 /* If keycode is 0, then keyval was not recognized; also return
791 unknown if Latin-1 input was requested and the character is not in
793 if(keycode == 0 || (!unicode && keycode > 255))
794 return keycode_Unknown;
800 force_char_input_from_queue(winid_t win, event_t *event)
802 ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
803 guint keyval = GPOINTER_TO_UINT(g_async_queue_pop(glk_data->char_input_queue));
805 glk_cancel_char_event(win);
808 ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
810 g_signal_emit_by_name(glk, "char-input", win->rock, keyval);
813 event->type = evtype_CharInput;
815 event->val1 = keyval_to_glk_keycode(keyval, win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE);
820 force_line_input_from_queue(winid_t win, event_t *event)
822 ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
823 const gchar *text = g_async_queue_pop(glk_data->line_input_queue);
824 glui32 chars_written = 0;
827 if(win->type == wintype_TextBuffer)
829 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
830 GtkTextIter start, end;
832 /* Remove signal handlers so the line input doesn't get picked up again */
833 g_signal_handler_block(buffer, win->insert_text_handler);
835 /* Erase any text that was already typed */
836 GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
837 gtk_text_buffer_get_iter_at_mark(buffer, &start, input_position);
838 gtk_text_buffer_get_end_iter(buffer, &end);
839 gtk_text_buffer_delete(buffer, &start, &end);
841 /* Make the window uneditable again */
842 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
844 /* Insert the forced input into the window */
845 gtk_text_buffer_get_end_iter(buffer, &end);
846 gchar *text_to_insert = g_strconcat(text, "\n", NULL);
847 gtk_text_buffer_insert_with_tags_by_name(buffer, &end, text_to_insert, -1, "input", NULL);
848 chars_written = finish_text_buffer_line_input(win, TRUE);
850 else if(win->type == wintype_TextGrid)
852 /* Remove signal handlers so the line input doesn't get picked up again */
853 g_signal_handler_block(win->widget, win->char_input_keypress_handler);
855 /* Insert the forced input into the window */
856 gtk_entry_set_text(GTK_ENTRY(win->input_entry), text);
857 chars_written = finish_text_grid_line_input(win, TRUE);
861 event->type = evtype_LineInput;
863 event->val1 = chars_written;