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