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