719816f58eeb77c1567228379c7e192b0f61c76a
[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  * |[ 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
378         if(split == NULL && glk_data->root_window != NULL)
379         {
380                 ILLEGAL("Tried to open a new root window, but there is already a root window");
381                 return NULL;
382         }
383         
384         gdk_threads_enter();
385         
386         /* Create the new window */
387         winid_t win = g_new0(struct glk_window_struct, 1);
388         win->magic = MAGIC_WINDOW;
389         win->rock = rock;
390         win->type = wintype;
391         win->window_node = g_node_new(win);
392
393         switch(wintype)
394         {
395                 case wintype_Blank:
396                 {
397                         /* A blank window will be a label without any text */
398                         GtkWidget *label = gtk_label_new("");
399                         gtk_widget_show(label);
400                         
401                         win->widget = label;
402                         win->frame = label;
403                         /* A blank window has no size */
404                         win->unit_width = 0;
405                         win->unit_height = 0;
406                         /* You can print to a blank window's stream, but it does nothing */
407                         win->window_stream = window_stream_new(win);
408                         win->echo_stream = NULL;
409                 }
410                         break;
411                 
412                 case wintype_TextGrid:
413                 {
414                     GtkWidget *textview = gtk_text_view_new();
415
416                     gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_NONE );
417                     gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
418                         gtk_widget_show(textview);
419                                 
420                         /* Set the window's font */
421                         gtk_widget_modify_font(textview, glk_data->monospace_font_desc);
422                     
423                     win->widget = textview;
424                     win->frame = textview;
425                         
426                         /* Determine the size of a "0" character in pixels */
427                         PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
428                         pango_layout_set_font_description(zero, glk_data->monospace_font_desc);
429                         pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height));
430                         g_object_unref(zero);
431                         
432                         /* Set the other parameters (width and height are set later) */
433                         win->window_stream = window_stream_new(win);
434                         win->echo_stream = NULL;
435                         win->input_request_type = INPUT_REQUEST_NONE;
436                         win->line_input_buffer = NULL;
437                         win->line_input_buffer_unicode = NULL;
438                         
439                         /* Connect signal handlers */
440                         win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
441                         g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
442                 }
443                     break;
444                 
445                 case wintype_TextBuffer:
446                 {
447                         GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
448                         GtkWidget *textview = gtk_text_view_new();
449                         GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
450
451                         gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
452                         
453                         gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR );
454                         gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
455
456                         gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
457                         gtk_widget_show_all(scrolledwindow);
458
459                         /* Set the window's font */
460                         gtk_widget_modify_font(textview, glk_data->default_font_desc);
461                         
462                         win->widget = textview;
463                         win->frame = scrolledwindow;
464                         
465                         /* Determine the size of a "0" character in pixels */
466                         PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
467                         pango_layout_set_font_description(zero, glk_data->default_font_desc);
468                         pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height));
469                         g_object_unref(zero);
470                         
471                         /* Set the other parameters */
472                         win->window_stream = window_stream_new(win);
473                         win->echo_stream = NULL;
474                         win->input_request_type = INPUT_REQUEST_NONE;
475                         win->line_input_buffer = NULL;
476                         win->line_input_buffer_unicode = NULL;
477
478                         /* Connect signal handlers */
479                         win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
480                         g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
481
482                         win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win );
483                         g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler );
484
485                         /* Create an editable tag to indicate uneditable parts of the window
486                         (for line input) */
487                         gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
488
489                         /* Mark the position where the user will input text */
490                         GtkTextIter end;
491                         gtk_text_buffer_get_end_iter(textbuffer, &end);
492                         gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE);
493                 }
494                         break;
495                         
496                 default:
497                         gdk_threads_leave();
498                         ILLEGAL_PARAM("Unknown window type: %u", wintype);
499                         g_free(win);
500                         g_node_destroy(glk_data->root_window);
501                         glk_data->root_window = NULL;
502                         return NULL;
503         }
504
505         /* Set the minimum size to "as small as possible" so it doesn't depend on
506          the size of the window contents */
507         gtk_widget_set_size_request(win->widget, 0, 0);
508         gtk_widget_set_size_request(win->frame, 0, 0);
509         
510         if(split)
511         {
512                 /* When splitting, construct a new parent window
513                  * copying most characteristics from the window that is being split */
514                 winid_t pair = g_new0(struct glk_window_struct, 1);
515                 pair->magic = MAGIC_WINDOW;
516                 pair->rock = 0;
517                 pair->type = wintype_Pair;
518                 pair->window_node = g_node_new(pair);
519                 /* You can print to a pair window's window stream, but it has no effect */
520                 pair->window_stream = window_stream_new(pair);
521                 pair->echo_stream = NULL;
522
523                 /* The pair window must know about its children's split method */
524                 pair->key_window = win;
525                 pair->split_method = method;
526                 pair->constraint_size = size;
527                 
528                 /* Insert the new window into the window tree */
529                 if(split->window_node->parent == NULL)
530                         glk_data->root_window = pair->window_node;
531                 else 
532                 {
533                         if( split->window_node == g_node_first_sibling(split->window_node) )
534                                 g_node_prepend(split->window_node->parent, pair->window_node);
535                         else
536                                 g_node_append(split->window_node->parent, pair->window_node);
537                         g_node_unlink(split->window_node);
538                 }
539                 /* Place the windows in the correct order */
540                 switch(method & winmethod_DirMask)
541                 {
542                         case winmethod_Left:
543                         case winmethod_Above:
544                                 g_node_append(pair->window_node, win->window_node);
545                                 g_node_append(pair->window_node, split->window_node);
546                                 break;
547                         case winmethod_Right:
548                         case winmethod_Below:
549                                 g_node_append(pair->window_node, split->window_node);
550                                 g_node_append(pair->window_node, win->window_node);
551                                 break;
552                 }
553
554         } else {
555                 /* Set the window as root window */
556                 glk_data->root_window = win->window_node;
557         }
558
559         /* Set the window as a child of the Glk widget */
560         gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self));
561         gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
562         
563         gdk_threads_leave();
564         
565         /* For blank or pair windows, this is almost a no-op. For text grid and
566          text buffer windows, this will wait for GTK to draw the window. Otherwise,
567          opening a window and getting its size immediately will give you the wrong
568          size. */
569         glk_window_get_size(win, NULL, NULL);
570         
571     /* For text grid windows, fill the buffer with blanks. */
572     if(wintype == wintype_TextGrid)
573     {
574         /* Create the cursor position mark */
575                 gdk_threads_enter();
576         GtkTextIter begin;
577         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
578         gtk_text_buffer_get_start_iter(buffer, &begin);
579         gtk_text_buffer_create_mark(buffer, "cursor_position", &begin, TRUE);
580         gdk_threads_leave();
581                 
582         /* Fill the buffer with blanks and move the cursor to the upper left */
583         glk_window_clear(win);
584     }
585
586         return win;
587 }
588
589 /* Internal function: if node's key window is closing_win or one of its
590  children, set node's key window to NULL. */
591 static gboolean 
592 remove_key_windows(GNode *node, winid_t closing_win)
593 {
594         winid_t win = (winid_t)node->data;
595         if(win->key_window && (win->key_window == closing_win || g_node_is_ancestor(closing_win->window_node, win->key_window->window_node)))
596                 win->key_window = NULL;
597         return FALSE; /* Don't stop the traversal */
598 }
599
600 /* Internal function: destroy this window's GTK widgets, window streams, 
601  and those of all its children */
602 static void
603 destroy_windows_below(winid_t win, stream_result_t *result)
604 {
605         switch(win->type)
606         {
607                 case wintype_Blank:
608                         gdk_threads_enter();
609                         gtk_widget_unparent(win->widget);
610                         gdk_threads_leave();
611                         break;
612         
613             case wintype_TextGrid:
614                 case wintype_TextBuffer:
615                         gdk_threads_enter();
616                         gtk_widget_unparent(win->frame);
617                         gdk_threads_leave();
618                         /* TODO: Cancel all input requests */
619                         break;
620
621                 case wintype_Pair:
622                         destroy_windows_below(win->window_node->children->data, NULL);
623                         destroy_windows_below(win->window_node->children->next->data, NULL);
624                         break;
625
626                 default:
627                         ILLEGAL_PARAM("Unknown window type: %u", win->type);
628                         return;
629         }
630         stream_close_common(win->window_stream, result);
631 }
632
633 /* Internal function: free the winid_t structure of this window and those of all its children */
634 static void
635 free_winids_below(winid_t win)
636 {
637         if(win->type == wintype_Pair) {
638                 free_winids_below(win->window_node->children->data);
639                 free_winids_below(win->window_node->children->next->data);
640         }
641         win->magic = MAGIC_FREE;
642         g_free(win);
643 }
644
645 /**
646  * glk_window_close:
647  * @win: Window to close.
648  * @result: Pointer to a #stream_result_t in which to store the write count.
649  *
650  * Closes @win, which is pretty much exactly the opposite of opening a window.
651  * It is legal to close all your windows, or to close the root window (which is
652  * the same thing.) 
653  *
654  * The @result argument is filled with the output character count of the window
655  * stream. See <link linkend="chimara-Streams">Streams</link> and <link
656  * linkend="chimara-Closing-Streams">Closing Streams</link>.
657  * 
658  * When you close a window (and it is not the root window), the other window
659  * in its pair takes over all the freed-up area. Let's close D, in the current
660  * example:
661  * <informaltable frame="none"><tgroup cols="2"><tbody><row>
662  * <entry><mediaobject><imageobject><imagedata fileref="fig10.png"/>
663  * </imageobject></mediaobject></entry>
664  * <entry><mediaobject><textobject><literallayout class="monospaced">
665  *      O1
666  *     / \
667  *    O2  B
668  *   / \
669  *  A   C
670  * </literallayout></textobject></mediaobject></entry> 
671  * </row></tbody></tgroup></informaltable>
672  * 
673  * Notice what has happened. D is gone. O3 is gone, and its 50-50 left-right
674  * split has gone with it. The other size constraints are unchanged; O2 is
675  * still committed to giving its upper child two rows, as measured in the font
676  * of O2's key window, which is C. Conveniently, O2's upper child is C, just as
677  * it was before we created D. In fact, now that D is gone, everything is back
678  * to the way it was before we created D.
679  * 
680  * But what if we had closed C instead of D? We would have gotten this:
681  * <informaltable frame="none"><tgroup cols="2"><tbody><row>
682  * <entry><mediaobject><imageobject><imagedata fileref="fig11.png"/>
683  * </imageobject></mediaobject></entry>
684  * <entry><mediaobject><textobject><literallayout class="monospaced">
685  *      O1
686  *     / \
687  *    O2  B
688  *   / \
689  *  A   D
690  * </literallayout></textobject></mediaobject></entry> 
691  * </row></tbody></tgroup></informaltable>
692  * 
693  * Again, O3 is gone. But D has collapsed to zero height. This is because its
694  * height is controlled by O2, and O2's key window was C, and C is now gone. O2
695  * no longer has a key window at all, so it cannot compute a height for its
696  * upper child, so it defaults to zero.
697  * 
698  * <note><para>
699  *   This may seem to be an inconvenient choice. That is deliberate. You should
700  *   not leave a pair window with no key, and the zero-height default reminds
701  *   you not to. You can use glk_window_set_arrangement() to set a new split
702  *   measurement and key window. See <link 
703  *   linkend="chimara-Changing-Window-Constraints">Changing Window
704  *   Constraints</link>.
705  * </para></note>
706  */
707 void
708 glk_window_close(winid_t win, stream_result_t *result)
709 {
710         VALID_WINDOW(win, return);
711         
712         /* If any pair windows have this window or its children as a key window,
713          set their key window to NULL */
714         g_node_traverse(glk_data->root_window, G_IN_ORDER, G_TRAVERSE_NON_LEAVES, -1, (GNodeTraverseFunc)remove_key_windows, win);
715         
716         /* Close all the window streams and destroy the widgets of this window
717          and below, before trashing the window tree */
718         destroy_windows_below(win, result);
719         
720         /* Then free the winid_t structures below this node, but not this one itself */
721         if(win->type == wintype_Pair) {
722                 free_winids_below(win->window_node->children->data);
723                 free_winids_below(win->window_node->children->next->data);
724         }
725         /* So now we should be left with a skeleton tree hanging off this node */       
726         
727         /* Parent window changes from a split window into the sibling window */
728         /* The parent of any window is either a pair window or NULL */
729         GNode *pair_node = win->window_node->parent;
730         g_node_destroy(win->window_node);
731         /* If win was not the root window: */
732         if(pair_node != NULL)
733         {
734                 gboolean new_child_on_left = ( pair_node == g_node_first_sibling(pair_node) );
735                 GNode *sibling_node = pair_node->children; /* only one child left */
736                 GNode *new_parent_node = pair_node->parent;
737                 g_node_unlink(pair_node);
738                 g_node_unlink(sibling_node);
739                 /* pair_node and sibling_node should now be totally unconnected to the tree */
740                 
741                 if(new_parent_node == NULL)
742                 {
743                         glk_data->root_window = sibling_node;
744                 } 
745                 else 
746                 {
747                         if(new_child_on_left)
748                                 g_node_prepend(new_parent_node, sibling_node);
749                         else
750                                 g_node_append(new_parent_node, sibling_node);
751                 }
752
753                 winid_t pair = (winid_t) pair_node->data;
754                 g_node_destroy(pair_node);
755                 
756                 pair->magic = MAGIC_FREE;
757                 g_free(pair);
758         } 
759         else /* it was the root window */
760         {
761                 glk_data->root_window = NULL;
762         }
763
764         win->magic = MAGIC_FREE;
765         g_free(win);
766
767         /* Schedule a redraw */
768         gdk_threads_enter();
769         gtk_widget_queue_resize( GTK_WIDGET(glk_data->self) );
770         gdk_window_process_all_updates();
771         gdk_threads_leave();
772 }
773
774 /**
775  * glk_window_clear:
776  * @win: A window.
777  *
778  * Erases @win. The meaning of this depends on the window type.
779  * <variablelist>
780  * <varlistentry>
781  *  <term>Text buffer</term>
782  *  <listitem><para>
783  *   This may do any number of things, such as delete all text in the window, or
784  *   print enough blank lines to scroll all text beyond visibility, or insert a
785  *   page-break marker which is treated specially by the display part of the
786  *   library.
787  *  </para></listitem>
788  * </varlistentry>
789  * <varlistentry>
790  *  <term>Text grid</term>
791  *  <listitem><para>
792  *   This will clear the window, filling all positions with blanks. The window
793  *   cursor is moved to the top left corner (position 0,0).
794  *  </para></listitem>
795  * </varlistentry>
796  * <varlistentry>
797  *  <term>Graphics</term>
798  *  <listitem><para>
799  *   Clears the entire window to its current background color. See <link
800  *   linkend="chimara-Graphics-Windows">Graphics Windows</link>.
801  *  </para></listitem>
802  * </varlistentry>
803  * <varlistentry>
804  *  <term>Other window types</term>
805  *  <listitem><para>No effect.</para></listitem>
806  * </varlistentry>
807  * </variablelist>
808  *
809  * It is illegal to erase a window which has line input pending. 
810  */
811 void
812 glk_window_clear(winid_t win)
813 {
814         VALID_WINDOW(win, return);
815         g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE);
816         
817         switch(win->type)
818         {
819                 case wintype_Blank:
820                 case wintype_Pair:
821                         /* do nothing */
822                         break;
823                 
824                 case wintype_TextGrid:
825                     /* fill the buffer with blanks */
826                 {
827                     gdk_threads_enter();
828                     
829             /* Manually put newlines at the end of each row of characters in the buffer; manual newlines make resizing the window's grid easier. */
830             gchar *blanks = g_strnfill(win->width, ' ');
831             gchar **blanklines = g_new0(gchar *, win->height + 1);
832             int count;
833             for(count = 0; count < win->height; count++)
834                 blanklines[count] = blanks;
835             blanklines[win->height] = NULL;
836             gchar *text = g_strjoinv("\n", blanklines);
837             g_free(blanklines); /* not g_strfreev() */
838             g_free(blanks);
839             
840             GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
841             gtk_text_buffer_set_text(textbuffer, text, -1);
842             g_free(text);
843             
844             GtkTextIter begin;
845             gtk_text_buffer_get_start_iter(textbuffer, &begin);
846             gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &begin);
847                     
848                     gdk_threads_leave();
849                 }
850                     break;
851                 
852                 case wintype_TextBuffer:
853                         /* delete all text in the window */
854                 {
855                         gdk_threads_enter();
856
857                         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
858                         GtkTextIter start, end;
859                         gtk_text_buffer_get_bounds(buffer, &start, &end);
860                         gtk_text_buffer_delete(buffer, &start, &end);
861
862                         gdk_threads_leave();
863                 }
864                         break;
865                 
866                 default:
867                         ILLEGAL_PARAM("Unknown window type: %d", win->type);
868         }
869 }
870
871 /**
872  * glk_set_window:
873  * @win: A window.
874  *
875  * Sets the current stream to @win's window stream. It is exactly equivalent to
876  * <code>#glk_stream_set_current(#glk_window_get_stream(@win))</code>.
877  */
878 void
879 glk_set_window(winid_t win)
880 {
881         VALID_WINDOW_OR_NULL(win, return);
882         glk_stream_set_current( glk_window_get_stream(win) );
883 }
884
885 /**
886  * glk_window_get_stream:
887  * @win: A window.
888  *
889  * Returns the stream which is associated with @win. (See <link 
890  * linkend="chimara-Window-Streams">Window Streams</link>.) Every window has a
891  * stream which can be printed to, but this may not be useful, depending on the
892  * window type.
893  * 
894  * <note><para>
895  *   For example, printing to a blank window's stream has no effect.
896  * </para></note>
897  *
898  * Returns: A window stream.
899  */
900 strid_t glk_window_get_stream(winid_t win)
901 {
902         VALID_WINDOW(win, return NULL);
903         return win->window_stream;
904 }
905
906 /**
907  * glk_window_set_echo_stream:
908  * @win: A window.
909  * @str: A stream to attach to the window, or %NULL.
910  *
911  * Sets @win's echo stream to @str, which can be any valid output stream. You
912  * can reset a window to stop echoing by calling 
913  * <code>#glk_window_set_echo_stream(@win, %NULL)</code>.
914  *
915  * It is illegal to set a window's echo stream to be its 
916  * <emphasis>own</emphasis> window stream. That would create an infinite loop,
917  * and is nearly certain to crash the Glk library. It is similarly illegal to
918  * create a longer loop (two or more windows echoing to each other.)
919  */
920 void
921 glk_window_set_echo_stream(winid_t win, strid_t str)
922 {
923         VALID_WINDOW(win, return);
924         VALID_STREAM_OR_NULL(str, return);
925         
926         /* Test for an infinite loop */
927         strid_t next = str;
928         for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream)
929         {
930                 if(next == win->window_stream)
931                 {
932                         ILLEGAL("Infinite loop detected");
933                         win->echo_stream = NULL;
934                         return;
935                 }
936         }
937         
938         win->echo_stream = str;
939 }
940
941 /**
942  * glk_window_get_echo_stream:
943  * @win: A window.
944  *
945  * Returns the echo stream of window @win. Initially, a window has no echo
946  * stream, so <code>#glk_window_get_echo_stream(@win)</code> will return %NULL.
947  *
948  * Returns: A stream, or %NULL.
949  */
950 strid_t
951 glk_window_get_echo_stream(winid_t win)
952 {
953         VALID_WINDOW(win, return NULL);
954         return win->echo_stream;
955 }
956
957 /**
958  * glk_window_get_size:
959  * @win: A window.
960  * @widthptr: Pointer to a location to store the window's width, or %NULL.
961  * @heightptr: Pointer to a location to store the window's height, or %NULL.
962  *
963  * Simply returns the actual size of the window, in its measurement system.
964  * As described in <link linkend="chimara-Other-API-Conventions">Other API 
965  * Conventions</link>, either @widthptr or @heightptr can be %NULL, if you
966  * only want one measurement. 
967  *
968  * <note><para>Or, in fact, both, if you want to waste time.</para></note>
969  */
970 void
971 glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
972 {
973         VALID_WINDOW(win, return);
974
975     switch(win->type)
976     {
977         case wintype_Blank:
978                 case wintype_Pair:
979             if(widthptr != NULL)
980                 *widthptr = 0;
981             if(heightptr != NULL)
982                 *heightptr = 0;
983             break;
984             
985         case wintype_TextGrid:
986                         gdk_threads_enter();
987                         /* Wait for the window to be drawn, and then cache the width and height */
988                         gdk_window_process_all_updates();
989                         while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
990                     {
991                         /* Release the GDK lock momentarily */
992                         gdk_threads_leave();
993                         gdk_threads_enter();
994                         while(gtk_events_pending())
995                             gtk_main_iteration();
996                     }
997                     
998                         win->width = (glui32)(win->widget->allocation.width / win->unit_width);
999                     win->height = (glui32)(win->widget->allocation.height / win->unit_height);
1000             gdk_threads_leave();
1001                         
1002             if(widthptr != NULL)
1003                 *widthptr = win->width;
1004             if(heightptr != NULL)
1005                 *heightptr = win->height;
1006             break;
1007             
1008         case wintype_TextBuffer:
1009             /* 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. */
1010             gdk_threads_enter();
1011             /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
1012             {
1013                 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.");
1014                 guess the size from the parent window;
1015                 break;
1016             } */
1017             
1018             /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */
1019             gdk_window_process_all_updates();
1020                         while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
1021             {
1022                 /* Release the GDK lock momentarily */
1023                 gdk_threads_leave();
1024                 gdk_threads_enter();
1025                 while(gtk_events_pending())
1026                     gtk_main_iteration();
1027             }
1028                 
1029             if(widthptr != NULL)
1030                 *widthptr = (glui32)(win->widget->allocation.width / win->unit_width);
1031             if(heightptr != NULL)
1032                 *heightptr = (glui32)(win->widget->allocation.height / win->unit_height);
1033             gdk_threads_leave();
1034             
1035             break;
1036             
1037         default:
1038             ILLEGAL_PARAM("Unknown window type: %u", win->type);
1039     }
1040 }
1041  
1042 /**
1043  * glk_window_move_cursor:
1044  * @win: A text grid window.
1045  * @xpos: Horizontal cursor position.
1046  * @ypos: Vertical cursor position.
1047  * 
1048  * Sets the cursor position. If you move the cursor right past the end of a 
1049  * line, it wraps; the next character which is printed will appear at the
1050  * beginning of the next line.
1051  * 
1052  * If you move the cursor below the last line, or when the cursor reaches the
1053  * end of the last line, it goes <quote>off the screen</quote> and further
1054  * output has no effect. You must call glk_window_move_cursor() or
1055  * glk_window_clear() to move the cursor back into the visible region.
1056  * 
1057  * <note><para>
1058  *  Note that the arguments of glk_window_move_cursor() are <type>unsigned 
1059  *  int</type>s. This is okay, since there are no negative positions. If you try
1060  *  to pass a negative value, Glk will interpret it as a huge positive value,
1061  *  and it will wrap or go off the last line.
1062  * </para></note>
1063  *
1064  * <note><para>
1065  *  Also note that the output cursor is not necessarily visible. In particular,
1066  *  when you are requesting line or character input in a grid window, you cannot
1067  *  rely on the cursor position to prompt the player where input is indicated.
1068  *  You should print some character prompt at that spot &mdash; a 
1069  *  <quote>&gt;</quote> character, for example.
1070  * </para></note>
1071  */
1072 void
1073 glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
1074 {
1075         VALID_WINDOW(win, return);
1076         g_return_if_fail(win->type == wintype_TextGrid);
1077         
1078         /* Calculate actual position if cursor is moved past the right edge */
1079         if(xpos >= win->width)
1080         {
1081             ypos += xpos / win->width;
1082             xpos %= win->width;
1083         }
1084         /* Go to the end if the cursor is moved off the bottom edge */
1085         if(ypos >= win->height)
1086         {
1087             xpos = win->width - 1;
1088             ypos = win->height - 1;
1089         }
1090         
1091         gdk_threads_enter();
1092         
1093         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
1094         GtkTextIter newpos;
1095         /* There must actually be a character at xpos, or the following function will choke */
1096         gtk_text_buffer_get_iter_at_line_offset(buffer, &newpos, ypos, xpos);
1097         gtk_text_buffer_move_mark_by_name(buffer, "cursor_position", &newpos);
1098         
1099         gdk_threads_leave();
1100 }
1101