5 /* Forward declarations */
6 static int flush_text_buffer(winid_t win);
7 static int flush_text_grid(winid_t win);
9 /* Internal function: code common to both flavors of char event request */
11 request_char_event_common(winid_t win, gboolean unicode)
13 VALID_WINDOW(win, return);
14 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
15 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
17 win->input_request_type = unicode? INPUT_REQUEST_CHARACTER_UNICODE : INPUT_REQUEST_CHARACTER;
18 g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
22 /* If the request is in a text buffer window, scroll to the end of the
23 text buffer. TODO: This may scroll text off the top of the window that the
24 user hasn't read yet. We need to implement a paging mechanism. */
25 if(win->type == wintype_TextBuffer)
27 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
29 gtk_text_buffer_get_end_iter(buffer, &iter);
30 gtk_text_buffer_place_cursor(buffer, &iter);
31 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), gtk_text_buffer_get_insert(buffer));
32 /* Why doesn't this always work?? */
35 gtk_widget_grab_focus( GTK_WIDGET(win->widget) );
40 * glk_request_char_event:
41 * @win: A window to request char events from.
43 * Request input of a Latin-1 character or special key. A window cannot have
44 * requests for both character and line input at the same time. Nor can it have
45 * requests for character input of both types (Latin-1 and Unicode). It is
46 * illegal to call glk_request_char_event() if the window already has a pending
47 * request for either character or line input.
50 glk_request_char_event(winid_t win)
52 request_char_event_common(win, FALSE);
56 * glk_request_char_event_uni:
57 * @win: A window to request char events from.
59 * Request input of a Unicode character or special key. See
60 * glk_request_char_event().
63 glk_request_char_event_uni(winid_t win)
65 request_char_event_common(win, TRUE);
69 * glk_cancel_char_event:
70 * @win: A window to cancel the latest char event request on.
72 * This cancels a pending request for character input. (Either Latin-1 or
73 * Unicode.) For convenience, it is legal to call glk_cancel_char_event() even
74 * if there is no charcter input request on that window. Glk will ignore the
78 glk_cancel_char_event(winid_t win)
80 VALID_WINDOW(win, return);
81 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
83 if(win->input_request_type == INPUT_REQUEST_CHARACTER || win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE)
85 win->input_request_type = INPUT_REQUEST_NONE;
86 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
90 /* Internal function: Request either latin-1 or unicode line input, in a text grid window. */
92 text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
96 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
98 GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
99 GtkTextIter start_iter, end_iter;
100 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, cursor);
102 /* Determine the maximum length of the line input */
103 gint cursorpos = gtk_text_iter_get_line_offset(&start_iter);
104 /* Odd; the Glk spec says the maximum input length is
105 windowwidth - 1 - cursorposition. I say no, because if cursorposition is
106 zero, then the input should fill the whole line. FIXME??? */
107 win->input_length = MIN(win->width - cursorpos, win->line_input_buffer_max_len);
108 end_iter = start_iter;
109 gtk_text_iter_set_line_offset(&end_iter, cursorpos + win->input_length);
111 /* If the buffer currently has a selection with one bound in the middle of
112 the input field, then deselect it. Otherwise the input field gets trashed */
113 GtkTextIter start_sel, end_sel;
114 if( gtk_text_buffer_get_selection_bounds(buffer, &start_sel, &end_sel) )
116 if( gtk_text_iter_in_range(&start_sel, &start_iter, &end_iter) )
117 gtk_text_buffer_place_cursor(buffer, &end_sel);
118 if( gtk_text_iter_in_range(&end_sel, &start_iter, &end_iter) )
119 gtk_text_buffer_place_cursor(buffer, &start_sel);
122 /* Erase the text currently in the input field and replace it with a GtkEntry */
123 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
124 win->input_anchor = gtk_text_buffer_create_child_anchor(buffer, &start_iter);
125 win->input_entry = gtk_entry_new();
126 /* Set the entry's font to match that of the window */
127 GtkRcStyle *style = gtk_widget_get_modifier_style(win->widget); /* Don't free */
128 gtk_widget_modify_font(win->input_entry, style->font_desc);
129 /* Make the entry as small as possible to fit with the text */
130 gtk_entry_set_has_frame(GTK_ENTRY(win->input_entry), FALSE);
131 GtkBorder border = { 0, 0, 0, 0 };
132 gtk_entry_set_inner_border(GTK_ENTRY(win->input_entry), &border);
133 gtk_entry_set_max_length(GTK_ENTRY(win->input_entry), win->input_length);
134 gtk_entry_set_width_chars(GTK_ENTRY(win->input_entry), win->input_length);
136 /* Insert pre-entered text if needed */
138 gtk_entry_set_text(GTK_ENTRY(win->input_entry), inserttext);
140 /* Set background color of entry (TODO: implement as property) */
142 gdk_color_parse("grey", &background);
143 gtk_widget_modify_base(win->input_entry, GTK_STATE_NORMAL, &background);
145 g_signal_connect(win->input_entry, "activate", G_CALLBACK(on_input_entry_activate), win);
147 gtk_widget_show(win->input_entry);
148 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(win->widget), win->input_entry, win->input_anchor);
150 g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
152 gtk_widget_grab_focus(win->input_entry);
157 /* Internal function: Request either latin-1 or unicode line input, in a text buffer window. */
159 text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
163 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
165 /* Move the input_position mark to the end of the window_buffer */
166 GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
167 GtkTextIter end_iter;
168 gtk_text_buffer_get_end_iter(buffer, &end_iter);
169 gtk_text_buffer_move_mark(buffer, input_position, &end_iter);
171 /* Set the entire contents of the window_buffer as uneditable
172 * (so input can only be entered at the end) */
173 GtkTextIter start_iter;
174 gtk_text_buffer_get_start_iter(buffer, &start_iter);
175 gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
176 gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
178 /* Insert pre-entered text if needed */
180 gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1);
182 /* Scroll to input point */
183 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position);
185 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
186 g_signal_handler_unblock(buffer, win->insert_text_handler);
188 gtk_widget_grab_focus(win->widget);
194 * glk_request_line_event:
195 * @win: A text buffer or text grid window to request line input on.
196 * @buf: A buffer of at least @maxlen bytes.
197 * @maxlen: Length of the buffer.
198 * @initlen: The number of characters in @buf to pre-enter.
200 * Requests input of a line of Latin-1 characters. A window cannot have requests
201 * for both character and line input at the same time. Nor can it have requests
202 * for line input of both types (Latin-1 and Unicode). It is illegal to call
203 * glk_request_line_event() if the window already has a pending request for
204 * either character or line input.
206 * The @buf argument is a pointer to space where the line input will be stored.
207 * (This may not be %NULL.) @maxlen is the length of this space, in bytes; the
208 * library will not accept more characters than this. If @initlen is nonzero,
209 * then the first @initlen bytes of @buf will be entered as pre-existing input
210 * — just as if the player had typed them himself. (The player can continue
211 * composing after this pre-entered input, or delete it or edit as usual.)
213 * The contents of the buffer are undefined until the input is completed (either
214 * by a line input event, or glk_cancel_line_event(). The library may or may not
215 * fill in the buffer as the player composes, while the input is still pending;
216 * it is illegal to change the contents of the buffer yourself.
219 glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen)
221 VALID_WINDOW(win, return);
222 g_return_if_fail(buf);
223 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
224 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
225 g_return_if_fail(initlen <= maxlen);
227 win->input_request_type = INPUT_REQUEST_LINE;
228 win->line_input_buffer = buf;
229 win->line_input_buffer_max_len = maxlen;
231 gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup("");
234 case wintype_TextBuffer:
235 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
237 case wintype_TextGrid:
238 text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
245 * glk_request_line_event_uni:
246 * @win: A text buffer or text grid window to request line input on.
247 * @buf: A buffer of at least @maxlen characters.
248 * @maxlen: Length of the buffer.
249 * @initlen: The number of characters in @buf to pre-enter.
251 * Request input of a line of Unicode characters. This works the same as
252 * glk_request_line_event(), except the result is stored in an array of
253 * <type>glui32</type> values instead of an array of characters, and the values
254 * may be any valid Unicode code points.
256 * The result will be in Unicode Normalization Form C. This basically means that
257 * composite characters will be single characters where possible, instead of
258 * sequences of base and combining marks. See
259 * <ulink url="http://www.unicode.org/reports/tr15/">Unicode Standard Annex #15
260 * </ulink> for the details.
263 glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen)
265 VALID_WINDOW(win, return);
266 g_return_if_fail(buf);
267 g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
268 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
269 g_return_if_fail(initlen <= maxlen);
271 win->input_request_type = INPUT_REQUEST_LINE_UNICODE;
272 win->line_input_buffer_unicode = buf;
273 win->line_input_buffer_max_len = maxlen;
277 utf8 = convert_ucs4_to_utf8(buf, initlen);
286 case wintype_TextBuffer:
287 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8);
289 case wintype_TextGrid:
290 text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8);
297 * glk_cancel_line_event:
298 * @win: A text buffer or text grid window to cancel line input on.
299 * @event: Will be filled in if the user had already input something.
301 * This cancels a pending request for line input. (Either Latin-1 or Unicode.)
303 * The event pointed to by the event argument will be filled in as if the
304 * player had hit <keycap>enter</keycap>, and the input composed so far will be stored in the
305 * buffer; see below. If you do not care about this information, pass %NULL as
306 * the @event argument. (The buffer will still be filled.)
308 * For convenience, it is legal to call glk_cancel_line_event() even if there
309 * is no line input request on that window. The event type will be set to
310 * %evtype_None in this case.
313 glk_cancel_line_event(winid_t win, event_t *event)
315 VALID_WINDOW(win, return);
316 g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
319 event->type = evtype_None;
325 if(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE)
328 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
330 int chars_written = 0;
332 if(win->type == wintype_TextGrid) {
333 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
334 chars_written = flush_text_grid(win);
335 } else if(win->type == wintype_TextBuffer) {
336 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
337 g_signal_handler_block(window_buffer, win->insert_text_handler);
338 chars_written = flush_text_buffer(win);
341 if(event != NULL && chars_written > 0) {
342 event->type = evtype_LineInput;
343 event->val1 = chars_written;
347 /* 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. */
349 on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
351 /* If this is a text grid window, and line input is active, then redirect the key press to the line input GtkEntry */
352 if( win->type == wintype_TextGrid && (win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) )
354 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
355 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down
356 || event->keyval == GDK_Left || event->keyval == GDK_KP_Left
357 || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
358 || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab
359 || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up
360 || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down
361 || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
362 || event->keyval == GDK_End || event->keyval == GDK_KP_End)
363 return FALSE; /* Don't redirect these keys */
364 gtk_widget_grab_focus(win->input_entry);
365 gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1);
366 gboolean retval = TRUE;
367 g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval);
368 return retval; /* Block this key event if the entry handled it */
370 if(win->input_request_type != INPUT_REQUEST_CHARACTER &&
371 win->input_request_type != INPUT_REQUEST_CHARACTER_UNICODE)
376 switch(event->keyval) {
378 case GDK_KP_Up: keycode = keycode_Up; break;
380 case GDK_KP_Down: keycode = keycode_Down; break;
382 case GDK_KP_Left: keycode = keycode_Left; break;
384 case GDK_KP_Right: keycode = keycode_Right; break;
387 case GDK_KP_Enter: keycode = keycode_Return; break;
390 case GDK_KP_Delete: keycode = keycode_Delete; break;
391 case GDK_Escape: keycode = keycode_Escape; break;
393 case GDK_KP_Tab: keycode = keycode_Tab; break;
395 case GDK_KP_Page_Up: keycode = keycode_PageUp; break;
397 case GDK_KP_Page_Down: keycode = keycode_PageDown; break;
399 case GDK_KP_Home: keycode = keycode_Home; break;
401 case GDK_KP_End: keycode = keycode_End; break;
403 case GDK_KP_F1: keycode = keycode_Func1; break;
405 case GDK_KP_F2: keycode = keycode_Func2; break;
407 case GDK_KP_F3: keycode = keycode_Func3; break;
409 case GDK_KP_F4: keycode = keycode_Func4; break;
410 case GDK_F5: keycode = keycode_Func5; break;
411 case GDK_F6: keycode = keycode_Func6; break;
412 case GDK_F7: keycode = keycode_Func7; break;
413 case GDK_F8: keycode = keycode_Func8; break;
414 case GDK_F9: keycode = keycode_Func9; break;
415 case GDK_F10: keycode = keycode_Func10; break;
416 case GDK_F11: keycode = keycode_Func11; break;
417 case GDK_F12: keycode = keycode_Func12; break;
419 keycode = gdk_keyval_to_unicode(event->keyval);
420 /* If keycode is 0, then keyval was not recognized; also return
421 unknown if Latin-1 input was requested and the character is not in
423 if(keycode == 0 || (win->input_request_type == INPUT_REQUEST_CHARACTER && keycode > 255))
424 keycode = keycode_Unknown;
427 event_throw(CHIMARA_GLK(gtk_widget_get_ancestor(widget, CHIMARA_TYPE_GLK)), evtype_CharInput, win, keycode, 0);
429 /* Only one keypress will be handled */
430 win->input_request_type = INPUT_REQUEST_NONE;
431 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
436 /* Internal function: finish handling a line input request, for both text grid and text buffer windows. */
438 write_to_window_buffer(winid_t win, const gchar *inserted_text)
442 /* Convert the string from UTF-8 to Latin-1 or Unicode */
443 if(win->input_request_type == INPUT_REQUEST_LINE)
446 gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written);
451 /* Place input in the echo stream */
452 if(win->echo_stream != NULL)
453 glk_put_string_stream(win->echo_stream, latin1);
455 /* Copy the string (bytes_written does not include the NULL at the end) */
456 copycount = MIN(win->line_input_buffer_max_len, bytes_written);
457 memcpy(win->line_input_buffer, latin1, copycount);
460 else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
463 gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written);
468 /* Place input in the echo stream */
469 if(win->echo_stream != NULL)
470 glk_put_string_stream_uni(win->echo_stream, unicode);
472 /* Copy the string (but not the NULL at the end) */
473 copycount = MIN(win->line_input_buffer_max_len, items_written);
474 memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar));
478 WARNING("Wrong input request type");
480 win->input_request_type = INPUT_REQUEST_NONE;
484 /* Internal function: Retrieves the input of a TextBuffer window and stores it in the window buffer.
485 * Returns the number of characters written, suitable for inclusion in a line input event. */
487 flush_text_buffer(winid_t win)
489 VALID_WINDOW(win, return 0);
490 g_return_val_if_fail(win->type == wintype_TextBuffer, 0);
492 GtkTextIter start_iter, end_iter, last_character;
494 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
495 GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
496 gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position);
497 gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
498 gtk_text_buffer_get_end_iter(window_buffer, &last_character);
499 gtk_text_iter_backward_cursor_position(&last_character);
501 gchar* last_char = gtk_text_buffer_get_text(window_buffer, &last_character, &end_iter, FALSE);
503 if( strchr(last_char, '\n') != NULL )
504 gtk_text_iter_backward_cursor_position(&end_iter);
506 gchar* inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
508 int chars_written = write_to_window_buffer(win, inserted_text);
509 g_free(inserted_text);
511 return chars_written;
514 /* Internal function: Retrieves the input of a TextGrid window and stores it in the window buffer.
515 * Returns the number of characters written, suitable for inclusion in a line input event. */
517 flush_text_grid(winid_t win)
519 VALID_WINDOW(win, return 0);
520 g_return_val_if_fail(win->type == wintype_TextGrid, 0);
522 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
524 gchar *text = g_strdup( gtk_entry_get_text(GTK_ENTRY(win->input_entry)) );
525 /* Move the focus back into the text view */
526 gtk_widget_grab_focus(win->widget);
527 /* Remove entry widget from text view */
528 /* Should be ok even though this is the widget's own signal handler */
529 gtk_container_remove( GTK_CONTAINER(win->widget), GTK_WIDGET(win->input_entry) );
530 win->input_entry = NULL;
531 /* Delete the child anchor */
532 GtkTextIter start, end;
533 gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor);
535 gtk_text_iter_forward_char(&end); /* Point after the child anchor */
536 gtk_text_buffer_delete(buffer, &start, &end);
537 win->input_anchor = NULL;
539 gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' ');
540 gchar *text_to_insert = g_strconcat(text, spaces, NULL);
542 gtk_text_buffer_insert(buffer, &start, text_to_insert, -1);
543 g_free(text_to_insert);
545 int chars_written = write_to_window_buffer(win, text);
548 return chars_written;
551 /* Internal function: Callback for signal insert-text on a text buffer window.
552 Runs after the default handler has already inserted the text.
553 FIXME: This function assumes that newline was the last character typed into the
554 window. That assumption is wrong if, for example, text containing a newline was
555 pasted into the window. */
557 after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win)
559 if( strchr(text, '\n') != NULL )
561 /* Remove signal handlers */
562 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
563 g_signal_handler_block(window_buffer, win->insert_text_handler);
565 /* Make the window uneditable again and retrieve the text that was input */
566 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
568 int chars_written = flush_text_buffer(win);
569 event_throw(CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK)), evtype_LineInput, win, chars_written, 0);
573 /* Internal function: Callback for signal activate on the line input GtkEntry
574 in a text grid window. */
576 on_input_entry_activate(GtkEntry *input_entry, winid_t win)
578 g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
580 int chars_written = flush_text_grid(win);
581 event_throw(CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK)), evtype_LineInput, win, chars_written, 0);