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
175 * continue composing after this pre-entered input, or delete it or edit as
178 * The contents of the buffer are undefined until the input is completed (either
179 * by a line input event, or glk_cancel_line_event(). The library may or may not
180 * fill in the buffer as the player composes, while the input is still pending;
181 * it is illegal to change the contents of the buffer yourself.
184 glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen)
186 VALID_WINDOW(win, return);
187 g_return_if_fail(buf);
188 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
189 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
190 g_return_if_fail(initlen <= maxlen);
192 win->input_request_type = INPUT_REQUEST_LINE;
193 win->line_input_buffer = buf;
194 win->line_input_buffer_max_len = maxlen;
196 gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup("");
199 case wintype_TextBuffer:
200 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
202 case wintype_TextGrid:
203 text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
210 * glk_request_line_event_uni:
211 * @win: A text buffer or text grid window to request line input on.
212 * @buf: A buffer of at least @maxlen characters.
213 * @maxlen: Length of the buffer.
214 * @initlen: The number of characters in @buf to pre-enter.
216 * Request input of a line of Unicode characters. This works the same as
217 * glk_request_line_event(), except the result is stored in an array of
218 * <type>glui32</type> values instead of an array of characters, and the values
219 * may be any valid Unicode code points.
221 * The result will be in Unicode Normalization Form C. This basically means that
222 * composite characters will be single characters where possible, instead of
223 * sequences of base and combining marks. See
224 * <ulink url="http://www.unicode.org/reports/tr15/">Unicode Standard Annex #15
225 * </ulink> for the details.
228 glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen)
230 VALID_WINDOW(win, return);
231 g_return_if_fail(buf);
232 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
233 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
234 g_return_if_fail(initlen <= maxlen);
236 win->input_request_type = INPUT_REQUEST_LINE_UNICODE;
237 win->line_input_buffer_unicode = buf;
238 win->line_input_buffer_max_len = maxlen;
242 utf8 = convert_ucs4_to_utf8(buf, initlen);
251 case wintype_TextBuffer:
252 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8);
254 case wintype_TextGrid:
255 text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8);
261 /* Internal function: cancel a line input request, for both text grid and text buffer windows. */
263 cancel_line_input_request(winid_t win, const gchar *inserted_text, event_t *event)
267 event->type = evtype_LineInput;
269 event->val1 = event->val2 = 0;
272 /* Convert the string from UTF-8 to Latin-1 or Unicode */
273 if(win->input_request_type == INPUT_REQUEST_LINE)
275 win->input_request_type = INPUT_REQUEST_NONE;
278 gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written);
283 /* Place input in the echo stream */
284 if(win->echo_stream != NULL)
285 glk_put_string_stream(win->echo_stream, latin1);
287 /* Copy the string (bytes_written does not include the NULL at the end) */
288 int copycount = MIN(win->line_input_buffer_max_len, bytes_written);
289 memcpy(win->line_input_buffer, latin1, copycount);
292 event->val1 = copycount;
294 else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
297 gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written);
302 /* Place input in the echo stream */
303 if(win->echo_stream != NULL)
304 glk_put_string_stream_uni(win->echo_stream, unicode);
306 /* Copy the string (but not the NULL at the end) */
307 int copycount = MIN(win->line_input_buffer_max_len, items_written);
308 memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar));
311 event->val1 = copycount;
314 WARNING("Wrong input request type");
318 * glk_cancel_line_event:
319 * @win: A text buffer or text grid window to cancel line input on.
320 * @event: Will be filled in if the user had already input something.
322 * This cancels a pending request for line input. (Either Latin-1 or Unicode.)
324 * The event pointed to by the @event argument will be filled in as if the
325 * player had hit <keycap>enter</keycap>, and the input composed so far will be
326 * stored in the buffer; see below. If you do not care about this information,
327 * pass %NULL as the @event argument. (The buffer will still be filled.)
329 * For convenience, it is legal to call glk_cancel_line_event() even if there
330 * is no line input request on that window. The event type will be set to
331 * #evtype_None in this case.
334 glk_cancel_line_event(winid_t win, event_t *event)
336 VALID_WINDOW(win, return);
337 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
339 if(win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
341 if(win->type == wintype_TextBuffer)
343 /* Remove signal handlers */
344 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
345 g_signal_handler_block(window_buffer, win->insert_text_handler);
347 /* Make the window uneditable again and retrieve the text that was input */
348 gchar *inserted_text;
349 GtkTextIter start_iter, end_iter;
351 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
352 GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
353 gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position);
354 gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
355 gtk_text_iter_backward_cursor_position(&end_iter); /* don't include \n */
357 inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
359 cancel_line_input_request(win, inserted_text, event);
360 g_free(inserted_text);
362 else if(win->type == wintype_TextGrid)
364 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
366 gchar *text = g_strdup( gtk_entry_get_text( GTK_ENTRY(win->input_entry) ) );
367 /* Move the focus back into the text view */
368 gtk_widget_grab_focus(win->widget);
369 /* Remove entry widget from text view */
370 gtk_container_remove(GTK_CONTAINER(win->widget), win->input_entry);
371 win->input_entry = NULL;
372 /* Delete the child anchor */
373 GtkTextIter start, end;
374 gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor);
376 gtk_text_iter_forward_char(&end); /* Point after the child anchor */
377 gtk_text_buffer_delete(buffer, &start, &end);
378 win->input_anchor = NULL;
380 gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' ');
381 gchar *text_to_insert = g_strconcat(text, spaces, NULL);
383 gtk_text_buffer_insert(buffer, &start, text_to_insert, -1);
384 g_free(text_to_insert);
386 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
388 cancel_line_input_request(win, text, event);
394 event->type = evtype_None;
397 /* 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. */
399 on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
401 /* If this is a text grid window, and line input is active, then redirect the key press to the line input GtkEntry */
402 if( win->type == wintype_TextGrid && (win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) )
404 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
405 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down
406 || event->keyval == GDK_Left || event->keyval == GDK_KP_Left
407 || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
408 || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab
409 || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up
410 || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down
411 || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
412 || event->keyval == GDK_End || event->keyval == GDK_KP_End)
413 return FALSE; /* Don't redirect these keys */
414 gtk_widget_grab_focus(win->input_entry);
415 gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1);
416 gboolean retval = TRUE;
417 g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval);
418 return retval; /* Block this key event if the entry handled it */
420 if(win->input_request_type != INPUT_REQUEST_CHARACTER &&
421 win->input_request_type != INPUT_REQUEST_CHARACTER_UNICODE)
426 switch(event->keyval) {
428 case GDK_KP_Up: keycode = keycode_Up; break;
430 case GDK_KP_Down: keycode = keycode_Down; break;
432 case GDK_KP_Left: keycode = keycode_Left; break;
434 case GDK_KP_Right: keycode = keycode_Right; break;
437 case GDK_KP_Enter: keycode = keycode_Return; break;
440 case GDK_KP_Delete: keycode = keycode_Delete; break;
441 case GDK_Escape: keycode = keycode_Escape; break;
443 case GDK_KP_Tab: keycode = keycode_Tab; break;
445 case GDK_KP_Page_Up: keycode = keycode_PageUp; break;
447 case GDK_KP_Page_Down: keycode = keycode_PageDown; break;
449 case GDK_KP_Home: keycode = keycode_Home; break;
451 case GDK_KP_End: keycode = keycode_End; break;
453 case GDK_KP_F1: keycode = keycode_Func1; break;
455 case GDK_KP_F2: keycode = keycode_Func2; break;
457 case GDK_KP_F3: keycode = keycode_Func3; break;
459 case GDK_KP_F4: keycode = keycode_Func4; break;
460 case GDK_F5: keycode = keycode_Func5; break;
461 case GDK_F6: keycode = keycode_Func6; break;
462 case GDK_F7: keycode = keycode_Func7; break;
463 case GDK_F8: keycode = keycode_Func8; break;
464 case GDK_F9: keycode = keycode_Func9; break;
465 case GDK_F10: keycode = keycode_Func10; break;
466 case GDK_F11: keycode = keycode_Func11; break;
467 case GDK_F12: keycode = keycode_Func12; break;
469 keycode = gdk_keyval_to_unicode(event->keyval);
470 /* If keycode is 0, then keyval was not recognized; also return
471 unknown if Latin-1 input was requested and the character is not in
473 if(keycode == 0 || (win->input_request_type == INPUT_REQUEST_CHARACTER && keycode > 255))
474 keycode = keycode_Unknown;
477 event_throw(evtype_CharInput, win, keycode, 0);
479 /* Only one keypress will be handled */
480 win->input_request_type = INPUT_REQUEST_NONE;
481 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
486 /* Internal function: finish handling a line input request, for both text grid and text buffer windows. */
488 end_line_input_request(winid_t win, const gchar *inserted_text)
490 /* Convert the string from UTF-8 to Latin-1 or Unicode */
491 if(win->input_request_type == INPUT_REQUEST_LINE)
493 win->input_request_type = INPUT_REQUEST_NONE;
496 gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written);
500 event_throw(evtype_LineInput, win, 0, 0);
504 /* Place input in the echo stream */
505 if(win->echo_stream != NULL)
506 glk_put_string_stream(win->echo_stream, latin1);
508 /* Copy the string (bytes_written does not include the NULL at the end) */
509 int copycount = MIN(win->line_input_buffer_max_len, bytes_written);
510 memcpy(win->line_input_buffer, latin1, copycount);
512 event_throw(evtype_LineInput, win, copycount, 0);
514 else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
516 win->input_request_type = INPUT_REQUEST_NONE;
519 gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written);
523 event_throw(evtype_LineInput, win, 0, 0);
527 /* Place input in the echo stream */
528 if(win->echo_stream != NULL)
529 glk_put_string_stream_uni(win->echo_stream, unicode);
531 /* Copy the string (but not the NULL at the end) */
532 int copycount = MIN(win->line_input_buffer_max_len, items_written);
533 memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar));
535 event_throw(evtype_LineInput, win, copycount, 0);
538 WARNING("Wrong input request type");
541 /* Internal function: Callback for signal insert-text on a text buffer window.
542 Runs after the default handler has already inserted the text.
543 FIXME: This function assumes that newline was the last character typed into the
544 window. That assumption is wrong if, for example, text containing a newline was
545 pasted into the window. */
547 after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win)
549 if( strchr(text, '\n') != NULL )
551 /* Remove signal handlers */
552 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
553 g_signal_handler_block(window_buffer, win->insert_text_handler);
555 /* Make the window uneditable again and retrieve the text that was input */
556 gchar *inserted_text;
557 GtkTextIter start_iter, end_iter;
559 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
560 GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
561 gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position);
562 gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
563 gtk_text_iter_backward_cursor_position(&end_iter); /* don't include \n */
565 inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
567 end_line_input_request(win, inserted_text);
568 g_free(inserted_text);
572 /* Internal function: Callback for signal activate on the line input GtkEntry
573 in a text grid window. */
575 on_input_entry_activate(GtkEntry *input_entry, winid_t win)
577 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
579 gchar *text = g_strdup(gtk_entry_get_text(input_entry));
580 /* Move the focus back into the text view */
581 gtk_widget_grab_focus(win->widget);
582 /* Remove entry widget from text view */
583 /* Should be ok even though this is the widget's own signal handler */
584 gtk_container_remove( GTK_CONTAINER(win->widget), GTK_WIDGET(input_entry) );
585 win->input_entry = NULL;
586 /* Delete the child anchor */
587 GtkTextIter start, end;
588 gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor);
590 gtk_text_iter_forward_char(&end); /* Point after the child anchor */
591 gtk_text_buffer_delete(buffer, &start, &end);
592 win->input_anchor = NULL;
594 gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' ');
595 gchar *text_to_insert = g_strconcat(text, spaces, NULL);
597 gtk_text_buffer_insert(buffer, &start, text_to_insert, -1);
598 g_free(text_to_insert);
600 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
602 end_line_input_request(win, text);