Implemented line input in text grid windows using a GtkEntry.
[rodin/chimara.git] / src / window.c
1 #include "window.h"
2 #include "chimara-glk-private.h"
3
4 extern ChimaraGlkPrivate *glk_data;
5
6 /**
7  * glk_window_iterate:
8  * @win: A window, or %NULL.
9  * @rockptr: Return location for the next window's rock, or %NULL.
10  *
11  * Iterates over the list of windows; if @win is %NULL, it returns the first
12  * window, otherwise the next window after @win. If there are no more, it
13  * returns #NULL. The window's rock is stored in @rockptr. If you don't want
14  * the rocks to be returned, you may set @rockptr to %NULL.
15  *
16  * The order in which windows are returned is arbitrary. The root window is
17  * not necessarily first, nor is it necessarily last. The order may change
18  * every time you create or destroy a window, invalidating the iteration.
19  *
20  * Returns: the next window, or %NULL if there are no more.
21  */
22 winid_t
23 glk_window_iterate(winid_t win, glui32 *rockptr)
24 {
25         GNode *retnode;
26         
27         if(win == NULL)
28                 retnode = glk_data->root_window;
29         else
30         {
31                 GNode *node = win->window_node;
32                 if( G_NODE_IS_LEAF(node) )
33                 {
34                         while(node && node->next == NULL)
35                                 node = node->parent;
36                         if(node)
37                                 retnode = node->next;
38                         else
39                                 retnode = NULL;
40                 }
41                 else
42                         retnode = g_node_first_child(node);
43         }
44         winid_t retval = retnode? (winid_t)retnode->data : NULL;
45                 
46         /* Store the window's rock in rockptr */
47         if(retval && rockptr)
48                 *rockptr = glk_window_get_rock(retval);
49                 
50         return retval;
51 }
52
53 /**
54  * glk_window_get_rock:
55  * @win: A window.
56  * 
57  * Returns @win's rock value. Pair windows always have rock 0; all other windows
58  * have the rock value you created them with.
59  *
60  * Returns: A rock value.
61  */
62 glui32
63 glk_window_get_rock(winid_t win)
64 {
65         g_return_val_if_fail(win != NULL, 0);
66         return win->rock;
67 }
68
69 /**
70  * glk_window_get_type:
71  * @win: A window.
72  *
73  * Returns @win's type, one of #wintype_Blank, #wintype_Pair,
74  * #wintype_TextBuffer, #wintype_TextGrid, or #wintype_Graphics.
75  *
76  * Returns: The window's type.
77  */
78 glui32
79 glk_window_get_type(winid_t win)
80 {
81         g_return_val_if_fail(win != NULL, 0);
82         return win->type;
83 }
84
85 /**
86  * glk_window_get_parent:
87  * @win: A window.
88  *
89  * Returns the window @win's parent window. If @win is the root window, this
90  * returns %NULL, since the root window has no parent. Remember that the parent
91  * of every window is a pair window; other window types are always childless.
92  *
93  * Returns: A window, or %NULL.
94  */
95 winid_t
96 glk_window_get_parent(winid_t win)
97 {
98         g_return_val_if_fail(win != NULL, NULL);
99         /* Value will also be NULL if win is the root window */
100         return (winid_t)win->window_node->parent->data;
101 }
102
103 /**
104  * glk_window_get_sibling:
105  * @win: A window.
106  *
107  * Returns the other child of the window @win's parent. If @win is the
108  * root window, this returns %NULL.
109  *
110  * Returns: A window, or %NULL.
111  */
112 winid_t
113 glk_window_get_sibling(winid_t win)
114 {
115         g_return_val_if_fail(win != NULL, NULL);
116         
117         if(G_NODE_IS_ROOT(win->window_node))
118                 return NULL;
119         if(win->window_node->next)
120                 return (winid_t)win->window_node->next;
121         return (winid_t)win->window_node->prev;
122 }
123
124 /**
125  * glk_window_get_root:
126  * 
127  * Returns the root window. If there are no windows, this returns %NULL.
128  *
129  * Returns: A window, or %NULL.
130  */
131 winid_t
132 glk_window_get_root()
133 {
134         if(glk_data->root_window == NULL)
135                 return NULL;
136         return (winid_t)glk_data->root_window->data;
137 }
138
139 /* Determine the size of a "0" character in pixels */
140 static void
141 text_window_get_char_size(GtkWidget *textview, int *width, int *height)
142 {
143     PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
144     pango_layout_get_pixel_size(zero, width, height);
145     g_object_unref(zero);
146 }
147
148 /**
149  * glk_window_open:
150  * @split: The window to split to create the new window. Must be 0 if there
151  * are no windows yet.
152  * @method: Position of the new window and method of size computation. One of
153  * #winmethod_Above, #winmethod_Below, #winmethod_Left, or #winmethod_Right
154  * OR'ed with #winmethod_Fixed or #winmethod_Proportional. If @wintype is
155  * #wintype_Blank, then #winmethod_Fixed is not allowed.
156  * @size: Size of the new window, in percentage points if @method is
157  * #winmethod_Proportional, otherwise in characters if @wintype is 
158  * #wintype_TextBuffer or #wintype_TextGrid, or pixels if @wintype is
159  * #wintype_Graphics.
160  * @wintype: Type of the new window. One of #wintype_Blank, #wintype_TextGrid,
161  * #wintype_TextBuffer, or #wintype_Graphics.
162  * @rock: The new window's rock value.
163  *
164  * If there are no windows, create a new root window. @split must be 0, and
165  * @method and @size are ignored. Otherwise, split window @split into two, with
166  * position, size, and type specified by @method, @size, and @wintype. See the
167  * Glk documentation for the window placement algorithm.
168  *
169  * Returns: the new window, or %NULL on error.
170  */
171 winid_t
172 glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, 
173                 glui32 rock)
174 {
175         /*
176         if(split)
177         {
178                 g_warning("glk_window_open: splitting of windows not implemented");
179                 return NULL;
180         }
181         */
182
183         if(split == NULL && glk_data->root_window != NULL)
184         {
185                 g_warning("glk_window_open: there is already a root window");
186                 return NULL;
187         }
188         
189         gdk_threads_enter();
190         
191         /* We only create one window and don't support any more than that */
192         winid_t win = g_new0(struct glk_window_struct, 1);
193         win->rock = rock;
194         win->type = wintype;
195         win->window_node = g_node_new(win);
196
197         switch(wintype)
198         {
199                 case wintype_Blank:
200                 {
201                         /* A blank window will be a label without any text */
202                         GtkWidget *label = gtk_label_new("");
203                         gtk_widget_show(label);
204                         
205                         win->widget = label;
206                         win->frame = label;
207                         /* A blank window has no size */
208                         win->unit_width = 0;
209                         win->unit_height = 0;
210                         /* You can print to a blank window's stream, but it does nothing */
211                         win->window_stream = window_stream_new(win);
212                         win->echo_stream = NULL;
213                 }
214                         break;
215                 
216                 case wintype_TextGrid:
217                 {
218                     GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
219                     GtkWidget *textview = gtk_text_view_new();
220                     
221                     gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_NEVER );
222                     
223                     gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_CHAR );
224                     gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
225                     
226                     gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
227                     gtk_widget_show_all(scrolledwindow);
228                                 
229                         /* Set the window's font */
230                     /* TODO: Use Pango to pick out a monospace font on the system */
231                         PangoFontDescription *font = pango_font_description_from_string("Monospace");
232                         gtk_widget_modify_font(textview, font);
233                         pango_font_description_free(font);
234                     
235                     win->widget = textview;
236                     win->frame = scrolledwindow;
237                     text_window_get_char_size( textview, &(win->unit_width), &(win->unit_height) );
238                         
239                         /* Set the other parameters (width and height are set later) */
240                         win->window_stream = window_stream_new(win);
241                         win->echo_stream = NULL;
242                         win->input_request_type = INPUT_REQUEST_NONE;
243                         win->line_input_buffer = NULL;
244                         win->line_input_buffer_unicode = NULL;
245                         
246                         /* Connect signal handlers */
247                         win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
248                         g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
249                 }
250                     break;
251                 
252                 case wintype_TextBuffer:
253                 {
254                         GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
255                         GtkWidget *textview = gtk_text_view_new();
256                         GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
257
258                         gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR );
259                         gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
260
261                         gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
262                         gtk_widget_show_all(scrolledwindow);
263
264                         win->widget = textview;
265                         win->frame = scrolledwindow;
266             text_window_get_char_size( textview, &(win->unit_width), &(win->unit_height) );
267                         
268                         /* Set the other parameters */
269                         win->window_stream = window_stream_new(win);
270                         win->echo_stream = NULL;
271                         win->input_request_type = INPUT_REQUEST_NONE;
272                         win->line_input_buffer = NULL;
273                         win->line_input_buffer_unicode = NULL;
274
275                         /* Connect signal handlers */
276                         win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
277                         g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
278
279                         win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win );
280                         g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler );
281
282                         /* Create an editable tag to indicate uneditable parts of the window
283                         (for line input) */
284                         gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
285
286                         /* Mark the position where the user will input text */
287                         GtkTextIter end;
288                         gtk_text_buffer_get_end_iter(textbuffer, &end);
289                         gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE);
290                 }
291                         break;
292                         
293                 default:
294                         gdk_threads_leave();
295                         g_warning("%s: unsupported window type", __func__);
296                         g_free(win);
297                         g_node_destroy(glk_data->root_window);
298                         glk_data->root_window = NULL;
299                         return NULL;
300         }
301
302         if(split)
303         {
304                 /* When splitting, construct a new parent window
305                  * copying most characteristics from the window that is being split */
306                 winid_t pair = g_new0(struct glk_window_struct, 1);
307                 pair->rock = 0;
308                 pair->type = wintype_Pair;
309                 pair->window_node = g_node_new(pair);
310                 pair->unit_width = split->unit_width;
311                 pair->unit_height = split->unit_height;
312                 pair->window_stream = NULL;
313                 pair->echo_stream = NULL;
314
315                 /* Insert the new window into the window tree */
316                 if(split->window_node->parent == NULL)
317                 {
318                         glk_data->root_window = pair->window_node;
319                 } else {
320                         g_node_append(split->window_node->parent, pair->window_node);
321                         g_node_unlink(split->window_node);
322                 }
323
324                 /* Keep track of the parent widget of the window that is being split */
325                 GtkWidget* old_parent = gtk_widget_get_parent(split->frame);
326                 gtk_widget_ref(split->frame);
327                 gtk_widget_unparent(split->frame);
328
329                 /* Place the windows in the correct order */
330                 switch(method & winmethod_DirMask)
331                 {
332                         case winmethod_Left:
333                                 pair->widget = gtk_hbox_new(FALSE, 0);
334                                 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
335                                 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
336                                 g_node_append(pair->window_node, split->window_node);
337                                 g_node_append(pair->window_node, win->window_node);
338                                 break;
339                         case winmethod_Right:
340                                 pair->widget = gtk_hbox_new(FALSE, 0);
341                                 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
342                                 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
343                                 g_node_append(pair->window_node, win->window_node);
344                                 g_node_append(pair->window_node, split->window_node);
345                                 break;
346                         case winmethod_Above:
347                                 pair->widget = gtk_vbox_new(FALSE, 0);
348                                 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
349                                 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
350                                 g_node_append(pair->window_node, split->window_node);
351                                 g_node_append(pair->window_node, win->window_node);
352                                 break;
353                         case winmethod_Below:
354                                 pair->widget = gtk_vbox_new(FALSE, 0);
355                                 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
356                                 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
357                                 g_node_append(pair->window_node, win->window_node);
358                                 g_node_append(pair->window_node, split->window_node);
359                                 break;
360                 }
361                 gtk_widget_unref(split->frame);
362
363                 /* TODO: set the new size of the windows */
364
365                 pair->frame = pair->widget;
366                 gtk_widget_set_parent(pair->widget, old_parent);
367                 gtk_widget_show(pair->widget);
368         } else {
369                 /* Set the window as root window */
370                 glk_data->root_window = win->window_node;
371                 gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self));
372                 gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
373         }
374
375     /* For text grid windows, wait until GTK draws the window (see note in glk_window_get_size() ), calculate the size and fill the buffer with blanks. */
376     if(wintype == wintype_TextGrid)
377     {
378         while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
379         {
380             /* Release the GDK lock momentarily */
381             gdk_threads_leave();
382             gdk_threads_enter();
383             while(gtk_events_pending())
384                 gtk_main_iteration();
385         }
386         win->width = (glui32)(win->widget->allocation.width / win->unit_width);
387         win->height = (glui32)(win->widget->allocation.height / win->unit_height);
388                 
389         /* Mark the cursor position */
390         GtkTextIter begin;
391         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
392         gtk_text_buffer_get_start_iter(buffer, &begin);
393         gtk_text_buffer_create_mark(buffer, "cursor_position", &begin, TRUE);
394         
395         /* Fill the buffer with blanks and move the cursor to the upper left */
396         gdk_threads_leave();
397         glk_window_clear(win);
398         gdk_threads_enter();
399         
400         /* Apparently this only works after the window has been realized */
401         gtk_text_view_set_overwrite( GTK_TEXT_VIEW(win->widget), TRUE );
402     }
403
404         gdk_threads_leave();
405
406         return win;
407 }
408
409 /**
410  * glk_window_close:
411  * @win: Window to close.
412  * @result: Pointer to a #stream_result_t in which to store the write count.
413  *
414  * Closes @win, which is pretty much exactly the opposite of opening a window.
415  * It is legal to close all your windows, or to close the root window (which is
416  * the same thing.) 
417  *
418  * The @result argument is filled with the output character count of the window
419  * stream.
420  */
421 void
422 glk_window_close(winid_t win, stream_result_t *result)
423 {
424         GNode* parent_node;
425
426         g_return_if_fail(win != NULL);
427
428         gdk_threads_enter();
429
430         switch(win->type)
431         {
432                 case wintype_Blank:
433                         gtk_widget_destroy(win->widget);
434                         break;
435         
436             case wintype_TextGrid:
437                 case wintype_TextBuffer:
438                         gtk_widget_destroy(win->frame);
439                         /* TODO: Cancel all input requests */
440                         break;
441
442                 case wintype_Pair:
443                 {
444                         GNode* left_child = g_node_first_child(win->window_node);
445                         GNode* right_child = g_node_last_child(win->window_node);
446
447                         glk_window_close((winid_t) left_child->data, result);
448                         glk_window_close((winid_t) right_child->data, result);
449
450                         gtk_widget_destroy(win->widget);
451                 }
452                         break;
453
454                 default:
455                         g_warning("%s: unsupported window type", __func__);
456                         gdk_threads_leave();
457                         return;
458         }
459
460         stream_close_common(win->window_stream, result);
461
462         /* Parent window changes from a split window into the sibling window */
463         if( (parent_node = win->window_node->parent) != NULL )
464         {
465                 winid_t pair = (winid_t) parent_node->data;
466                 if(parent_node->parent == NULL)
467                 {
468                         if(parent_node->next)
469                                 glk_data->root_window = parent_node->next;
470                         else if(parent_node->prev)
471                                 glk_data->root_window = parent_node->prev;
472                 } else {
473                         if(parent_node->next)
474                                 g_node_append(parent_node->parent, parent_node->next);
475                         else if(parent_node->prev)
476                                 g_node_append(parent_node->parent, parent_node->prev);
477                 }
478
479                 g_node_unlink(parent_node);
480                 g_free(pair);
481         }
482
483         g_node_destroy(win->window_node);
484         g_free(win);
485
486         gdk_threads_leave();
487 }
488
489 /**
490  * glk_window_clear:
491  * @win: A window.
492  *
493  * Erases the window @win. The meaning of this depends on the window type.
494  *
495  * <itemizedlist>
496  *  <listitem><para>
497  *   Text buffer: This may do any number of things, such as delete all text in 
498  *   the window, or print enough blank lines to scroll all text beyond 
499  *   visibility, or insert a page-break marker which is treated specially by the
500  *   display part of the library.
501  *  </para></listitem>
502  *  <listitem><para>
503  *   Text grid: This will clear the window, filling all positions with blanks.
504  *   The window cursor is moved to the top left corner (position 0,0).
505  *  </para></listitem>
506  *  <listitem><para>
507  *   Graphics: Clears the entire window to its current background color.
508  *  </para></listitem>
509  *  <listitem><para>
510  *   Other window types: No effect. 
511  *  </para></listitem>
512  * </itemizedlist>
513  *
514  * It is illegal to erase a window which has line input pending. 
515  */
516 void
517 glk_window_clear(winid_t win)
518 {
519         g_return_if_fail(win != NULL);
520         g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE);
521         
522         switch(win->type)
523         {
524                 case wintype_Blank:
525                 case wintype_Pair:
526                         /* do nothing */
527                         break;
528                 
529                 case wintype_TextGrid:
530                     /* fill the buffer with blanks */
531                 {
532                     gdk_threads_enter();
533                     
534             /* Manually put newlines at the end of each row of characters in the buffer; manual newlines make resizing the window's grid easier. */
535             gchar *blanks = g_strnfill(win->width, ' ');
536             gchar **blanklines = g_new0(gchar *, win->height + 1);
537             int count;
538             for(count = 0; count < win->height; count++)
539                 blanklines[count] = blanks;
540             blanklines[win->height] = NULL;
541             gchar *text = g_strjoinv("\n", blanklines);
542             g_free(blanklines); /* not g_strfreev() */
543             g_free(blanks);
544             
545             GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
546             gtk_text_buffer_set_text(textbuffer, text, -1);
547             g_free(text);
548             
549             GtkTextIter begin;
550             gtk_text_buffer_get_start_iter(textbuffer, &begin);
551             gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &begin);
552                     
553                     gdk_threads_leave();
554                 }
555                     break;
556                 
557                 case wintype_TextBuffer:
558                         /* delete all text in the window */
559                 {
560                         gdk_threads_enter();
561
562                         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
563                         GtkTextIter start, end;
564                         gtk_text_buffer_get_bounds(buffer, &start, &end);
565                         gtk_text_buffer_delete(buffer, &start, &end);
566
567                         gdk_threads_leave();
568                 }
569                         break;
570                 
571                 default:
572                         g_warning("glk_window_clear: unsupported window type");
573         }
574 }
575
576 /**
577  * glk_set_window:
578  * @win: A window.
579  *
580  * Sets the current stream to @win's window stream. It is exactly equivalent to
581  * <informalexample><programlisting>
582  *  glk_stream_set_current(glk_window_get_stream(win))
583  * </programlisting></informalexample>
584  */
585 void
586 glk_set_window(winid_t win)
587 {
588         glk_stream_set_current( glk_window_get_stream(win) );
589 }
590
591 /**
592  * glk_window_get_stream:
593  * @win: A window.
594  *
595  * Returns the stream which is associated with @win. Every window has a stream
596  * which can be printed to, but this may not be useful, depending on the window
597  * type. (For example, printing to a blank window's stream has no effect.)
598  *
599  * Returns: The window stream.
600  */
601 strid_t glk_window_get_stream(winid_t win)
602 {
603         g_return_val_if_fail(win != NULL, NULL);
604         return win->window_stream;
605 }
606
607 /**
608  * glk_window_set_echo_stream:
609  * @win: A window.
610  * @str: A stream to attach to the window, or %NULL.
611  *
612  * Attaches the stream @str to @win as a second stream. Any text printed to the
613  * window is also echoed to this second stream, which is called the window's
614  * "echo stream."
615  *
616  * Effectively, any call to glk_put_char() (or the other output commands) which
617  * is directed to @win's window stream, is replicated to @win's echo stream.
618  * This also goes for the style commands such as glk_set_style().
619  *
620  * Note that the echoing is one-way. You can still print text directly to the
621  * echo stream, and it will go wherever the stream is bound, but it does not
622  * back up and appear in the window.
623  *
624  * It is illegal to set a window's echo stream to be its own window stream,
625  * which would create an infinite loop. It is similarly illegal to create a
626  * longer loop (two or more windows echoing to each other.)
627  *
628  * You can reset a window to stop echoing by setting @str to %NULL.
629  */
630 void
631 glk_window_set_echo_stream(winid_t win, strid_t str)
632 {
633         g_return_if_fail(win != NULL);
634         
635         /* Test for an infinite loop */
636         strid_t next = str;
637         for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream)
638         {
639                 if(next == win->window_stream)
640                 {
641                         g_warning("%s: Infinite loop detected", __func__);
642                         win->echo_stream = NULL;
643                         return;
644                 }
645         }
646         
647         win->echo_stream = str;
648 }
649
650 /**
651  * glk_window_get_echo_stream:
652  * @win: A window.
653  *
654  * Returns the echo stream of window @win. If the window has no echo stream (as
655  * is initially the case) then this returns %NULL.
656  *
657  * Returns: A stream, or %NULL.
658  */
659 strid_t
660 glk_window_get_echo_stream(winid_t win)
661 {
662         g_return_val_if_fail(win != NULL, NULL);
663         return win->echo_stream;
664 }
665
666 /**
667  * glk_window_get_size:
668  * @win: A window.
669  * @widthptr: Pointer to a location to store the window's width, or %NULL.
670  * @heightptr: Pointer to a location to store the window's height, or %NULL.
671  *
672  * Simply returns the actual size of the window, in its measurement system.
673  * Either @widthptr or @heightptr can be %NULL, if you only want one 
674  * measurement. (Or, in fact, both, if you want to waste time.)
675  */
676 void
677 glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
678 {
679         g_return_if_fail(win != NULL);
680
681     switch(win->type)
682     {
683         case wintype_Blank:
684             if(widthptr != NULL)
685                 *widthptr = 0;
686             if(heightptr != NULL)
687                 *heightptr = 0;
688             break;
689             
690         case wintype_TextGrid:
691             /* The text grid caches its width and height */
692             if(widthptr != NULL)
693                 *widthptr = win->width;
694             if(heightptr != NULL)
695                 *heightptr = win->height;
696             break;
697             
698         case wintype_TextBuffer:
699             /* TODO: Glk wants to be able to get its windows' sizes as soon as they are created, but GTK doesn't decide on their sizes until they are drawn. The drawing happens somewhere in an idle function. A good method would be to make an educated guess of the window's size using the ChimaraGlk widget's size. */
700             gdk_threads_enter();
701             /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
702             {
703                 g_warning("glk_window_get_size: The Glk program requested the size of a window before it was allocated screen space by GTK. The window size is just an educated guess.");
704                 guess the size from the parent window;
705                 break;
706             } */
707             
708             /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */
709             while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
710             {
711                 /* Release the GDK lock momentarily */
712                 gdk_threads_leave();
713                 gdk_threads_enter();
714                 while(gtk_events_pending())
715                     gtk_main_iteration();
716             }
717                 
718             if(widthptr != NULL)
719                 *widthptr = (glui32)(win->widget->allocation.width / win->unit_width);
720             if(heightptr != NULL)
721                 *heightptr = (glui32)(win->widget->allocation.height / win->unit_height);
722             gdk_threads_leave();
723             
724             break;
725             
726         default:
727             g_warning("glk_window_get_size: Unsupported window type");
728     }
729 }
730
731 /**
732  * glk_window_move_cursor:
733  * @win: A text grid window.
734  * @xpos: Horizontal cursor position.
735  * @ypos: Vertical cursor position.
736  * 
737  * Sets the cursor position. If you move the cursor right past the end of a 
738  * line, it wraps; the next character which is printed will appear at the
739  * beginning of the next line.
740  * 
741  * If you move the cursor below the last line, or when the cursor reaches the
742  * end of the last line, it goes "off the screen" and further output has no
743  * effect. You must call glk_window_move_cursor() or glk_window_clear() to move
744  * the cursor back into the visible region.
745  * 
746  * <note><para>
747  *  Note that the arguments of glk_window_move_cursor() are <type>unsigned 
748  *  int</type>s. This is okay, since there are no negative positions. If you try
749  *  to pass a negative value, Glk will interpret it as a huge positive value,
750  *  and it will wrap or go off the last line.
751  * </para></note>
752  *
753  * <note><para>
754  *  Also note that the output cursor is not necessarily visible. In particular,
755  *  when you are requesting line or character input in a grid window, you cannot
756  *  rely on the cursor position to prompt the player where input is indicated.
757  *  You should print some character prompt at that spot -- a ">" character, for
758  *  example.
759  * </para></note>
760  */
761 void
762 glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
763 {
764         g_return_if_fail(win != NULL);
765         g_return_if_fail(win->type == wintype_TextGrid);
766         
767         /* Calculate actual position if cursor is moved past the right edge */
768         if(xpos >= win->width)
769         {
770             ypos += xpos / win->width;
771             xpos %= win->width;
772         }
773         /* Go to the end if the cursor is moved off the bottom edge */
774         if(ypos >= win->height)
775         {
776             xpos = win->width - 1;
777             ypos = win->height - 1;
778         }
779         
780         gdk_threads_enter();
781         
782         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
783         GtkTextIter newpos;
784         /* There must actually be a character at xpos, or the following function will choke */
785         gtk_text_buffer_get_iter_at_line_offset(buffer, &newpos, ypos, xpos);
786         gtk_text_buffer_move_mark_by_name(buffer, "cursor_position", &newpos);
787         
788         gdk_threads_leave();
789 }