3 #include "chimara-glk-private.h"
5 extern ChimaraGlkPrivate *glk_data;
9 * @win: A window, or %NULL.
10 * @rockptr: Return location for the next window's rock, or %NULL.
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
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
21 * Returns: the next window, or %NULL if there are no more.
24 glk_window_iterate(winid_t win, glui32 *rockptr)
26 VALID_WINDOW_OR_NULL(win, return NULL);
31 retnode = glk_data->root_window;
34 GNode *node = win->window_node;
35 if( G_NODE_IS_LEAF(node) )
37 while(node && node->next == NULL)
45 retnode = g_node_first_child(node);
47 winid_t retval = retnode? (winid_t)retnode->data : NULL;
49 /* Store the window's rock in rockptr */
51 *rockptr = glk_window_get_rock(retval);
57 * glk_window_get_rock:
60 * Returns @win's rock value. Pair windows always have rock 0; all other windows
61 * return whatever rock value you created them with.
63 * Returns: A rock value.
66 glk_window_get_rock(winid_t win)
68 VALID_WINDOW(win, return 0);
73 * glk_window_get_type:
76 * Returns @win's type, one of #wintype_Blank, #wintype_Pair,
77 * #wintype_TextBuffer, #wintype_TextGrid, or #wintype_Graphics.
79 * Returns: The window's type.
82 glk_window_get_type(winid_t win)
84 VALID_WINDOW(win, return 0);
89 * glk_window_get_parent:
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
97 * Returns: A window, or %NULL.
100 glk_window_get_parent(winid_t win)
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;
108 * glk_window_get_sibling:
111 * Returns the other child of @win's parent. If @win is the root window, this
114 * Returns: A window, or %NULL.
117 glk_window_get_sibling(winid_t win)
119 VALID_WINDOW(win, return NULL);
121 if(G_NODE_IS_ROOT(win->window_node))
123 if(win->window_node->next)
124 return (winid_t)win->window_node->next;
125 return (winid_t)win->window_node->prev;
129 * glk_window_get_root:
131 * Returns the root window. If there are no windows, this returns %NULL.
133 * Returns: A window, or %NULL.
136 glk_window_get_root()
138 if(glk_data->root_window == NULL)
140 return (winid_t)glk_data->root_window->data;
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
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.
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>).
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.
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.
174 * It is acceptable to gracefully exit, if the window you are creating is an
175 * important one — 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
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
184 * <mathphrase>X</mathphrase>
186 * percent going to one side, and
189 * <mathphrase>(100 - X)</mathphrase>
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.
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.
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.
203 * Remember that different windows may use different size fonts. Even two
204 * text grid windows may use fixed-size fonts of different sizes.
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.
212 * So to create a text buffer window which takes the top 40% of the original
213 * window's space, you would execute
214 * |[ newwin = #glk_window_open(win, #winmethod_Above | #winmethod_Proportional, 40, #wintype_TextBuffer, 0); ]|
216 * To create a text grid which is always five lines high, at the bottom of the
217 * original window, you would do
218 * |[ newwin = #glk_window_open(win, #winmethod_Below | #winmethod_Fixed, 5, #wintype_TextGrid, 0); ]|
220 * Note that the meaning of the @size argument depends on the @method argument.
221 * If the method is #winmethod_Fixed, it also depends on the @wintype argument.
222 * The new window is then called the <quote>key window</quote> of this split,
223 * because its window type determines how the split size is computed.
226 * For #winmethod_Proportional splits, you can still call the new window the
227 * <quote>key window</quote>. But the key window is not important for
228 * proportional splits, because the size will always be computed as a simple
229 * ratio of the available space, not a fixed size of one child window.
232 * This system is more or less peachy as long as all the constraints work out.
233 * What happens when there is a conflict? The rules are simple. Size control
234 * always flows down the tree, and the player is at the top. Let's bring out an
236 * <informaltable frame="none"><tgroup cols="2"><tbody><row>
237 * <entry><mediaobject><imageobject><imagedata fileref="fig5-7a.png"/>
238 * </imageobject></mediaobject></entry>
239 * <entry><mediaobject><textobject><literallayout class="monospaced">
245 * </literallayout></textobject></mediaobject></entry>
246 * </row></tbody></tgroup></informaltable>
248 * First we split A into A and B, with a 50% proportional split. Then we split
249 * A into A and C, with C above, C being a text grid window, and C gets a fixed
250 * size of two rows (as measured in its own font size). A gets whatever remains
251 * of the 50% it had before.
253 * Now the player stretches the window vertically.
254 * <informalfigure><mediaobject><imageobject><imagedata fileref="fig6.png"/>
255 * </imageobject></mediaobject></informalfigure>
257 * The library figures: the topmost split, the original A/B split, is 50-50. So
258 * B gets half the screen space, and the pair window next to it (the lower
259 * <quote>O</quote>) gets the other half. Then it looks at the lower
260 * <quote>O</quote>. C gets two rows; A gets the rest. All done.
262 * Then the user maliciously starts squeezing the window down, in stages:
263 * <informaltable frame="none"><tgroup cols="5"><tbody><row valign="top">
264 * <entry><mediaobject><imageobject><imagedata fileref="fig5-7a.png"/>
265 * </imageobject></mediaobject></entry>
266 * <entry><mediaobject><imageobject><imagedata fileref="fig7b.png"/>
267 * </imageobject></mediaobject></entry>
268 * <entry><mediaobject><imageobject><imagedata fileref="fig7c.png"/>
269 * </imageobject></mediaobject></entry>
270 * <entry><mediaobject><imageobject><imagedata fileref="fig7d.png"/>
271 * </imageobject></mediaobject></entry>
272 * <entry><mediaobject><imageobject><imagedata fileref="fig7e.png"/>
273 * </imageobject></mediaobject></entry>
274 * </row></tbody></tgroup></informaltable>
276 * The logic remains the same. B always gets half the space. At stage 3,
277 * there's no room left for A, so it winds up with zero height. Nothing
278 * displayed in A will be visible. At stage 4, there isn't even room in the
279 * upper 50% to give C its two rows; so it only gets one. Finally, C is
280 * squashed out of existence as well.
282 * When a window winds up undersized, it remembers what size it should be. In
283 * the example above, A remembers that it should be two rows; if the user
284 * expands the window to the original size, it would return to the original
287 * The downward flow of control is a bit harsh. After all, in stage 4, there's
288 * room for C to have its two rows if only B would give up some of its 50%. But
289 * this does not happen.
292 * This makes life much easier for the Glk library. To determine the
293 * configuration of a window, it only needs to look at the window's
294 * ancestors, never at its descendants. So window layout is a simple
295 * recursive algorithm, no backtracking.
298 * What happens when you split a fixed-size window? The resulting pair window
299 * — that is, the two new parts together — retain the same size
300 * constraint as the original window that was split. The key window for the
301 * original split is still the key window for that split, even though it's now
302 * a grandchild instead of a child.
304 * The easy, and correct, way to think about this is that the size constraint
305 * is stored by a window's parent, not the window itself; and a constraint
306 * consists of a pointer to a key window plus a size value.
308 * <informaltable frame="none"><tgroup cols="6"><tbody><row>
309 * <entry><mediaobject><imageobject><imagedata fileref="fig8a.png"/>
310 * </imageobject></mediaobject></entry>
311 * <entry><mediaobject><textobject><literallayout class="monospaced">
313 * </literallayout></textobject></mediaobject></entry>
314 * <entry><mediaobject><imageobject><imagedata fileref="fig8b.png"/>
315 * </imageobject></mediaobject></entry>
316 * <entry><mediaobject><textobject><literallayout class="monospaced">
320 * </literallayout></textobject></mediaobject></entry>
321 * <entry><mediaobject><imageobject><imagedata fileref="fig8c.png"/>
322 * </imageobject></mediaobject></entry>
323 * <entry><mediaobject><textobject><literallayout class="monospaced">
329 * </literallayout></textobject></mediaobject></entry>
330 * </row></tbody></tgroup></informaltable>
331 * After the first split, the new pair window (O1, which covers the whole
332 * screen) knows that its first child (A) is above the second, and gets 50% of
333 * its own area. (A is the key window for this split, but a proportional split
334 * doesn't care about key windows.)
336 * After the second split, all this remains true; O1 knows that its first child
337 * gets 50% of its space, and A is O1's key window. But now O1's first child is
338 * O2 instead of A. The newer pair window (O2) knows that its first child (C)
339 * is above the second, and gets a fixed size of two rows. (As measured in C's
340 * font, because C is O2's key window.)
342 * If we split C, now, the resulting pair will still be two C-font rows high
343 * — that is, tall enough for two lines of whatever font C displays. For
344 * the sake of example, we'll do this vertically.
345 * <informaltable frame="none"><tgroup cols="2"><tbody><row>
346 * <entry><mediaobject><imageobject><imagedata fileref="fig9.png"/>
347 * </imageobject></mediaobject></entry>
348 * <entry><mediaobject><textobject><literallayout class="monospaced">
356 * </literallayout></textobject></mediaobject></entry>
357 * </row></tbody></tgroup></informaltable>
359 * O3 now knows that its children have a 50-50 left-right split. O2 is still
360 * committed to giving its upper child, O3, two C-font rows. Again, this is
361 * because C is O2's key window.
364 * This turns out to be a good idea, because it means that C, the text grid
365 * window, is still two rows high. If O3 had been a upper-lower split, things
366 * wouldn't work out so neatly. But the rules would still apply. If you don't
367 * like this, don't do it.
370 * Returns: the new window, or %NULL on error.
373 glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype,
376 VALID_WINDOW_OR_NULL(split, return NULL);
377 g_return_val_if_fail(method == (method & (winmethod_DirMask | winmethod_DivisionMask)), NULL);
378 g_return_val_if_fail(!(((method & winmethod_DivisionMask) == winmethod_Proportional) && size > 100), NULL);
380 if(split == NULL && glk_data->root_window != NULL)
382 ILLEGAL("Tried to open a new root window, but there is already a root window");
388 /* Create the new window */
389 winid_t win = g_new0(struct glk_window_struct, 1);
390 win->magic = MAGIC_WINDOW;
393 win->window_node = g_node_new(win);
399 /* A blank window will be a label without any text */
400 GtkWidget *label = gtk_label_new("");
401 gtk_widget_show(label);
405 /* A blank window has no size */
407 win->unit_height = 0;
408 /* You can print to a blank window's stream, but it does nothing */
409 win->window_stream = window_stream_new(win);
410 win->echo_stream = NULL;
414 case wintype_TextGrid:
416 GtkWidget *textview = gtk_text_view_new();
418 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_NONE );
419 gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
420 gtk_widget_show(textview);
422 /* Set the window's font */
423 gtk_widget_modify_font(textview, glk_data->monospace_font_desc);
425 win->widget = textview;
426 win->frame = textview;
428 /* Determine the size of a "0" character in pixels */
429 PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
430 pango_layout_set_font_description(zero, glk_data->monospace_font_desc);
431 pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height));
432 g_object_unref(zero);
434 /* Set the other parameters (width and height are set later) */
435 win->window_stream = window_stream_new(win);
436 win->echo_stream = NULL;
437 win->input_request_type = INPUT_REQUEST_NONE;
438 win->line_input_buffer = NULL;
439 win->line_input_buffer_unicode = NULL;
441 /* Connect signal handlers */
442 win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
443 g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
447 case wintype_TextBuffer:
449 GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
450 GtkWidget *textview = gtk_text_view_new();
451 GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
453 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
455 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR );
456 gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
458 gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
459 gtk_widget_show_all(scrolledwindow);
461 /* Set the window's font */
462 gtk_widget_modify_font(textview, glk_data->default_font_desc);
464 win->widget = textview;
465 win->frame = scrolledwindow;
467 /* Determine the size of a "0" character in pixels */
468 PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
469 pango_layout_set_font_description(zero, glk_data->default_font_desc);
470 pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height));
471 g_object_unref(zero);
473 /* Set the other parameters */
474 win->window_stream = window_stream_new(win);
475 win->echo_stream = NULL;
476 win->input_request_type = INPUT_REQUEST_NONE;
477 win->line_input_buffer = NULL;
478 win->line_input_buffer_unicode = NULL;
480 /* Connect signal handlers */
481 win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
482 g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
484 win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win );
485 g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler );
487 /* Create an editable tag to indicate uneditable parts of the window
489 gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
491 /* Create the default styles available to the window stream */
492 style_init_textbuffer(textbuffer);
494 /* Mark the position where the user will input text */
496 gtk_text_buffer_get_end_iter(textbuffer, &end);
497 gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE);
503 ILLEGAL_PARAM("Unknown window type: %u", wintype);
505 g_node_destroy(glk_data->root_window);
506 glk_data->root_window = NULL;
510 /* Set the minimum size to "as small as possible" so it doesn't depend on
511 the size of the window contents */
512 gtk_widget_set_size_request(win->widget, 0, 0);
513 gtk_widget_set_size_request(win->frame, 0, 0);
517 /* When splitting, construct a new parent window
518 * copying most characteristics from the window that is being split */
519 winid_t pair = g_new0(struct glk_window_struct, 1);
520 pair->magic = MAGIC_WINDOW;
522 pair->type = wintype_Pair;
523 pair->window_node = g_node_new(pair);
524 /* You can print to a pair window's window stream, but it has no effect */
525 pair->window_stream = window_stream_new(pair);
526 pair->echo_stream = NULL;
528 /* The pair window must know about its children's split method */
529 pair->key_window = win;
530 pair->split_method = method;
531 pair->constraint_size = size;
533 /* Insert the new window into the window tree */
534 if(split->window_node->parent == NULL)
535 glk_data->root_window = pair->window_node;
538 if( split->window_node == g_node_first_sibling(split->window_node) )
539 g_node_prepend(split->window_node->parent, pair->window_node);
541 g_node_append(split->window_node->parent, pair->window_node);
542 g_node_unlink(split->window_node);
544 /* Place the windows in the correct order */
545 switch(method & winmethod_DirMask)
548 case winmethod_Above:
549 g_node_append(pair->window_node, win->window_node);
550 g_node_append(pair->window_node, split->window_node);
552 case winmethod_Right:
553 case winmethod_Below:
554 g_node_append(pair->window_node, split->window_node);
555 g_node_append(pair->window_node, win->window_node);
560 /* Set the window as root window */
561 glk_data->root_window = win->window_node;
564 /* Set the window as a child of the Glk widget */
565 gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self));
566 gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
570 /* For blank or pair windows, this is almost a no-op. For text grid and
571 text buffer windows, this will wait for GTK to draw the window. Otherwise,
572 opening a window and getting its size immediately will give you the wrong
574 glk_window_get_size(win, NULL, NULL);
576 /* For text grid windows, fill the buffer with blanks. */
577 if(wintype == wintype_TextGrid)
579 /* Create the cursor position mark */
582 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
583 gtk_text_buffer_get_start_iter(buffer, &begin);
584 gtk_text_buffer_create_mark(buffer, "cursor_position", &begin, TRUE);
587 /* Fill the buffer with blanks and move the cursor to the upper left */
588 glk_window_clear(win);
594 /* Internal function: if node's key window is closing_win or one of its
595 children, set node's key window to NULL. */
597 remove_key_windows(GNode *node, winid_t closing_win)
599 winid_t win = (winid_t)node->data;
600 if(win->key_window && (win->key_window == closing_win || g_node_is_ancestor(closing_win->window_node, win->key_window->window_node)))
601 win->key_window = NULL;
602 return FALSE; /* Don't stop the traversal */
605 /* Internal function: destroy this window's GTK widgets, window streams,
606 and those of all its children */
608 destroy_windows_below(winid_t win, stream_result_t *result)
614 gtk_widget_unparent(win->widget);
618 case wintype_TextGrid:
619 case wintype_TextBuffer:
621 gtk_widget_unparent(win->frame);
623 /* TODO: Cancel all input requests */
627 destroy_windows_below(win->window_node->children->data, NULL);
628 destroy_windows_below(win->window_node->children->next->data, NULL);
632 ILLEGAL_PARAM("Unknown window type: %u", win->type);
635 stream_close_common(win->window_stream, result);
638 /* Internal function: free the winid_t structure of this window and those of all its children */
640 free_winids_below(winid_t win)
642 if(win->type == wintype_Pair) {
643 free_winids_below(win->window_node->children->data);
644 free_winids_below(win->window_node->children->next->data);
646 win->magic = MAGIC_FREE;
652 * @win: Window to close.
653 * @result: Pointer to a #stream_result_t in which to store the write count.
655 * Closes @win, which is pretty much exactly the opposite of opening a window.
656 * It is legal to close all your windows, or to close the root window (which is
659 * The @result argument is filled with the output character count of the window
660 * stream. See <link linkend="chimara-Streams">Streams</link> and <link
661 * linkend="chimara-Closing-Streams">Closing Streams</link>.
663 * When you close a window (and it is not the root window), the other window
664 * in its pair takes over all the freed-up area. Let's close D, in the current
666 * <informaltable frame="none"><tgroup cols="2"><tbody><row>
667 * <entry><mediaobject><imageobject><imagedata fileref="fig10.png"/>
668 * </imageobject></mediaobject></entry>
669 * <entry><mediaobject><textobject><literallayout class="monospaced">
675 * </literallayout></textobject></mediaobject></entry>
676 * </row></tbody></tgroup></informaltable>
678 * Notice what has happened. D is gone. O3 is gone, and its 50-50 left-right
679 * split has gone with it. The other size constraints are unchanged; O2 is
680 * still committed to giving its upper child two rows, as measured in the font
681 * of O2's key window, which is C. Conveniently, O2's upper child is C, just as
682 * it was before we created D. In fact, now that D is gone, everything is back
683 * to the way it was before we created D.
685 * But what if we had closed C instead of D? We would have gotten this:
686 * <informaltable frame="none"><tgroup cols="2"><tbody><row>
687 * <entry><mediaobject><imageobject><imagedata fileref="fig11.png"/>
688 * </imageobject></mediaobject></entry>
689 * <entry><mediaobject><textobject><literallayout class="monospaced">
695 * </literallayout></textobject></mediaobject></entry>
696 * </row></tbody></tgroup></informaltable>
698 * Again, O3 is gone. But D has collapsed to zero height. This is because its
699 * height is controlled by O2, and O2's key window was C, and C is now gone. O2
700 * no longer has a key window at all, so it cannot compute a height for its
701 * upper child, so it defaults to zero.
704 * This may seem to be an inconvenient choice. That is deliberate. You should
705 * not leave a pair window with no key, and the zero-height default reminds
706 * you not to. You can use glk_window_set_arrangement() to set a new split
707 * measurement and key window. See <link
708 * linkend="chimara-Changing-Window-Constraints">Changing Window
709 * Constraints</link>.
713 glk_window_close(winid_t win, stream_result_t *result)
715 VALID_WINDOW(win, return);
717 /* If any pair windows have this window or its children as a key window,
718 set their key window to NULL */
719 g_node_traverse(glk_data->root_window, G_IN_ORDER, G_TRAVERSE_NON_LEAVES, -1, (GNodeTraverseFunc)remove_key_windows, win);
721 /* Close all the window streams and destroy the widgets of this window
722 and below, before trashing the window tree */
723 destroy_windows_below(win, result);
725 /* Then free the winid_t structures below this node, but not this one itself */
726 if(win->type == wintype_Pair) {
727 free_winids_below(win->window_node->children->data);
728 free_winids_below(win->window_node->children->next->data);
730 /* So now we should be left with a skeleton tree hanging off this node */
732 /* Parent window changes from a split window into the sibling window */
733 /* The parent of any window is either a pair window or NULL */
734 GNode *pair_node = win->window_node->parent;
735 g_node_destroy(win->window_node);
736 /* If win was not the root window: */
737 if(pair_node != NULL)
739 gboolean new_child_on_left = ( pair_node == g_node_first_sibling(pair_node) );
740 GNode *sibling_node = pair_node->children; /* only one child left */
741 GNode *new_parent_node = pair_node->parent;
742 g_node_unlink(pair_node);
743 g_node_unlink(sibling_node);
744 /* pair_node and sibling_node should now be totally unconnected to the tree */
746 if(new_parent_node == NULL)
748 glk_data->root_window = sibling_node;
752 if(new_child_on_left)
753 g_node_prepend(new_parent_node, sibling_node);
755 g_node_append(new_parent_node, sibling_node);
758 winid_t pair = (winid_t) pair_node->data;
759 g_node_destroy(pair_node);
761 pair->magic = MAGIC_FREE;
764 else /* it was the root window */
766 glk_data->root_window = NULL;
769 win->magic = MAGIC_FREE;
772 /* Schedule a redraw */
774 gtk_widget_queue_resize( GTK_WIDGET(glk_data->self) );
775 gdk_window_process_all_updates();
783 * Erases @win. The meaning of this depends on the window type.
786 * <term>Text buffer</term>
788 * This may do any number of things, such as delete all text in the window, or
789 * print enough blank lines to scroll all text beyond visibility, or insert a
790 * page-break marker which is treated specially by the display part of the
795 * <term>Text grid</term>
797 * This will clear the window, filling all positions with blanks. The window
798 * cursor is moved to the top left corner (position 0,0).
802 * <term>Graphics</term>
804 * Clears the entire window to its current background color. See <link
805 * linkend="chimara-Graphics-Windows">Graphics Windows</link>.
809 * <term>Other window types</term>
810 * <listitem><para>No effect.</para></listitem>
814 * It is illegal to erase a window which has line input pending.
817 glk_window_clear(winid_t win)
819 VALID_WINDOW(win, return);
820 g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE);
829 case wintype_TextGrid:
830 /* fill the buffer with blanks */
834 /* Manually put newlines at the end of each row of characters in the buffer; manual newlines make resizing the window's grid easier. */
835 gchar *blanks = g_strnfill(win->width, ' ');
836 gchar **blanklines = g_new0(gchar *, win->height + 1);
838 for(count = 0; count < win->height; count++)
839 blanklines[count] = blanks;
840 blanklines[win->height] = NULL;
841 gchar *text = g_strjoinv("\n", blanklines);
842 g_free(blanklines); /* not g_strfreev() */
845 GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
846 gtk_text_buffer_set_text(textbuffer, text, -1);
850 gtk_text_buffer_get_start_iter(textbuffer, &begin);
851 gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &begin);
857 case wintype_TextBuffer:
858 /* delete all text in the window */
862 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
863 GtkTextIter start, end;
864 gtk_text_buffer_get_bounds(buffer, &start, &end);
865 gtk_text_buffer_delete(buffer, &start, &end);
872 ILLEGAL_PARAM("Unknown window type: %d", win->type);
880 * Sets the current stream to @win's window stream. It is exactly equivalent to
881 * <code>#glk_stream_set_current(#glk_window_get_stream(@win))</code>.
884 glk_set_window(winid_t win)
886 VALID_WINDOW_OR_NULL(win, return);
887 glk_stream_set_current( glk_window_get_stream(win) );
891 * glk_window_get_stream:
894 * Returns the stream which is associated with @win. (See <link
895 * linkend="chimara-Window-Streams">Window Streams</link>.) Every window has a
896 * stream which can be printed to, but this may not be useful, depending on the
900 * For example, printing to a blank window's stream has no effect.
903 * Returns: A window stream.
905 strid_t glk_window_get_stream(winid_t win)
907 VALID_WINDOW(win, return NULL);
908 return win->window_stream;
912 * glk_window_set_echo_stream:
914 * @str: A stream to attach to the window, or %NULL.
916 * Sets @win's echo stream to @str, which can be any valid output stream. You
917 * can reset a window to stop echoing by calling
918 * <code>#glk_window_set_echo_stream(@win, %NULL)</code>.
920 * It is illegal to set a window's echo stream to be its
921 * <emphasis>own</emphasis> window stream. That would create an infinite loop,
922 * and is nearly certain to crash the Glk library. It is similarly illegal to
923 * create a longer loop (two or more windows echoing to each other.)
926 glk_window_set_echo_stream(winid_t win, strid_t str)
928 VALID_WINDOW(win, return);
929 VALID_STREAM_OR_NULL(str, return);
931 /* Test for an infinite loop */
933 for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream)
935 if(next == win->window_stream)
937 ILLEGAL("Infinite loop detected");
938 win->echo_stream = NULL;
943 win->echo_stream = str;
947 * glk_window_get_echo_stream:
950 * Returns the echo stream of window @win. Initially, a window has no echo
951 * stream, so <code>#glk_window_get_echo_stream(@win)</code> will return %NULL.
953 * Returns: A stream, or %NULL.
956 glk_window_get_echo_stream(winid_t win)
958 VALID_WINDOW(win, return NULL);
959 return win->echo_stream;
963 * glk_window_get_size:
965 * @widthptr: Pointer to a location to store the window's width, or %NULL.
966 * @heightptr: Pointer to a location to store the window's height, or %NULL.
968 * Simply returns the actual size of the window, in its measurement system.
969 * As described in <link linkend="chimara-Other-API-Conventions">Other API
970 * Conventions</link>, either @widthptr or @heightptr can be %NULL, if you
971 * only want one measurement.
973 * <note><para>Or, in fact, both, if you want to waste time.</para></note>
976 glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
978 VALID_WINDOW(win, return);
986 if(heightptr != NULL)
990 case wintype_TextGrid:
992 /* Wait for the window to be drawn, and then cache the width and height */
993 gdk_window_process_all_updates();
994 while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
996 /* Release the GDK lock momentarily */
999 while(gtk_events_pending())
1000 gtk_main_iteration();
1003 win->width = (glui32)(win->widget->allocation.width / win->unit_width);
1004 win->height = (glui32)(win->widget->allocation.height / win->unit_height);
1005 gdk_threads_leave();
1007 if(widthptr != NULL)
1008 *widthptr = win->width;
1009 if(heightptr != NULL)
1010 *heightptr = win->height;
1013 case wintype_TextBuffer:
1014 /* 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. */
1015 gdk_threads_enter();
1016 /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
1018 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.");
1019 guess the size from the parent window;
1023 /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */
1024 gdk_window_process_all_updates();
1025 while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
1027 /* Release the GDK lock momentarily */
1028 gdk_threads_leave();
1029 gdk_threads_enter();
1030 while(gtk_events_pending())
1031 gtk_main_iteration();
1034 if(widthptr != NULL)
1035 *widthptr = (glui32)(win->widget->allocation.width / win->unit_width);
1036 if(heightptr != NULL)
1037 *heightptr = (glui32)(win->widget->allocation.height / win->unit_height);
1038 gdk_threads_leave();
1043 ILLEGAL_PARAM("Unknown window type: %u", win->type);
1048 * glk_window_set_arrangement:
1049 * @win: a pair window to rearrange.
1050 * @method: new method of size computation. One of #winmethod_Above,
1051 * #winmethod_Below, #winmethod_Left, or #winmethod_Right OR'ed with
1052 * #winmethod_Fixed or #winmethod_Proportional.
1053 * @size: new size constraint, in percentage points if @method is
1054 * #winmethod_Proportional, otherwise in characters if @win's type is
1055 * #wintype_TextBuffer or #wintype_TextGrid, or pixels if @win's type is
1056 * #wintype_Graphics.
1057 * @keywin: new key window, or %NULL to leave the key window unchanged.
1059 * Changes the size of an existing split — that is, it changes the
1060 * constraint of a given pair window.
1062 * Consider the example above, where D has collapsed to zero height. Say D was a
1063 * text buffer window. You could make a more useful layout by doing
1066 * o2 = #glk_window_get_parent(d);
1067 * glk_window_set_arrangement(o2, #winmethod_Above | #winmethod_Fixed, 3, d);
1069 * That would set D (the upper child of O2) to be O2's key window, and give it a
1070 * fixed size of 3 rows.
1072 * If you later wanted to expand D, you could do
1073 * |[ glk_window_set_arrangement(o2, #winmethod_Above | #winmethod_Fixed, 5, NULL); ]|
1074 * That expands D to five rows. Note that, since O2's key window is already set
1075 * to D, it is not necessary to provide the @keywin argument; you can pass %NULL
1076 * to mean <quote>leave the key window unchanged.</quote>
1078 * If you do change the key window of a pair window, the new key window
1079 * <emphasis>must</emphasis> be a descendant of that pair window. In the current
1080 * example, you could change O2's key window to be A, but not B. The key window
1081 * also cannot be a pair window itself.
1083 * |[ glk_window_set_arrangement(o2, #winmethod_Below | #winmethod_Fixed, 3, NULL); ]|
1084 * This changes the constraint to be on the <emphasis>lower</emphasis> child of
1085 * O2, which is A. The key window is still D; so A would then be three rows high
1086 * as measured in D's font, and D would get the rest of O2's space. That may not
1087 * be what you want. To set A to be three rows high as measured in A's font, you
1089 * |[ glk_window_set_arrangement(o2, #winmethod_Below | #winmethod_Fixed, 3, a); ]|
1091 * Or you could change O2 to a proportional split:
1092 * |[ glk_window_set_arrangement(o2, #winmethod_Below | #winmethod_Proportional, 30, NULL); ]|
1094 * |[ glk_window_set_arrangement(o2, #winmethod_Above | #winmethod_Proportional, 70, NULL); ]|
1095 * These do exactly the same thing, since 30% above is the same as
1096 * 70% below. You don't need to specify a key window with a proportional
1097 * split, so the @keywin argument is %NULL. (You could actually specify either A
1098 * or D as the key window, but it wouldn't affect the result.)
1100 * Whatever constraint you set, glk_window_get_size() will tell you the actual
1101 * window size you got.
1103 * Note that you can resize windows, but you can't flip or rotate them. You
1104 * can't move A above D, or change O2 to a vertical split where A is left or
1107 * To get this effect you could close one of the windows, and re-split the
1108 * other one with glk_window_open().
1112 glk_window_set_arrangement(winid_t win, glui32 method, glui32 size, winid_t keywin)
1114 VALID_WINDOW(win, return);
1115 VALID_WINDOW_OR_NULL(keywin, return);
1116 g_return_if_fail(win->type == wintype_Pair);
1119 g_return_if_fail(keywin->type != wintype_Pair);
1120 g_return_if_fail(g_node_is_ancestor(win->window_node, keywin->window_node));
1122 g_return_if_fail(method == (method & (winmethod_DirMask | winmethod_DivisionMask)));
1123 g_return_if_fail(!(((method & winmethod_DivisionMask) == winmethod_Proportional) && size > 100));
1125 win->split_method = method;
1126 win->constraint_size = size;
1128 win->key_window = keywin;
1130 /* Tell GTK to rearrange the windows */
1131 gdk_threads_enter();
1132 gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
1133 gdk_window_process_all_updates();
1134 gdk_threads_leave();
1138 * glk_window_get_arrangement:
1139 * @win: a pair window.
1140 * @methodptr: return location for the constraint flags of @win, or %NULL.
1141 * @sizeptr: return location for the constraint size of @win, or %NULL.
1142 * @keywinptr: return location for the key window of @win, or %NULL.
1144 * Queries the constraint of a given pair window.
1147 glk_window_get_arrangement(winid_t win, glui32 *methodptr, glui32 *sizeptr, winid_t *keywinptr)
1149 VALID_WINDOW(win, return);
1150 g_return_if_fail(win->type == wintype_Pair);
1153 *methodptr = win->split_method;
1155 *sizeptr = win->constraint_size;
1157 *keywinptr = win->key_window;
1161 * glk_window_move_cursor:
1162 * @win: A text grid window.
1163 * @xpos: Horizontal cursor position.
1164 * @ypos: Vertical cursor position.
1166 * Sets the cursor position. If you move the cursor right past the end of a
1167 * line, it wraps; the next character which is printed will appear at the
1168 * beginning of the next line.
1170 * If you move the cursor below the last line, or when the cursor reaches the
1171 * end of the last line, it goes <quote>off the screen</quote> and further
1172 * output has no effect. You must call glk_window_move_cursor() or
1173 * glk_window_clear() to move the cursor back into the visible region.
1176 * Note that the arguments of glk_window_move_cursor() are <type>unsigned
1177 * int</type>s. This is okay, since there are no negative positions. If you try
1178 * to pass a negative value, Glk will interpret it as a huge positive value,
1179 * and it will wrap or go off the last line.
1183 * Also note that the output cursor is not necessarily visible. In particular,
1184 * when you are requesting line or character input in a grid window, you cannot
1185 * rely on the cursor position to prompt the player where input is indicated.
1186 * You should print some character prompt at that spot — a
1187 * <quote>></quote> character, for example.
1191 glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
1193 VALID_WINDOW(win, return);
1194 g_return_if_fail(win->type == wintype_TextGrid);
1196 /* Don't do anything if the window is shrunk down to nothing */
1197 if(win->width == 0 || win->height == 0)
1200 /* Calculate actual position if cursor is moved past the right edge */
1201 if(xpos >= win->width)
1203 ypos += xpos / win->width;
1206 /* Go to the end if the cursor is moved off the bottom edge */
1207 if(ypos >= win->height)
1209 xpos = win->width - 1;
1210 ypos = win->height - 1;
1213 gdk_threads_enter();
1215 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
1217 /* There must actually be a character at xpos, or the following function will choke */
1218 gtk_text_buffer_get_iter_at_line_offset(buffer, &newpos, ypos, xpos);
1219 gtk_text_buffer_move_mark_by_name(buffer, "cursor_position", &newpos);
1221 gdk_threads_leave();