First implementation of history buffer.
[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     
157     gtk_widget_show(win->input_entry);
158     gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(win->widget), win->input_entry, win->input_anchor);
159         
160         g_signal_handler_unblock( G_OBJECT(win->widget), win->char_input_keypress_handler );
161
162         gtk_widget_grab_focus(win->input_entry);
163         
164         gdk_threads_leave();
165 }
166     
167 /* Internal function: Request either latin-1 or unicode line input, in a text buffer window. */
168 static void
169 text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
170 {
171         flush_window_buffer(win);
172
173         gdk_threads_enter();
174         
175         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
176
177     /* Move the input_position mark to the end of the window_buffer */
178     GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
179     GtkTextIter end_iter;
180     gtk_text_buffer_get_end_iter(buffer, &end_iter);
181     gtk_text_buffer_move_mark(buffer, input_position, &end_iter);
182
183     /* Set the entire contents of the window_buffer as uneditable
184      * (so input can only be entered at the end) */
185     GtkTextIter start_iter;
186     gtk_text_buffer_get_start_iter(buffer, &start_iter);
187     gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
188     gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
189     
190     /* Insert pre-entered text if needed */
191     if(insert)
192         gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1);
193     
194     /* Scroll to input point */
195     gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position);
196     
197     gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
198     g_signal_handler_unblock(buffer, win->insert_text_handler);
199
200         gtk_widget_grab_focus(win->widget);
201         
202         gdk_threads_leave();
203 }
204
205 /**
206  * glk_request_line_event:
207  * @win: A text buffer or text grid window to request line input on.
208  * @buf: A buffer of at least @maxlen bytes.
209  * @maxlen: Length of the buffer.
210  * @initlen: The number of characters in @buf to pre-enter.
211  *
212  * Requests input of a line of Latin-1 characters. A window cannot have requests
213  * for both character and line input at the same time. Nor can it have requests
214  * for line input of both types (Latin-1 and Unicode). It is illegal to call
215  * glk_request_line_event() if the window already has a pending request for
216  * either character or line input.
217  * 
218  * The @buf argument is a pointer to space where the line input will be stored.
219  * (This may not be %NULL.) @maxlen is the length of this space, in bytes; the
220  * library will not accept more characters than this. If @initlen is nonzero,
221  * then the first @initlen bytes of @buf will be entered as pre-existing input
222  * — just as if the player had typed them himself. (The player can continue
223  * composing after this pre-entered input, or delete it or edit as usual.)
224  * 
225  * The contents of the buffer are undefined until the input is completed (either
226  * by a line input event, or glk_cancel_line_event(). The library may or may not
227  * fill in the buffer as the player composes, while the input is still pending;
228  * it is illegal to change the contents of the buffer yourself. 
229  */
230 void
231 glk_request_line_event(winid_t win, char *buf, glui32 maxlen, glui32 initlen)
232 {
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);
238
239         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
240         
241         /* Register the buffer */
242         if(glk_data->register_arr)
243         win->buffer_rock = (*glk_data->register_arr)(buf, maxlen, "&+#!Cn");
244         
245         win->input_request_type = INPUT_REQUEST_LINE;
246         win->line_input_buffer = buf;
247         win->line_input_buffer_max_len = maxlen;
248
249         gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup("");
250         switch(win->type)
251         {
252             case wintype_TextBuffer:
253                 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
254                 break;
255             case wintype_TextGrid:
256                 text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
257                 break;
258     }
259         g_free(inserttext);
260         
261         /* Emit the "waiting" signal to let listeners know we are ready for input */
262         g_signal_emit_by_name(glk_data->self, "waiting");
263 }
264
265 /**
266  * glk_request_line_event_uni:
267  * @win: A text buffer or text grid window to request line input on.
268  * @buf: A buffer of at least @maxlen characters.
269  * @maxlen: Length of the buffer.
270  * @initlen: The number of characters in @buf to pre-enter.
271  *
272  * Request input of a line of Unicode characters. This works the same as
273  * glk_request_line_event(), except the result is stored in an array of
274  * <type>glui32</type> values instead of an array of characters, and the values
275  * may be any valid Unicode code points.
276  * 
277  * The result will be in Unicode Normalization Form C. This basically means that
278  * composite characters will be single characters where possible, instead of
279  * sequences of base and combining marks. See 
280  * <ulink url="http://www.unicode.org/reports/tr15/">Unicode Standard Annex 
281  * #15</ulink> for the details.
282  */
283 void
284 glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen)
285 {
286         VALID_WINDOW(win, return);
287         g_return_if_fail(buf);
288         g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
289         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
290         g_return_if_fail(initlen <= maxlen);
291
292         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
293         
294         /* Register the buffer */
295         if(glk_data->register_arr)
296         win->buffer_rock = (*glk_data->register_arr)(buf, maxlen, "&+#!Iu");
297
298         win->input_request_type = INPUT_REQUEST_LINE_UNICODE;
299         win->line_input_buffer_unicode = buf;
300         win->line_input_buffer_max_len = maxlen;
301
302         gchar *utf8;
303         if(initlen > 0) {
304                 utf8 = convert_ucs4_to_utf8(buf, initlen);
305                 if(utf8 == NULL)
306                         return;
307         }
308         else
309                 utf8 = g_strdup("");
310
311     switch(win->type)
312         {
313             case wintype_TextBuffer:
314                 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8);
315                 break;
316             case wintype_TextGrid:
317                 text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8);
318                 break;
319     }           
320         g_free(utf8);
321         
322         /* Emit the "waiting" signal to let listeners know we are ready for input */
323         g_signal_emit_by_name(glk_data->self, "waiting");
324 }
325
326 /**
327  * glk_cancel_line_event:
328  * @win: A text buffer or text grid window to cancel line input on.
329  * @event: Will be filled in if the user had already input something.
330  *
331  * This cancels a pending request for line input. (Either Latin-1 or Unicode.)
332  *
333  * The event pointed to by the event argument will be filled in as if the
334  * player had hit <keycap>enter</keycap>, and the input composed so far will be 
335  * stored in the buffer; see below. If you do not care about this information, 
336  * pass %NULL as the @event argument. (The buffer will still be filled.) 
337  *
338  * For convenience, it is legal to call glk_cancel_line_event() even if there
339  * is no line input request on that window. The event type will be set to
340  * %evtype_None in this case.
341  */
342 void
343 glk_cancel_line_event(winid_t win, event_t *event)
344 {
345         VALID_WINDOW(win, return);
346         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
347
348         if(event != NULL) {
349                 event->type = evtype_None;
350                 event->win = win;
351                 event->val1 = 0;
352                 event->val2 = 0;
353         }
354
355         if(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE)
356                 return;
357
358         g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
359
360         int chars_written = 0;
361
362         if(win->type == wintype_TextGrid) {
363                 g_signal_handler_block( G_OBJECT(win->widget), win->char_input_keypress_handler );
364                 chars_written = finish_text_grid_line_input(win, FALSE);
365         } else if(win->type == wintype_TextBuffer) {
366                 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
367                 g_signal_handler_block(window_buffer, win->insert_text_handler);
368                 chars_written = finish_text_buffer_line_input(win, FALSE);
369         }
370
371         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
372         if(glk_data->unregister_arr) 
373         {
374                 if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE)
375                         (*glk_data->unregister_arr)(win->line_input_buffer_unicode, win->line_input_buffer_max_len, "&+#!Iu", win->buffer_rock);
376                 else
377                 (*glk_data->unregister_arr)(win->line_input_buffer, win->line_input_buffer_max_len, "&+#!Cn", win->buffer_rock);
378     }
379         
380         if(event != NULL && chars_written > 0) {
381                 event->type = evtype_LineInput;
382                 event->val1 = chars_written;
383         }
384 }
385
386 /* 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. */
387 gboolean
388 on_char_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
389 {
390         glui32 keycode = keyval_to_glk_keycode(event->keyval, win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE);
391
392         ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(widget, CHIMARA_TYPE_GLK));
393         g_assert(glk);
394         event_throw(glk, evtype_CharInput, win, keycode, 0);
395         g_signal_emit_by_name(glk, "char-input", win->rock, event->keyval);
396
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->char_input_keypress_handler );
400
401         return TRUE;
402 }
403
404 gboolean
405 on_line_input_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
406 {
407         switch(win->type)
408         {
409                 case wintype_TextBuffer:
410                         if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
411                                 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
412                         {
413                                 /* Prevent falling off the end of the history list */
414                                 if( (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
415                                         && win->history_pos && win->history_pos->next == NULL)
416                                         return TRUE;
417                                 if( (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
418                                         && (win->history_pos == NULL || win->history_pos->prev == NULL) )
419                                         return TRUE;
420                         
421                                 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(win->widget));
422                                 GtkTextIter start, end;
423                                 /* Erase any text that was already typed */
424                                 GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
425                                 gtk_text_buffer_get_iter_at_mark(buffer, &start, input_position);
426                                 gtk_text_buffer_get_end_iter(buffer, &end);
427                                 
428                                 if(win->history_pos == NULL) {
429                                         gchar *current_input = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
430                                         win->history = g_list_prepend(win->history, current_input);
431                                         win->history_pos = win->history;
432                                 }
433                                 
434                                 gtk_text_buffer_delete(buffer, &start, &end);
435                 
436                                 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
437                                 {
438                                         if(win->history_pos)
439                                                 win->history_pos = g_list_next(win->history_pos);
440                                         else
441                                                 win->history_pos = win->history;
442                                 }
443                                 else /* down */
444                                         win->history_pos = g_list_previous(win->history_pos);
445                 
446                                 /* Insert the history item into the window */
447                                 gtk_text_buffer_get_end_iter(buffer, &end);
448                                 
449                                 g_signal_handler_block(buffer, win->insert_text_handler);
450                                 gtk_text_buffer_insert_with_tags_by_name(buffer, &end, win->history_pos->data, -1, "input", NULL);
451                                 g_signal_handler_unblock(buffer, win->insert_text_handler);
452                                 return TRUE;
453                         }
454                         return FALSE;
455
456                 /* If this is a text grid window, then redirect the key press to the line input GtkEntry */
457                 case wintype_TextGrid:
458                 {
459                         if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
460                                 || event->keyval == GDK_Down || event->keyval == GDK_KP_Down
461                                 || event->keyval == GDK_Left || event->keyval == GDK_KP_Left
462                                 || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
463                                 || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab
464                                 || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up
465                                 || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down
466                                 || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
467                                 || event->keyval == GDK_End || event->keyval == GDK_KP_End)
468                                 return FALSE; /* Don't redirect these keys */
469                         gtk_widget_grab_focus(win->input_entry);
470                         gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1);
471                         gboolean retval = TRUE;
472                         g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval);
473                         return retval; /* Block this key event if the entry handled it */
474                 }
475         }
476         return FALSE;
477 }
478
479 /* Internal function: finish handling a line input request, for both text grid and text buffer windows. */
480 static int
481 write_to_window_buffer(winid_t win, const gchar *inserted_text)
482 {
483         int copycount = 0;
484
485     /* Convert the string from UTF-8 to Latin-1 or Unicode */
486     if(win->input_request_type == INPUT_REQUEST_LINE) 
487     {
488         gsize bytes_written;
489         gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written);
490         
491         if(latin1 == NULL)
492             return 0;
493
494         /* Place input in the echo stream */
495         if(win->echo_stream != NULL) 
496             glk_put_string_stream(win->echo_stream, latin1);
497
498         /* Copy the string (bytes_written does not include the NULL at the end) */
499         copycount = MIN(win->line_input_buffer_max_len, bytes_written);
500         memcpy(win->line_input_buffer, latin1, copycount);
501         g_free(latin1);
502     }
503     else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE) 
504     {
505         glong items_written;
506         gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written);
507         
508         if(unicode == NULL)
509             return 0;
510
511         /* Place input in the echo stream */
512         if(win->echo_stream != NULL) 
513             glk_put_string_stream_uni(win->echo_stream, unicode);
514
515         /* Copy the string (but not the NULL at the end) */
516         copycount = MIN(win->line_input_buffer_max_len, items_written);
517         memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar));
518         g_free(unicode);
519     }
520     else 
521         WARNING("Wrong input request type");
522
523     win->input_request_type = INPUT_REQUEST_NONE;
524         return copycount;
525 }
526
527 /* Internal function: Retrieves the input of a TextBuffer window and stores it in the window buffer.
528  * Returns the number of characters written, suitable for inclusion in a line input event. */
529 static int
530 finish_text_buffer_line_input(winid_t win, gboolean emit_signal)
531 {
532         VALID_WINDOW(win, return 0);
533         g_return_val_if_fail(win->type == wintype_TextBuffer, 0);
534
535         GtkTextIter start_iter, end_iter, last_character;
536
537         GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
538         GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
539         gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position);
540         gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
541         gtk_text_buffer_get_end_iter(window_buffer, &last_character);
542         gtk_text_iter_backward_cursor_position(&last_character);
543
544         gchar* last_char = gtk_text_buffer_get_text(window_buffer, &last_character, &end_iter, FALSE);
545
546         if( strchr(last_char, '\n') != NULL )
547                 gtk_text_iter_backward_cursor_position(&end_iter);
548
549         gchar* inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
550
551         int chars_written = write_to_window_buffer(win, inserted_text);
552         if(emit_signal)
553         {
554                 ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
555                 g_assert(glk);
556                 g_signal_emit_by_name(glk, "line-input", win->rock, inserted_text);
557         }
558         
559         /* Add the text to the window input history */
560         if(win->history_pos != NULL)
561         {
562                 g_free(win->history->data);
563                 win->history = g_list_delete_link(win->history, win->history);
564         }
565         win->history = g_list_prepend(win->history, g_strdup(inserted_text));   
566         win->history_pos = NULL;
567         
568         g_free(inserted_text);
569
570         return chars_written;
571 }
572
573 /* Internal function: Retrieves the input of a TextGrid window and stores it in the window buffer.
574  * Returns the number of characters written, suitable for inclusion in a line input event. */
575 static int
576 finish_text_grid_line_input(winid_t win, gboolean emit_signal)
577 {
578         VALID_WINDOW(win, return 0);
579         g_return_val_if_fail(win->type == wintype_TextGrid, 0);
580
581         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
582         
583         gchar *text = g_strdup( gtk_entry_get_text(GTK_ENTRY(win->input_entry)) );
584         /* Move the focus back into the text view */
585         gtk_widget_grab_focus(win->widget);
586         /* Remove entry widget from text view */
587         /* Should be ok even though this is the widget's own signal handler */
588         gtk_container_remove( GTK_CONTAINER(win->widget), GTK_WIDGET(win->input_entry) );
589         win->input_entry = NULL;
590         /* Delete the child anchor */
591         GtkTextIter start, end;
592         gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor);
593         end = start;
594         gtk_text_iter_forward_char(&end); /* Point after the child anchor */
595         gtk_text_buffer_delete(buffer, &start, &end);
596         win->input_anchor = NULL;
597         
598     gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' ');
599     gchar *text_to_insert = g_strconcat(text, spaces, NULL);
600         g_free(spaces);
601     gtk_text_buffer_insert(buffer, &start, text_to_insert, -1);
602     g_free(text_to_insert);
603     
604     int chars_written = write_to_window_buffer(win, text);
605     if(emit_signal)
606     {
607                 ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
608                 g_assert(glk);
609                 g_signal_emit_by_name(glk, "line-input", win->rock, text);
610     }
611         g_free(text);
612
613         return chars_written;
614 }
615
616 /* Internal function: Callback for signal insert-text on a text buffer window.
617 Runs after the default handler has already inserted the text.
618 FIXME: This function assumes that newline was the last character typed into the
619 window. That assumption is wrong if, for example, text containing a newline was
620 pasted into the window. */
621 void
622 after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win) 
623 {
624         /* Set the history position to NULL and erase the text we were already editing */
625         if(win->history_pos != NULL)
626         {
627                 g_free(win->history->data);
628                 win->history = g_list_delete_link(win->history, win->history);
629                 win->history_pos = NULL;
630         }
631         if( strchr(text, '\n') != NULL ) 
632         {
633                 /* Remove signal handlers */
634                 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
635                 g_signal_handler_block(window_buffer, win->insert_text_handler);
636                 
637                 /* Make the window uneditable again and retrieve the text that was input */
638         gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
639
640         int chars_written = finish_text_buffer_line_input(win, TRUE);
641         ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
642                 event_throw(glk, evtype_LineInput, win, chars_written, 0);
643         }
644 }
645
646 /* Internal function: Callback for signal activate on the line input GtkEntry
647 in a text grid window. */
648 void
649 on_input_entry_activate(GtkEntry *input_entry, winid_t win)
650 {
651         g_signal_handler_block(win->widget, win->char_input_keypress_handler);
652
653         int chars_written = finish_text_grid_line_input(win, TRUE);
654         ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
655         event_throw(glk, evtype_LineInput, win, chars_written, 0);
656 }
657
658 glui32
659 keyval_to_glk_keycode(guint keyval, gboolean unicode)
660 {
661         glui32 keycode;
662         switch(keyval) {
663                 case GDK_Up:
664                 case GDK_KP_Up: return keycode_Up;
665                 case GDK_Down: 
666                 case GDK_KP_Down: return keycode_Down;
667                 case GDK_Left:
668                 case GDK_KP_Left: return keycode_Left; 
669                 case GDK_Right:
670                 case GDK_KP_Right: return keycode_Right; 
671                 case GDK_Linefeed:
672                 case GDK_Return:
673                 case GDK_KP_Enter: return keycode_Return; 
674                 case GDK_Delete:
675                 case GDK_BackSpace:
676                 case GDK_KP_Delete: return keycode_Delete; 
677                 case GDK_Escape: return keycode_Escape; 
678                 case GDK_Tab: 
679                 case GDK_KP_Tab: return keycode_Tab; 
680                 case GDK_Page_Up:
681                 case GDK_KP_Page_Up: return keycode_PageUp; 
682                 case GDK_Page_Down:
683                 case GDK_KP_Page_Down: return keycode_PageDown; 
684                 case GDK_Home:
685                 case GDK_KP_Home: return keycode_Home; 
686                 case GDK_End:
687                 case GDK_KP_End: return keycode_End; 
688                 case GDK_F1: 
689                 case GDK_KP_F1: return keycode_Func1; 
690                 case GDK_F2: 
691                 case GDK_KP_F2: return keycode_Func2; 
692                 case GDK_F3: 
693                 case GDK_KP_F3: return keycode_Func3; 
694                 case GDK_F4: 
695                 case GDK_KP_F4: return keycode_Func4; 
696                 case GDK_F5: return keycode_Func5; 
697                 case GDK_F6: return keycode_Func6; 
698                 case GDK_F7: return keycode_Func7; 
699                 case GDK_F8: return keycode_Func8; 
700                 case GDK_F9: return keycode_Func9; 
701                 case GDK_F10: return keycode_Func10; 
702                 case GDK_F11: return keycode_Func11; 
703                 case GDK_F12: return keycode_Func12; 
704                 default:
705                         keycode = gdk_keyval_to_unicode(keyval);
706                         /* If keycode is 0, then keyval was not recognized; also return
707                         unknown if Latin-1 input was requested and the character is not in
708                         Latin-1 */
709                         if(keycode == 0 || (!unicode && keycode > 255))
710                                 return keycode_Unknown;
711                         return keycode;
712         }
713 }
714
715 void
716 force_char_input_from_queue(winid_t win, event_t *event)
717 {
718         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
719         guint keyval = GPOINTER_TO_UINT(g_async_queue_pop(glk_data->char_input_queue));
720         
721         glk_cancel_char_event(win);
722         
723         gdk_threads_enter();
724         ChimaraGlk *glk = CHIMARA_GLK(gtk_widget_get_ancestor(win->widget, CHIMARA_TYPE_GLK));
725         g_assert(glk);
726         g_signal_emit_by_name(glk, "char-input", win->rock, keyval);
727         gdk_threads_leave();
728         
729         event->type = evtype_CharInput;
730         event->win = win;
731         event->val1 = keyval_to_glk_keycode(keyval, win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE);
732         event->val2 = 0;
733 }
734
735 void
736 force_line_input_from_queue(winid_t win, event_t *event)
737 {
738         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
739         const gchar *text = g_async_queue_pop(glk_data->line_input_queue);
740         glui32 chars_written = 0;
741         
742         gdk_threads_enter();
743         if(win->type == wintype_TextBuffer)
744         {
745                 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
746                 GtkTextIter start, end;
747                 
748                 /* Remove signal handlers so the line input doesn't get picked up again */
749                 g_signal_handler_block(buffer, win->insert_text_handler);
750                 
751                 /* Erase any text that was already typed */
752                 GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
753                 gtk_text_buffer_get_iter_at_mark(buffer, &start, input_position);
754                 gtk_text_buffer_get_end_iter(buffer, &end);
755                 gtk_text_buffer_delete(buffer, &start, &end);
756                 
757                 /* Make the window uneditable again */
758                 gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
759                 
760                 /* Insert the forced input into the window */
761                 gtk_text_buffer_get_end_iter(buffer, &end);
762                 gchar *text_to_insert = g_strconcat(text, "\n", NULL);
763                 gtk_text_buffer_insert_with_tags_by_name(buffer, &end, text_to_insert, -1, "input", NULL);
764                 chars_written = finish_text_buffer_line_input(win, TRUE);               
765         }
766         else if(win->type == wintype_TextGrid)
767         {
768                 /* Remove signal handlers so the line input doesn't get picked up again */
769                 g_signal_handler_block(win->widget, win->char_input_keypress_handler);
770                 
771                 /* Insert the forced input into the window */
772                 gtk_entry_set_text(GTK_ENTRY(win->input_entry), text);
773                 chars_written = finish_text_grid_line_input(win, TRUE);
774         }
775         gdk_threads_leave();
776         
777         event->type = evtype_LineInput;
778         event->win = win;
779         event->val1 = chars_written;
780         event->val2 = 0;
781 }