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