Fixed reversevideo
[rodin/chimara.git] / libchimara / input.c
1 #include "charset.h"
2 #include "magic.h"
3 #include "input.h"
4 #include "chimara-glk-private.h"
5
6 extern GPrivate *glk_data_key;
7
8 /* Forward declarations */
9 static int finish_text_buffer_line_input(winid_t win, gboolean emit_signal);
10 static int finish_text_grid_line_input(winid_t win, gboolean emit_signal);
11
12 /* Internal function: code common to both flavors of char event request */
13 void
14 request_char_event_common(winid_t win, gboolean unicode)
15 {
16         VALID_WINDOW(win, return);
17         g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
18         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
19
20         flush_window_buffer(win);
21         
22         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
23
24         win->input_request_type = unicode? INPUT_REQUEST_CHARACTER_UNICODE : INPUT_REQUEST_CHARACTER;
25         g_signal_handler_unblock( G_OBJECT(win->widget), win->char_input_keypress_handler );
26
27         gdk_threads_enter();
28         
29         /* If the request is in a text buffer window, scroll to the end of the
30          text buffer. TODO: This may scroll text off the top of the window that the
31          user hasn't read yet. We need to implement a paging mechanism. */
32         if(win->type == wintype_TextBuffer)
33         {
34                 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
35                 GtkTextIter iter;
36                 gtk_text_buffer_get_end_iter(buffer, &iter);
37                 gtk_text_buffer_place_cursor(buffer, &iter);
38                 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), gtk_text_buffer_get_insert(buffer));
39                 /* Why doesn't this always work?? */
40         }
41         
42         gtk_widget_grab_focus( GTK_WIDGET(win->widget) );
43         gdk_threads_leave();
44         
45         /* Emit the "waiting" signal to let listeners know we are ready for input */
46         g_signal_emit_by_name(glk_data->self, "waiting");
47 }
48
49 /** 
50  * glk_request_char_event:
51  * @win: A window to request char events from.
52  *
53  * Request input of a Latin-1 character or special key. A window cannot have 
54  * requests for both character and line input at the same time. Nor can it have
55  * requests for character input of both types (Latin-1 and Unicode). It is
56  * illegal to call glk_request_char_event() if the window already has a pending
57  * request for either character or line input. 
58  */
59 void
60 glk_request_char_event(winid_t win)
61 {
62         request_char_event_common(win, FALSE);
63 }
64
65 /** 
66  * glk_request_char_event_uni:
67  * @win: A window to request char events from.
68  *
69  * Request input of a Unicode character or special key. See 
70  * glk_request_char_event().
71  */
72 void
73 glk_request_char_event_uni(winid_t win)
74 {
75         request_char_event_common(win, TRUE);
76 }
77
78 /**
79  * glk_cancel_char_event:
80  * @win: A window to cancel the latest char event request on.
81  *
82  * This cancels a pending request for character input. (Either Latin-1 or
83  * Unicode.) For convenience, it is legal to call glk_cancel_char_event() even
84  * if there is no charcter input request on that window. Glk will ignore the
85  * call in this case.
86  */
87 void
88 glk_cancel_char_event(winid_t win)
89 {
90         VALID_WINDOW(win, return);
91         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
92         
93         if(win->input_request_type == INPUT_REQUEST_CHARACTER || win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE)
94         {
95                 win->input_request_type = INPUT_REQUEST_NONE;
96                 g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
97         }
98 }
99
100 /* Internal function: Request either latin-1 or unicode line input, in a text grid window. */
101 static void
102 text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
103 {
104         gdk_threads_enter();
105         
106         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
107
108     GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
109     GtkTextIter start_iter, end_iter;
110     gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, cursor);
111     
112     /* Determine the maximum length of the line input */
113     gint cursorpos = gtk_text_iter_get_line_offset(&start_iter);
114     /* Odd; the Glk spec says the maximum input length is
115     windowwidth - 1 - cursorposition. I say no, because if cursorposition is
116     zero, then the input should fill the whole line. FIXME??? */
117     win->input_length = MIN(win->width - cursorpos, win->line_input_buffer_max_len);
118     end_iter = start_iter;
119     gtk_text_iter_set_line_offset(&end_iter, cursorpos + win->input_length);
120     
121         /* If the buffer currently has a selection with one bound in the middle of
122         the input field, then deselect it. Otherwise the input field gets trashed */
123         GtkTextIter start_sel, end_sel;
124         if( gtk_text_buffer_get_selection_bounds(buffer, &start_sel, &end_sel) )
125         {
126                 if( gtk_text_iter_in_range(&start_sel, &start_iter, &end_iter) )
127                         gtk_text_buffer_place_cursor(buffer, &end_sel);
128                 if( gtk_text_iter_in_range(&end_sel, &start_iter, &end_iter) )
129                         gtk_text_buffer_place_cursor(buffer, &start_sel);
130         }
131         
132     /* Erase the text currently in the input field and replace it with a GtkEntry */
133     gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
134     win->input_anchor = gtk_text_buffer_create_child_anchor(buffer, &start_iter);
135     win->input_entry = gtk_entry_new();
136         /* Set the entry's font to match that of the window */
137     GtkRcStyle *style = gtk_widget_get_modifier_style(win->widget);     /* Don't free */
138         gtk_widget_modify_font(win->input_entry, style->font_desc);
139         /* Make the entry as small as possible to fit with the text */
140         gtk_entry_set_has_frame(GTK_ENTRY(win->input_entry), FALSE);
141         GtkBorder border = { 0, 0, 0, 0 };
142         gtk_entry_set_inner_border(GTK_ENTRY(win->input_entry), &border);
143     gtk_entry_set_max_length(GTK_ENTRY(win->input_entry), win->input_length);
144     gtk_entry_set_width_chars(GTK_ENTRY(win->input_entry), win->input_length);
145     
146     /* Insert pre-entered text if needed */
147     if(insert)
148         gtk_entry_set_text(GTK_ENTRY(win->input_entry), inserttext);
149     
150     /* Set background color of entry (TODO: implement as property) */
151     GdkColor background;
152         gdk_color_parse("grey", &background);
153     gtk_widget_modify_base(win->input_entry, GTK_STATE_NORMAL, &background);
154     
155     g_signal_connect(win->input_entry, "activate", G_CALLBACK(on_input_entry_activate), win);
156     g_signal_connect(win->input_entry, "key-press-event", G_CALLBACK(on_input_entry_key_press_event), win);
157     win->line_input_entry_changed = g_signal_connect(win->input_entry, "changed", G_CALLBACK(on_input_entry_changed), win);
158     
159     gtk_widget_show(win->input_entry);
160     gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(win->widget), win->input_entry, win->input_anchor);
161         
162         g_signal_handler_unblock( G_OBJECT(win->widget), win->char_input_keypress_handler );
163
164         gtk_widget_grab_focus(win->input_entry);
165         
166         gdk_threads_leave();
167 }
168     
169 /* Internal function: Request either latin-1 or unicode line input, in a text buffer window. */
170 static void
171 text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
172 {
173         flush_window_buffer(win);
174
175         gdk_threads_enter();
176         
177         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
178
179     /* Move the input_position mark to the end of the window_buffer */
180     GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
181     GtkTextIter end_iter;
182     gtk_text_buffer_get_end_iter(buffer, &end_iter);
183     gtk_text_buffer_move_mark(buffer, input_position, &end_iter);
184
185     /* Set the entire contents of the window_buffer as uneditable
186      * (so input can only be entered at the end) */
187     GtkTextIter start_iter;
188     gtk_text_buffer_get_start_iter(buffer, &start_iter);
189     gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
190     gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
191     
192     /* Insert pre-entered text if needed */
193     if(insert) {
194         gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1);
195                 gtk_text_buffer_get_end_iter(buffer, &end_iter); /* update after text insertion */
196         }
197     
198     /* Scroll to input point */
199     gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position);
200
201         /* Apply the correct style to the input prompt */
202         GtkTextIter input_iter;
203     gtk_text_buffer_get_iter_at_mark(buffer, &input_iter, input_position);
204     gtk_text_buffer_apply_tag_by_name(buffer, "input", &input_iter, &end_iter);
205     
206     gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
207     g_signal_handler_unblock(buffer, win->insert_text_handler);
208
209         gtk_widget_grab_focus(win->widget);
210         
211         gdk_threads_leave();
212 }
213
214 /**
215  * glk_request_line_event:
216  * @win: A text buffer or text grid window to request line input on.
217  * @buf: A buffer of at least @maxlen bytes.
218  * @maxlen: Length of the buffer.
219  * @initlen: The number of characters in @buf to pre-enter.
220  *
221  * Requests input of a line of Latin-1 characters. A window cannot have requests
222  * for both character and line input at the same time. Nor can it have requests
223  * for line input of both types (Latin-1 and Unicode). It is illegal to call
224  * glk_request_line_event() if the window already has a pending request for
225  * either character or line input.
226  * 
227  * The @buf argument is a pointer to space where the line input will be stored.
228  * (This may not be %NULL.) @maxlen is the length of this space, in bytes; the
229  * library will not accept more characters than this. If @initlen is nonzero,
230  * then the first @initlen bytes of @buf will be entered as pre-existing input
231  * — just as if the player had typed them himself. (The player can continue
232  * composing after this pre-entered input, or delete it or edit as usual.)
233  * 
234  * The contents of the buffer are undefined until the input is completed (either
235  * by a line input event, or glk_cancel_line_event(). The library may or may not
236  * fill in the buffer as the player composes, while the input is still pending;
237  * it is illegal to change the contents of the buffer yourself. 
238  */
239 void
240 glk_request_line_event(winid_t win, char *buf, glui32 maxlen, glui32 initlen)
241 {
242         VALID_WINDOW(win, return);
243         g_return_if_fail(buf);
244         g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
245         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
246         g_return_if_fail(initlen <= maxlen);
247
248         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
249         
250         /* Register the buffer */
251         if(glk_data->register_arr)
252         win->buffer_rock = (*glk_data->register_arr)(buf, maxlen, "&+#!Cn");
253         
254         win->input_request_type = INPUT_REQUEST_LINE;
255         win->line_input_buffer = buf;
256         win->line_input_buffer_max_len = maxlen;
257
258         gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup("");
259         switch(win->type)
260         {
261             case wintype_TextBuffer:
262                 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
263                 break;
264             case wintype_TextGrid:
265                 text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
266                 break;
267     }
268         g_free(inserttext);
269         
270         /* Emit the "waiting" signal to let listeners know we are ready for input */
271         g_signal_emit_by_name(glk_data->self, "waiting");
272 }
273
274 /**
275  * glk_request_line_event_uni:
276  * @win: A text buffer or text grid window to request line input on.
277  * @buf: A buffer of at least @maxlen characters.
278  * @maxlen: Length of the buffer.
279  * @initlen: The number of characters in @buf to pre-enter.
280  *
281  * Request input of a line of Unicode characters. This works the same as
282  * glk_request_line_event(), except the result is stored in an array of
283  * <type>glui32</type> values instead of an array of characters, and the values
284  * may be any valid Unicode code points.
285  * 
286  * The result will be in Unicode Normalization Form C. This basically means that
287  * composite characters will be single characters where possible, instead of
288  * sequences of base and combining marks. See 
289  * <ulink url="http://www.unicode.org/reports/tr15/">Unicode Standard Annex 
290  * #15</ulink> for the details.
291  */
292 void
293 glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen)
294 {
295         VALID_WINDOW(win, return);
296         g_return_if_fail(buf);
297         g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
298         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
299         g_return_if_fail(initlen <= maxlen);
300
301         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
302         
303         /* Register the buffer */
304         if(glk_data->register_arr)
305         win->buffer_rock = (*glk_data->register_arr)(buf, maxlen, "&+#!Iu");
306
307         win->input_request_type = INPUT_REQUEST_LINE_UNICODE;
308         win->line_input_buffer_unicode = buf;
309         win->line_input_buffer_max_len = maxlen;
310
311         gchar *utf8;
312         if(initlen > 0) {
313                 utf8 = convert_ucs4_to_utf8(buf, initlen);
314                 if(utf8 == NULL)
315                         return;
316         }
317         else
318                 utf8 = g_strdup("");
319
320     switch(win->type)
321         {
322             case wintype_TextBuffer:
323                 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8);
324                 break;
325             case wintype_TextGrid:
326                 text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8);
327                 break;
328     }           
329         g_free(utf8);
330         
331         /* Emit the "waiting" signal to let listeners know we are ready for input */
332         g_signal_emit_by_name(glk_data->self, "waiting");
333 }
334
335 /**
336  * glk_cancel_line_event:
337  * @win: A text buffer or text grid window to cancel line input on.
338  * @event: Will be filled in if the user had already input something.
339  *
340  * This cancels a pending request for line input. (Either Latin-1 or Unicode.)
341  *
342  * The event pointed to by the event argument will be filled in as if the
343  * player had hit <keycap>enter</keycap>, and the input composed so far will be 
344  * stored in the buffer; see below. If you do not care about this information, 
345  * pass %NULL as the @event argument. (The buffer will still be filled.) 
346  *
347  * For convenience, it is legal to call glk_cancel_line_event() even if there
348  * is no line input request on that window. The event type will be set to
349  * %evtype_None in this case.
350  */
351 void
352 glk_cancel_line_event(winid_t win, event_t *event)
353 {
354         VALID_WINDOW(win, return);
355         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
356
357         if(event != NULL) {
358                 event->type = evtype_None;
359                 event->win = win;
360                 event->val1 = 0;
361                 event->val2 = 0;
362         }
363
364         if(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE)
365                 return;
366
367         g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
368
369         int chars_written = 0;
370
371         if(win->type == wintype_TextGrid) {
372                 g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
373                 chars_written = finish_text_grid_line_input(win, FALSE);
374         } else if(win->type == wintype_TextBuffer) {
375                 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
376                 g_signal_handler_block(window_buffer, win->insert_text_handler);
377                 chars_written = finish_text_buffer_line_input(win, FALSE);
378         }
379
380         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
381         if(glk_data->unregister_arr) 
382         {
383                 if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
384                         (*glk_data->unregister_arr)(win->line_input_buffer_unicode, win->line_input_buffer_max_len, "&+#!Iu", win->buffer_rock);
385                 else
386                 (*glk_data->unregister_arr)(win->line_input_buffer, win->line_input_buffer_max_len, "&+#!Cn", win->buffer_rock);
387     }
388         
389         if(event != NULL && chars_written > 0) {
390                 event->type = evtype_LineInput;
391                 event->val1 = chars_written;
392         }
393 }
394
395 /* 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. */
396 gboolean
397 on_char_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
398 {
399         glui32 keycode = keyval_to_glk_keycode(event->keyval, win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE);
400
401         ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(widget, CHIMARA_TYPE_GLK));
402         g_assert(glk);
403         event_throw(glk, evtype_CharInput, win, keycode, 0);
404         g_signal_emit_by_name(glk, "char-input", win->rock, event->keyval);
405
406         /* Only one keypress will be handled */
407         win->input_request_type = INPUT_REQUEST_NONE;
408         g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
409
410         return TRUE;
411 }
412
413 gboolean
414 on_line_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
415 {
416         switch(win->type)
417         {
418                 case wintype_TextBuffer:
419                         if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
420                                 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
421                         {
422                                 /* Prevent falling off the end of the history list */
423                                 if( (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
424                                         && win->history_pos && win->history_pos->next == NULL)
425                                         return TRUE;
426                                 if( (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
427                                         && (win->history_pos == NULL || win->history_pos->prev == NULL) )
428                                         return TRUE;
429                         
430                                 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(win->widget));
431                                 GtkTextIter start, end;
432                                 /* Erase any text that was already typed */
433                                 GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
434                                 gtk_text_buffer_get_iter_at_mark(buffer, &start, input_position);
435                                 gtk_text_buffer_get_end_iter(buffer, &end);
436                                 
437                                 if(win->history_pos == NULL) {
438                                         gchar *current_input = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
439                                         win->history = g_list_prepend(win->history, current_input);
440                                         win->history_pos = win->history;
441                                 }
442                                 
443                                 gtk_text_buffer_delete(buffer, &start, &end);
444                 
445                                 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
446                                 {
447                                         if(win->history_pos)
448                                                 win->history_pos = g_list_next(win->history_pos);
449                                         else
450                                                 win->history_pos = win->history;
451                                 }
452                                 else /* down */
453                                         win->history_pos = g_list_previous(win->history_pos);
454                 
455                                 /* Insert the history item into the window */
456                                 gtk_text_buffer_get_end_iter(buffer, &end);
457                                 
458                                 g_signal_handler_block(buffer, win->insert_text_handler);
459                                 gtk_text_buffer_insert_with_tags_by_name(buffer, &end, win->history_pos->data, -1, "input", NULL);
460                                 g_signal_handler_unblock(buffer, win->insert_text_handler);
461                                 return TRUE;
462                         }
463                         return FALSE;
464
465                 /* If this is a text grid window, then redirect the key press to the line input GtkEntry */
466                 case wintype_TextGrid:
467                 {
468                         if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
469                                 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down
470                                 || event->keyval == GDK_Left || event->keyval == GDK_KP_Left
471                                 || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
472                                 || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab
473                                 || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up
474                                 || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down
475                                 || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
476                                 || event->keyval == GDK_End || event->keyval == GDK_KP_End)
477                                 return FALSE; /* Don't redirect these keys */
478                         gtk_widget_grab_focus(win->input_entry);
479                         gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1);
480                         gboolean retval = TRUE;
481                         g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval);
482                         return retval; /* Block this key event if the entry handled it */
483                 }
484         }
485         return FALSE;
486 }
487
488 /* Internal function: finish handling a line input request, for both text grid and text buffer windows. */
489 static int
490 write_to_window_buffer(winid_t win, const gchar *inserted_text)
491 {
492         int copycount = 0;
493
494     /* Convert the string from UTF-8 to Latin-1 or Unicode */
495     if(win->input_request_type == INPUT_REQUEST_LINE) 
496     {
497         gsize bytes_written;
498         gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written);
499         
500         if(latin1 == NULL)
501             return 0;
502
503         /* Place input in the echo stream */
504         if(win->echo_stream != NULL) 
505             glk_put_string_stream(win->echo_stream, latin1);
506
507         /* Copy the string (bytes_written does not include the NULL at the end) */
508         copycount = MIN(win->line_input_buffer_max_len, bytes_written);
509         memcpy(win->line_input_buffer, latin1, copycount);
510         g_free(latin1);
511     }
512     else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE) 
513     {
514         glong items_written;
515         gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written);
516         
517         if(unicode == NULL)
518             return 0;
519
520         /* Place input in the echo stream */
521         if(win->echo_stream != NULL) 
522             glk_put_string_stream_uni(win->echo_stream, unicode);
523
524         /* Copy the string (but not the NULL at the end) */
525         copycount = MIN(win->line_input_buffer_max_len, items_written);
526         memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar));
527         g_free(unicode);
528     }
529     else 
530         WARNING("Wrong input request type");
531
532     win->input_request_type = INPUT_REQUEST_NONE;
533         return copycount;
534 }
535
536 /* Internal function: Retrieves the input of a TextBuffer window and stores it in the window buffer.
537  * Returns the number of characters written, suitable for inclusion in a line input event. */
538 static int
539 finish_text_buffer_line_input(winid_t win, gboolean emit_signal)
540 {
541         VALID_WINDOW(win, return 0);
542         g_return_val_if_fail(win->type == wintype_TextBuffer, 0);
543
544         GtkTextIter start_iter, end_iter, last_character;
545
546         GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
547         GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
548         gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position);
549         gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
550         gtk_text_buffer_get_end_iter(window_buffer, &last_character);
551         gtk_text_iter_backward_cursor_position(&last_character);
552
553         gchar* last_char = gtk_text_buffer_get_text(window_buffer, &last_character, &end_iter, FALSE);
554
555         if( strchr(last_char, '\n') != NULL )
556                 gtk_text_iter_backward_cursor_position(&end_iter);
557
558         gchar* inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
559
560         int chars_written = write_to_window_buffer(win, inserted_text);
561         if(emit_signal)
562         {
563                 ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
564                 g_assert(glk);
565                 g_signal_emit_by_name(glk, "line-input", win->rock, inserted_text);
566         }
567         
568         /* Add the text to the window input history */
569         if(win->history_pos != NULL)
570         {
571                 g_free(win->history->data);
572                 win->history = g_list_delete_link(win->history, win->history);
573         }
574         if(*inserted_text != 0)
575                 win->history = g_list_prepend(win->history, g_strdup(inserted_text));   
576         
577         win->history_pos = NULL;
578         
579         g_free(inserted_text);
580
581         return chars_written;
582 }
583
584 /* Internal function: Retrieves the input of a TextGrid window and stores it in the window buffer.
585  * Returns the number of characters written, suitable for inclusion in a line input event. */
586 static int
587 finish_text_grid_line_input(winid_t win, gboolean emit_signal)
588 {
589         VALID_WINDOW(win, return 0);
590         g_return_val_if_fail(win->type == wintype_TextGrid, 0);
591
592         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
593         
594         gchar *text = g_strdup( gtk_entry_get_text(GTK_ENTRY(win->input_entry)) );
595         /* Move the focus back into the text view */
596         gtk_widget_grab_focus(win->widget);
597         /* Remove entry widget from text view */
598         /* Should be ok even though this is the widget's own signal handler */
599         gtk_container_remove( GTK_CONTAINER(win->widget), GTK_WIDGET(win->input_entry) );
600         win->input_entry = NULL;
601         /* Delete the child anchor */
602         GtkTextIter start, end;
603         gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor);
604         end = start;
605         gtk_text_iter_forward_char(&end); /* Point after the child anchor */
606         gtk_text_buffer_delete(buffer, &start, &end);
607         win->input_anchor = NULL;
608         
609     gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' ');
610     gchar *text_to_insert = g_strconcat(text, spaces, NULL);
611         g_free(spaces);
612     gtk_text_buffer_insert(buffer, &start, text_to_insert, -1);
613     g_free(text_to_insert);
614     
615     int chars_written = write_to_window_buffer(win, text);
616     if(emit_signal)
617     {
618                 ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
619                 g_assert(glk);
620                 g_signal_emit_by_name(glk, "line-input", win->rock, text);
621     }
622         
623         /* Add the text to the window input history */
624         if(win->history_pos != NULL)
625         {
626                 g_free(win->history->data);
627                 win->history = g_list_delete_link(win->history, win->history);
628         }
629         if(*text != 0)
630                 win->history = g_list_prepend(win->history, g_strdup(text));    
631         win->history_pos = NULL;
632         
633         g_free(text);
634         return chars_written;
635 }
636
637 /* Internal function: Callback for signal insert-text on a text buffer window.
638 Runs after the default handler has already inserted the text.
639 FIXME: This function assumes that newline was the last character typed into the
640 window. That assumption is wrong if, for example, text containing a newline was
641 pasted into the window. */
642 void
643 after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win) 
644 {
645         GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
646
647         /* Set the history position to NULL and erase the text we were already editing */
648         if(win->history_pos != NULL)
649         {
650                 g_free(win->history->data);
651                 win->history = g_list_delete_link(win->history, win->history);
652                 win->history_pos = NULL;
653         }
654         if( strchr(text, '\n') != NULL ) 
655         {
656                 /* Remove signal handlers */
657                 g_signal_handler_block(window_buffer, win->insert_text_handler);
658                 
659                 /* Make the window uneditable again and retrieve the text that was input */
660         gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
661
662         int chars_written = finish_text_buffer_line_input(win, TRUE);
663         ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
664                 event_throw(glk, evtype_LineInput, win, chars_written, 0);
665         }
666
667         /* Apply the 'input' style to the text that was entered */
668         GtkTextIter end_iter;
669         gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
670         GtkTextIter input_iter;
671         GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
672         gtk_text_buffer_get_iter_at_mark(window_buffer, &input_iter, input_position);
673         gtk_text_buffer_apply_tag_by_name(window_buffer, "input", &input_iter, &end_iter);
674 }
675
676 /* Internal function: Callback for signal activate on the line input GtkEntry
677 in a text grid window. */
678 void
679 on_input_entry_activate(GtkEntry *input_entry, winid_t win)
680 {
681         g_signal_handler_block(win->widget, win->char_input_keypress_handler);
682
683         int chars_written = finish_text_grid_line_input(win, TRUE);
684         ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
685         event_throw(glk, evtype_LineInput, win, chars_written, 0);
686 }
687
688 /* Internal function: Callback for signal key-press-event on the line input 
689 GtkEntry in a text grid window. */
690 gboolean
691 on_input_entry_key_press_event(GtkEntry *input_entry, GdkEventKey *event, winid_t win)
692 {
693         if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
694                 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
695         {
696                 /* Prevent falling off the end of the history list */
697                 if( (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
698                         && win->history_pos && win->history_pos->next == NULL)
699                         return TRUE;
700                 if( (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
701                         && (win->history_pos == NULL || win->history_pos->prev == NULL) )
702                         return TRUE;
703         
704                 if(win->history_pos == NULL) 
705                 {
706                         gchar *current_input = gtk_entry_get_text(input_entry);
707                         win->history = g_list_prepend(win->history, current_input);
708                         win->history_pos = win->history;
709                 }
710
711                 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
712                 {
713                         if(win->history_pos)
714                                 win->history_pos = g_list_next(win->history_pos);
715                         else
716                                 win->history_pos = win->history;
717                 }
718                 else /* down */
719                         win->history_pos = g_list_previous(win->history_pos);
720
721                 /* Insert the history item into the window */
722                 g_signal_handler_block(input_entry, win->line_input_entry_changed);
723                 gtk_entry_set_text(input_entry, win->history_pos->data);
724                 g_signal_handler_unblock(input_entry, win->line_input_entry_changed);
725                 return TRUE;
726         }
727         return FALSE;
728 }
729
730 void
731 on_input_entry_changed(GtkEditable *editable, winid_t win)
732 {
733         /* Set the history position to NULL and erase the text we were already editing */
734         if(win->history_pos != NULL)
735         {
736                 g_free(win->history->data);
737                 win->history = g_list_delete_link(win->history, win->history);
738                 win->history_pos = NULL;
739         }
740 }
741
742 glui32
743 keyval_to_glk_keycode(guint keyval, gboolean unicode)
744 {
745         glui32 keycode;
746         switch(keyval) {
747                 case GDK_Up:
748                 case GDK_KP_Up: return keycode_Up;
749                 case GDK_Down: 
750                 case GDK_KP_Down: return keycode_Down;
751                 case GDK_Left:
752                 case GDK_KP_Left: return keycode_Left; 
753                 case GDK_Right:
754                 case GDK_KP_Right: return keycode_Right; 
755                 case GDK_Linefeed:
756                 case GDK_Return:
757                 case GDK_KP_Enter: return keycode_Return; 
758                 case GDK_Delete:
759                 case GDK_BackSpace:
760                 case GDK_KP_Delete: return keycode_Delete; 
761                 case GDK_Escape: return keycode_Escape; 
762                 case GDK_Tab: 
763                 case GDK_KP_Tab: return keycode_Tab; 
764                 case GDK_Page_Up:
765                 case GDK_KP_Page_Up: return keycode_PageUp; 
766                 case GDK_Page_Down:
767                 case GDK_KP_Page_Down: return keycode_PageDown; 
768                 case GDK_Home:
769                 case GDK_KP_Home: return keycode_Home; 
770                 case GDK_End:
771                 case GDK_KP_End: return keycode_End; 
772                 case GDK_F1: 
773                 case GDK_KP_F1: return keycode_Func1; 
774                 case GDK_F2: 
775                 case GDK_KP_F2: return keycode_Func2; 
776                 case GDK_F3: 
777                 case GDK_KP_F3: return keycode_Func3; 
778                 case GDK_F4: 
779                 case GDK_KP_F4: return keycode_Func4; 
780                 case GDK_F5: return keycode_Func5; 
781                 case GDK_F6: return keycode_Func6; 
782                 case GDK_F7: return keycode_Func7; 
783                 case GDK_F8: return keycode_Func8; 
784                 case GDK_F9: return keycode_Func9; 
785                 case GDK_F10: return keycode_Func10; 
786                 case GDK_F11: return keycode_Func11; 
787                 case GDK_F12: return keycode_Func12; 
788                 default:
789                         keycode = gdk_keyval_to_unicode(keyval);
790                         /* If keycode is 0, then keyval was not recognized; also return
791                         unknown if Latin-1 input was requested and the character is not in
792                         Latin-1 */
793                         if(keycode == 0 || (!unicode && keycode > 255))
794                                 return keycode_Unknown;
795                         return keycode;
796         }
797 }
798
799 void
800 force_char_input_from_queue(winid_t win, event_t *event)
801 {
802         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
803         guint keyval = GPOINTER_TO_UINT(g_async_queue_pop(glk_data->char_input_queue));
804         
805         glk_cancel_char_event(win);
806         
807         gdk_threads_enter();
808         ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
809         g_assert(glk);
810         g_signal_emit_by_name(glk, "char-input", win->rock, keyval);
811         gdk_threads_leave();
812         
813         event->type = evtype_CharInput;
814         event->win = win;
815         event->val1 = keyval_to_glk_keycode(keyval, win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE);
816         event->val2 = 0;
817 }
818
819 void
820 force_line_input_from_queue(winid_t win, event_t *event)
821 {
822         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
823         const gchar *text = g_async_queue_pop(glk_data->line_input_queue);
824         glui32 chars_written = 0;
825         
826         gdk_threads_enter();
827         if(win->type == wintype_TextBuffer)
828         {
829                 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
830                 GtkTextIter start, end;
831                 
832                 /* Remove signal handlers so the line input doesn't get picked up again */
833                 g_signal_handler_block(buffer, win->insert_text_handler);
834                 
835                 /* Erase any text that was already typed */
836                 GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
837                 gtk_text_buffer_get_iter_at_mark(buffer, &start, input_position);
838                 gtk_text_buffer_get_end_iter(buffer, &end);
839                 gtk_text_buffer_delete(buffer, &start, &end);
840                 
841                 /* Make the window uneditable again */
842                 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
843                 
844                 /* Insert the forced input into the window */
845                 gtk_text_buffer_get_end_iter(buffer, &end);
846                 gchar *text_to_insert = g_strconcat(text, "\n", NULL);
847                 gtk_text_buffer_insert_with_tags_by_name(buffer, &end, text_to_insert, -1, "input", NULL);
848                 chars_written = finish_text_buffer_line_input(win, TRUE);               
849         }
850         else if(win->type == wintype_TextGrid)
851         {
852                 /* Remove signal handlers so the line input doesn't get picked up again */
853                 g_signal_handler_block(win->widget, win->char_input_keypress_handler);
854                 
855                 /* Insert the forced input into the window */
856                 gtk_entry_set_text(GTK_ENTRY(win->input_entry), text);
857                 chars_written = finish_text_grid_line_input(win, TRUE);
858         }
859         gdk_threads_leave();
860         
861         event->type = evtype_LineInput;
862         event->win = win;
863         event->val1 = chars_written;
864         event->val2 = 0;
865 }