6 * glk_request_char_event:
7 * @win: A window to request char events from.
9 * Request input of a Latin-1 character or special key. A window cannot have
10 * requests for both character and line input at the same time. Nor can it have
11 * requests for character input of both types (Latin-1 and Unicode). It is
12 * illegal to call glk_request_char_event() if the window already has a pending
13 * request for either character or line input.
16 glk_request_char_event(winid_t win)
18 VALID_WINDOW(win, return);
19 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
20 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
22 win->input_request_type = INPUT_REQUEST_CHARACTER;
23 g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
27 * glk_request_char_event_uni:
28 * @win: A window to request char events from.
30 * Request input of a Unicode character or special key. See
31 * glk_request_char_event().
34 glk_request_char_event_uni(winid_t win)
36 VALID_WINDOW(win, return);
37 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
38 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
40 win->input_request_type = INPUT_REQUEST_CHARACTER_UNICODE;
41 g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
45 * glk_cancel_char_event:
46 * @win: A window to cancel the latest char event request on.
48 * This cancels a pending request for character input. (Either Latin-1 or
49 * Unicode.) For convenience, it is legal to call glk_cancel_char_event() even
50 * if there is no charcter input request on that window. Glk will ignore the
54 glk_cancel_char_event(winid_t win)
56 VALID_WINDOW(win, return);
57 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
59 if(win->input_request_type == INPUT_REQUEST_CHARACTER || win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE)
61 win->input_request_type = INPUT_REQUEST_NONE;
62 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
66 /* Internal function: Request either latin-1 or unicode line input, in a text grid window. */
68 text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
70 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
72 GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
73 GtkTextIter start_iter, end_iter;
74 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, cursor);
76 /* Determine the maximum length of the line input */
77 gint cursorpos = gtk_text_iter_get_line_offset(&start_iter);
78 /* Odd; the Glk spec says the maximum input length is
79 windowwidth - 1 - cursorposition. I say no, because if cursorposition is
80 zero, then the input should fill the whole line. FIXME??? */
81 win->input_length = MIN(win->width - cursorpos, win->line_input_buffer_max_len);
82 end_iter = start_iter;
83 gtk_text_iter_set_line_offset(&end_iter, cursorpos + win->input_length);
85 /* If the buffer currently has a selection with one bound in the middle of
86 the input field, then deselect it. Otherwise the input field gets trashed */
87 GtkTextIter start_sel, end_sel;
88 if( gtk_text_buffer_get_selection_bounds(buffer, &start_sel, &end_sel) )
90 if( gtk_text_iter_in_range(&start_sel, &start_iter, &end_iter) )
91 gtk_text_buffer_place_cursor(buffer, &end_sel);
92 if( gtk_text_iter_in_range(&end_sel, &start_iter, &end_iter) )
93 gtk_text_buffer_place_cursor(buffer, &start_sel);
96 /* Erase the text currently in the input field and replace it with a GtkEntry */
97 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
98 win->input_anchor = gtk_text_buffer_create_child_anchor(buffer, &start_iter);
99 win->input_entry = gtk_entry_new();
100 /* Set the entry's font to match that of the window */
101 GtkRcStyle *style = gtk_widget_get_modifier_style(win->widget); /* Don't free */
102 gtk_widget_modify_font(win->input_entry, style->font_desc);
103 /* Make the entry as small as possible to fit with the text */
104 gtk_entry_set_has_frame(GTK_ENTRY(win->input_entry), FALSE);
105 GtkBorder border = { 0, 0, 0, 0 };
106 gtk_entry_set_inner_border(GTK_ENTRY(win->input_entry), &border);
107 gtk_entry_set_max_length(GTK_ENTRY(win->input_entry), win->input_length);
108 gtk_entry_set_width_chars(GTK_ENTRY(win->input_entry), win->input_length);
110 /* Insert pre-entered text if needed */
112 gtk_entry_set_text(GTK_ENTRY(win->input_entry), inserttext);
114 /* Set background color of entry (TODO: implement as property) */
116 gdk_color_parse("grey", &background);
117 gtk_widget_modify_base(win->input_entry, GTK_STATE_NORMAL, &background);
119 g_signal_connect(win->input_entry, "activate", G_CALLBACK(on_input_entry_activate), win);
121 gtk_widget_show(win->input_entry);
122 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(win->widget), win->input_entry, win->input_anchor);
124 g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
127 /* Internal function: Request either latin-1 or unicode line input, in a text buffer window. */
129 text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
131 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
133 /* Move the input_position mark to the end of the window_buffer */
134 GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
135 GtkTextIter end_iter;
136 gtk_text_buffer_get_end_iter(buffer, &end_iter);
137 gtk_text_buffer_move_mark(buffer, input_position, &end_iter);
139 /* Set the entire contents of the window_buffer as uneditable
140 * (so input can only be entered at the end) */
141 GtkTextIter start_iter;
142 gtk_text_buffer_get_start_iter(buffer, &start_iter);
143 gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
144 gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
146 /* Insert pre-entered text if needed */
148 gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1);
150 /* Scroll to input point */
151 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position);
153 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
154 g_signal_handler_unblock(buffer, win->insert_text_handler);
158 * glk_request_line_event:
159 * @win: A text buffer or text grid window to request line input on.
160 * @buf: A buffer of at least @maxlen bytes.
161 * @maxlen: Length of the buffer.
162 * @initlen: The number of characters in @buf to pre-enter.
164 * Requests input of a line of Latin-1 characters. A window cannot have requests
165 * for both character and line input at the same time. Nor can it have requests
166 * for line input of both types (Latin-1 and Unicode). It is illegal to call
167 * glk_request_line_event() if the window already has a pending request for
168 * either character or line input.
170 * The @buf argument is a pointer to space where the line input will be stored.
171 * (This may not be %NULL.) @maxlen is the length of this space, in bytes; the
172 * library will not accept more characters than this. If @initlen is nonzero,
173 * then the first @initlen bytes of @buf will be entered as pre-existing input
174 * — just as if the player had typed them himself. (The player can continue
175 * composing after this pre-entered input, or delete it or edit as usual.)
177 * The contents of the buffer are undefined until the input is completed (either
178 * by a line input event, or glk_cancel_line_event(). The library may or may not
179 * fill in the buffer as the player composes, while the input is still pending;
180 * it is illegal to change the contents of the buffer yourself.
183 glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen)
185 VALID_WINDOW(win, return);
186 g_return_if_fail(buf);
187 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
188 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
189 g_return_if_fail(initlen <= maxlen);
191 win->input_request_type = INPUT_REQUEST_LINE;
192 win->line_input_buffer = buf;
193 win->line_input_buffer_max_len = maxlen;
195 gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup("");
198 case wintype_TextBuffer:
199 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
201 case wintype_TextGrid:
202 text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
209 * glk_request_line_event_uni:
210 * @win: A text buffer or text grid window to request line input on.
211 * @buf: A buffer of at least @maxlen characters.
212 * @maxlen: Length of the buffer.
213 * @initlen: The number of characters in @buf to pre-enter.
215 * Request input of a line of Unicode characters. This works the same as
216 * glk_request_line_event(), except the result is stored in an array of
217 * <type>glui32</type> values instead of an array of characters, and the values
218 * may be any valid Unicode code points.
220 * The result will be in Unicode Normalization Form C. This basically means that
221 * composite characters will be single characters where possible, instead of
222 * sequences of base and combining marks. See
223 * <ulink url="http://www.unicode.org/reports/tr15/">Unicode Standard Annex #15
224 * </ulink> for the details.
227 glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen)
229 VALID_WINDOW(win, return);
230 g_return_if_fail(buf);
231 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
232 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
233 g_return_if_fail(initlen <= maxlen);
235 win->input_request_type = INPUT_REQUEST_LINE_UNICODE;
236 win->line_input_buffer_unicode = buf;
237 win->line_input_buffer_max_len = maxlen;
241 utf8 = convert_ucs4_to_utf8(buf, initlen);
250 case wintype_TextBuffer:
251 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8);
253 case wintype_TextGrid:
254 text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8);
261 * glk_cancel_line_event:
262 * @win: A text buffer or text grid window to cancel line input on.
263 * @event: Will be filled in if the user had already input something.
265 * This cancels a pending request for line input. (Either Latin-1 or Unicode.)
267 * The event pointed to by the event argument will be filled in as if the
268 * player had hit <keycap>enter</keycap>, and the input composed so far will be stored in the
269 * buffer; see below. If you do not care about this information, pass %NULL as
270 * the @event argument. (The buffer will still be filled.)
272 * For convenience, it is legal to call glk_cancel_line_event() even if there
273 * is no line input request on that window. The event type will be set to
274 * #evtype_None in this case.
277 glk_cancel_line_event(winid_t win, event_t *event)
280 VALID_WINDOW(win, return);
281 g_return_if_fail(win->input_request_type != INPUT_REQUEST_NONE);
282 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
284 event->type = evtype_None;
289 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
291 int chars_written = 0;
293 if(win->type == wintype_TextGrid) {
294 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
295 chars_written = flush_text_grid(win);
296 } else if(win->type == wintype_TextBuffer) {
297 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
298 g_signal_handler_block(window_buffer, win->insert_text_handler);
299 chars_written = flush_text_buffer(win);
302 if(chars_written > 0) {
303 event->type = evtype_LineInput;
304 event->val1 = chars_written;
308 /* 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. */
310 on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
312 /* If this is a text grid window, and line input is active, then redirect the key press to the line input GtkEntry */
313 if( win->type == wintype_TextGrid && (win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) )
315 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
316 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down
317 || event->keyval == GDK_Left || event->keyval == GDK_KP_Left
318 || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
319 || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab
320 || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up
321 || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down
322 || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
323 || event->keyval == GDK_End || event->keyval == GDK_KP_End)
324 return FALSE; /* Don't redirect these keys */
325 gtk_widget_grab_focus(win->input_entry);
326 gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1);
327 gboolean retval = TRUE;
328 g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval);
329 return retval; /* Block this key event if the entry handled it */
331 if(win->input_request_type != INPUT_REQUEST_CHARACTER &&
332 win->input_request_type != INPUT_REQUEST_CHARACTER_UNICODE)
337 switch(event->keyval) {
339 case GDK_KP_Up: keycode = keycode_Up; break;
341 case GDK_KP_Down: keycode = keycode_Down; break;
343 case GDK_KP_Left: keycode = keycode_Left; break;
345 case GDK_KP_Right: keycode = keycode_Right; break;
348 case GDK_KP_Enter: keycode = keycode_Return; break;
351 case GDK_KP_Delete: keycode = keycode_Delete; break;
352 case GDK_Escape: keycode = keycode_Escape; break;
354 case GDK_KP_Tab: keycode = keycode_Tab; break;
356 case GDK_KP_Page_Up: keycode = keycode_PageUp; break;
358 case GDK_KP_Page_Down: keycode = keycode_PageDown; break;
360 case GDK_KP_Home: keycode = keycode_Home; break;
362 case GDK_KP_End: keycode = keycode_End; break;
364 case GDK_KP_F1: keycode = keycode_Func1; break;
366 case GDK_KP_F2: keycode = keycode_Func2; break;
368 case GDK_KP_F3: keycode = keycode_Func3; break;
370 case GDK_KP_F4: keycode = keycode_Func4; break;
371 case GDK_F5: keycode = keycode_Func5; break;
372 case GDK_F6: keycode = keycode_Func6; break;
373 case GDK_F7: keycode = keycode_Func7; break;
374 case GDK_F8: keycode = keycode_Func8; break;
375 case GDK_F9: keycode = keycode_Func9; break;
376 case GDK_F10: keycode = keycode_Func10; break;
377 case GDK_F11: keycode = keycode_Func11; break;
378 case GDK_F12: keycode = keycode_Func12; break;
380 keycode = gdk_keyval_to_unicode(event->keyval);
381 /* If keycode is 0, then keyval was not recognized; also return
382 unknown if Latin-1 input was requested and the character is not in
384 if(keycode == 0 || (win->input_request_type == INPUT_REQUEST_CHARACTER && keycode > 255))
385 keycode = keycode_Unknown;
388 event_throw(evtype_CharInput, win, keycode, 0);
390 /* Only one keypress will be handled */
391 win->input_request_type = INPUT_REQUEST_NONE;
392 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
397 /* Internal function: finish handling a line input request, for both text grid and text buffer windows. */
399 write_to_window_buffer(winid_t win, const gchar *inserted_text)
403 /* Convert the string from UTF-8 to Latin-1 or Unicode */
404 if(win->input_request_type == INPUT_REQUEST_LINE)
407 gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written);
412 /* Place input in the echo stream */
413 if(win->echo_stream != NULL)
414 glk_put_string_stream(win->echo_stream, latin1);
416 /* Copy the string (bytes_written does not include the NULL at the end) */
417 copycount = MIN(win->line_input_buffer_max_len, bytes_written);
418 memcpy(win->line_input_buffer, latin1, copycount);
421 else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
424 gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written);
429 /* Place input in the echo stream */
430 if(win->echo_stream != NULL)
431 glk_put_string_stream_uni(win->echo_stream, unicode);
433 /* Copy the string (but not the NULL at the end) */
434 copycount = MIN(win->line_input_buffer_max_len, items_written);
435 memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar));
439 WARNING("Wrong input request type");
441 win->input_request_type = INPUT_REQUEST_NONE;
445 /* Internal function: Retrieves the input of a TextBuffer window and stores it in the window buffer.
446 * Returns the number of characters written, suitable for inclusion in a line input event. */
448 flush_text_buffer(winid_t win)
450 VALID_WINDOW(win, return 0);
451 g_return_val_if_fail(win->type == wintype_TextBuffer, 0);
453 GtkTextIter start_iter, end_iter, last_character;
455 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
456 GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
457 gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position);
458 gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
459 gtk_text_buffer_get_end_iter(window_buffer, &last_character);
460 gtk_text_iter_backward_cursor_position(&last_character);
462 gchar* last_char = gtk_text_buffer_get_text(window_buffer, &last_character, &end_iter, FALSE);
464 if( strchr(last_char, '\n') != NULL )
465 gtk_text_iter_backward_cursor_position(&end_iter);
467 gchar* inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
469 int chars_written = write_to_window_buffer(win, inserted_text);
470 g_free(inserted_text);
472 return chars_written;
475 /* Internal function: Retrieves the input of a TextGrid window and stores it in the window buffer.
476 * Returns the number of characters written, suitable for inclusion in a line input event. */
478 flush_text_grid(winid_t win)
480 VALID_WINDOW(win, return 0);
481 g_return_val_if_fail(win->type == wintype_TextBuffer, 0);
483 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
485 gchar *text = g_strdup( gtk_entry_get_text(GTK_ENTRY(win->input_entry)) );
486 /* Move the focus back into the text view */
487 gtk_widget_grab_focus(win->widget);
488 /* Remove entry widget from text view */
489 /* Should be ok even though this is the widget's own signal handler */
490 gtk_container_remove( GTK_CONTAINER(win->widget), GTK_WIDGET(win->input_entry) );
491 win->input_entry = NULL;
492 /* Delete the child anchor */
493 GtkTextIter start, end;
494 gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor);
496 gtk_text_iter_forward_char(&end); /* Point after the child anchor */
497 gtk_text_buffer_delete(buffer, &start, &end);
498 win->input_anchor = NULL;
500 gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' ');
501 gchar *text_to_insert = g_strconcat(text, spaces, NULL);
503 gtk_text_buffer_insert(buffer, &start, text_to_insert, -1);
504 g_free(text_to_insert);
506 int chars_written = write_to_window_buffer(win, text);
509 return chars_written;
512 /* Internal function: Callback for signal insert-text on a text buffer window.
513 Runs after the default handler has already inserted the text.
514 FIXME: This function assumes that newline was the last character typed into the
515 window. That assumption is wrong if, for example, text containing a newline was
516 pasted into the window. */
518 after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win)
520 if( strchr(text, '\n') != NULL )
522 /* Remove signal handlers */
523 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
524 g_signal_handler_block(window_buffer, win->insert_text_handler);
526 /* Make the window uneditable again and retrieve the text that was input */
527 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
529 int chars_written = flush_text_buffer(win);
530 event_throw(evtype_LineInput, win, chars_written, 0);
534 /* Internal function: Callback for signal activate on the line input GtkEntry
535 in a text grid window. */
537 on_input_entry_activate(GtkEntry *input_entry, winid_t win)
539 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
541 int chars_written = flush_text_grid(win);
542 event_throw(evtype_LineInput, win, chars_written, 0);