Implemented evtype_Arrange events, fixing #8.
[rodin/chimara.git] / libchimara / 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  * |[ newwin = #glk_window_open(win, #winmethod_Above | #winmethod_Proportional, 40, #wintype_TextBuffer, 0); ]|
215  *
216  * To create a text grid which is always five lines high, at the bottom of the
217  * original window, you would do
218  * |[ newwin = #glk_window_open(win, #winmethod_Below | #winmethod_Fixed, 5, #wintype_TextGrid, 0); ]|
219  * 
220  * Note that the meaning of the @size argument depends on the @method argument.
221  * If the method is #winmethod_Fixed, it also depends on the @wintype argument.
222  * The new window is then called the <quote>key window</quote> of this split,
223  * because its window type determines how the split size is computed.
224  * 
225  * <note><para>
226  *   For #winmethod_Proportional splits, you can still call the new window the
227  *   <quote>key window</quote>. But the key window is not important for
228  *   proportional splits, because the size will always be computed as a simple
229  *   ratio of the available space, not a fixed size of one child window.
230  * </para></note>
231  * 
232  * This system is more or less peachy as long as all the constraints work out.
233  * What happens when there is a conflict? The rules are simple. Size control
234  * always flows down the tree, and the player is at the top. Let's bring out an
235  * example:
236  * <informaltable frame="none"><tgroup cols="2"><tbody><row>
237  * <entry><mediaobject><imageobject><imagedata fileref="fig5-7a.png"/>
238  * </imageobject></mediaobject></entry>
239  * <entry><mediaobject><textobject><literallayout class="monospaced">
240  *      O
241  *     / \
242  *    O   B
243  *   / \
244  *  A   C
245  * </literallayout></textobject></mediaobject></entry>
246  * </row></tbody></tgroup></informaltable>
247  * 
248  * First we split A into A and B, with a 50% proportional split. Then we split
249  * A into A and C, with C above, C being a text grid window, and C gets a fixed
250  * size of two rows (as measured in its own font size). A gets whatever remains
251  * of the 50% it had before.
252  * 
253  * Now the player stretches the window vertically.
254  * <informalfigure><mediaobject><imageobject><imagedata fileref="fig6.png"/>
255  * </imageobject></mediaobject></informalfigure>
256  * 
257  * The library figures: the topmost split, the original A/B split, is 50-50. So
258  * B gets half the screen space, and the pair window next to it (the lower
259  * <quote>O</quote>) gets the other half. Then it looks at the lower 
260  * <quote>O</quote>. C gets two rows; A gets the rest. All done.
261  * 
262  * Then the user maliciously starts squeezing the window down, in stages:
263  * <informaltable frame="none"><tgroup cols="5"><tbody><row valign="top">
264  * <entry><mediaobject><imageobject><imagedata fileref="fig5-7a.png"/>
265  * </imageobject></mediaobject></entry>
266  * <entry><mediaobject><imageobject><imagedata fileref="fig7b.png"/>
267  * </imageobject></mediaobject></entry>
268  * <entry><mediaobject><imageobject><imagedata fileref="fig7c.png"/>
269  * </imageobject></mediaobject></entry>
270  * <entry><mediaobject><imageobject><imagedata fileref="fig7d.png"/>
271  * </imageobject></mediaobject></entry>
272  * <entry><mediaobject><imageobject><imagedata fileref="fig7e.png"/>
273  * </imageobject></mediaobject></entry>
274  * </row></tbody></tgroup></informaltable>
275  * 
276  * The logic remains the same. B always gets half the space. At stage 3,
277  * there's no room left for A, so it winds up with zero height. Nothing
278  * displayed in A will be visible. At stage 4, there isn't even room in the
279  * upper 50% to give C its two rows; so it only gets one. Finally, C is
280  * squashed out of existence as well.
281  * 
282  * When a window winds up undersized, it remembers what size it should be. In
283  * the example above, A remembers that it should be two rows; if the user
284  * expands the window to the original size, it would return to the original
285  * layout.
286  * 
287  * The downward flow of control is a bit harsh. After all, in stage 4, there's
288  * room for C to have its two rows if only B would give up some of its 50%. But
289  * this does not happen.
290  * 
291  * <note><para>
292  *   This makes life much easier for the Glk library. To determine the
293  *   configuration of a window, it only needs to look at the window's
294  *   ancestors, never at its descendants. So window layout is a simple
295  *   recursive algorithm, no backtracking.
296  * </para></note>
297  * 
298  * What happens when you split a fixed-size window? The resulting pair window
299  * &mdash; that is, the two new parts together &mdash; retain the same size
300  * constraint as the original window that was split. The key window for the
301  * original split is still the key window for that split, even though it's now
302  * a grandchild instead of a child.
303  * 
304  * The easy, and correct, way to think about this is that the size constraint
305  * is stored by a window's parent, not the window itself; and a constraint
306  * consists of a pointer to a key window plus a size value.
307  * 
308  * <informaltable frame="none"><tgroup cols="6"><tbody><row>
309  * <entry><mediaobject><imageobject><imagedata fileref="fig8a.png"/>
310  * </imageobject></mediaobject></entry>
311  * <entry><mediaobject><textobject><literallayout class="monospaced">
312  *  A   
313  * </literallayout></textobject></mediaobject></entry>
314  * <entry><mediaobject><imageobject><imagedata fileref="fig8b.png"/>
315  * </imageobject></mediaobject></entry>
316  * <entry><mediaobject><textobject><literallayout class="monospaced">
317  *    O1  
318  *   / \  
319  *  A   B 
320  * </literallayout></textobject></mediaobject></entry> 
321  * <entry><mediaobject><imageobject><imagedata fileref="fig8c.png"/>
322  * </imageobject></mediaobject></entry>
323  * <entry><mediaobject><textobject><literallayout class="monospaced">
324  *      O1  
325  *     / \  
326  *    O2  B 
327  *   / \    
328  *  A   C   
329  * </literallayout></textobject></mediaobject></entry> 
330  * </row></tbody></tgroup></informaltable>
331  * After the first split, the new pair window (O1, which covers the whole
332  * screen) knows that its first child (A) is above the second, and gets 50% of
333  * its own area. (A is the key window for this split, but a proportional split
334  * doesn't care about key windows.)
335  * 
336  * After the second split, all this remains true; O1 knows that its first child
337  * gets 50% of its space, and A is O1's key window. But now O1's first child is
338  * O2 instead of A. The newer pair window (O2) knows that its first child (C)
339  * is above the second, and gets a fixed size of two rows. (As measured in C's
340  * font, because C is O2's key window.)
341  * 
342  * If we split C, now, the resulting pair will still be two C-font rows high
343  * &mdash; that is, tall enough for two lines of whatever font C displays. For
344  * the sake of example, we'll do this vertically.
345  * <informaltable frame="none"><tgroup cols="2"><tbody><row>
346  * <entry><mediaobject><imageobject><imagedata fileref="fig9.png"/>
347  * </imageobject></mediaobject></entry>
348  * <entry><mediaobject><textobject><literallayout class="monospaced">
349  *      O1
350  *     / \
351  *    O2  B
352  *   / \
353  *  A   O3
354  *     / \
355  *    C   D
356  * </literallayout></textobject></mediaobject></entry> 
357  * </row></tbody></tgroup></informaltable>
358  * 
359  * O3 now knows that its children have a 50-50 left-right split. O2 is still
360  * committed to giving its upper child, O3, two C-font rows. Again, this is
361  * because C is O2's key window. 
362  *
363  * <note><para>
364  *   This turns out to be a good idea, because it means that C, the text grid
365  *   window, is still two rows high. If O3 had been a upper-lower split, things
366  *   wouldn't work out so neatly. But the rules would still apply. If you don't
367  *   like this, don't do it.
368  * </para></note>
369  *
370  * Returns: the new window, or %NULL on error.
371  */
372 winid_t
373 glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, 
374                 glui32 rock)
375 {
376         VALID_WINDOW_OR_NULL(split, return NULL);
377         g_return_val_if_fail(method == (method & (winmethod_DirMask | winmethod_DivisionMask)), NULL);
378         g_return_val_if_fail(!(((method & winmethod_DivisionMask) == winmethod_Proportional) && size > 100), NULL);     
379
380         if(split == NULL && glk_data->root_window != NULL)
381         {
382                 ILLEGAL("Tried to open a new root window, but there is already a root window");
383                 return NULL;
384         }
385         
386         gdk_threads_enter();
387         
388         /* Create the new window */
389         winid_t win = g_new0(struct glk_window_struct, 1);
390         win->magic = MAGIC_WINDOW;
391         win->rock = rock;
392         win->type = wintype;
393         win->window_node = g_node_new(win);
394
395         switch(wintype)
396         {
397                 case wintype_Blank:
398                 {
399                         /* A blank window will be a label without any text */
400                         GtkWidget *label = gtk_label_new("");
401                         gtk_widget_show(label);
402                         
403                         win->widget = label;
404                         win->frame = label;
405                         /* A blank window has no size */
406                         win->unit_width = 0;
407                         win->unit_height = 0;
408                         /* You can print to a blank window's stream, but it does nothing */
409                         win->window_stream = window_stream_new(win);
410                         win->echo_stream = NULL;
411                 }
412                         break;
413                 
414                 case wintype_TextGrid:
415                 {
416                     GtkWidget *textview = gtk_text_view_new();
417
418                     gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_NONE );
419                     gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
420                         gtk_widget_show(textview);
421                                 
422                         /* Set the window's font */
423                         gtk_widget_modify_font(textview, glk_data->monospace_font_desc);
424                     
425                     win->widget = textview;
426                     win->frame = textview;
427                         
428                         /* Determine the size of a "0" character in pixels */
429                         PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
430                         pango_layout_set_font_description(zero, glk_data->monospace_font_desc);
431                         pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height));
432                         g_object_unref(zero);
433                         
434                         /* Set the other parameters (width and height are set later) */
435                         win->window_stream = window_stream_new(win);
436                         win->echo_stream = NULL;
437                         win->input_request_type = INPUT_REQUEST_NONE;
438                         win->line_input_buffer = NULL;
439                         win->line_input_buffer_unicode = NULL;
440                         
441                         /* Connect signal handlers */
442                         win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
443                         g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
444                 }
445                     break;
446                 
447                 case wintype_TextBuffer:
448                 {
449                         GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
450                         GtkWidget *textview = gtk_text_view_new();
451                         GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
452
453                         gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
454                         
455                         gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR );
456                         gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
457
458                         gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
459                         gtk_widget_show_all(scrolledwindow);
460
461                         /* Set the window's font */
462                         gtk_widget_modify_font(textview, glk_data->default_font_desc);
463                         
464                         win->widget = textview;
465                         win->frame = scrolledwindow;
466                         
467                         /* Determine the size of a "0" character in pixels */
468                         PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
469                         pango_layout_set_font_description(zero, glk_data->default_font_desc);
470                         pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height));
471                         g_object_unref(zero);
472                         
473                         /* Set the other parameters */
474                         win->window_stream = window_stream_new(win);
475                         win->echo_stream = NULL;
476                         win->input_request_type = INPUT_REQUEST_NONE;
477                         win->line_input_buffer = NULL;
478                         win->line_input_buffer_unicode = NULL;
479
480                         /* Connect signal handlers */
481                         win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
482                         g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
483
484                         win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win );
485                         g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler );
486
487                         /* Create an editable tag to indicate uneditable parts of the window
488                         (for line input) */
489                         gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
490
491                         /* Create the default styles available to the window stream */
492                         style_init_textbuffer(textbuffer);
493
494                         /* Mark the position where the user will input text */
495                         GtkTextIter end;
496                         gtk_text_buffer_get_end_iter(textbuffer, &end);
497                         gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE);
498                 }
499                         break;
500                         
501                 default:
502                         gdk_threads_leave();
503                         ILLEGAL_PARAM("Unknown window type: %u", wintype);
504                         g_free(win);
505                         g_node_destroy(glk_data->root_window);
506                         glk_data->root_window = NULL;
507                         return NULL;
508         }
509
510         /* Set the minimum size to "as small as possible" so it doesn't depend on
511          the size of the window contents */
512         gtk_widget_set_size_request(win->widget, 0, 0);
513         gtk_widget_set_size_request(win->frame, 0, 0);
514         
515         if(split)
516         {
517                 /* When splitting, construct a new parent window
518                  * copying most characteristics from the window that is being split */
519                 winid_t pair = g_new0(struct glk_window_struct, 1);
520                 pair->magic = MAGIC_WINDOW;
521                 pair->rock = 0;
522                 pair->type = wintype_Pair;
523                 pair->window_node = g_node_new(pair);
524                 /* You can print to a pair window's window stream, but it has no effect */
525                 pair->window_stream = window_stream_new(pair);
526                 pair->echo_stream = NULL;
527
528                 /* The pair window must know about its children's split method */
529                 pair->key_window = win;
530                 pair->split_method = method;
531                 pair->constraint_size = size;
532                 
533                 /* Insert the new window into the window tree */
534                 if(split->window_node->parent == NULL)
535                         glk_data->root_window = pair->window_node;
536                 else 
537                 {
538                         if( split->window_node == g_node_first_sibling(split->window_node) )
539                                 g_node_prepend(split->window_node->parent, pair->window_node);
540                         else
541                                 g_node_append(split->window_node->parent, pair->window_node);
542                         g_node_unlink(split->window_node);
543                 }
544                 /* Place the windows in the correct order */
545                 switch(method & winmethod_DirMask)
546                 {
547                         case winmethod_Left:
548                         case winmethod_Above:
549                                 g_node_append(pair->window_node, win->window_node);
550                                 g_node_append(pair->window_node, split->window_node);
551                                 break;
552                         case winmethod_Right:
553                         case winmethod_Below:
554                                 g_node_append(pair->window_node, split->window_node);
555                                 g_node_append(pair->window_node, win->window_node);
556                                 break;
557                 }
558
559         } else {
560                 /* Set the window as root window */
561                 glk_data->root_window = win->window_node;
562         }
563
564         /* Set the window as a child of the Glk widget, don't trigger an arrange event */
565         g_mutex_lock(glk_data->arrange_lock);
566         glk_data->ignore_next_arrange_event = TRUE;
567         g_mutex_unlock(glk_data->arrange_lock);
568         gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self));
569         gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
570         
571         gdk_threads_leave();
572         
573         /* For blank or pair windows, this is almost a no-op. For text grid and
574          text buffer windows, this will wait for GTK to draw the window. Otherwise,
575          opening a window and getting its size immediately will give you the wrong
576          size. */
577         glk_window_get_size(win, NULL, NULL);
578         
579     /* For text grid windows, fill the buffer with blanks. */
580     if(wintype == wintype_TextGrid)
581     {
582         /* Create the cursor position mark */
583                 gdk_threads_enter();
584         GtkTextIter begin;
585         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
586         gtk_text_buffer_get_start_iter(buffer, &begin);
587         gtk_text_buffer_create_mark(buffer, "cursor_position", &begin, TRUE);
588         gdk_threads_leave();
589                 
590         /* Fill the buffer with blanks and move the cursor to the upper left */
591         glk_window_clear(win);
592     }
593
594         return win;
595 }
596
597 /* Internal function: if node's key window is closing_win or one of its
598  children, set node's key window to NULL. */
599 static gboolean 
600 remove_key_windows(GNode *node, winid_t closing_win)
601 {
602         winid_t win = (winid_t)node->data;
603         if(win->key_window && (win->key_window == closing_win || g_node_is_ancestor(closing_win->window_node, win->key_window->window_node)))
604                 win->key_window = NULL;
605         return FALSE; /* Don't stop the traversal */
606 }
607
608 /* Internal function: destroy this window's GTK widgets, window streams, 
609  and those of all its children. GDK threads must be locked. */
610 static void
611 destroy_windows_below(winid_t win, stream_result_t *result)
612 {
613         switch(win->type)
614         {
615                 case wintype_Blank:
616                         gtk_widget_unparent(win->widget);
617                         break;
618         
619             case wintype_TextGrid:
620                 case wintype_TextBuffer:
621                         gtk_widget_unparent(win->frame);
622                         /* TODO: Cancel all input requests */
623                         break;
624
625                 case wintype_Pair:
626                         destroy_windows_below(win->window_node->children->data, NULL);
627                         destroy_windows_below(win->window_node->children->next->data, NULL);
628                         break;
629
630                 default:
631                         ILLEGAL_PARAM("Unknown window type: %u", win->type);
632                         return;
633         }
634         stream_close_common(win->window_stream, result);
635 }
636
637 /* Internal function: free the winid_t structure of this window and those of all its children */
638 static void
639 free_winids_below(winid_t win)
640 {
641         if(win->type == wintype_Pair) {
642                 free_winids_below(win->window_node->children->data);
643                 free_winids_below(win->window_node->children->next->data);
644         }
645         win->magic = MAGIC_FREE;
646         g_free(win);
647 }
648
649 /**
650  * glk_window_close:
651  * @win: Window to close.
652  * @result: Pointer to a #stream_result_t in which to store the write count.
653  *
654  * Closes @win, which is pretty much exactly the opposite of opening a window.
655  * It is legal to close all your windows, or to close the root window (which is
656  * the same thing.) 
657  *
658  * The @result argument is filled with the output character count of the window
659  * stream. See <link linkend="chimara-Streams">Streams</link> and <link
660  * linkend="chimara-Closing-Streams">Closing Streams</link>.
661  * 
662  * When you close a window (and it is not the root window), the other window
663  * in its pair takes over all the freed-up area. Let's close D, in the current
664  * example:
665  * <informaltable frame="none"><tgroup cols="2"><tbody><row>
666  * <entry><mediaobject><imageobject><imagedata fileref="fig10.png"/>
667  * </imageobject></mediaobject></entry>
668  * <entry><mediaobject><textobject><literallayout class="monospaced">
669  *      O1
670  *     / \
671  *    O2  B
672  *   / \
673  *  A   C
674  * </literallayout></textobject></mediaobject></entry> 
675  * </row></tbody></tgroup></informaltable>
676  * 
677  * Notice what has happened. D is gone. O3 is gone, and its 50-50 left-right
678  * split has gone with it. The other size constraints are unchanged; O2 is
679  * still committed to giving its upper child two rows, as measured in the font
680  * of O2's key window, which is C. Conveniently, O2's upper child is C, just as
681  * it was before we created D. In fact, now that D is gone, everything is back
682  * to the way it was before we created D.
683  * 
684  * But what if we had closed C instead of D? We would have gotten this:
685  * <informaltable frame="none"><tgroup cols="2"><tbody><row>
686  * <entry><mediaobject><imageobject><imagedata fileref="fig11.png"/>
687  * </imageobject></mediaobject></entry>
688  * <entry><mediaobject><textobject><literallayout class="monospaced">
689  *      O1
690  *     / \
691  *    O2  B
692  *   / \
693  *  A   D
694  * </literallayout></textobject></mediaobject></entry> 
695  * </row></tbody></tgroup></informaltable>
696  * 
697  * Again, O3 is gone. But D has collapsed to zero height. This is because its
698  * height is controlled by O2, and O2's key window was C, and C is now gone. O2
699  * no longer has a key window at all, so it cannot compute a height for its
700  * upper child, so it defaults to zero.
701  * 
702  * <note><para>
703  *   This may seem to be an inconvenient choice. That is deliberate. You should
704  *   not leave a pair window with no key, and the zero-height default reminds
705  *   you not to. You can use glk_window_set_arrangement() to set a new split
706  *   measurement and key window. See <link 
707  *   linkend="chimara-Changing-Window-Constraints">Changing Window
708  *   Constraints</link>.
709  * </para></note>
710  */
711 void
712 glk_window_close(winid_t win, stream_result_t *result)
713 {
714         VALID_WINDOW(win, return);
715         
716         gdk_threads_enter(); /* Prevent redraw while we're trashing the window */
717         
718         /* If any pair windows have this window or its children as a key window,
719          set their key window to NULL */
720         g_node_traverse(glk_data->root_window, G_IN_ORDER, G_TRAVERSE_NON_LEAVES, -1, (GNodeTraverseFunc)remove_key_windows, win);
721         
722         /* Close all the window streams and destroy the widgets of this window
723          and below, before trashing the window tree */
724         destroy_windows_below(win, result);
725         
726         /* Then free the winid_t structures below this node, but not this one itself */
727         if(win->type == wintype_Pair) {
728                 free_winids_below(win->window_node->children->data);
729                 free_winids_below(win->window_node->children->next->data);
730         }
731         /* So now we should be left with a skeleton tree hanging off this node */       
732         
733         /* Parent window changes from a split window into the sibling window */
734         /* The parent of any window is either a pair window or NULL */
735         GNode *pair_node = win->window_node->parent;
736         g_node_destroy(win->window_node);
737         /* If win was not the root window: */
738         if(pair_node != NULL)
739         {
740                 gboolean new_child_on_left = ( pair_node == g_node_first_sibling(pair_node) );
741                 GNode *sibling_node = pair_node->children; /* only one child left */
742                 GNode *new_parent_node = pair_node->parent;
743                 g_node_unlink(pair_node);
744                 g_node_unlink(sibling_node);
745                 /* pair_node and sibling_node should now be totally unconnected to the tree */
746                 
747                 if(new_parent_node == NULL)
748                 {
749                         glk_data->root_window = sibling_node;
750                 } 
751                 else 
752                 {
753                         if(new_child_on_left)
754                                 g_node_prepend(new_parent_node, sibling_node);
755                         else
756                                 g_node_append(new_parent_node, sibling_node);
757                 }
758
759                 winid_t pair = (winid_t) pair_node->data;
760                 g_node_destroy(pair_node);
761                 
762                 pair->magic = MAGIC_FREE;
763                 g_free(pair);
764         } 
765         else /* it was the root window */
766         {
767                 glk_data->root_window = NULL;
768         }
769
770         win->magic = MAGIC_FREE;
771         g_free(win);
772
773         /* Schedule a redraw */
774         g_mutex_lock(glk_data->arrange_lock);
775         glk_data->ignore_next_arrange_event = TRUE;
776         g_mutex_unlock(glk_data->arrange_lock);
777         gtk_widget_queue_resize( GTK_WIDGET(glk_data->self) );
778         gdk_threads_leave();
779 }
780
781 /**
782  * glk_window_clear:
783  * @win: A window.
784  *
785  * Erases @win. The meaning of this depends on the window type.
786  * <variablelist>
787  * <varlistentry>
788  *  <term>Text buffer</term>
789  *  <listitem><para>
790  *   This may do any number of things, such as delete all text in the window, or
791  *   print enough blank lines to scroll all text beyond visibility, or insert a
792  *   page-break marker which is treated specially by the display part of the
793  *   library.
794  *  </para></listitem>
795  * </varlistentry>
796  * <varlistentry>
797  *  <term>Text grid</term>
798  *  <listitem><para>
799  *   This will clear the window, filling all positions with blanks. The window
800  *   cursor is moved to the top left corner (position 0,0).
801  *  </para></listitem>
802  * </varlistentry>
803  * <varlistentry>
804  *  <term>Graphics</term>
805  *  <listitem><para>
806  *   Clears the entire window to its current background color. See <link
807  *   linkend="chimara-Graphics-Windows">Graphics Windows</link>.
808  *  </para></listitem>
809  * </varlistentry>
810  * <varlistentry>
811  *  <term>Other window types</term>
812  *  <listitem><para>No effect.</para></listitem>
813  * </varlistentry>
814  * </variablelist>
815  *
816  * It is illegal to erase a window which has line input pending. 
817  */
818 void
819 glk_window_clear(winid_t win)
820 {
821         VALID_WINDOW(win, return);
822         g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE);
823         
824         switch(win->type)
825         {
826                 case wintype_Blank:
827                 case wintype_Pair:
828                         /* do nothing */
829                         break;
830                 
831                 case wintype_TextGrid:
832                     /* fill the buffer with blanks */
833                 {
834                     gdk_threads_enter();
835                     
836             /* Manually put newlines at the end of each row of characters in the buffer; manual newlines make resizing the window's grid easier. */
837             gchar *blanks = g_strnfill(win->width, ' ');
838             gchar **blanklines = g_new0(gchar *, win->height + 1);
839             int count;
840             for(count = 0; count < win->height; count++)
841                 blanklines[count] = blanks;
842             blanklines[win->height] = NULL;
843             gchar *text = g_strjoinv("\n", blanklines);
844             g_free(blanklines); /* not g_strfreev() */
845             g_free(blanks);
846             
847             GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
848             gtk_text_buffer_set_text(textbuffer, text, -1);
849             g_free(text);
850             
851             GtkTextIter begin;
852             gtk_text_buffer_get_start_iter(textbuffer, &begin);
853             gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &begin);
854                     
855                     gdk_threads_leave();
856                 }
857                     break;
858                 
859                 case wintype_TextBuffer:
860                         /* delete all text in the window */
861                 {
862                         gdk_threads_enter();
863
864                         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
865                         GtkTextIter start, end;
866                         gtk_text_buffer_get_bounds(buffer, &start, &end);
867                         gtk_text_buffer_delete(buffer, &start, &end);
868
869                         gdk_threads_leave();
870                 }
871                         break;
872                 
873                 default:
874                         ILLEGAL_PARAM("Unknown window type: %d", win->type);
875         }
876 }
877
878 /**
879  * glk_set_window:
880  * @win: A window.
881  *
882  * Sets the current stream to @win's window stream. It is exactly equivalent to
883  * <code>#glk_stream_set_current(#glk_window_get_stream(@win))</code>.
884  */
885 void
886 glk_set_window(winid_t win)
887 {
888         VALID_WINDOW_OR_NULL(win, return);
889         glk_stream_set_current( glk_window_get_stream(win) );
890 }
891
892 /**
893  * glk_window_get_stream:
894  * @win: A window.
895  *
896  * Returns the stream which is associated with @win. (See <link 
897  * linkend="chimara-Window-Streams">Window Streams</link>.) Every window has a
898  * stream which can be printed to, but this may not be useful, depending on the
899  * window type.
900  * 
901  * <note><para>
902  *   For example, printing to a blank window's stream has no effect.
903  * </para></note>
904  *
905  * Returns: A window stream.
906  */
907 strid_t glk_window_get_stream(winid_t win)
908 {
909         VALID_WINDOW(win, return NULL);
910         return win->window_stream;
911 }
912
913 /**
914  * glk_window_set_echo_stream:
915  * @win: A window.
916  * @str: A stream to attach to the window, or %NULL.
917  *
918  * Sets @win's echo stream to @str, which can be any valid output stream. You
919  * can reset a window to stop echoing by calling 
920  * <code>#glk_window_set_echo_stream(@win, %NULL)</code>.
921  *
922  * It is illegal to set a window's echo stream to be its 
923  * <emphasis>own</emphasis> window stream. That would create an infinite loop,
924  * and is nearly certain to crash the Glk library. It is similarly illegal to
925  * create a longer loop (two or more windows echoing to each other.)
926  */
927 void
928 glk_window_set_echo_stream(winid_t win, strid_t str)
929 {
930         VALID_WINDOW(win, return);
931         VALID_STREAM_OR_NULL(str, return);
932         
933         /* Test for an infinite loop */
934         strid_t next = str;
935         for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream)
936         {
937                 if(next == win->window_stream)
938                 {
939                         ILLEGAL("Infinite loop detected");
940                         win->echo_stream = NULL;
941                         return;
942                 }
943         }
944         
945         win->echo_stream = str;
946 }
947
948 /**
949  * glk_window_get_echo_stream:
950  * @win: A window.
951  *
952  * Returns the echo stream of window @win. Initially, a window has no echo
953  * stream, so <code>#glk_window_get_echo_stream(@win)</code> will return %NULL.
954  *
955  * Returns: A stream, or %NULL.
956  */
957 strid_t
958 glk_window_get_echo_stream(winid_t win)
959 {
960         VALID_WINDOW(win, return NULL);
961         return win->echo_stream;
962 }
963
964 /**
965  * glk_window_get_size:
966  * @win: A window.
967  * @widthptr: Pointer to a location to store the window's width, or %NULL.
968  * @heightptr: Pointer to a location to store the window's height, or %NULL.
969  *
970  * Simply returns the actual size of the window, in its measurement system.
971  * As described in <link linkend="chimara-Other-API-Conventions">Other API 
972  * Conventions</link>, either @widthptr or @heightptr can be %NULL, if you
973  * only want one measurement. 
974  *
975  * <note><para>Or, in fact, both, if you want to waste time.</para></note>
976  */
977 void
978 glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
979 {
980         VALID_WINDOW(win, return);
981
982     switch(win->type)
983     {
984         case wintype_Blank:
985                 case wintype_Pair:
986             if(widthptr != NULL)
987                 *widthptr = 0;
988             if(heightptr != NULL)
989                 *heightptr = 0;
990             break;
991             
992         case wintype_TextGrid:
993                         gdk_threads_enter();
994                         /* Wait for the window to be drawn, and then cache the width and height */
995                         gdk_window_process_all_updates();
996                         while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
997                     {
998                         /* Release the GDK lock momentarily */
999                         gdk_threads_leave();
1000                         gdk_threads_enter();
1001                         while(gtk_events_pending())
1002                             gtk_main_iteration();
1003                     }
1004                     
1005                         win->width = (glui32)(win->widget->allocation.width / win->unit_width);
1006                     win->height = (glui32)(win->widget->allocation.height / win->unit_height);
1007             gdk_threads_leave();
1008                         
1009             if(widthptr != NULL)
1010                 *widthptr = win->width;
1011             if(heightptr != NULL)
1012                 *heightptr = win->height;
1013             break;
1014             
1015         case wintype_TextBuffer:
1016             /* 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. */
1017             gdk_threads_enter();
1018             /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
1019             {
1020                 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.");
1021                 guess the size from the parent window;
1022                 break;
1023             } */
1024             
1025             /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */
1026             gdk_window_process_all_updates();
1027                         while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
1028             {
1029                 /* Release the GDK lock momentarily */
1030                 gdk_threads_leave();
1031                 gdk_threads_enter();
1032                 while(gtk_events_pending())
1033                     gtk_main_iteration();
1034             }
1035                 
1036             if(widthptr != NULL)
1037                 *widthptr = (glui32)(win->widget->allocation.width / win->unit_width);
1038             if(heightptr != NULL)
1039                 *heightptr = (glui32)(win->widget->allocation.height / win->unit_height);
1040             gdk_threads_leave();
1041             
1042             break;
1043             
1044         default:
1045             ILLEGAL_PARAM("Unknown window type: %u", win->type);
1046     }
1047 }
1048
1049 /**
1050  * glk_window_set_arrangement:
1051  * @win: a pair window to rearrange.
1052  * @method: new method of size computation. One of #winmethod_Above, 
1053  * #winmethod_Below, #winmethod_Left, or #winmethod_Right OR'ed with 
1054  * #winmethod_Fixed or #winmethod_Proportional.
1055  * @size: new size constraint, in percentage points if @method is
1056  * #winmethod_Proportional, otherwise in characters if @win's type is 
1057  * #wintype_TextBuffer or #wintype_TextGrid, or pixels if @win's type is
1058  * #wintype_Graphics.
1059  * @keywin: new key window, or %NULL to leave the key window unchanged.
1060  *
1061  * Changes the size of an existing split &mdash; that is, it changes the 
1062  * constraint of a given pair window.
1063  * 
1064  * Consider the example above, where D has collapsed to zero height. Say D was a
1065  * text buffer window. You could make a more useful layout by doing
1066  * |[
1067  * #winid_t o2;
1068  * o2 = #glk_window_get_parent(d);
1069  * glk_window_set_arrangement(o2, #winmethod_Above | #winmethod_Fixed, 3, d);
1070  * ]|
1071  * That would set D (the upper child of O2) to be O2's key window, and give it a
1072  * fixed size of 3 rows.
1073  * 
1074  * If you later wanted to expand D, you could do
1075  * |[ glk_window_set_arrangement(o2, #winmethod_Above | #winmethod_Fixed, 5, NULL); ]|
1076  * That expands D to five rows. Note that, since O2's key window is already set 
1077  * to D, it is not necessary to provide the @keywin argument; you can pass %NULL
1078  * to mean <quote>leave the key window unchanged.</quote>
1079  * 
1080  * If you do change the key window of a pair window, the new key window 
1081  * <emphasis>must</emphasis> be a descendant of that pair window. In the current
1082  * example, you could change O2's key window to be A, but not B. The key window
1083  * also cannot be a pair window itself.
1084  * 
1085  * |[ glk_window_set_arrangement(o2, #winmethod_Below | #winmethod_Fixed, 3, NULL); ]|
1086  * This changes the constraint to be on the <emphasis>lower</emphasis> child of 
1087  * O2, which is A. The key window is still D; so A would then be three rows high
1088  * as measured in D's font, and D would get the rest of O2's space. That may not
1089  * be what you want. To set A to be three rows high as measured in A's font, you
1090  * would do
1091  * |[ glk_window_set_arrangement(o2, #winmethod_Below | #winmethod_Fixed, 3, a); ]|
1092  * 
1093  * Or you could change O2 to a proportional split:
1094  * |[ glk_window_set_arrangement(o2, #winmethod_Below | #winmethod_Proportional, 30, NULL); ]|
1095  * or
1096  * |[ glk_window_set_arrangement(o2, #winmethod_Above | #winmethod_Proportional, 70, NULL); ]|
1097  * These do exactly the same thing, since 30&percnt; above is the same as 
1098  * 70&percnt; below. You don't need to specify a key window with a proportional
1099  * split, so the @keywin argument is %NULL. (You could actually specify either A
1100  * or D as the key window, but it wouldn't affect the result.)
1101  * 
1102  * Whatever constraint you set, glk_window_get_size() will tell you the actual 
1103  * window size you got.
1104  * 
1105  * Note that you can resize windows, but you can't flip or rotate them. You 
1106  * can't move A above D, or change O2 to a vertical split where A is left or 
1107  * right of D. 
1108  * <note><para>
1109  *   To get this effect you could close one of the windows, and re-split the 
1110  *   other one with glk_window_open().
1111  * </para></note>
1112  */
1113 void
1114 glk_window_set_arrangement(winid_t win, glui32 method, glui32 size, winid_t keywin)
1115 {
1116         VALID_WINDOW(win, return);
1117         VALID_WINDOW_OR_NULL(keywin, return);
1118         g_return_if_fail(win->type == wintype_Pair);
1119         if(keywin)
1120         {
1121                 g_return_if_fail(keywin->type != wintype_Pair);
1122                 g_return_if_fail(g_node_is_ancestor(win->window_node, keywin->window_node));
1123         }
1124         g_return_if_fail(method == (method & (winmethod_DirMask | winmethod_DivisionMask)));
1125         g_return_if_fail(!(((method & winmethod_DivisionMask) == winmethod_Proportional) && size > 100));
1126         
1127         win->split_method = method;
1128         win->constraint_size = size;
1129         if(keywin)
1130                 win->key_window = keywin;
1131
1132         /* Tell GTK to rearrange the windows */
1133         gdk_threads_enter();
1134         g_mutex_lock(glk_data->arrange_lock);
1135         glk_data->ignore_next_arrange_event = TRUE;
1136         g_mutex_unlock(glk_data->arrange_lock);
1137         gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
1138         gdk_threads_leave();
1139 }
1140
1141 /**
1142  * glk_window_get_arrangement:
1143  * @win: a pair window.
1144  * @methodptr: return location for the constraint flags of @win, or %NULL.
1145  * @sizeptr: return location for the constraint size of @win, or %NULL.
1146  * @keywinptr: return location for the key window of @win, or %NULL.
1147  *
1148  * Queries the constraint of a given pair window.
1149  */
1150 void
1151 glk_window_get_arrangement(winid_t win, glui32 *methodptr, glui32 *sizeptr, winid_t *keywinptr)
1152 {
1153         VALID_WINDOW(win, return);
1154         g_return_if_fail(win->type == wintype_Pair);
1155         
1156         if(methodptr)
1157                 *methodptr = win->split_method;
1158         if(sizeptr)
1159                 *sizeptr = win->constraint_size;
1160         if(keywinptr)
1161                 *keywinptr = win->key_window;
1162 }
1163
1164 /**
1165  * glk_window_move_cursor:
1166  * @win: A text grid window.
1167  * @xpos: Horizontal cursor position.
1168  * @ypos: Vertical cursor position.
1169  * 
1170  * Sets the cursor position. If you move the cursor right past the end of a 
1171  * line, it wraps; the next character which is printed will appear at the
1172  * beginning of the next line.
1173  * 
1174  * If you move the cursor below the last line, or when the cursor reaches the
1175  * end of the last line, it goes <quote>off the screen</quote> and further
1176  * output has no effect. You must call glk_window_move_cursor() or
1177  * glk_window_clear() to move the cursor back into the visible region.
1178  * 
1179  * <note><para>
1180  *  Note that the arguments of glk_window_move_cursor() are <type>unsigned 
1181  *  int</type>s. This is okay, since there are no negative positions. If you try
1182  *  to pass a negative value, Glk will interpret it as a huge positive value,
1183  *  and it will wrap or go off the last line.
1184  * </para></note>
1185  *
1186  * <note><para>
1187  *  Also note that the output cursor is not necessarily visible. In particular,
1188  *  when you are requesting line or character input in a grid window, you cannot
1189  *  rely on the cursor position to prompt the player where input is indicated.
1190  *  You should print some character prompt at that spot &mdash; a 
1191  *  <quote>&gt;</quote> character, for example.
1192  * </para></note>
1193  */
1194 void
1195 glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
1196 {
1197         VALID_WINDOW(win, return);
1198         g_return_if_fail(win->type == wintype_TextGrid);
1199         
1200         /* Don't do anything if the window is shrunk down to nothing */
1201         if(win->width == 0 || win->height == 0)
1202                 return;
1203         
1204         /* Calculate actual position if cursor is moved past the right edge */
1205         if(xpos >= win->width)
1206         {
1207             ypos += xpos / win->width;
1208             xpos %= win->width;
1209         }
1210         /* Go to the end if the cursor is moved off the bottom edge */
1211         if(ypos >= win->height)
1212         {
1213             xpos = win->width - 1;
1214             ypos = win->height - 1;
1215         }
1216         
1217         gdk_threads_enter();
1218         
1219         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
1220         GtkTextIter newpos;
1221         /* There must actually be a character at xpos, or the following function will choke */
1222         gtk_text_buffer_get_iter_at_line_offset(buffer, &newpos, ypos, xpos);
1223         gtk_text_buffer_move_mark_by_name(buffer, "cursor_position", &newpos);
1224         
1225         gdk_threads_leave();
1226 }
1227