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