Use gdk_window_process_all_updates() to send window update events immediately instead...
[rodin/chimara.git] / src / window.c
1 #include "window.h"
2 #include "magic.h"
3 #include "chimara-glk-private.h"
4
5 extern ChimaraGlkPrivate *glk_data;
6
7 /**
8  * glk_window_iterate:
9  * @win: A window, or %NULL.
10  * @rockptr: Return location for the next window's rock, or %NULL.
11  *
12  * This function can be used to iterate through the list of all open windows
13  * (including pair windows.) See <link 
14  * linkend="chimara-Iterating-Through-Opaque-Objects">Iterating Through Opaque
15  * Objects</link>.
16  *
17  * As that section describes, the order in which windows are returned is
18  * arbitrary. The root window is not necessarily first, nor is it necessarily
19  * last.
20  *
21  * Returns: the next window, or %NULL if there are no more.
22  */
23 winid_t
24 glk_window_iterate(winid_t win, glui32 *rockptr)
25 {
26         VALID_WINDOW_OR_NULL(win, return NULL);
27         
28         GNode *retnode;
29         
30         if(win == NULL)
31                 retnode = glk_data->root_window;
32         else
33         {
34                 GNode *node = win->window_node;
35                 if( G_NODE_IS_LEAF(node) )
36                 {
37                         while(node && node->next == NULL)
38                                 node = node->parent;
39                         if(node)
40                                 retnode = node->next;
41                         else
42                                 retnode = NULL;
43                 }
44                 else
45                         retnode = g_node_first_child(node);
46         }
47         winid_t retval = retnode? (winid_t)retnode->data : NULL;
48                 
49         /* Store the window's rock in rockptr */
50         if(retval && rockptr)
51                 *rockptr = glk_window_get_rock(retval);
52                 
53         return retval;
54 }
55
56 /**
57  * glk_window_get_rock:
58  * @win: A window.
59  * 
60  * Returns @win's rock value. Pair windows always have rock 0; all other windows
61  * return whatever rock value you created them with.
62  *
63  * Returns: A rock value.
64  */
65 glui32
66 glk_window_get_rock(winid_t win)
67 {
68         VALID_WINDOW(win, return 0);
69         return win->rock;
70 }
71
72 /**
73  * glk_window_get_type:
74  * @win: A window.
75  *
76  * Returns @win's type, one of #wintype_Blank, #wintype_Pair,
77  * #wintype_TextBuffer, #wintype_TextGrid, or #wintype_Graphics.
78  *
79  * Returns: The window's type.
80  */
81 glui32
82 glk_window_get_type(winid_t win)
83 {
84         VALID_WINDOW(win, return 0);
85         return win->type;
86 }
87
88 /**
89  * glk_window_get_parent:
90  * @win: A window.
91  *
92  * Returns the window which is the parent of @win. If @win is the root window,
93  * this returns %NULL, since the root window has no parent. Remember that the
94  * parent of every window is a pair window; other window types are always
95  * childless.
96  *
97  * Returns: A window, or %NULL.
98  */
99 winid_t
100 glk_window_get_parent(winid_t win)
101 {
102         VALID_WINDOW(win, return NULL);
103         /* Value will also be NULL if win is the root window */
104         return (winid_t)win->window_node->parent->data;
105 }
106
107 /**
108  * glk_window_get_sibling:
109  * @win: A window.
110  *
111  * Returns the other child of @win's parent. If @win is the root window, this
112  * returns %NULL.
113  *
114  * Returns: A window, or %NULL.
115  */
116 winid_t
117 glk_window_get_sibling(winid_t win)
118 {
119         VALID_WINDOW(win, return NULL);
120         
121         if(G_NODE_IS_ROOT(win->window_node))
122                 return NULL;
123         if(win->window_node->next)
124                 return (winid_t)win->window_node->next;
125         return (winid_t)win->window_node->prev;
126 }
127
128 /**
129  * glk_window_get_root:
130  * 
131  * Returns the root window. If there are no windows, this returns %NULL.
132  *
133  * Returns: A window, or %NULL.
134  */
135 winid_t
136 glk_window_get_root()
137 {
138         if(glk_data->root_window == NULL)
139                 return NULL;
140         return (winid_t)glk_data->root_window->data;
141 }
142
143 /**
144  * glk_window_open:
145  * @split: The window to split to create the new window. Must be 0 if there
146  * are no windows yet.
147  * @method: Position of the new window and method of size computation. One of
148  * #winmethod_Above, #winmethod_Below, #winmethod_Left, or #winmethod_Right
149  * OR'ed with #winmethod_Fixed or #winmethod_Proportional. If @wintype is
150  * #wintype_Blank, then #winmethod_Fixed is not allowed.
151  * @size: Size of the new window, in percentage points if @method is
152  * #winmethod_Proportional, otherwise in characters if @wintype is 
153  * #wintype_TextBuffer or #wintype_TextGrid, or pixels if @wintype is
154  * #wintype_Graphics.
155  * @wintype: Type of the new window. One of #wintype_Blank, #wintype_TextGrid,
156  * #wintype_TextBuffer, or #wintype_Graphics.
157  * @rock: The new window's rock value.
158  *
159  * Creates a new window. If there are no windows, the first three arguments are
160  * meaningless. @split <emphasis>must</emphasis> be 0, and @method and @size
161  * are ignored. @wintype is the type of window you're creating, and @rock is
162  * the rock (see <link linkend="chimara-Rocks">Rocks</link>).
163  *
164  * If any windows exist, new windows must be created by splitting existing
165  * ones. @split is the window you want to split; this <emphasis>must 
166  * not</emphasis> be zero. @method is a mask of constants to specify the
167  * direction and the split method (see below). @size is the size of the split.
168  * @wintype is the type of window you're creating, and @rock is the rock.
169  *
170  * Remember that it is possible that the library will be unable to create a new
171  * window, in which case glk_window_open() will return %NULL.
172  * 
173  * <note><para>
174  *   It is acceptable to gracefully exit, if the window you are creating is an
175  *   important one &mdash; such as your first window. But you should not try to
176  *   perform any window operation on the id until you have tested to make sure
177  *   it is non-zero.
178  * </para></note>
179  * 
180  * The examples we've seen so far have the simplest kind of size control. (Yes,
181  * this is <quote>below</quote>.) Every pair is a percentage split, with 
182  * <inlineequation>
183  *   <alt>X</alt>
184  *   <mathphrase>X</mathphrase>
185  * </inlineequation>
186  * percent going to one side, and 
187  * <inlineequation>
188  *   <alt>(100-X)</alt>
189  *   <mathphrase>(100 - X)</mathphrase>
190  * </inlineequation> 
191  * percent going to the other side. If the player resizes the window, the whole
192  * mess expands, contracts, or stretches in a uniform way.
193  * 
194  * As I said above, you can also make fixed-size splits. This is a little more
195  * complicated, because you have to know how this fixed size is measured.
196  * 
197  * Sizes are measured in a way which is different for each window type. For
198  * example, a text grid window is measured by the size of its fixed-width font.
199  * You can make a text grid window which is fixed at a height of four rows, or
200  * ten columns. A text buffer window is measured by the size of its font.
201  * 
202  * <note><para>
203  *   Remember that different windows may use different size fonts. Even two
204  *   text grid windows may use fixed-size fonts of different sizes.
205  * </para></note>
206  *
207  * Graphics windows are measured in pixels, not characters. Blank windows
208  * aren't measured at all; there's no meaningful way to measure them, and
209  * therefore you can't create a blank window of a fixed size, only of a
210  * proportional (percentage) size.
211  * 
212  * So to create a text buffer window which takes the top 40% of the original
213  * window's space, you would execute
214  * <informalexample><programlisting>
215  * newwin = #glk_window_open(win, #winmethod_Above | #winmethod_Proportional, 40, #wintype_TextBuffer, 0);
216  * </programlisting></informalexample>
217  *
218  * To create a text grid which is always five lines high, at the bottom of the
219  * original window, you would do
220  * <informalexample><programlisting>
221  * newwin = #glk_window_open(win, #winmethod_Below | #winmethod_Fixed, 5, #wintype_TextGrid, 0);
222  * </programlisting></informalexample>
223  * 
224  * Note that the meaning of the @size argument depends on the @method argument.
225  * If the method is #winmethod_Fixed, it also depends on the @wintype argument.
226  * The new window is then called the <quote>key window</quote> of this split,
227  * because its window type determines how the split size is computed.
228  * 
229  * <note><para>
230  *   For #winmethod_Proportional splits, you can still call the new window the
231  *   <quote>key window</quote>. But the key window is not important for
232  *   proportional splits, because the size will always be computed as a simple
233  *   ratio of the available space, not a fixed size of one child window.
234  * </para></note>
235  * 
236  * This system is more or less peachy as long as all the constraints work out.
237  * What happens when there is a conflict? The rules are simple. Size control
238  * always flows down the tree, and the player is at the top. Let's bring out an
239  * example:
240  * <informaltable frame="none"><tgroup cols="2"><tbody><row>
241  * <entry><mediaobject><imageobject><imagedata fileref="fig5-7a.png"/>
242  * </imageobject></mediaobject></entry>
243  * <entry><mediaobject><textobject><literallayout class="monospaced">
244  *      O
245  *     / \
246  *    O   B
247  *   / \
248  *  A   C
249  * </literallayout></textobject></mediaobject></entry>
250  * </row></tbody></tgroup></informaltable>
251  * 
252  * First we split A into A and B, with a 50% proportional split. Then we split
253  * A into A and C, with C above, C being a text grid window, and C gets a fixed
254  * size of two rows (as measured in its own font size). A gets whatever remains
255  * of the 50% it had before.
256  * 
257  * Now the player stretches the window vertically.
258  * <informalfigure><mediaobject><imageobject><imagedata fileref="fig6.png"/>
259  * </imageobject></mediaobject></informalfigure>
260  * 
261  * The library figures: the topmost split, the original A/B split, is 50-50. So
262  * B gets half the screen space, and the pair window next to it (the lower
263  * <quote>O</quote>) gets the other half. Then it looks at the lower 
264  * <quote>O</quote>. C gets two rows; A gets the rest. All done.
265  * 
266  * Then the user maliciously starts squeezing the window down, in stages:
267  * <informaltable frame="none"><tgroup cols="5"><tbody><row valign="top">
268  * <entry><mediaobject><imageobject><imagedata fileref="fig5-7a.png"/>
269  * </imageobject></mediaobject></entry>
270  * <entry><mediaobject><imageobject><imagedata fileref="fig7b.png"/>
271  * </imageobject></mediaobject></entry>
272  * <entry><mediaobject><imageobject><imagedata fileref="fig7c.png"/>
273  * </imageobject></mediaobject></entry>
274  * <entry><mediaobject><imageobject><imagedata fileref="fig7d.png"/>
275  * </imageobject></mediaobject></entry>
276  * <entry><mediaobject><imageobject><imagedata fileref="fig7e.png"/>
277  * </imageobject></mediaobject></entry>
278  * </row></tbody></tgroup></informaltable>
279  * 
280  * The logic remains the same. B always gets half the space. At stage 3,
281  * there's no room left for A, so it winds up with zero height. Nothing
282  * displayed in A will be visible. At stage 4, there isn't even room in the
283  * upper 50% to give C its two rows; so it only gets one. Finally, C is
284  * squashed out of existence as well.
285  * 
286  * When a window winds up undersized, it remembers what size it should be. In
287  * the example above, A remembers that it should be two rows; if the user
288  * expands the window to the original size, it would return to the original
289  * layout.
290  * 
291  * The downward flow of control is a bit harsh. After all, in stage 4, there's
292  * room for C to have its two rows if only B would give up some of its 50%. But
293  * this does not happen.
294  * 
295  * <note><para>
296  *   This makes life much easier for the Glk library. To determine the
297  *   configuration of a window, it only needs to look at the window's
298  *   ancestors, never at its descendants. So window layout is a simple
299  *   recursive algorithm, no backtracking.
300  * </para></note>
301  * 
302  * What happens when you split a fixed-size window? The resulting pair window
303  * &mdash; that is, the two new parts together &mdash; retain the same size
304  * constraint as the original window that was split. The key window for the
305  * original split is still the key window for that split, even though it's now
306  * a grandchild instead of a child.
307  * 
308  * The easy, and correct, way to think about this is that the size constraint
309  * is stored by a window's parent, not the window itself; and a constraint
310  * consists of a pointer to a key window plus a size value.
311  * 
312  * <informaltable frame="none"><tgroup cols="6"><tbody><row>
313  * <entry><mediaobject><imageobject><imagedata fileref="fig8a.png"/>
314  * </imageobject></mediaobject></entry>
315  * <entry><mediaobject><textobject><literallayout class="monospaced">
316  *  A   
317  * </literallayout></textobject></mediaobject></entry>
318  * <entry><mediaobject><imageobject><imagedata fileref="fig8b.png"/>
319  * </imageobject></mediaobject></entry>
320  * <entry><mediaobject><textobject><literallayout class="monospaced">
321  *    O1  
322  *   / \  
323  *  A   B 
324  * </literallayout></textobject></mediaobject></entry> 
325  * <entry><mediaobject><imageobject><imagedata fileref="fig8c.png"/>
326  * </imageobject></mediaobject></entry>
327  * <entry><mediaobject><textobject><literallayout class="monospaced">
328  *      O1  
329  *     / \  
330  *    O2  B 
331  *   / \    
332  *  A   C   
333  * </literallayout></textobject></mediaobject></entry> 
334  * </row></tbody></tgroup></informaltable>
335  * After the first split, the new pair window (O1, which covers the whole
336  * screen) knows that its first child (A) is above the second, and gets 50% of
337  * its own area. (A is the key window for this split, but a proportional split
338  * doesn't care about key windows.)
339  * 
340  * After the second split, all this remains true; O1 knows that its first child
341  * gets 50% of its space, and A is O1's key window. But now O1's first child is
342  * O2 instead of A. The newer pair window (O2) knows that its first child (C)
343  * is above the second, and gets a fixed size of two rows. (As measured in C's
344  * font, because C is O2's key window.)
345  * 
346  * If we split C, now, the resulting pair will still be two C-font rows high
347  * &mdash; that is, tall enough for two lines of whatever font C displays. For
348  * the sake of example, we'll do this vertically.
349  * <informaltable frame="none"><tgroup cols="2"><tbody><row>
350  * <entry><mediaobject><imageobject><imagedata fileref="fig9.png"/>
351  * </imageobject></mediaobject></entry>
352  * <entry><mediaobject><textobject><literallayout class="monospaced">
353  *      O1
354  *     / \
355  *    O2  B
356  *   / \
357  *  A   O3
358  *     / \
359  *    C   D
360  * </literallayout></textobject></mediaobject></entry> 
361  * </row></tbody></tgroup></informaltable>
362  * 
363  * O3 now knows that its children have a 50-50 left-right split. O2 is still
364  * committed to giving its upper child, O3, two C-font rows. Again, this is
365  * because C is O2's key window. 
366  *
367  * <note><para>
368  *   This turns out to be a good idea, because it means that C, the text grid
369  *   window, is still two rows high. If O3 had been a upper-lower split, things
370  *   wouldn't work out so neatly. But the rules would still apply. If you don't
371  *   like this, don't do it.
372  * </para></note>
373  *
374  * Returns: the new window, or %NULL on error.
375  */
376 winid_t
377 glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, 
378                 glui32 rock)
379 {
380         VALID_WINDOW_OR_NULL(split, return NULL);
381
382         if(split == NULL && glk_data->root_window != NULL)
383         {
384                 ILLEGAL("Tried to open a new root window, but there is already a root window");
385                 return NULL;
386         }
387         
388         gdk_threads_enter();
389         
390         /* Create the new window */
391         winid_t win = g_new0(struct glk_window_struct, 1);
392         win->magic = MAGIC_WINDOW;
393         win->rock = rock;
394         win->type = wintype;
395         win->window_node = g_node_new(win);
396
397         switch(wintype)
398         {
399                 case wintype_Blank:
400                 {
401                         /* A blank window will be a label without any text */
402                         GtkWidget *label = gtk_label_new("");
403                         gtk_widget_show(label);
404                         
405                         win->widget = label;
406                         win->frame = label;
407                         /* A blank window has no size */
408                         win->unit_width = 0;
409                         win->unit_height = 0;
410                         /* You can print to a blank window's stream, but it does nothing */
411                         win->window_stream = window_stream_new(win);
412                         win->echo_stream = NULL;
413                 }
414                         break;
415                 
416                 case wintype_TextGrid:
417                 {
418                     GtkWidget *textview = gtk_text_view_new();
419
420                     gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_NONE );
421                     gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
422                         gtk_widget_show(textview);
423                                 
424                         /* Set the window's font */
425                         gtk_widget_modify_font(textview, glk_data->monospace_font_desc);
426                     
427                     win->widget = textview;
428                     win->frame = textview;
429                         
430                         /* Determine the size of a "0" character in pixels */
431                         PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
432                         pango_layout_set_font_description(zero, glk_data->monospace_font_desc);
433                         pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height));
434                         g_object_unref(zero);
435                         
436                         /* Set the other parameters (width and height are set later) */
437                         win->window_stream = window_stream_new(win);
438                         win->echo_stream = NULL;
439                         win->input_request_type = INPUT_REQUEST_NONE;
440                         win->line_input_buffer = NULL;
441                         win->line_input_buffer_unicode = NULL;
442                         
443                         /* Connect signal handlers */
444                         win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
445                         g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
446                 }
447                     break;
448                 
449                 case wintype_TextBuffer:
450                 {
451                         GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
452                         GtkWidget *textview = gtk_text_view_new();
453                         GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
454
455                         gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
456                         
457                         gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR );
458                         gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
459
460                         gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
461                         gtk_widget_show_all(scrolledwindow);
462
463                         /* Set the window's font */
464                         gtk_widget_modify_font(textview, glk_data->default_font_desc);
465                         
466                         win->widget = textview;
467                         win->frame = scrolledwindow;
468                         
469                         /* Determine the size of a "0" character in pixels */
470                         PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
471                         pango_layout_set_font_description(zero, glk_data->default_font_desc);
472                         pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height));
473                         g_object_unref(zero);
474                         
475                         /* Set the other parameters */
476                         win->window_stream = window_stream_new(win);
477                         win->echo_stream = NULL;
478                         win->input_request_type = INPUT_REQUEST_NONE;
479                         win->line_input_buffer = NULL;
480                         win->line_input_buffer_unicode = NULL;
481
482                         /* Connect signal handlers */
483                         win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
484                         g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
485
486                         win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win );
487                         g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler );
488
489                         /* Create an editable tag to indicate uneditable parts of the window
490                         (for line input) */
491                         gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
492
493                         /* Mark the position where the user will input text */
494                         GtkTextIter end;
495                         gtk_text_buffer_get_end_iter(textbuffer, &end);
496                         gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE);
497                 }
498                         break;
499                         
500                 default:
501                         gdk_threads_leave();
502                         ILLEGAL_PARAM("Unknown window type: %u", wintype);
503                         g_free(win);
504                         g_node_destroy(glk_data->root_window);
505                         glk_data->root_window = NULL;
506                         return NULL;
507         }
508
509         /* Set the minimum size to "as small as possible" so it doesn't depend on
510          the size of the window contents */
511         gtk_widget_set_size_request(win->widget, 0, 0);
512         gtk_widget_set_size_request(win->frame, 0, 0);
513         
514         if(split)
515         {
516                 /* When splitting, construct a new parent window
517                  * copying most characteristics from the window that is being split */
518                 winid_t pair = g_new0(struct glk_window_struct, 1);
519                 pair->magic = MAGIC_WINDOW;
520                 pair->rock = 0;
521                 pair->type = wintype_Pair;
522                 pair->window_node = g_node_new(pair);
523                 /* You can print to a pair window's window stream, but it has no effect */
524                 pair->window_stream = window_stream_new(pair);
525                 pair->echo_stream = NULL;
526
527                 /* The pair window must know about its children's split method */
528                 pair->key_window = win;
529                 pair->split_method = method;
530                 pair->constraint_size = size;
531                 
532                 /* Insert the new window into the window tree */
533                 if(split->window_node->parent == NULL)
534                         glk_data->root_window = pair->window_node;
535                 else 
536                 {
537                         if( split->window_node == g_node_first_sibling(split->window_node) )
538                                 g_node_prepend(split->window_node->parent, pair->window_node);
539                         else
540                                 g_node_append(split->window_node->parent, pair->window_node);
541                         g_node_unlink(split->window_node);
542                 }
543                 /* Place the windows in the correct order */
544                 switch(method & winmethod_DirMask)
545                 {
546                         case winmethod_Left:
547                         case winmethod_Above:
548                                 g_node_append(pair->window_node, win->window_node);
549                                 g_node_append(pair->window_node, split->window_node);
550                                 break;
551                         case winmethod_Right:
552                         case winmethod_Below:
553                                 g_node_append(pair->window_node, split->window_node);
554                                 g_node_append(pair->window_node, win->window_node);
555                                 break;
556                 }
557
558         } else {
559                 /* Set the window as root window */
560                 glk_data->root_window = win->window_node;
561         }
562
563         /* Set the window as a child of the Glk widget */
564         gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self));
565         gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
566         gdk_window_process_all_updates();
567         
568         gdk_threads_leave();
569         
570         /* For blank or pair windows, this is almost a no-op. For text grid and
571          text buffer windows, this will wait for GTK to draw the window. Otherwise,
572          opening a window and getting its size immediately will give you the wrong
573          size. */
574         glk_window_get_size(win, NULL, NULL);
575         
576     /* For text grid windows, fill the buffer with blanks. */
577     if(wintype == wintype_TextGrid)
578     {
579         /* Create the cursor position mark */
580                 gdk_threads_enter();
581         GtkTextIter begin;
582         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
583         gtk_text_buffer_get_start_iter(buffer, &begin);
584         gtk_text_buffer_create_mark(buffer, "cursor_position", &begin, TRUE);
585         gdk_threads_leave();
586                 
587         /* Fill the buffer with blanks and move the cursor to the upper left */
588         glk_window_clear(win);
589     }
590
591         return win;
592 }
593
594 /* Internal function: if node's key window is closing_win or one of its
595  children, set node's key window to NULL. */
596 static gboolean 
597 remove_key_windows(GNode *node, winid_t closing_win)
598 {
599         winid_t win = (winid_t)node->data;
600         if(win->key_window && (win->key_window == closing_win || g_node_is_ancestor(closing_win->window_node, win->key_window->window_node)))
601                 win->key_window = NULL;
602         return FALSE; /* Don't stop the traversal */
603 }
604
605 /* Internal function: destroy this window's GTK widgets, window streams, 
606  and those of all its children */
607 static void
608 destroy_windows_below(winid_t win, stream_result_t *result)
609 {
610         switch(win->type)
611         {
612                 case wintype_Blank:
613                         gdk_threads_enter();
614                         gtk_widget_unparent(win->widget);
615                         gdk_threads_leave();
616                         break;
617         
618             case wintype_TextGrid:
619                 case wintype_TextBuffer:
620                         gdk_threads_enter();
621                         gtk_widget_unparent(win->frame);
622                         gdk_threads_leave();
623                         /* TODO: Cancel all input requests */
624                         break;
625
626                 case wintype_Pair:
627                         destroy_windows_below(win->window_node->children->data, NULL);
628                         destroy_windows_below(win->window_node->children->next->data, NULL);
629                         break;
630
631                 default:
632                         ILLEGAL_PARAM("Unknown window type: %u", win->type);
633                         return;
634         }
635         stream_close_common(win->window_stream, result);
636 }
637
638 /* Internal function: free the winid_t structure of this window and those of all its children */
639 static void
640 free_winids_below(winid_t win)
641 {
642         if(win->type == wintype_Pair) {
643                 free_winids_below(win->window_node->children->data);
644                 free_winids_below(win->window_node->children->next->data);
645         }
646         win->magic = MAGIC_FREE;
647         g_free(win);
648 }
649
650 /**
651  * glk_window_close:
652  * @win: Window to close.
653  * @result: Pointer to a #stream_result_t in which to store the write count.
654  *
655  * Closes @win, which is pretty much exactly the opposite of opening a window.
656  * It is legal to close all your windows, or to close the root window (which is
657  * the same thing.) 
658  *
659  * The @result argument is filled with the output character count of the window
660  * stream. See <link linkend="chimara-Streams">Streams</link> and <link
661  * linkend="chimara-Closing-Streams">Closing Streams</link>.
662  * 
663  * When you close a window (and it is not the root window), the other window
664  * in its pair takes over all the freed-up area. Let's close D, in the current
665  * example:
666  * <informaltable frame="none"><tgroup cols="2"><tbody><row>
667  * <entry><mediaobject><imageobject><imagedata fileref="fig10.png"/>
668  * </imageobject></mediaobject></entry>
669  * <entry><mediaobject><textobject><literallayout class="monospaced">
670  *      O1
671  *     / \
672  *    O2  B
673  *   / \
674  *  A   C
675  * </literallayout></textobject></mediaobject></entry> 
676  * </row></tbody></tgroup></informaltable>
677  * 
678  * Notice what has happened. D is gone. O3 is gone, and its 50-50 left-right
679  * split has gone with it. The other size constraints are unchanged; O2 is
680  * still committed to giving its upper child two rows, as measured in the font
681  * of O2's key window, which is C. Conveniently, O2's upper child is C, just as
682  * it was before we created D. In fact, now that D is gone, everything is back
683  * to the way it was before we created D.
684  * 
685  * But what if we had closed C instead of D? We would have gotten this:
686  * <informaltable frame="none"><tgroup cols="2"><tbody><row>
687  * <entry><mediaobject><imageobject><imagedata fileref="fig11.png"/>
688  * </imageobject></mediaobject></entry>
689  * <entry><mediaobject><textobject><literallayout class="monospaced">
690  *      O1
691  *     / \
692  *    O2  B
693  *   / \
694  *  A   D
695  * </literallayout></textobject></mediaobject></entry> 
696  * </row></tbody></tgroup></informaltable>
697  * 
698  * Again, O3 is gone. But D has collapsed to zero height. This is because its
699  * height is controlled by O2, and O2's key window was C, and C is now gone. O2
700  * no longer has a key window at all, so it cannot compute a height for its
701  * upper child, so it defaults to zero.
702  * 
703  * <note><para>
704  *   This may seem to be an inconvenient choice. That is deliberate. You should
705  *   not leave a pair window with no key, and the zero-height default reminds
706  *   you not to. You can use glk_window_set_arrangement() to set a new split
707  *   measurement and key window. See <link 
708  *   linkend="chimara-Changing-Window-Constraints">Changing Window
709  *   Constraints</link>.
710  * </para></note>
711  */
712 void
713 glk_window_close(winid_t win, stream_result_t *result)
714 {
715         VALID_WINDOW(win, return);
716         
717         /* If any pair windows have this window or its children as a key window,
718          set their key window to NULL */
719         g_node_traverse(glk_data->root_window, G_IN_ORDER, G_TRAVERSE_NON_LEAVES, -1, (GNodeTraverseFunc)remove_key_windows, win);
720         
721         /* Close all the window streams and destroy the widgets of this window
722          and below, before trashing the window tree */
723         destroy_windows_below(win, result);
724         
725         /* Then free the winid_t structures below this node, but not this one itself */
726         if(win->type == wintype_Pair) {
727                 free_winids_below(win->window_node->children->data);
728                 free_winids_below(win->window_node->children->next->data);
729         }
730         /* So now we should be left with a skeleton tree hanging off this node */       
731         
732         /* Parent window changes from a split window into the sibling window */
733         /* The parent of any window is either a pair window or NULL */
734         GNode *pair_node = win->window_node->parent;
735         g_node_destroy(win->window_node);
736         /* If win was not the root window: */
737         if(pair_node != NULL)
738         {
739                 gboolean new_child_on_left = ( pair_node == g_node_first_sibling(pair_node) );
740                 GNode *sibling_node = pair_node->children; /* only one child left */
741                 GNode *new_parent_node = pair_node->parent;
742                 g_node_unlink(pair_node);
743                 g_node_unlink(sibling_node);
744                 /* pair_node and sibling_node should now be totally unconnected to the tree */
745                 
746                 if(new_parent_node == NULL)
747                 {
748                         glk_data->root_window = sibling_node;
749                 } 
750                 else 
751                 {
752                         if(new_child_on_left)
753                                 g_node_prepend(new_parent_node, sibling_node);
754                         else
755                                 g_node_append(new_parent_node, sibling_node);
756                 }
757
758                 winid_t pair = (winid_t) pair_node->data;
759                 g_node_destroy(pair_node);
760                 
761                 pair->magic = MAGIC_FREE;
762                 g_free(pair);
763         } 
764         else /* it was the root window */
765         {
766                 glk_data->root_window = NULL;
767         }
768
769         win->magic = MAGIC_FREE;
770         g_free(win);
771
772         /* Schedule a redraw */
773         gdk_threads_enter();
774         gtk_widget_queue_resize( GTK_WIDGET(glk_data->self) );
775         gdk_window_process_all_updates();
776         gdk_threads_leave();
777 }
778
779 /**
780  * glk_window_clear:
781  * @win: A window.
782  *
783  * Erases @win. The meaning of this depends on the window type.
784  * <variablelist>
785  * <varlistentry>
786  *  <term>Text buffer</term>
787  *  <listitem><para>
788  *   This may do any number of things, such as delete all text in the window, or
789  *   print enough blank lines to scroll all text beyond visibility, or insert a
790  *   page-break marker which is treated specially by the display part of the
791  *   library.
792  *  </para></listitem>
793  * </varlistentry>
794  * <varlistentry>
795  *  <term>Text grid</term>
796  *  <listitem><para>
797  *   This will clear the window, filling all positions with blanks. The window
798  *   cursor is moved to the top left corner (position 0,0).
799  *  </para></listitem>
800  * </varlistentry>
801  * <varlistentry>
802  *  <term>Graphics</term>
803  *  <listitem><para>
804  *   Clears the entire window to its current background color. See <link
805  *   linkend="chimara-Graphics-Windows">Graphics Windows</link>.
806  *  </para></listitem>
807  * </varlistentry>
808  * <varlistentry>
809  *  <term>Other window types</term>
810  *  <listitem><para>No effect.</para></listitem>
811  * </varlistentry>
812  * </variablelist>
813  *
814  * It is illegal to erase a window which has line input pending. 
815  */
816 void
817 glk_window_clear(winid_t win)
818 {
819         VALID_WINDOW(win, return);
820         g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE);
821         
822         switch(win->type)
823         {
824                 case wintype_Blank:
825                 case wintype_Pair:
826                         /* do nothing */
827                         break;
828                 
829                 case wintype_TextGrid:
830                     /* fill the buffer with blanks */
831                 {
832                     gdk_threads_enter();
833                     
834             /* Manually put newlines at the end of each row of characters in the buffer; manual newlines make resizing the window's grid easier. */
835             gchar *blanks = g_strnfill(win->width, ' ');
836             gchar **blanklines = g_new0(gchar *, win->height + 1);
837             int count;
838             for(count = 0; count < win->height; count++)
839                 blanklines[count] = blanks;
840             blanklines[win->height] = NULL;
841             gchar *text = g_strjoinv("\n", blanklines);
842             g_free(blanklines); /* not g_strfreev() */
843             g_free(blanks);
844             
845             GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
846             gtk_text_buffer_set_text(textbuffer, text, -1);
847             g_free(text);
848             
849             GtkTextIter begin;
850             gtk_text_buffer_get_start_iter(textbuffer, &begin);
851             gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &begin);
852                     
853                     gdk_threads_leave();
854                 }
855                     break;
856                 
857                 case wintype_TextBuffer:
858                         /* delete all text in the window */
859                 {
860                         gdk_threads_enter();
861
862                         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
863                         GtkTextIter start, end;
864                         gtk_text_buffer_get_bounds(buffer, &start, &end);
865                         gtk_text_buffer_delete(buffer, &start, &end);
866
867                         gdk_threads_leave();
868                 }
869                         break;
870                 
871                 default:
872                         ILLEGAL_PARAM("Unknown window type: %d", win->type);
873         }
874 }
875
876 /**
877  * glk_set_window:
878  * @win: A window.
879  *
880  * Sets the current stream to @win's window stream. It is exactly equivalent to
881  * <code>#glk_stream_set_current(#glk_window_get_stream(@win))</code>.
882  */
883 void
884 glk_set_window(winid_t win)
885 {
886         VALID_WINDOW_OR_NULL(win, return);
887         glk_stream_set_current( glk_window_get_stream(win) );
888 }
889
890 /**
891  * glk_window_get_stream:
892  * @win: A window.
893  *
894  * Returns the stream which is associated with @win. (See <link 
895  * linkend="chimara-Window-Streams">Window Streams</link>.) Every window has a
896  * stream which can be printed to, but this may not be useful, depending on the
897  * window type.
898  * 
899  * <note><para>
900  *   For example, printing to a blank window's stream has no effect.
901  * </para></note>
902  *
903  * Returns: A window stream.
904  */
905 strid_t glk_window_get_stream(winid_t win)
906 {
907         VALID_WINDOW(win, return NULL);
908         return win->window_stream;
909 }
910
911 /**
912  * glk_window_set_echo_stream:
913  * @win: A window.
914  * @str: A stream to attach to the window, or %NULL.
915  *
916  * Sets @win's echo stream to @str, which can be any valid output stream. You
917  * can reset a window to stop echoing by calling 
918  * <code>#glk_window_set_echo_stream(@win, %NULL)</code>.
919  *
920  * It is illegal to set a window's echo stream to be its 
921  * <emphasis>own</emphasis> window stream. That would create an infinite loop,
922  * and is nearly certain to crash the Glk library. It is similarly illegal to
923  * create a longer loop (two or more windows echoing to each other.)
924  */
925 void
926 glk_window_set_echo_stream(winid_t win, strid_t str)
927 {
928         VALID_WINDOW(win, return);
929         VALID_STREAM_OR_NULL(str, return);
930         
931         /* Test for an infinite loop */
932         strid_t next = str;
933         for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream)
934         {
935                 if(next == win->window_stream)
936                 {
937                         ILLEGAL("Infinite loop detected");
938                         win->echo_stream = NULL;
939                         return;
940                 }
941         }
942         
943         win->echo_stream = str;
944 }
945
946 /**
947  * glk_window_get_echo_stream:
948  * @win: A window.
949  *
950  * Returns the echo stream of window @win. Initially, a window has no echo
951  * stream, so <code>#glk_window_get_echo_stream(@win)</code> will return %NULL.
952  *
953  * Returns: A stream, or %NULL.
954  */
955 strid_t
956 glk_window_get_echo_stream(winid_t win)
957 {
958         VALID_WINDOW(win, return NULL);
959         return win->echo_stream;
960 }
961
962 /**
963  * glk_window_get_size:
964  * @win: A window.
965  * @widthptr: Pointer to a location to store the window's width, or %NULL.
966  * @heightptr: Pointer to a location to store the window's height, or %NULL.
967  *
968  * Simply returns the actual size of the window, in its measurement system.
969  * As described in <link linkend="chimara-Other-API-Conventions">Other API 
970  * Conventions</link>, either @widthptr or @heightptr can be %NULL, if you
971  * only want one measurement. 
972  *
973  * <note><para>Or, in fact, both, if you want to waste time.</para></note>
974  */
975 void
976 glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
977 {
978         VALID_WINDOW(win, return);
979
980     switch(win->type)
981     {
982         case wintype_Blank:
983                 case wintype_Pair:
984             if(widthptr != NULL)
985                 *widthptr = 0;
986             if(heightptr != NULL)
987                 *heightptr = 0;
988             break;
989             
990         case wintype_TextGrid:
991                         gdk_threads_enter();
992                         /* Wait for the window to be drawn, and then cache the width and height */
993                         gdk_window_process_all_updates();
994                         while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
995                     {
996                         /* Release the GDK lock momentarily */
997                         gdk_threads_leave();
998                         gdk_threads_enter();
999                         while(gtk_events_pending())
1000                             gtk_main_iteration();
1001                     }
1002                     
1003                         win->width = (glui32)(win->widget->allocation.width / win->unit_width);
1004                     win->height = (glui32)(win->widget->allocation.height / win->unit_height);
1005             gdk_threads_leave();
1006                         
1007             if(widthptr != NULL)
1008                 *widthptr = win->width;
1009             if(heightptr != NULL)
1010                 *heightptr = win->height;
1011             break;
1012             
1013         case wintype_TextBuffer:
1014             /* 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. */
1015             gdk_threads_enter();
1016             /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
1017             {
1018                 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.");
1019                 guess the size from the parent window;
1020                 break;
1021             } */
1022             
1023             /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */
1024             gdk_window_process_all_updates();
1025                         while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
1026             {
1027                 /* Release the GDK lock momentarily */
1028                 gdk_threads_leave();
1029                 gdk_threads_enter();
1030                 while(gtk_events_pending())
1031                     gtk_main_iteration();
1032             }
1033                 
1034             if(widthptr != NULL)
1035                 *widthptr = (glui32)(win->widget->allocation.width / win->unit_width);
1036             if(heightptr != NULL)
1037                 *heightptr = (glui32)(win->widget->allocation.height / win->unit_height);
1038             gdk_threads_leave();
1039             
1040             break;
1041             
1042         default:
1043             ILLEGAL_PARAM("Unknown window type: %u", win->type);
1044     }
1045 }
1046  
1047 /**
1048  * glk_window_move_cursor:
1049  * @win: A text grid window.
1050  * @xpos: Horizontal cursor position.
1051  * @ypos: Vertical cursor position.
1052  * 
1053  * Sets the cursor position. If you move the cursor right past the end of a 
1054  * line, it wraps; the next character which is printed will appear at the
1055  * beginning of the next line.
1056  * 
1057  * If you move the cursor below the last line, or when the cursor reaches the
1058  * end of the last line, it goes <quote>off the screen</quote> and further
1059  * output has no effect. You must call glk_window_move_cursor() or
1060  * glk_window_clear() to move the cursor back into the visible region.
1061  * 
1062  * <note><para>
1063  *  Note that the arguments of glk_window_move_cursor() are <type>unsigned 
1064  *  int</type>s. This is okay, since there are no negative positions. If you try
1065  *  to pass a negative value, Glk will interpret it as a huge positive value,
1066  *  and it will wrap or go off the last line.
1067  * </para></note>
1068  *
1069  * <note><para>
1070  *  Also note that the output cursor is not necessarily visible. In particular,
1071  *  when you are requesting line or character input in a grid window, you cannot
1072  *  rely on the cursor position to prompt the player where input is indicated.
1073  *  You should print some character prompt at that spot &mdash; a 
1074  *  <quote>&gt;</quote> character, for example.
1075  * </para></note>
1076  */
1077 void
1078 glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
1079 {
1080         VALID_WINDOW(win, return);
1081         g_return_if_fail(win->type == wintype_TextGrid);
1082         
1083         /* Calculate actual position if cursor is moved past the right edge */
1084         if(xpos >= win->width)
1085         {
1086             ypos += xpos / win->width;
1087             xpos %= win->width;
1088         }
1089         /* Go to the end if the cursor is moved off the bottom edge */
1090         if(ypos >= win->height)
1091         {
1092             xpos = win->width - 1;
1093             ypos = win->height - 1;
1094         }
1095         
1096         gdk_threads_enter();
1097         
1098         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
1099         GtkTextIter newpos;
1100         /* There must actually be a character at xpos, or the following function will choke */
1101         gtk_text_buffer_get_iter_at_line_offset(buffer, &newpos, ypos, xpos);
1102         gtk_text_buffer_move_mark_by_name(buffer, "cursor_position", &newpos);
1103         
1104         gdk_threads_leave();
1105 }
1106