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)
74 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
76 GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
77 GtkTextIter start_iter, end_iter;
78 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, cursor);
80 /* Determine the maximum length of the line input */
81 gint cursorpos = gtk_text_iter_get_line_offset(&start_iter);
82 /* Odd; the Glk spec says the maximum input length is
83 windowwidth - 1 - cursorposition. I say no, because if cursorposition is
84 zero, then the input should fill the whole line. FIXME??? */
85 win->input_length = MIN(win->width - cursorpos, win->line_input_buffer_max_len);
86 end_iter = start_iter;
87 gtk_text_iter_set_line_offset(&end_iter, cursorpos + win->input_length);
89 /* If the buffer currently has a selection with one bound in the middle of
90 the input field, then deselect it. Otherwise the input field gets trashed */
91 GtkTextIter start_sel, end_sel;
92 if( gtk_text_buffer_get_selection_bounds(buffer, &start_sel, &end_sel) )
94 if( gtk_text_iter_in_range(&start_sel, &start_iter, &end_iter) )
95 gtk_text_buffer_place_cursor(buffer, &end_sel);
96 if( gtk_text_iter_in_range(&end_sel, &start_iter, &end_iter) )
97 gtk_text_buffer_place_cursor(buffer, &start_sel);
100 /* Erase the text currently in the input field and replace it with a GtkEntry */
101 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
102 win->input_anchor = gtk_text_buffer_create_child_anchor(buffer, &start_iter);
103 win->input_entry = gtk_entry_new();
104 /* Set the entry's font to match that of the window */
105 GtkRcStyle *style = gtk_widget_get_modifier_style(win->widget); /* Don't free */
106 gtk_widget_modify_font(win->input_entry, style->font_desc);
107 /* Make the entry as small as possible to fit with the text */
108 gtk_entry_set_has_frame(GTK_ENTRY(win->input_entry), FALSE);
109 GtkBorder border = { 0, 0, 0, 0 };
110 gtk_entry_set_inner_border(GTK_ENTRY(win->input_entry), &border);
111 gtk_entry_set_max_length(GTK_ENTRY(win->input_entry), win->input_length);
112 gtk_entry_set_width_chars(GTK_ENTRY(win->input_entry), win->input_length);
114 /* Insert pre-entered text if needed */
116 gtk_entry_set_text(GTK_ENTRY(win->input_entry), inserttext);
118 /* Set background color of entry (TODO: implement as property) */
120 gdk_color_parse("grey", &background);
121 gtk_widget_modify_base(win->input_entry, GTK_STATE_NORMAL, &background);
123 g_signal_connect(win->input_entry, "activate", G_CALLBACK(on_input_entry_activate), win);
125 gtk_widget_show(win->input_entry);
126 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(win->widget), win->input_entry, win->input_anchor);
128 g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
131 /* Internal function: Request either latin-1 or unicode line input, in a text buffer window. */
133 text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
135 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
137 /* Move the input_position mark to the end of the window_buffer */
138 GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
139 GtkTextIter end_iter;
140 gtk_text_buffer_get_end_iter(buffer, &end_iter);
141 gtk_text_buffer_move_mark(buffer, input_position, &end_iter);
143 /* Set the entire contents of the window_buffer as uneditable
144 * (so input can only be entered at the end) */
145 GtkTextIter start_iter;
146 gtk_text_buffer_get_start_iter(buffer, &start_iter);
147 gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
148 gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
150 /* Insert pre-entered text if needed */
152 gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1);
154 /* Scroll to input point */
155 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position);
157 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
158 g_signal_handler_unblock(buffer, win->insert_text_handler);
162 * glk_request_line_event:
163 * @win: A text buffer or text grid window to request line input on.
164 * @buf: A buffer of at least @maxlen bytes.
165 * @maxlen: Length of the buffer.
166 * @initlen: The number of characters in @buf to pre-enter.
168 * Requests input of a line of Latin-1 characters. A window cannot have requests
169 * for both character and line input at the same time. Nor can it have requests
170 * for line input of both types (Latin-1 and Unicode). It is illegal to call
171 * glk_request_line_event() if the window already has a pending request for
172 * either character or line input.
174 * The @buf argument is a pointer to space where the line input will be stored.
175 * (This may not be %NULL.) @maxlen is the length of this space, in bytes; the
176 * library will not accept more characters than this. If @initlen is nonzero,
177 * then the first @initlen bytes of @buf will be entered as pre-existing input
178 * — just as if the player had typed them himself. (The player can continue
179 * composing after this pre-entered input, or delete it or edit as usual.)
181 * The contents of the buffer are undefined until the input is completed (either
182 * by a line input event, or glk_cancel_line_event(). The library may or may not
183 * fill in the buffer as the player composes, while the input is still pending;
184 * it is illegal to change the contents of the buffer yourself.
187 glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen)
189 VALID_WINDOW(win, return);
190 g_return_if_fail(buf);
191 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
192 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
193 g_return_if_fail(initlen <= maxlen);
195 win->input_request_type = INPUT_REQUEST_LINE;
196 win->line_input_buffer = buf;
197 win->line_input_buffer_max_len = maxlen;
199 gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup("");
202 case wintype_TextBuffer:
203 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
205 case wintype_TextGrid:
206 text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
213 * glk_request_line_event_uni:
214 * @win: A text buffer or text grid window to request line input on.
215 * @buf: A buffer of at least @maxlen characters.
216 * @maxlen: Length of the buffer.
217 * @initlen: The number of characters in @buf to pre-enter.
219 * Request input of a line of Unicode characters. This works the same as
220 * glk_request_line_event(), except the result is stored in an array of
221 * <type>glui32</type> values instead of an array of characters, and the values
222 * may be any valid Unicode code points.
224 * The result will be in Unicode Normalization Form C. This basically means that
225 * composite characters will be single characters where possible, instead of
226 * sequences of base and combining marks. See
227 * <ulink url="http://www.unicode.org/reports/tr15/">Unicode Standard Annex #15
228 * </ulink> for the details.
231 glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen)
233 VALID_WINDOW(win, return);
234 g_return_if_fail(buf);
235 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
236 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
237 g_return_if_fail(initlen <= maxlen);
239 win->input_request_type = INPUT_REQUEST_LINE_UNICODE;
240 win->line_input_buffer_unicode = buf;
241 win->line_input_buffer_max_len = maxlen;
245 utf8 = convert_ucs4_to_utf8(buf, initlen);
254 case wintype_TextBuffer:
255 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8);
257 case wintype_TextGrid:
258 text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8);
265 * glk_cancel_line_event:
266 * @win: A text buffer or text grid window to cancel line input on.
267 * @event: Will be filled in if the user had already input something.
269 * This cancels a pending request for line input. (Either Latin-1 or Unicode.)
271 * The event pointed to by the event argument will be filled in as if the
272 * player had hit <keycap>enter</keycap>, and the input composed so far will be stored in the
273 * buffer; see below. If you do not care about this information, pass %NULL as
274 * the @event argument. (The buffer will still be filled.)
276 * For convenience, it is legal to call glk_cancel_line_event() even if there
277 * is no line input request on that window. The event type will be set to
278 * %evtype_None in this case.
281 glk_cancel_line_event(winid_t win, event_t *event)
283 VALID_WINDOW(win, return);
284 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
287 event->type = evtype_None;
293 if(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE)
296 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
298 int chars_written = 0;
300 if(win->type == wintype_TextGrid) {
301 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
302 chars_written = flush_text_grid(win);
303 } else if(win->type == wintype_TextBuffer) {
304 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
305 g_signal_handler_block(window_buffer, win->insert_text_handler);
306 chars_written = flush_text_buffer(win);
309 if(event != NULL && chars_written > 0) {
310 event->type = evtype_LineInput;
311 event->val1 = chars_written;
315 /* 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. */
317 on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
319 /* If this is a text grid window, and line input is active, then redirect the key press to the line input GtkEntry */
320 if( win->type == wintype_TextGrid && (win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) )
322 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
323 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down
324 || event->keyval == GDK_Left || event->keyval == GDK_KP_Left
325 || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
326 || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab
327 || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up
328 || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down
329 || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
330 || event->keyval == GDK_End || event->keyval == GDK_KP_End)
331 return FALSE; /* Don't redirect these keys */
332 gtk_widget_grab_focus(win->input_entry);
333 gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1);
334 gboolean retval = TRUE;
335 g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval);
336 return retval; /* Block this key event if the entry handled it */
338 if(win->input_request_type != INPUT_REQUEST_CHARACTER &&
339 win->input_request_type != INPUT_REQUEST_CHARACTER_UNICODE)
344 switch(event->keyval) {
346 case GDK_KP_Up: keycode = keycode_Up; break;
348 case GDK_KP_Down: keycode = keycode_Down; break;
350 case GDK_KP_Left: keycode = keycode_Left; break;
352 case GDK_KP_Right: keycode = keycode_Right; break;
355 case GDK_KP_Enter: keycode = keycode_Return; break;
358 case GDK_KP_Delete: keycode = keycode_Delete; break;
359 case GDK_Escape: keycode = keycode_Escape; break;
361 case GDK_KP_Tab: keycode = keycode_Tab; break;
363 case GDK_KP_Page_Up: keycode = keycode_PageUp; break;
365 case GDK_KP_Page_Down: keycode = keycode_PageDown; break;
367 case GDK_KP_Home: keycode = keycode_Home; break;
369 case GDK_KP_End: keycode = keycode_End; break;
371 case GDK_KP_F1: keycode = keycode_Func1; break;
373 case GDK_KP_F2: keycode = keycode_Func2; break;
375 case GDK_KP_F3: keycode = keycode_Func3; break;
377 case GDK_KP_F4: keycode = keycode_Func4; break;
378 case GDK_F5: keycode = keycode_Func5; break;
379 case GDK_F6: keycode = keycode_Func6; break;
380 case GDK_F7: keycode = keycode_Func7; break;
381 case GDK_F8: keycode = keycode_Func8; break;
382 case GDK_F9: keycode = keycode_Func9; break;
383 case GDK_F10: keycode = keycode_Func10; break;
384 case GDK_F11: keycode = keycode_Func11; break;
385 case GDK_F12: keycode = keycode_Func12; break;
387 keycode = gdk_keyval_to_unicode(event->keyval);
388 /* If keycode is 0, then keyval was not recognized; also return
389 unknown if Latin-1 input was requested and the character is not in
391 if(keycode == 0 || (win->input_request_type == INPUT_REQUEST_CHARACTER && keycode > 255))
392 keycode = keycode_Unknown;
395 event_throw(evtype_CharInput, win, keycode, 0);
397 /* Only one keypress will be handled */
398 win->input_request_type = INPUT_REQUEST_NONE;
399 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
404 /* Internal function: finish handling a line input request, for both text grid and text buffer windows. */
406 write_to_window_buffer(winid_t win, const gchar *inserted_text)
410 /* Convert the string from UTF-8 to Latin-1 or Unicode */
411 if(win->input_request_type == INPUT_REQUEST_LINE)
414 gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written);
419 /* Place input in the echo stream */
420 if(win->echo_stream != NULL)
421 glk_put_string_stream(win->echo_stream, latin1);
423 /* Copy the string (bytes_written does not include the NULL at the end) */
424 copycount = MIN(win->line_input_buffer_max_len, bytes_written);
425 memcpy(win->line_input_buffer, latin1, copycount);
428 else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
431 gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written);
436 /* Place input in the echo stream */
437 if(win->echo_stream != NULL)
438 glk_put_string_stream_uni(win->echo_stream, unicode);
440 /* Copy the string (but not the NULL at the end) */
441 copycount = MIN(win->line_input_buffer_max_len, items_written);
442 memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar));
446 WARNING("Wrong input request type");
448 win->input_request_type = INPUT_REQUEST_NONE;
452 /* Internal function: Retrieves the input of a TextBuffer window and stores it in the window buffer.
453 * Returns the number of characters written, suitable for inclusion in a line input event. */
455 flush_text_buffer(winid_t win)
457 VALID_WINDOW(win, return 0);
458 g_return_val_if_fail(win->type == wintype_TextBuffer, 0);
460 GtkTextIter start_iter, end_iter, last_character;
462 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
463 GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
464 gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position);
465 gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
466 gtk_text_buffer_get_end_iter(window_buffer, &last_character);
467 gtk_text_iter_backward_cursor_position(&last_character);
469 gchar* last_char = gtk_text_buffer_get_text(window_buffer, &last_character, &end_iter, FALSE);
471 if( strchr(last_char, '\n') != NULL )
472 gtk_text_iter_backward_cursor_position(&end_iter);
474 gchar* inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
476 int chars_written = write_to_window_buffer(win, inserted_text);
477 g_free(inserted_text);
479 return chars_written;
482 /* Internal function: Retrieves the input of a TextGrid window and stores it in the window buffer.
483 * Returns the number of characters written, suitable for inclusion in a line input event. */
485 flush_text_grid(winid_t win)
487 VALID_WINDOW(win, return 0);
488 g_return_val_if_fail(win->type == wintype_TextBuffer, 0);
490 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
492 gchar *text = g_strdup( gtk_entry_get_text(GTK_ENTRY(win->input_entry)) );
493 /* Move the focus back into the text view */
494 gtk_widget_grab_focus(win->widget);
495 /* Remove entry widget from text view */
496 /* Should be ok even though this is the widget's own signal handler */
497 gtk_container_remove( GTK_CONTAINER(win->widget), GTK_WIDGET(win->input_entry) );
498 win->input_entry = NULL;
499 /* Delete the child anchor */
500 GtkTextIter start, end;
501 gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor);
503 gtk_text_iter_forward_char(&end); /* Point after the child anchor */
504 gtk_text_buffer_delete(buffer, &start, &end);
505 win->input_anchor = NULL;
507 gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' ');
508 gchar *text_to_insert = g_strconcat(text, spaces, NULL);
510 gtk_text_buffer_insert(buffer, &start, text_to_insert, -1);
511 g_free(text_to_insert);
513 int chars_written = write_to_window_buffer(win, text);
516 return chars_written;
519 /* Internal function: Callback for signal insert-text on a text buffer window.
520 Runs after the default handler has already inserted the text.
521 FIXME: This function assumes that newline was the last character typed into the
522 window. That assumption is wrong if, for example, text containing a newline was
523 pasted into the window. */
525 after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win)
527 if( strchr(text, '\n') != NULL )
529 /* Remove signal handlers */
530 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
531 g_signal_handler_block(window_buffer, win->insert_text_handler);
533 /* Make the window uneditable again and retrieve the text that was input */
534 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
536 int chars_written = flush_text_buffer(win);
537 event_throw(evtype_LineInput, win, chars_written, 0);
541 /* Internal function: Callback for signal activate on the line input GtkEntry
542 in a text grid window. */
544 on_input_entry_activate(GtkEntry *input_entry, winid_t win)
546 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
548 int chars_written = flush_text_grid(win);
549 event_throw(evtype_LineInput, win, chars_written, 0);