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 );
44 /* Internal function: Request either latin-1 or unicode line input, in a text grid window. */
46 text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
48 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
50 GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
51 GtkTextIter start_iter, end_iter;
52 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, cursor);
54 /* Determine the maximum length of the line input */
55 gint cursorpos = gtk_text_iter_get_line_offset(&start_iter);
56 /* Odd; the Glk spec says the maximum input length is
57 windowwidth - 1 - cursorposition. I say no, because if cursorposition is
58 zero, then the input should fill the whole line. FIXME??? */
59 win->input_length = MIN(win->width - cursorpos, win->line_input_buffer_max_len);
60 end_iter = start_iter;
61 gtk_text_iter_set_line_offset(&end_iter, cursorpos + win->input_length);
63 /* If the buffer currently has a selection with one bound in the middle of
64 the input field, then deselect it. Otherwise the input field gets trashed */
65 GtkTextIter start_sel, end_sel;
66 if( gtk_text_buffer_get_selection_bounds(buffer, &start_sel, &end_sel) )
68 if( gtk_text_iter_in_range(&start_sel, &start_iter, &end_iter) )
69 gtk_text_buffer_place_cursor(buffer, &end_sel);
70 if( gtk_text_iter_in_range(&end_sel, &start_iter, &end_iter) )
71 gtk_text_buffer_place_cursor(buffer, &start_sel);
74 /* Erase the text currently in the input field and replace it with a GtkEntry */
75 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
76 win->input_anchor = gtk_text_buffer_create_child_anchor(buffer, &start_iter);
77 win->input_entry = gtk_entry_new();
78 /* Set the entry's font to match that of the window */
79 GtkRcStyle *style = gtk_widget_get_modifier_style(win->widget); /* Don't free */
80 gtk_widget_modify_font(win->input_entry, style->font_desc);
81 /* Make the entry as small as possible to fit with the text */
82 gtk_entry_set_has_frame(GTK_ENTRY(win->input_entry), FALSE);
83 GtkBorder border = { 0, 0, 0, 0 };
84 gtk_entry_set_inner_border(GTK_ENTRY(win->input_entry), &border);
85 gtk_entry_set_max_length(GTK_ENTRY(win->input_entry), win->input_length);
86 gtk_entry_set_width_chars(GTK_ENTRY(win->input_entry), win->input_length);
88 /* Insert pre-entered text if needed */
90 gtk_entry_set_text(GTK_ENTRY(win->input_entry), inserttext);
92 /* Set background color of entry (TODO: implement as property) */
94 gdk_color_parse("grey", &background);
95 gtk_widget_modify_base(win->input_entry, GTK_STATE_NORMAL, &background);
97 g_signal_connect(win->input_entry, "activate", G_CALLBACK(on_input_entry_activate), win);
99 gtk_widget_show(win->input_entry);
100 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(win->widget), win->input_entry, win->input_anchor);
102 g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
105 /* Internal function: Request either latin-1 or unicode line input, in a text buffer window. */
107 text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
109 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
111 /* Move the input_position mark to the end of the window_buffer */
112 GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
113 GtkTextIter end_iter;
114 gtk_text_buffer_get_end_iter(buffer, &end_iter);
115 gtk_text_buffer_move_mark(buffer, input_position, &end_iter);
117 /* Set the entire contents of the window_buffer as uneditable
118 * (so input can only be entered at the end) */
119 GtkTextIter start_iter;
120 gtk_text_buffer_get_start_iter(buffer, &start_iter);
121 gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
122 gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
124 /* Insert pre-entered text if needed */
126 gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1);
128 /* Scroll to input point */
129 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position);
131 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
132 g_signal_handler_unblock(buffer, win->insert_text_handler);
136 * glk_request_line_event:
137 * @win: A text buffer or text grid window to request line input on.
138 * @buf: A buffer of at least @maxlen bytes.
139 * @maxlen: Length of the buffer.
140 * @initlen: The number of characters in @buf to pre-enter.
142 * Requests input of a line of Latin-1 characters. A window cannot have requests
143 * for both character and line input at the same time. Nor can it have requests
144 * for line input of both types (Latin-1 and Unicode). It is illegal to call
145 * glk_request_line_event() if the window already has a pending request for
146 * either character or line input.
148 * The @buf argument is a pointer to space where the line input will be stored.
149 * (This may not be %NULL.) @maxlen is the length of this space, in bytes; the
150 * library will not accept more characters than this. If @initlen is nonzero,
151 * then the first @initlen bytes of @buf will be entered as pre-existing input
152 * -- just as if the player had typed them himself. (The player can continue
153 * composing after this pre-entered input, or delete it or edit as usual.)
155 * The contents of the buffer are undefined until the input is completed (either
156 * by a line input event, or glk_cancel_line_event(). The library may or may not
157 * fill in the buffer as the player composes, while the input is still pending;
158 * it is illegal to change the contents of the buffer yourself.
161 glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen)
163 VALID_WINDOW(win, return);
164 g_return_if_fail(buf);
165 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
166 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
167 g_return_if_fail(initlen <= maxlen);
169 win->input_request_type = INPUT_REQUEST_LINE;
170 win->line_input_buffer = buf;
171 win->line_input_buffer_max_len = maxlen;
173 gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup("");
176 case wintype_TextBuffer:
177 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
179 case wintype_TextGrid:
180 text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
187 * glk_request_line_event_uni:
188 * @win: A text buffer or text grid window to request line input on.
189 * @buf: A buffer of at least @maxlen characters.
190 * @maxlen: Length of the buffer.
191 * @initlen: The number of characters in @buf to pre-enter.
193 * Request input of a line of Unicode characters. This works the same as
194 * glk_request_line_event(), except the result is stored in an array of
195 * <type>glui32</type> values instead of an array of characters, and the values
196 * may be any valid Unicode code points.
198 * The result will be in Unicode Normalization Form C. This basically means that
199 * composite characters will be single characters where possible, instead of
200 * sequences of base and combining marks. See
201 * <ulink url="http://www.unicode.org/reports/tr15/">Unicode Standard Annex #15
202 * </ulink> for the details.
205 glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen)
207 VALID_WINDOW(win, return);
208 g_return_if_fail(buf);
209 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
210 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
211 g_return_if_fail(initlen <= maxlen);
213 win->input_request_type = INPUT_REQUEST_LINE_UNICODE;
214 win->line_input_buffer_unicode = buf;
215 win->line_input_buffer_max_len = maxlen;
219 utf8 = convert_ucs4_to_utf8(buf, initlen);
228 case wintype_TextBuffer:
229 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8);
231 case wintype_TextGrid:
232 text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8);
238 /* 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. */
240 on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
242 /* If this is a text grid window, and line input is active, then redirect the key press to the line input GtkEntry */
243 if( win->type == wintype_TextGrid && (win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) )
245 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
246 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down
247 || event->keyval == GDK_Left || event->keyval == GDK_KP_Left
248 || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
249 || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab
250 || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up
251 || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down
252 || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
253 || event->keyval == GDK_End || event->keyval == GDK_KP_End)
254 return FALSE; /* Don't redirect these keys */
255 gtk_widget_grab_focus(win->input_entry);
256 gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1);
257 gboolean retval = TRUE;
258 g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval);
259 return retval; /* Block this key event if the entry handled it */
261 if(win->input_request_type != INPUT_REQUEST_CHARACTER &&
262 win->input_request_type != INPUT_REQUEST_CHARACTER_UNICODE)
267 switch(event->keyval) {
269 case GDK_KP_Up: keycode = keycode_Up; break;
271 case GDK_KP_Down: keycode = keycode_Down; break;
273 case GDK_KP_Left: keycode = keycode_Left; break;
275 case GDK_KP_Right: keycode = keycode_Right; break;
278 case GDK_KP_Enter: keycode = keycode_Return; break;
281 case GDK_KP_Delete: keycode = keycode_Delete; break;
282 case GDK_Escape: keycode = keycode_Escape; break;
284 case GDK_KP_Tab: keycode = keycode_Tab; break;
286 case GDK_KP_Page_Up: keycode = keycode_PageUp; break;
288 case GDK_KP_Page_Down: keycode = keycode_PageDown; break;
290 case GDK_KP_Home: keycode = keycode_Home; break;
292 case GDK_KP_End: keycode = keycode_End; break;
294 case GDK_KP_F1: keycode = keycode_Func1; break;
296 case GDK_KP_F2: keycode = keycode_Func2; break;
298 case GDK_KP_F3: keycode = keycode_Func3; break;
300 case GDK_KP_F4: keycode = keycode_Func4; break;
301 case GDK_F5: keycode = keycode_Func5; break;
302 case GDK_F6: keycode = keycode_Func6; break;
303 case GDK_F7: keycode = keycode_Func7; break;
304 case GDK_F8: keycode = keycode_Func8; break;
305 case GDK_F9: keycode = keycode_Func9; break;
306 case GDK_F10: keycode = keycode_Func10; break;
307 case GDK_F11: keycode = keycode_Func11; break;
308 case GDK_F12: keycode = keycode_Func12; break;
310 keycode = gdk_keyval_to_unicode(event->keyval);
311 /* If keycode is 0, then keyval was not recognized; also return
312 unknown if Latin-1 input was requested and the character is not in
314 if(keycode == 0 || (win->input_request_type == INPUT_REQUEST_CHARACTER && keycode > 255))
315 keycode = keycode_Unknown;
318 event_throw(evtype_CharInput, win, keycode, 0);
320 /* Only one keypress will be handled */
321 win->input_request_type = INPUT_REQUEST_NONE;
322 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
327 /* Internal function: finish handling a line input request, for both text grid and text buffer windows. */
329 end_line_input_request(winid_t win, const gchar *inserted_text)
331 /* Convert the string from UTF-8 to Latin-1 or Unicode */
332 if(win->input_request_type == INPUT_REQUEST_LINE)
335 gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written);
339 event_throw(evtype_LineInput, win, 0, 0);
343 /* Place input in the echo stream */
344 if(win->echo_stream != NULL)
345 glk_put_string_stream(win->echo_stream, latin1);
347 /* Copy the string (bytes_written does not include the NULL at the end) */
348 int copycount = MIN(win->line_input_buffer_max_len, bytes_written);
349 memcpy(win->line_input_buffer, latin1, copycount);
351 event_throw(evtype_LineInput, win, copycount, 0);
353 else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
356 gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written);
360 event_throw(evtype_LineInput, win, 0, 0);
364 /* Place input in the echo stream */
365 if(win->echo_stream != NULL)
366 glk_put_string_stream_uni(win->echo_stream, unicode);
368 /* Copy the string (but not the NULL at the end) */
369 int copycount = MIN(win->line_input_buffer_max_len, items_written);
370 memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar));
372 event_throw(evtype_LineInput, win, copycount, 0);
375 WARNING("Wrong input request type");
377 win->input_request_type = INPUT_REQUEST_NONE;
380 /* Internal function: Callback for signal insert-text on a text buffer window.
381 Runs after the default handler has already inserted the text.
382 FIXME: This function assumes that newline was the last character typed into the
383 window. That assumption is wrong if, for example, text containing a newline was
384 pasted into the window. */
386 after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win)
388 if( strchr(text, '\n') != NULL )
390 /* Remove signal handlers */
391 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
392 g_signal_handler_block(window_buffer, win->insert_text_handler);
394 /* Make the window uneditable again and retrieve the text that was input */
395 gchar *inserted_text;
396 GtkTextIter start_iter, end_iter;
398 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
399 GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
400 gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position);
401 gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
402 gtk_text_iter_backward_cursor_position(&end_iter); /* don't include \n */
404 inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
406 end_line_input_request(win, inserted_text);
407 g_free(inserted_text);
411 /* Internal function: Callback for signal activate on the line input GtkEntry
412 in a text grid window. */
414 on_input_entry_activate(GtkEntry *input_entry, winid_t win)
416 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
418 gchar *text = g_strdup(gtk_entry_get_text(input_entry));
419 /* Move the focus back into the text view */
420 gtk_widget_grab_focus(win->widget);
421 /* Remove entry widget from text view */
422 /* Should be ok even though this is the widget's own signal handler */
423 gtk_container_remove( GTK_CONTAINER(win->widget), GTK_WIDGET(input_entry) );
424 win->input_entry = NULL;
425 /* Delete the child anchor */
426 GtkTextIter start, end;
427 gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor);
429 gtk_text_iter_forward_char(&end); /* Point after the child anchor */
430 gtk_text_buffer_delete(buffer, &start, &end);
431 win->input_anchor = NULL;
433 gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' ');
434 gchar *text_to_insert = g_strconcat(text, spaces, NULL);
436 gtk_text_buffer_insert(buffer, &start, text_to_insert, -1);
437 g_free(text_to_insert);
439 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
441 end_line_input_request(win, text);