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