8956c2c53438dad52dd3446d88acd3012c54a1f3
[rodin/chimara.git] / src / input.c
1 #include "charset.h"
2 #include "input.h"
3
4 /** glk_request_char_event:
5  * @win: A window to request char events from.
6  *
7  * Request input of a Latin-1 character or special key. A window cannot have 
8  * requests for both character and line input at the same time. Nor can it have
9  * requests for character input of both types (Latin-1 and Unicode). It is
10  * illegal to call glk_request_char_event() if the window already has a pending
11  * request for either character or line input. 
12  */
13 void
14 glk_request_char_event(winid_t win)
15 {
16         g_return_if_fail(win);
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         win->input_request_type = INPUT_REQUEST_CHARACTER;
21         g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
22 }
23
24 /** glk_request_char_event_uni:
25  * @win: A window to request char events from.
26  *
27  * Request input of a Unicode character or special key. See 
28  * glk_request_char_event().
29  */
30 void
31 glk_request_char_event_uni(winid_t win)
32 {
33         g_return_if_fail(win);
34         g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
35         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
36
37         win->input_request_type = INPUT_REQUEST_CHARACTER_UNICODE;
38         g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
39 }
40
41 /* Internal function: Request either latin-1 or unicode line input, in a text grid window. */
42 void
43 text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
44 {
45         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
46
47     GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position");
48     GtkTextIter start_iter, end_iter;
49     gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, cursor);
50     
51     /* Determine the maximum length of the line input */
52     gint cursorpos = gtk_text_iter_get_line_offset(&start_iter);
53     /* Odd; the Glk spec says the maximum input length is
54     windowwidth - 1 - cursorposition. I say no, because if cursorposition is
55     zero, then the input should fill the whole line. FIXME??? */
56     win->input_length = MIN(win->width - cursorpos, win->line_input_buffer_max_len);
57     end_iter = start_iter;
58     gtk_text_iter_set_line_offset(&end_iter, cursorpos + win->input_length);
59     
60     /* Erase the text currently in the input field and replace it with a GtkEntry */
61     gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
62     win->input_anchor = gtk_text_buffer_create_child_anchor(buffer, &start_iter);
63     win->input_entry = gtk_entry_new();
64         /* Set the entry's font to match that of the window */
65     GtkRcStyle *style = gtk_widget_get_modifier_style(win->widget);     /* Don't free */
66         gtk_widget_modify_font(win->input_entry, style->font_desc);
67         /* Make the entry as small as possible to fit with the text */
68         gtk_entry_set_has_frame(GTK_ENTRY(win->input_entry), FALSE);
69         GtkBorder border = { 0, 0, 0, 0 };
70         gtk_entry_set_inner_border(GTK_ENTRY(win->input_entry), &border);
71     gtk_entry_set_max_length(GTK_ENTRY(win->input_entry), win->input_length);
72     gtk_entry_set_width_chars(GTK_ENTRY(win->input_entry), win->input_length);
73     
74     /* Insert pre-entered text if needed */
75     if(insert)
76         gtk_entry_set_text(GTK_ENTRY(win->input_entry), inserttext);
77     
78     /* Set background color of entry (TODO: implement as property) */
79     GdkColor background;
80         gdk_color_parse("grey", &background);
81     gtk_widget_modify_base(win->input_entry, GTK_STATE_NORMAL, &background);
82     
83     g_signal_connect(win->input_entry, "activate", G_CALLBACK(on_input_entry_activate), win);
84     
85     gtk_widget_show(win->input_entry);
86     gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(win->widget), win->input_entry, win->input_anchor);
87         
88         g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler );
89 }
90     
91 /* Internal function: Request either latin-1 or unicode line input, in a text buffer window. */
92 void
93 text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext)
94 {
95         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
96
97     /* Move the input_position mark to the end of the window_buffer */
98     GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
99     GtkTextIter end_iter;
100     gtk_text_buffer_get_end_iter(buffer, &end_iter);
101     gtk_text_buffer_move_mark(buffer, input_position, &end_iter);
102
103     /* Set the entire contents of the window_buffer as uneditable
104      * (so input can only be entered at the end) */
105     GtkTextIter start_iter;
106     gtk_text_buffer_get_start_iter(buffer, &start_iter);
107     gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
108     gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter);
109     
110     /* Insert pre-entered text if needed */
111     if(insert)
112         gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1);
113     
114     /* Scroll to input point */
115     gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position);
116     
117     gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE);
118     g_signal_handler_unblock(buffer, win->insert_text_handler);
119 }
120
121 /**
122  * glk_request_line_event:
123  * @win: A text buffer or text grid window to request line input on.
124  * @buf: A buffer of at least @maxlen bytes.
125  * @maxlen: Length of the buffer.
126  * @initlen: The number of characters in @buf to pre-enter.
127  *
128  * Requests input of a line of Latin-1 characters. A window cannot have requests
129  * for both character and line input at the same time. Nor can it have requests
130  * for line input of both types (Latin-1 and Unicode). It is illegal to call
131  * glk_request_line_event() if the window already has a pending request for
132  * either character or line input.
133  * 
134  * The @buf argument is a pointer to space where the line input will be stored.
135  * (This may not be %NULL.) @maxlen is the length of this space, in bytes; the
136  * library will not accept more characters than this. If @initlen is nonzero,
137  * then the first @initlen bytes of @buf will be entered as pre-existing input
138  * -- just as if the player had typed them himself. (The player can continue
139  * composing after this pre-entered input, or delete it or edit as usual.)
140  * 
141  * The contents of the buffer are undefined until the input is completed (either
142  * by a line input event, or glk_cancel_line_event(). The library may or may not
143  * fill in the buffer as the player composes, while the input is still pending;
144  * it is illegal to change the contents of the buffer yourself. 
145  */
146 void
147 glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen)
148 {
149         g_return_if_fail(win);
150         g_return_if_fail(buf);
151         g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
152         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
153         g_return_if_fail(initlen <= maxlen);
154
155         win->input_request_type = INPUT_REQUEST_LINE;
156         win->line_input_buffer = buf;
157         win->line_input_buffer_max_len = maxlen;
158
159         gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup("");
160         switch(win->type)
161         {
162             case wintype_TextBuffer:
163                 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
164                 break;
165             case wintype_TextGrid:
166                 text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext);
167                 break;
168         default:
169             g_assert_not_reached();
170     }
171         g_free(inserttext);
172 }
173
174 /**
175  * glk_request_line_event_uni:
176  * @win: A text buffer or text grid window to request line input on.
177  * @buf: A buffer of at least @maxlen characters.
178  * @maxlen: Length of the buffer.
179  * @initlen: The number of characters in @buf to pre-enter.
180  *
181  * Request input of a line of Unicode characters. This works the same as
182  * glk_request_line_event(), except the result is stored in an array of
183  * <type>glui32</type> values instead of an array of characters, and the values
184  * may be any valid Unicode code points.
185  * 
186  * The result will be in Unicode Normalization Form C. This basically means that
187  * composite characters will be single characters where possible, instead of
188  * sequences of base and combining marks. See 
189  * <ulink url="http://www.unicode.org/reports/tr15/">Unicode Standard Annex #15
190  * </ulink> for the details.
191  */
192 void
193 glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen)
194 {
195         g_return_if_fail(win);
196         g_return_if_fail(buf);
197         g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE);
198         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
199         g_return_if_fail(initlen <= maxlen);
200
201         win->input_request_type = INPUT_REQUEST_LINE_UNICODE;
202         win->line_input_buffer_unicode = buf;
203         win->line_input_buffer_max_len = maxlen;
204
205         gchar *utf8;
206         if(initlen > 0) {
207                 utf8 = convert_ucs4_to_utf8(buf, initlen);
208                 if(utf8 == NULL)
209                         return;
210         }
211         else
212                 utf8 = g_strdup("");
213
214     switch(win->type)
215         {
216             case wintype_TextBuffer:
217                 text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8);
218                 break;
219             case wintype_TextGrid:
220                 text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8);
221                 break;
222         default:
223             g_assert_not_reached();
224     }           
225         g_free(utf8);
226 }
227
228 /* 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. */
229 gboolean
230 on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win)
231 {
232         /* If this is a text grid window, and line input is active, then redirect the key press to the line input GtkEntry */
233         if( win->type == wintype_TextGrid && (win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) )
234         {
235                 if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up
236                     || event->keyval == GDK_Down || event->keyval == GDK_KP_Down
237                     || event->keyval == GDK_Left || event->keyval == GDK_KP_Left
238                     || event->keyval == GDK_Right || event->keyval == GDK_KP_Right
239                     || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab
240                     || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up
241                     || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down
242                     || event->keyval == GDK_Home || event->keyval == GDK_KP_Home
243                     || event->keyval == GDK_End || event->keyval == GDK_KP_End)
244                         return FALSE; /* Don't redirect these keys */
245                 gtk_widget_grab_focus(win->input_entry);
246                 gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1);
247                 gboolean retval = TRUE;
248                 g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval);
249                 return retval; /* Block this key event if the entry handled it */
250         }
251         if(win->input_request_type != INPUT_REQUEST_CHARACTER && 
252                 win->input_request_type != INPUT_REQUEST_CHARACTER_UNICODE)
253                 return FALSE;
254
255         int keycode;
256
257         switch(event->keyval) {
258                 case GDK_Up:
259                 case GDK_KP_Up: keycode = keycode_Up; break;
260                 case GDK_Down: 
261                 case GDK_KP_Down: keycode = keycode_Down; break;
262                 case GDK_Left:
263                 case GDK_KP_Left: keycode = keycode_Left; break;
264                 case GDK_Right:
265                 case GDK_KP_Right: keycode = keycode_Right; break;
266                 case GDK_Linefeed:
267                 case GDK_Return:
268                 case GDK_KP_Enter: keycode = keycode_Return; break;
269                 case GDK_Delete:
270                 case GDK_BackSpace:
271                 case GDK_KP_Delete: keycode = keycode_Delete; break;
272                 case GDK_Escape: keycode = keycode_Escape; break;
273                 case GDK_Tab: 
274                 case GDK_KP_Tab: keycode = keycode_Tab; break;
275                 case GDK_Page_Up:
276                 case GDK_KP_Page_Up: keycode = keycode_PageUp; break;
277                 case GDK_Page_Down:
278                 case GDK_KP_Page_Down: keycode = keycode_PageDown; break;
279                 case GDK_Home:
280                 case GDK_KP_Home: keycode = keycode_Home; break;
281                 case GDK_End:
282                 case GDK_KP_End: keycode = keycode_End; break;
283                 case GDK_F1: 
284                 case GDK_KP_F1: keycode = keycode_Func1; break;
285                 case GDK_F2: 
286                 case GDK_KP_F2: keycode = keycode_Func2; break;
287                 case GDK_F3: 
288                 case GDK_KP_F3: keycode = keycode_Func3; break;
289                 case GDK_F4: 
290                 case GDK_KP_F4: keycode = keycode_Func4; break;
291                 case GDK_F5: keycode = keycode_Func5; break;
292                 case GDK_F6: keycode = keycode_Func6; break;
293                 case GDK_F7: keycode = keycode_Func7; break;
294                 case GDK_F8: keycode = keycode_Func8; break;
295                 case GDK_F9: keycode = keycode_Func9; break;
296                 case GDK_F10: keycode = keycode_Func10; break;
297                 case GDK_F11: keycode = keycode_Func11; break;
298                 case GDK_F12: keycode = keycode_Func12; break;
299                 default:
300                         keycode = gdk_keyval_to_unicode(event->keyval);
301                         /* If keycode is 0, then keyval was not recognized; also return
302                         unknown if Latin-1 input was requested and the character is not in
303                         Latin-1 */
304                         if(keycode == 0 || (win->input_request_type == INPUT_REQUEST_CHARACTER && keycode > 255))
305                                 keycode = keycode_Unknown;      
306         }
307
308         event_throw(evtype_CharInput, win, keycode, 0);
309         
310         /* Only one keypress will be handled */
311         win->input_request_type = INPUT_REQUEST_NONE;
312         g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
313
314         return TRUE;
315 }
316
317 /* Internal function: finish handling a line input request, for both text grid and text buffer windows. */
318 static void
319 end_line_input_request(winid_t win, const gchar *inserted_text)
320 {
321     /* Convert the string from UTF-8 to Latin-1 or Unicode */
322     if(win->input_request_type == INPUT_REQUEST_LINE) 
323     {
324         gsize bytes_written;
325         gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written);
326         
327         if(latin1 == NULL)
328         {
329             event_throw(evtype_LineInput, win, 0, 0);
330             return;
331         }
332
333         /* Place input in the echo stream */
334         if(win->echo_stream != NULL) 
335             glk_put_string_stream(win->echo_stream, latin1);
336
337         /* Copy the string (bytes_written does not include the NULL at the end) */
338         int copycount = MIN(win->line_input_buffer_max_len, bytes_written);
339         memcpy(win->line_input_buffer, latin1, copycount);
340         g_free(latin1);
341         event_throw(evtype_LineInput, win, copycount, 0);
342     }
343     else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE) 
344     {
345         glong items_written;
346         gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written);
347         
348         if(unicode == NULL)
349         {
350             event_throw(evtype_LineInput, win, 0, 0);
351             return;
352         }
353
354         /* Place input in the echo stream */
355         if(win->echo_stream != NULL) 
356             glk_put_string_stream_uni(win->echo_stream, unicode);
357
358         /* Copy the string (but not the NULL at the end) */
359         int copycount = MIN(win->line_input_buffer_max_len, items_written);
360         memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar));
361         g_free(unicode);
362         event_throw(evtype_LineInput, win, copycount, 0);
363     }
364     else 
365         g_warning("%s: Wrong input request type.", __func__);
366
367     win->input_request_type = INPUT_REQUEST_NONE;
368 }
369
370 /* Internal function: Callback for signal insert-text on a text buffer window.
371 Runs after the default handler has already inserted the text.
372 FIXME: This function assumes that newline was the last character typed into the
373 window. That assumption is wrong if, for example, text containing a newline was
374 pasted into the window. */
375 void
376 after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win) 
377 {
378         if( strchr(text, '\n') != NULL ) 
379         {
380                 /* Remove signal handlers */
381                 GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
382                 g_signal_handler_block(window_buffer, win->insert_text_handler);
383                 
384                 /* Make the window uneditable again and retrieve the text that was input */
385                 gchar *inserted_text;
386                 GtkTextIter start_iter, end_iter;
387
388         gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE);
389         GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position");
390         gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position);
391         gtk_text_buffer_get_end_iter(window_buffer, &end_iter);
392                 gtk_text_iter_backward_cursor_position(&end_iter); /* don't include \n */
393         
394         inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE);
395
396         end_line_input_request(win, inserted_text);
397         g_free(inserted_text);
398         }
399 }
400
401 /* Internal function: Callback for signal activate on the line input GtkEntry
402 in a text grid window. */
403 void
404 on_input_entry_activate(GtkEntry *input_entry, winid_t win)
405 {
406         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
407         
408         gchar *text = g_strdup(gtk_entry_get_text(input_entry));
409         /* Move the focus back into the text view */
410         gtk_widget_grab_focus(win->widget);
411         /* Remove entry widget from text view */
412         /* Should be ok even though this is the widget's own signal handler */
413         gtk_container_remove( GTK_CONTAINER(win->widget), GTK_WIDGET(input_entry) );
414         win->input_entry = NULL;
415         /* Delete the child anchor */
416         GtkTextIter start, end;
417         gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor);
418         end = start;
419         gtk_text_iter_forward_char(&end); /* Point after the child anchor */
420         gtk_text_buffer_delete(buffer, &start, &end);
421         win->input_anchor = NULL;
422         
423     gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' ');
424     gchar *text_to_insert = g_strconcat(text, spaces, NULL);
425         g_free(spaces);
426     gtk_text_buffer_insert(buffer, &start, text_to_insert, -1);
427     g_free(text_to_insert);
428     
429         g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler );
430         
431     end_line_input_request(win, text);
432         g_free(text);
433 }