5 /* Forward declarations */
6 static int flush_text_buffer(winid_t win);
7 static int flush_text_grid(winid_t win);
10 * glk_request_char_event:
11 * @win: A window to request char events from.
13 * Request input of a Latin-1 character or special key. A window cannot have
14 * requests for both character and line input at the same time. Nor can it have
15 * requests for character input of both types (Latin-1 and Unicode). It is
16 * illegal to call glk_request_char_event() if the window already has a pending
17 * request for either character or line input.
20 glk_request_char_event(winid_t win)
22 VALID_WINDOW(win, return);
23 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
24 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
26 win->input_request_type = INPUT_REQUEST_CHARACTER;
27 g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
31 * glk_request_char_event_uni:
32 * @win: A window to request char events from.
34 * Request input of a Unicode character or special key. See
35 * glk_request_char_event().
38 glk_request_char_event_uni(winid_t win)
40 VALID_WINDOW(win, return);
41 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
42 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
44 win->input_request_type = INPUT_REQUEST_CHARACTER_UNICODE;
45 g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
49 * glk_cancel_char_event:
50 * @win: A window to cancel the latest char event request on.
52 * This cancels a pending request for character input. (Either Latin-1 or
53 * Unicode.) For convenience, it is legal to call glk_cancel_char_event() even
54 * if there is no charcter input request on that window. Glk will ignore the
58 glk_cancel_char_event(winid_t win)
60 VALID_WINDOW(win, return);
61 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
63 if(win->input_request_type == INPUT_REQUEST_CHARACTER || win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE)
65 win->input_request_type = INPUT_REQUEST_NONE;
66 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
70 /* Internal function: Request either latin-1 or unicode line input, in a text grid window. */
72 text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
76 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
78 GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
79 GtkTextIter start_iter, end_iter;
80 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, cursor);
82 /* Determine the maximum length of the line input */
83 gint cursorpos = gtk_text_iter_get_line_offset(&start_iter);
84 /* Odd; the Glk spec says the maximum input length is
85 windowwidth - 1 - cursorposition. I say no, because if cursorposition is
86 zero, then the input should fill the whole line. FIXME??? */
87 win->input_length = MIN(win->width - cursorpos, win->line_input_buffer_max_len);
88 end_iter = start_iter;
89 gtk_text_iter_set_line_offset(&end_iter, cursorpos + win->input_length);
91 /* If the buffer currently has a selection with one bound in the middle of
92 the input field, then deselect it. Otherwise the input field gets trashed */
93 GtkTextIter start_sel, end_sel;
94 if( gtk_text_buffer_get_selection_bounds(buffer, &start_sel, &end_sel) )
96 if( gtk_text_iter_in_range(&start_sel, &start_iter, &end_iter) )
97 gtk_text_buffer_place_cursor(buffer, &end_sel);
98 if( gtk_text_iter_in_range(&end_sel, &start_iter, &end_iter) )
99 gtk_text_buffer_place_cursor(buffer, &start_sel);
102 /* Erase the text currently in the input field and replace it with a GtkEntry */
103 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
104 win->input_anchor = gtk_text_buffer_create_child_anchor(buffer, &start_iter);
105 win->input_entry = gtk_entry_new();
106 /* Set the entry's font to match that of the window */
107 GtkRcStyle *style = gtk_widget_get_modifier_style(win->widget); /* Don't free */
108 gtk_widget_modify_font(win->input_entry, style->font_desc);
109 /* Make the entry as small as possible to fit with the text */
110 gtk_entry_set_has_frame(GTK_ENTRY(win->input_entry), FALSE);
111 GtkBorder border = { 0, 0, 0, 0 };
112 gtk_entry_set_inner_border(GTK_ENTRY(win->input_entry), &border);
113 gtk_entry_set_max_length(GTK_ENTRY(win->input_entry), win->input_length);
114 gtk_entry_set_width_chars(GTK_ENTRY(win->input_entry), win->input_length);
116 /* Insert pre-entered text if needed */
118 gtk_entry_set_text(GTK_ENTRY(win->input_entry), inserttext);
120 /* Set background color of entry (TODO: implement as property) */
122 gdk_color_parse("grey", &background);
123 gtk_widget_modify_base(win->input_entry, GTK_STATE_NORMAL, &background);
125 g_signal_connect(win->input_entry, "activate", G_CALLBACK(on_input_entry_activate), win);
127 gtk_widget_show(win->input_entry);
128 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(win->widget), win->input_entry, win->input_anchor);
130 g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
135 /* Internal function: Request either latin-1 or unicode line input, in a text buffer window. */
137 text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
141 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
143 /* Move the input_position mark to the end of the window_buffer */
144 GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
145 GtkTextIter end_iter;
146 gtk_text_buffer_get_end_iter(buffer, &end_iter);
147 gtk_text_buffer_move_mark(buffer, input_position, &end_iter);
149 /* Set the entire contents of the window_buffer as uneditable
150 * (so input can only be entered at the end) */
151 GtkTextIter start_iter;
152 gtk_text_buffer_get_start_iter(buffer, &start_iter);
153 gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
154 gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
156 /* Insert pre-entered text if needed */
158 gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1);
160 /* Scroll to input point */
161 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position);
163 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
164 g_signal_handler_unblock(buffer, win->insert_text_handler);
170 * glk_request_line_event:
171 * @win: A text buffer or text grid window to request line input on.
172 * @buf: A buffer of at least @maxlen bytes.
173 * @maxlen: Length of the buffer.
174 * @initlen: The number of characters in @buf to pre-enter.
176 * Requests input of a line of Latin-1 characters. A window cannot have requests
177 * for both character and line input at the same time. Nor can it have requests
178 * for line input of both types (Latin-1 and Unicode). It is illegal to call
179 * glk_request_line_event() if the window already has a pending request for
180 * either character or line input.
182 * The @buf argument is a pointer to space where the line input will be stored.
183 * (This may not be %NULL.) @maxlen is the length of this space, in bytes; the
184 * library will not accept more characters than this. If @initlen is nonzero,
185 * then the first @initlen bytes of @buf will be entered as pre-existing input
186 * — just as if the player had typed them himself. (The player can continue
187 * composing after this pre-entered input, or delete it or edit as usual.)
189 * The contents of the buffer are undefined until the input is completed (either
190 * by a line input event, or glk_cancel_line_event(). The library may or may not
191 * fill in the buffer as the player composes, while the input is still pending;
192 * it is illegal to change the contents of the buffer yourself.
195 glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen)
197 VALID_WINDOW(win, return);
198 g_return_if_fail(buf);
199 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
200 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
201 g_return_if_fail(initlen <= maxlen);
203 win->input_request_type = INPUT_REQUEST_LINE;
204 win->line_input_buffer = buf;
205 win->line_input_buffer_max_len = maxlen;
207 gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup("");
210 case wintype_TextBuffer:
211 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
213 case wintype_TextGrid:
214 text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
221 * glk_request_line_event_uni:
222 * @win: A text buffer or text grid window to request line input on.
223 * @buf: A buffer of at least @maxlen characters.
224 * @maxlen: Length of the buffer.
225 * @initlen: The number of characters in @buf to pre-enter.
227 * Request input of a line of Unicode characters. This works the same as
228 * glk_request_line_event(), except the result is stored in an array of
229 * <type>glui32</type> values instead of an array of characters, and the values
230 * may be any valid Unicode code points.
232 * The result will be in Unicode Normalization Form C. This basically means that
233 * composite characters will be single characters where possible, instead of
234 * sequences of base and combining marks. See
235 * <ulink url="http://www.unicode.org/reports/tr15/">Unicode Standard Annex #15
236 * </ulink> for the details.
239 glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen)
241 VALID_WINDOW(win, return);
242 g_return_if_fail(buf);
243 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
244 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
245 g_return_if_fail(initlen <= maxlen);
247 win->input_request_type = INPUT_REQUEST_LINE_UNICODE;
248 win->line_input_buffer_unicode = buf;
249 win->line_input_buffer_max_len = maxlen;
253 utf8 = convert_ucs4_to_utf8(buf, initlen);
262 case wintype_TextBuffer:
263 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8);
265 case wintype_TextGrid:
266 text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8);
273 * glk_cancel_line_event:
274 * @win: A text buffer or text grid window to cancel line input on.
275 * @event: Will be filled in if the user had already input something.
277 * This cancels a pending request for line input. (Either Latin-1 or Unicode.)
279 * The event pointed to by the event argument will be filled in as if the
280 * player had hit <keycap>enter</keycap>, and the input composed so far will be stored in the
281 * buffer; see below. If you do not care about this information, pass %NULL as
282 * the @event argument. (The buffer will still be filled.)
284 * For convenience, it is legal to call glk_cancel_line_event() even if there
285 * is no line input request on that window. The event type will be set to
286 * %evtype_None in this case.
289 glk_cancel_line_event(winid_t win, event_t *event)
291 VALID_WINDOW(win, return);
292 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
295 event->type = evtype_None;
301 if(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE)
304 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
306 int chars_written = 0;
308 if(win->type == wintype_TextGrid) {
309 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
310 chars_written = flush_text_grid(win);
311 } else if(win->type == wintype_TextBuffer) {
312 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
313 g_signal_handler_block(window_buffer, win->insert_text_handler);
314 chars_written = flush_text_buffer(win);
317 if(event != NULL && chars_written > 0) {
318 event->type = evtype_LineInput;
319 event->val1 = chars_written;
323 /* 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. */
325 on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
327 /* If this is a text grid window, and line input is active, then redirect the key press to the line input GtkEntry */
328 if( win->type == wintype_TextGrid && (win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) )
330 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
331 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down
332 || event->keyval == GDK_Left || event->keyval == GDK_KP_Left
333 || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
334 || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab
335 || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up
336 || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down
337 || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
338 || event->keyval == GDK_End || event->keyval == GDK_KP_End)
339 return FALSE; /* Don't redirect these keys */
340 gtk_widget_grab_focus(win->input_entry);
341 gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1);
342 gboolean retval = TRUE;
343 g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval);
344 return retval; /* Block this key event if the entry handled it */
346 if(win->input_request_type != INPUT_REQUEST_CHARACTER &&
347 win->input_request_type != INPUT_REQUEST_CHARACTER_UNICODE)
352 switch(event->keyval) {
354 case GDK_KP_Up: keycode = keycode_Up; break;
356 case GDK_KP_Down: keycode = keycode_Down; break;
358 case GDK_KP_Left: keycode = keycode_Left; break;
360 case GDK_KP_Right: keycode = keycode_Right; break;
363 case GDK_KP_Enter: keycode = keycode_Return; break;
366 case GDK_KP_Delete: keycode = keycode_Delete; break;
367 case GDK_Escape: keycode = keycode_Escape; break;
369 case GDK_KP_Tab: keycode = keycode_Tab; break;
371 case GDK_KP_Page_Up: keycode = keycode_PageUp; break;
373 case GDK_KP_Page_Down: keycode = keycode_PageDown; break;
375 case GDK_KP_Home: keycode = keycode_Home; break;
377 case GDK_KP_End: keycode = keycode_End; break;
379 case GDK_KP_F1: keycode = keycode_Func1; break;
381 case GDK_KP_F2: keycode = keycode_Func2; break;
383 case GDK_KP_F3: keycode = keycode_Func3; break;
385 case GDK_KP_F4: keycode = keycode_Func4; break;
386 case GDK_F5: keycode = keycode_Func5; break;
387 case GDK_F6: keycode = keycode_Func6; break;
388 case GDK_F7: keycode = keycode_Func7; break;
389 case GDK_F8: keycode = keycode_Func8; break;
390 case GDK_F9: keycode = keycode_Func9; break;
391 case GDK_F10: keycode = keycode_Func10; break;
392 case GDK_F11: keycode = keycode_Func11; break;
393 case GDK_F12: keycode = keycode_Func12; break;
395 keycode = gdk_keyval_to_unicode(event->keyval);
396 /* If keycode is 0, then keyval was not recognized; also return
397 unknown if Latin-1 input was requested and the character is not in
399 if(keycode == 0 || (win->input_request_type == INPUT_REQUEST_CHARACTER && keycode > 255))
400 keycode = keycode_Unknown;
403 event_throw(evtype_CharInput, win, keycode, 0);
405 /* Only one keypress will be handled */
406 win->input_request_type = INPUT_REQUEST_NONE;
407 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
412 /* Internal function: finish handling a line input request, for both text grid and text buffer windows. */
414 write_to_window_buffer(winid_t win, const gchar *inserted_text)
418 /* Convert the string from UTF-8 to Latin-1 or Unicode */
419 if(win->input_request_type == INPUT_REQUEST_LINE)
422 gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written);
427 /* Place input in the echo stream */
428 if(win->echo_stream != NULL)
429 glk_put_string_stream(win->echo_stream, latin1);
431 /* Copy the string (bytes_written does not include the NULL at the end) */
432 copycount = MIN(win->line_input_buffer_max_len, bytes_written);
433 memcpy(win->line_input_buffer, latin1, copycount);
436 else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
439 gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written);
444 /* Place input in the echo stream */
445 if(win->echo_stream != NULL)
446 glk_put_string_stream_uni(win->echo_stream, unicode);
448 /* Copy the string (but not the NULL at the end) */
449 copycount = MIN(win->line_input_buffer_max_len, items_written);
450 memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar));
454 WARNING("Wrong input request type");
456 win->input_request_type = INPUT_REQUEST_NONE;
460 /* Internal function: Retrieves the input of a TextBuffer window and stores it in the window buffer.
461 * Returns the number of characters written, suitable for inclusion in a line input event. */
463 flush_text_buffer(winid_t win)
465 VALID_WINDOW(win, return 0);
466 g_return_val_if_fail(win->type == wintype_TextBuffer, 0);
468 GtkTextIter start_iter, end_iter, last_character;
470 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
471 GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
472 gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position);
473 gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
474 gtk_text_buffer_get_end_iter(window_buffer, &last_character);
475 gtk_text_iter_backward_cursor_position(&last_character);
477 gchar* last_char = gtk_text_buffer_get_text(window_buffer, &last_character, &end_iter, FALSE);
479 if( strchr(last_char, '\n') != NULL )
480 gtk_text_iter_backward_cursor_position(&end_iter);
482 gchar* inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
484 int chars_written = write_to_window_buffer(win, inserted_text);
485 g_free(inserted_text);
487 return chars_written;
490 /* Internal function: Retrieves the input of a TextGrid window and stores it in the window buffer.
491 * Returns the number of characters written, suitable for inclusion in a line input event. */
493 flush_text_grid(winid_t win)
495 VALID_WINDOW(win, return 0);
496 g_return_val_if_fail(win->type == wintype_TextBuffer, 0);
498 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
500 gchar *text = g_strdup( gtk_entry_get_text(GTK_ENTRY(win->input_entry)) );
501 /* Move the focus back into the text view */
502 gtk_widget_grab_focus(win->widget);
503 /* Remove entry widget from text view */
504 /* Should be ok even though this is the widget's own signal handler */
505 gtk_container_remove( GTK_CONTAINER(win->widget), GTK_WIDGET(win->input_entry) );
506 win->input_entry = NULL;
507 /* Delete the child anchor */
508 GtkTextIter start, end;
509 gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor);
511 gtk_text_iter_forward_char(&end); /* Point after the child anchor */
512 gtk_text_buffer_delete(buffer, &start, &end);
513 win->input_anchor = NULL;
515 gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' ');
516 gchar *text_to_insert = g_strconcat(text, spaces, NULL);
518 gtk_text_buffer_insert(buffer, &start, text_to_insert, -1);
519 g_free(text_to_insert);
521 int chars_written = write_to_window_buffer(win, text);
524 return chars_written;
527 /* Internal function: Callback for signal insert-text on a text buffer window.
528 Runs after the default handler has already inserted the text.
529 FIXME: This function assumes that newline was the last character typed into the
530 window. That assumption is wrong if, for example, text containing a newline was
531 pasted into the window. */
533 after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win)
535 if( strchr(text, '\n') != NULL )
537 /* Remove signal handlers */
538 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
539 g_signal_handler_block(window_buffer, win->insert_text_handler);
541 /* Make the window uneditable again and retrieve the text that was input */
542 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
544 int chars_written = flush_text_buffer(win);
545 event_throw(evtype_LineInput, win, chars_written, 0);
549 /* Internal function: Callback for signal activate on the line input GtkEntry
550 in a text grid window. */
552 on_input_entry_activate(GtkEntry *input_entry, winid_t win)
554 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
556 int chars_written = flush_text_grid(win);
557 event_throw(evtype_LineInput, win, chars_written, 0);