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