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