+ /* The pair window must know about its children's split method */
+ pair->key_window = win;
+ pair->split_method = method;
+ pair->constraint_size = size;
+
+ /* Insert the new window into the window tree */
+ if(split->window_node->parent == NULL)
+ glk_data->root_window = pair->window_node;
+ else
+ {
+ if( split->window_node == g_node_first_sibling(split->window_node) )
+ g_node_prepend(split->window_node->parent, pair->window_node);
+ else
+ g_node_append(split->window_node->parent, pair->window_node);
+ g_node_unlink(split->window_node);
+ }
+ /* Place the windows in the correct order */
+ switch(method & winmethod_DirMask)
+ {
+ case winmethod_Left:
+ case winmethod_Above:
+ g_node_append(pair->window_node, win->window_node);
+ g_node_append(pair->window_node, split->window_node);
+ break;
+ case winmethod_Right:
+ case winmethod_Below:
+ g_node_append(pair->window_node, split->window_node);
+ g_node_append(pair->window_node, win->window_node);
+ break;
+ }
+
+ } else {
+ /* Set the window as root window */
+ glk_data->root_window = win->window_node;
+ }
+
+ /* Set the window as a child of the Glk widget */
+ gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self));
+ gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
+
+ gdk_threads_leave();
+
+ /* For blank or pair windows, this is almost a no-op. For text grid and
+ text buffer windows, this will wait for GTK to draw the window. Otherwise,
+ opening a window and getting its size immediately will give you the wrong
+ size. */
+ glk_window_get_size(win, NULL, NULL);
+
+ /* For text grid windows, fill the buffer with blanks. */
+ if(wintype == wintype_TextGrid)
+ {
+ /* Create the cursor position mark */
+ gdk_threads_enter();
+ GtkTextIter begin;
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
+ gtk_text_buffer_get_start_iter(buffer, &begin);
+ gtk_text_buffer_create_mark(buffer, "cursor_position", &begin, TRUE);
+ gdk_threads_leave();
+
+ /* Fill the buffer with blanks and move the cursor to the upper left */
+ glk_window_clear(win);
+ }
+
+ return win;
+}
+
+/* Internal function: if node's key window is closing_win or one of its
+ children, set node's key window to NULL. */
+static gboolean
+remove_key_windows(GNode *node, winid_t closing_win)
+{
+ winid_t win = (winid_t)node->data;
+ if(win->key_window && (win->key_window == closing_win || g_node_is_ancestor(closing_win->window_node, win->key_window->window_node)))
+ win->key_window = NULL;
+ return FALSE; /* Don't stop the traversal */
+}
+
+/* Internal function: destroy this window's GTK widgets, window streams,
+ and those of all its children */
+static void
+destroy_windows_below(winid_t win, stream_result_t *result)
+{
+ switch(win->type)
+ {
+ case wintype_Blank:
+ gdk_threads_enter();
+ gtk_widget_unparent(win->widget);
+ gdk_threads_leave();
+ break;
+
+ case wintype_TextGrid:
+ case wintype_TextBuffer:
+ gdk_threads_enter();
+ gtk_widget_unparent(win->frame);
+ gdk_threads_leave();
+ /* TODO: Cancel all input requests */
+ break;
+
+ case wintype_Pair:
+ destroy_windows_below(win->window_node->children->data, NULL);
+ destroy_windows_below(win->window_node->children->next->data, NULL);
+ break;
+
+ default:
+ ILLEGAL_PARAM("Unknown window type: %u", win->type);
+ return;
+ }
+ stream_close_common(win->window_stream, result);
+}
+
+/* Internal function: free the winid_t structure of this window and those of all its children */
+static void
+free_winids_below(winid_t win)
+{
+ if(win->type == wintype_Pair) {
+ free_winids_below(win->window_node->children->data);
+ free_winids_below(win->window_node->children->next->data);
+ }
+ win->magic = MAGIC_FREE;
+ g_free(win);
+}
+
+/**
+ * glk_window_close:
+ * @win: Window to close.
+ * @result: Pointer to a #stream_result_t in which to store the write count.
+ *
+ * Closes @win, which is pretty much exactly the opposite of opening a window.
+ * It is legal to close all your windows, or to close the root window (which is
+ * the same thing.)
+ *
+ * The @result argument is filled with the output character count of the window
+ * stream. See <link linkend="chimara-Streams">Streams</link> and <link
+ * linkend="chimara-Closing-Streams">Closing Streams</link>.
+ *
+ * When you close a window (and it is not the root window), the other window
+ * in its pair takes over all the freed-up area. Let's close D, in the current
+ * example:
+ * <informaltable frame="none"><tgroup cols="2"><tbody><row>
+ * <entry><mediaobject><imageobject><imagedata fileref="fig10.png"/>
+ * </imageobject></mediaobject></entry>
+ * <entry><mediaobject><textobject><literallayout class="monospaced">
+ * O1
+ * / \
+ * O2 B
+ * / \
+ * A C
+ * </literallayout></textobject></mediaobject></entry>
+ * </row></tbody></tgroup></informaltable>
+ *
+ * Notice what has happened. D is gone. O3 is gone, and its 50-50 left-right
+ * split has gone with it. The other size constraints are unchanged; O2 is
+ * still committed to giving its upper child two rows, as measured in the font
+ * of O2's key window, which is C. Conveniently, O2's upper child is C, just as
+ * it was before we created D. In fact, now that D is gone, everything is back
+ * to the way it was before we created D.
+ *
+ * But what if we had closed C instead of D? We would have gotten this:
+ * <informaltable frame="none"><tgroup cols="2"><tbody><row>
+ * <entry><mediaobject><imageobject><imagedata fileref="fig11.png"/>
+ * </imageobject></mediaobject></entry>
+ * <entry><mediaobject><textobject><literallayout class="monospaced">
+ * O1
+ * / \
+ * O2 B
+ * / \
+ * A D
+ * </literallayout></textobject></mediaobject></entry>
+ * </row></tbody></tgroup></informaltable>
+ *
+ * Again, O3 is gone. But D has collapsed to zero height. This is because its
+ * height is controlled by O2, and O2's key window was C, and C is now gone. O2
+ * no longer has a key window at all, so it cannot compute a height for its
+ * upper child, so it defaults to zero.
+ *
+ * <note><para>
+ * This may seem to be an inconvenient choice. That is deliberate. You should
+ * not leave a pair window with no key, and the zero-height default reminds
+ * you not to. You can use glk_window_set_arrangement() to set a new split
+ * measurement and key window. See <link
+ * linkend="chimara-Changing-Window-Constraints">Changing Window
+ * Constraints</link>.
+ * </para></note>
+ */
+void
+glk_window_close(winid_t win, stream_result_t *result)
+{
+ VALID_WINDOW(win, return);
+
+ /* If any pair windows have this window or its children as a key window,
+ set their key window to NULL */
+ g_node_traverse(glk_data->root_window, G_IN_ORDER, G_TRAVERSE_NON_LEAVES, -1, (GNodeTraverseFunc)remove_key_windows, win);
+
+ /* Close all the window streams and destroy the widgets of this window
+ and below, before trashing the window tree */
+ destroy_windows_below(win, result);
+
+ /* Then free the winid_t structures below this node, but not this one itself */
+ if(win->type == wintype_Pair) {
+ free_winids_below(win->window_node->children->data);
+ free_winids_below(win->window_node->children->next->data);
+ }
+ /* So now we should be left with a skeleton tree hanging off this node */
+
+ /* Parent window changes from a split window into the sibling window */
+ /* The parent of any window is either a pair window or NULL */
+ GNode *pair_node = win->window_node->parent;
+ g_node_destroy(win->window_node);
+ /* If win was not the root window: */
+ if(pair_node != NULL)
+ {
+ gboolean new_child_on_left = ( pair_node == g_node_first_sibling(pair_node) );
+ GNode *sibling_node = pair_node->children; /* only one child left */
+ GNode *new_parent_node = pair_node->parent;
+ g_node_unlink(pair_node);
+ g_node_unlink(sibling_node);
+ /* pair_node and sibling_node should now be totally unconnected to the tree */
+
+ if(new_parent_node == NULL)
+ {
+ glk_data->root_window = sibling_node;
+ }
+ else
+ {
+ if(new_child_on_left)
+ g_node_prepend(new_parent_node, sibling_node);
+ else
+ g_node_append(new_parent_node, sibling_node);
+ }
+
+ winid_t pair = (winid_t) pair_node->data;
+ g_node_destroy(pair_node);
+
+ pair->magic = MAGIC_FREE;
+ g_free(pair);
+ }
+ else /* it was the root window */
+ {
+ glk_data->root_window = NULL;
+ }
+
+ win->magic = MAGIC_FREE;
+ g_free(win);
+
+ /* Schedule a redraw */
+ gdk_threads_enter();
+ gtk_widget_queue_resize( GTK_WIDGET(glk_data->self) );
+ gdk_window_process_all_updates();
+ gdk_threads_leave();
+}
+
+/**
+ * glk_window_clear:
+ * @win: A window.
+ *
+ * Erases @win. The meaning of this depends on the window type.
+ * <variablelist>
+ * <varlistentry>
+ * <term>Text buffer</term>
+ * <listitem><para>
+ * This may do any number of things, such as delete all text in the window, or
+ * print enough blank lines to scroll all text beyond visibility, or insert a
+ * page-break marker which is treated specially by the display part of the
+ * library.
+ * </para></listitem>
+ * </varlistentry>
+ * <varlistentry>
+ * <term>Text grid</term>
+ * <listitem><para>
+ * This will clear the window, filling all positions with blanks. The window
+ * cursor is moved to the top left corner (position 0,0).
+ * </para></listitem>
+ * </varlistentry>
+ * <varlistentry>
+ * <term>Graphics</term>
+ * <listitem><para>
+ * Clears the entire window to its current background color. See <link
+ * linkend="chimara-Graphics-Windows">Graphics Windows</link>.
+ * </para></listitem>
+ * </varlistentry>
+ * <varlistentry>
+ * <term>Other window types</term>
+ * <listitem><para>No effect.</para></listitem>
+ * </varlistentry>
+ * </variablelist>
+ *
+ * It is illegal to erase a window which has line input pending.
+ */
+void
+glk_window_clear(winid_t win)
+{
+ VALID_WINDOW(win, return);
+ g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE);
+
+ switch(win->type)
+ {
+ case wintype_Blank:
+ case wintype_Pair:
+ /* do nothing */
+ break;
+
+ case wintype_TextGrid:
+ /* fill the buffer with blanks */
+ {
+ gdk_threads_enter();
+
+ /* Manually put newlines at the end of each row of characters in the buffer; manual newlines make resizing the window's grid easier. */
+ gchar *blanks = g_strnfill(win->width, ' ');
+ gchar **blanklines = g_new0(gchar *, win->height + 1);
+ int count;
+ for(count = 0; count < win->height; count++)
+ blanklines[count] = blanks;
+ blanklines[win->height] = NULL;
+ gchar *text = g_strjoinv("\n", blanklines);
+ g_free(blanklines); /* not g_strfreev() */
+ g_free(blanks);
+
+ GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
+ gtk_text_buffer_set_text(textbuffer, text, -1);
+ g_free(text);
+
+ GtkTextIter begin;
+ gtk_text_buffer_get_start_iter(textbuffer, &begin);
+ gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &begin);
+
+ gdk_threads_leave();
+ }
+ break;
+
+ case wintype_TextBuffer:
+ /* delete all text in the window */
+ {
+ gdk_threads_enter();
+
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
+ GtkTextIter start, end;
+ gtk_text_buffer_get_bounds(buffer, &start, &end);
+ gtk_text_buffer_delete(buffer, &start, &end);
+
+ gdk_threads_leave();
+ }
+ break;
+
+ default:
+ ILLEGAL_PARAM("Unknown window type: %d", win->type);
+ }