382592c152381d7f334eb650d70cf71085ed29c2
[rodin/chimara.git] / src / input.c
1 #include "input.h"
2
3 /** glk_request_char_event:
4  * @win: A window to request char events from.
5  *
6  * Request input of a Latin-1 character or special key. A window cannot have 
7  * requests for both character and line input at the same time. Nor can it have
8  * requests for character input of both types (Latin-1 and Unicode). It is
9  * illegal to call glk_request_char_event() if the window already has a pending
10  * request for either character or line input. 
11  */
12 void
13 glk_request_char_event(winid_t win)
14 {
15         g_return_if_fail(win);
16         g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
17         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
18
19         win->input_request_type = INPUT_REQUEST_CHARACTER;
20         g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
21 }
22
23 /** glk_request_char_event_uni:
24  * @win: A window to request char events from.
25  *
26  * Request input of a Unicode character or special key. See 
27  * glk_request_char_event().
28  */
29 void
30 glk_request_char_event_uni(winid_t win)
31 {
32         g_return_if_fail(win);
33         g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
34         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
35
36         win->input_request_type = INPUT_REQUEST_CHARACTER_UNICODE;
37         g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
38 }
39
40 /* Internal function: Request either latin-1 or unicode line input, in a text grid window. */
41 void
42 text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
43 {
44         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
45
46     GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
47     GtkTextIter start_iter, end_iter;
48     gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, cursor);
49     
50     /* Determine the maximum length of the line input */
51     gint cursorpos = gtk_text_iter_get_line_offset(&start_iter);
52     gint input_length = MIN(win->width - cursorpos, win->line_input_buffer_max_len);
53     end_iter = start_iter;
54     gtk_text_iter_set_line_offset(&end_iter, cursorpos + input_length);
55     
56     /* Insert pre-entered text if needed */
57     gchar *text;
58     if(!insert)
59         text = g_strnfill(input_length, ' ');
60     else
61     {
62         gchar *blanks = g_strnfill(input_length - g_utf8_strlen(inserttext, -1), ' ');
63         text = g_strconcat(inserttext, blanks, NULL);
64         g_free(blanks);
65     }            
66     
67     /* Erase the text currently in the input field and replace it with blanks or the insert text, with the editable "input_field" tag */
68     gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
69     gtk_text_buffer_insert_with_tags_by_name(buffer, &start_iter, text, -1, "input_field", NULL);
70     g_free(text);
71
72     g_signal_handler_unblock(GTK_TEXT_VIEW(win->widget), win->insert_text_handler);
73 }
74     
75 /* Internal function: Request either latin-1 or unicode line input, in a text buffer window. */
76 void
77 text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
78 {
79         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
80
81     /* Move the input_position mark to the end of the window_buffer */
82     GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
83     GtkTextIter end_iter;
84     gtk_text_buffer_get_end_iter(buffer, &end_iter);
85     gtk_text_buffer_move_mark(buffer, input_position, &end_iter);
86
87     /* Set the entire contents of the window_buffer as uneditable
88      * (so input can only be entered at the end) */
89     GtkTextIter start_iter;
90     gtk_text_buffer_get_start_iter(buffer, &start_iter);
91     gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
92     gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
93     
94     /* Insert pre-entered text if needed */
95     if(insert)
96         gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1);
97     
98     /* Scroll to input point */
99     gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position);
100     
101     gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
102     g_signal_handler_unblock(buffer, win->insert_text_handler);
103 }
104
105 /**
106  * glk_request_line_event:
107  * @win: A text buffer or text grid window to request line input on.
108  * @buf: A buffer of at least @maxlen bytes.
109  * @maxlen: Length of the buffer.
110  * @initlen: The number of characters in @buf to pre-enter.
111  *
112  * Requests input of a line of Latin-1 characters. A window cannot have requests
113  * for both character and line input at the same time. Nor can it have requests
114  * for line input of both types (Latin-1 and Unicode). It is illegal to call
115  * glk_request_line_event() if the window already has a pending request for
116  * either character or line input.
117  * 
118  * The @buf argument is a pointer to space where the line input will be stored.
119  * (This may not be %NULL.) @maxlen is the length of this space, in bytes; the
120  * library will not accept more characters than this. If @initlen is nonzero,
121  * then the first @initlen bytes of @buf will be entered as pre-existing input
122  * -- just as if the player had typed them himself. (The player can continue
123  * composing after this pre-entered input, or delete it or edit as usual.)
124  * 
125  * The contents of the buffer are undefined until the input is completed (either
126  * by a line input event, or glk_cancel_line_event(). The library may or may not
127  * fill in the buffer as the player composes, while the input is still pending;
128  * it is illegal to change the contents of the buffer yourself. 
129  */
130 void
131 glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen)
132 {
133         g_return_if_fail(win);
134         g_return_if_fail(buf);
135         g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
136         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
137         g_return_if_fail(initlen <= maxlen);
138
139         win->input_request_type = INPUT_REQUEST_LINE;
140         win->line_input_buffer = buf;
141         win->line_input_buffer_max_len = maxlen;
142
143         gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup("");
144         switch(win->type)
145         {
146             case wintype_TextBuffer:
147                 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
148                 break;
149             case wintype_TextGrid:
150                 text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
151                 break;
152         default:
153             g_assert_not_reached();
154     }
155         g_free(inserttext);
156 }
157
158 /**
159  * glk_request_line_event_uni:
160  * @win: A text buffer or text grid window to request line input on.
161  * @buf: A buffer of at least @maxlen characters.
162  * @maxlen: Length of the buffer.
163  * @initlen: The number of characters in @buf to pre-enter.
164  *
165  * Request input of a line of Unicode characters. This works the same as
166  * glk_request_line_event(), except the result is stored in an array of
167  * <type>glui32</type> values instead of an array of characters, and the values
168  * may be any valid Unicode code points.
169  * 
170  * The result will be in Unicode Normalization Form C. This basically means that
171  * composite characters will be single characters where possible, instead of
172  * sequences of base and combining marks. See 
173  * <ulink url="http://www.unicode.org/reports/tr15/">Unicode Standard Annex #15
174  * </ulink> for the details.
175  */
176 void
177 glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen)
178 {
179         g_return_if_fail(win);
180         g_return_if_fail(buf);
181         g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
182         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
183
184         win->input_request_type = INPUT_REQUEST_LINE_UNICODE;
185         win->line_input_buffer_unicode = buf;
186         win->line_input_buffer_max_len = maxlen;
187
188         gchar *utf8;
189         if(initlen > 0) {
190                 GError *error = NULL;
191                 utf8 = g_ucs4_to_utf8(buf, initlen, NULL, NULL, &error);
192                         
193                 if(utf8 == NULL)
194                 {
195                         g_warning("Error during unicode->utf8 conversion: %s", error->message);
196                         return;
197                 }
198         }
199         else
200                 utf8 = g_strdup("");
201
202     switch(win->type)
203         {
204             case wintype_TextBuffer:
205                 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8);
206                 break;
207             case wintype_TextGrid:
208                 text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8);
209                 break;
210         default:
211             g_assert_not_reached();
212     }           
213         g_free(utf8);
214 }
215
216 /* Internal function: Callback for signal key-press-event on a text buffer or text grid window. */
217 gboolean
218 on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
219 {    
220         if(win->input_request_type != INPUT_REQUEST_CHARACTER && 
221                 win->input_request_type != INPUT_REQUEST_CHARACTER_UNICODE)
222                 return FALSE;
223
224         int keycode;
225
226         switch(event->keyval) {
227                 case GDK_Up:
228                 case GDK_KP_Up: keycode = keycode_Up; break;
229                 case GDK_Down: 
230                 case GDK_KP_Down: keycode = keycode_Down; break;
231                 case GDK_Left:
232                 case GDK_KP_Left: keycode = keycode_Left; break;
233                 case GDK_Right:
234                 case GDK_KP_Right: keycode = keycode_Right; break;
235                 case GDK_Linefeed:
236                 case GDK_Return:
237                 case GDK_KP_Enter: keycode = keycode_Return; break;
238                 case GDK_Delete:
239                 case GDK_BackSpace:
240                 case GDK_KP_Delete: keycode = keycode_Delete; break;
241                 case GDK_Escape: keycode = keycode_Escape; break;
242                 case GDK_Tab: 
243                 case GDK_KP_Tab: keycode = keycode_Tab; break;
244                 case GDK_Page_Up:
245                 case GDK_KP_Page_Up: keycode = keycode_PageUp; break;
246                 case GDK_Page_Down:
247                 case GDK_KP_Page_Down: keycode = keycode_PageDown; break;
248                 case GDK_Home:
249                 case GDK_KP_Home: keycode = keycode_Home; break;
250                 case GDK_End:
251                 case GDK_KP_End: keycode = keycode_End; break;
252                 case GDK_F1: 
253                 case GDK_KP_F1: keycode = keycode_Func1; break;
254                 case GDK_F2: 
255                 case GDK_KP_F2: keycode = keycode_Func2; break;
256                 case GDK_F3: 
257                 case GDK_KP_F3: keycode = keycode_Func3; break;
258                 case GDK_F4: 
259                 case GDK_KP_F4: keycode = keycode_Func4; break;
260                 case GDK_F5: keycode = keycode_Func5; break;
261                 case GDK_F6: keycode = keycode_Func6; break;
262                 case GDK_F7: keycode = keycode_Func7; break;
263                 case GDK_F8: keycode = keycode_Func8; break;
264                 case GDK_F9: keycode = keycode_Func9; break;
265                 case GDK_F10: keycode = keycode_Func10; break;
266                 case GDK_F11: keycode = keycode_Func11; break;
267                 case GDK_F12: keycode = keycode_Func12; break;
268                 default:
269                         keycode = gdk_keyval_to_unicode(event->keyval);
270                         /* If keycode is 0, then keyval was not recognized; also return
271                         unknown if Latin-1 input was requested and the character is not in
272                         Latin-1 */
273                         if(keycode == 0 || (win->input_request_type == INPUT_REQUEST_CHARACTER && keycode > 255))
274                                 keycode = keycode_Unknown;      
275         }
276
277         event_throw(evtype_CharInput, win, keycode, 0);
278         
279         /* Only one keypress will be handled */
280         win->input_request_type = INPUT_REQUEST_NONE;
281         g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
282
283         return TRUE;
284 }
285
286 /* Internal function: finish handling a line input request, for both text grid and text buffer windows. */
287 static void
288 end_line_input_request(winid_t win, gchar *inserted_text)
289 {
290     /* Convert the string from UTF-8 to Latin-1 or Unicode */
291     if(win->input_request_type == INPUT_REQUEST_LINE) 
292     {
293         GError *error = NULL;
294         gchar *latin1;
295         gsize bytes_written;
296         latin1 = g_convert_with_fallback(inserted_text, -1, "ISO-8859-1", "UTF-8", "?", NULL, &bytes_written, &error);
297         
298         if(latin1 == NULL)
299         {
300             g_warning("Error during utf8->latin1 conversion: %s", error->message);
301             event_throw(evtype_LineInput, win, 0, 0);
302             return;
303         }
304
305         /* Place input in the echo stream */
306         if(win->echo_stream != NULL) 
307             glk_put_string_stream(win->echo_stream, latin1);
308
309         /* Copy the string (but not the NULL at the end) */
310         int copycount = MIN(win->line_input_buffer_max_len, bytes_written - 1);
311         memcpy(win->line_input_buffer, latin1, copycount);
312         g_free(latin1);
313         event_throw(evtype_LineInput, win, copycount, 0);
314     }
315     else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE) 
316     {
317         gunichar *unicode;
318         glong items_written;
319         unicode = g_utf8_to_ucs4_fast(inserted_text, -1, &items_written);
320         
321         if(unicode == NULL)
322         {
323             g_warning("Error during utf8->unicode conversion");
324             event_throw(evtype_LineInput, win, 0, 0);
325             return;
326         }
327
328         /* Place input in the echo stream */
329         /* TODO: fixme
330         if(win->echo_stream != NULL) 
331             glk_put_string_stream_uni(window->echo_stream, unicode);*/
332
333         /* Copy the string (but not the NULL at the end) */
334         int copycount = MIN(win->line_input_buffer_max_len, items_written);
335         memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar));
336         g_free(unicode);
337         event_throw(evtype_LineInput, win, copycount, 0);
338     }
339     else 
340         g_warning("%s: Wrong input request type.", __func__);
341
342     win->input_request_type = INPUT_REQUEST_NONE;
343 }
344
345 /* Internal function: calculate the bounds of the line input field of a text grid window. Fill them into start_iter and end_iter */
346 static void
347 text_grid_get_line_input_bounds(winid_t win, GtkTextIter *start_iter, GtkTextIter *end_iter)
348 {
349     GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
350     /* "cursor_position" is the Glk output cursor position, which is also the position of the start of the line input buffer */
351     GtkTextMark *input_start = gtk_text_buffer_get_mark(buffer, "cursor_position");
352     gtk_text_buffer_get_iter_at_mark(buffer, start_iter, input_start);
353     gint startpos = gtk_text_iter_get_line_offset(start_iter);
354     gint input_length = MIN(win->width - startpos, win->line_input_buffer_max_len);
355     *end_iter = *start_iter;
356     gtk_text_iter_set_line_offset(end_iter, startpos + input_length);
357 }
358
359 /* Internal function: Retag the input field bounds */
360 static void
361 text_grid_retag_line_input_bounds(winid_t win)
362 {
363     GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
364     GtkTextIter start, end, input_start, input_end;
365     text_grid_get_line_input_bounds(win, &input_start, &input_end);
366     gtk_text_buffer_get_start_iter(buffer, &start);
367     gtk_text_buffer_get_end_iter(buffer, &end);
368     gtk_text_buffer_remove_tag_by_name(buffer, "input_field", &start, &end);
369     gtk_text_buffer_apply_tag_by_name(buffer, "input_field", &input_start, &input_end);
370 }
371
372 /* Internal function: Callback for signal key-press-event on a text grid window. Only unblocked during line input, so that we can catch all the keys that have different functions in text grid line input, before they mess up the window. */
373 gboolean
374 on_text_grid_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
375 {
376     g_return_val_if_fail(win->type == wintype_TextGrid, FALSE);
377     g_return_val_if_fail(win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE, FALSE);
378     
379     GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
380     
381     switch(event->keyval) {
382         /* Backspace deletes the character before the cursor, moves the characters in the rest of the input field back one, and inserts a space at the end */
383         case GDK_BackSpace:
384         {
385             GtkTextIter insert, bound, input_start, input_end;
386             /* Backspace acts like Delete if there is text selected */
387             if( gtk_text_buffer_get_selection_bounds(buffer, &insert, &bound) )
388             {
389                 text_grid_get_line_input_bounds(win, &input_start, &input_end);
390                 
391                 /* Determine whether part of the selected range is in the input field, and if so, what part */
392                 gint insert_where = gtk_text_iter_compare(&insert, &input_start);
393                 gint bound_where = gtk_text_iter_compare(&bound, &input_start);
394                 if(insert_where < 0 && bound_where <= 0)
395                     return FALSE;
396                 if(insert_where < 0)
397                     insert = input_start;
398                     
399                 insert_where = gtk_text_iter_compare(&insert, &input_end);
400                 bound_where = gtk_text_iter_compare(&bound, &input_end);
401                 if(insert_where >= 0 && bound_where > 0)
402                     return FALSE;
403                 if(bound_where > 0)
404                     bound = input_end;
405                 
406                 /* insert and bound are now within the input field */
407                 gsize num_spaces = gtk_text_iter_get_offset(&bound) - gtk_text_iter_get_offset(&insert);
408                 gchar *spaces = g_strnfill(num_spaces, ' ');
409                 /* Create a temporary mark with right gravity at the end of the input field */
410                 GtkTextMark *input_end_mark = gtk_text_buffer_create_mark(buffer, NULL, &input_end, FALSE);
411                 gtk_text_buffer_delete(buffer, &insert, &bound);
412                 gtk_text_buffer_get_iter_at_mark(buffer, &input_end, input_end_mark);
413                 gtk_text_buffer_delete_mark(buffer, input_end_mark);
414                 gtk_text_buffer_insert(buffer, &input_end, spaces, -1);
415                 g_free(spaces);
416                 text_grid_retag_line_input_bounds(win);
417                 
418                 return TRUE;
419             }
420             
421             text_grid_get_line_input_bounds(win, &input_start, &input_end);
422             /* if cursor is outside input field, continue as default */
423             if( !gtk_text_iter_in_range(&insert, &input_start, &input_end) )
424                 return FALSE;
425             /* if cursor is at start of field, do nothing */
426             if( gtk_text_iter_equal(&insert, &input_start) )
427                 return TRUE;
428             gtk_text_iter_backward_cursor_position(&insert);
429             /* Create a temporary mark with right gravity at the end of the input field */
430             GtkTextMark *input_end_mark = gtk_text_buffer_create_mark(buffer, NULL, &input_end, FALSE);
431             gtk_text_buffer_delete(buffer, &insert, &bound);
432             /* Revalidate end iterator */
433             gtk_text_buffer_get_iter_at_mark(buffer, &input_end, input_end_mark);
434             gtk_text_buffer_delete_mark(buffer, input_end_mark);
435             gtk_text_buffer_insert(buffer, &input_end, " ", -1);
436             text_grid_retag_line_input_bounds(win);
437         }
438             return TRUE;
439         
440         /* Various forms of Enter */
441         case GDK_Linefeed:
442         case GDK_Return:
443         case GDK_KP_Enter:
444         {
445             GtkTextIter input_start, input_end, start, end;
446             g_signal_handler_block(GTK_TEXT_VIEW(win->widget), win->insert_text_handler);
447         
448             /* Get the input text */
449             text_grid_get_line_input_bounds(win, &input_start, &input_end);
450             gtk_text_iter_forward_char(&input_end);
451             gchar *inserted_text = g_strchomp( gtk_text_buffer_get_text(buffer, &input_start, &input_end, FALSE) );
452             
453             /* Make the buffer uneditable again and remove the special tags */
454             gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
455             gtk_text_buffer_get_start_iter(buffer, &start);
456             gtk_text_buffer_get_end_iter(buffer, &end);
457             gtk_text_buffer_remove_tag_by_name(buffer, "input_field", &start, &end);
458             
459             end_line_input_request(win, inserted_text);
460             g_free(inserted_text);
461             
462             /* Block the enter key */
463             return TRUE; 
464         }
465     }
466     return FALSE;
467 }
468
469 /* Internal function: Callback for signal insert-text on a text buffer window.
470 Runs after the default handler has already inserted the text.*/
471 void
472 after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win) 
473 {
474         if( strchr(text, '\n') != NULL ) 
475         {
476                 /* Remove signal handlers */
477                 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
478                 g_signal_handler_block(window_buffer, win->insert_text_handler);
479                 
480                 /* Make the window uneditable again and retrieve the text that was input */
481                 gchar *inserted_text;
482                 GtkTextIter start_iter, end_iter;
483
484         gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
485         GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
486         gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position);
487         gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
488         
489         inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
490
491         end_line_input_request(win, inserted_text);
492         g_free(inserted_text);
493         }
494 }