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