Backend support for using dynamic styles. See also Ticket #49.
[rodin/chimara.git] / libchimara / window.c
index bb5fde32cf5d6e168dc06a7b247b046ff46008b1..826eacd288a146ddeb06a51a2957331af9802d60 100644 (file)
@@ -1,8 +1,78 @@
+#include <glib.h>
 #include "window.h"
 #include "magic.h"
 #include "chimara-glk-private.h"
+#include "gi_dispa.h"
+#include "pager.h"
 
-extern ChimaraGlkPrivate *glk_data;
+extern GPrivate *glk_data_key;
+
+static winid_t
+window_new_common(glui32 rock)
+{
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       winid_t win = g_new0(struct glk_window_struct, 1);
+       
+       win->magic = MAGIC_WINDOW;
+       win->rock = rock;
+       if(glk_data->register_obj)
+               win->disprock = (*glk_data->register_obj)(win, gidisp_Class_Window);
+       
+       win->window_node = g_node_new(win);
+       
+       /* Every window has a window stream, but printing to it might have no effect */
+       win->window_stream = stream_new_common(0);
+       win->window_stream->file_mode = filemode_Write;
+       win->window_stream->type = STREAM_TYPE_WINDOW;
+       win->window_stream->window = win;
+       win->window_stream->style = "normal";
+       win->window_stream->glk_style = "normal";
+
+       win->echo_stream = NULL;
+       win->input_request_type = INPUT_REQUEST_NONE;
+       win->line_input_buffer = NULL;
+       win->line_input_buffer_unicode = NULL;
+       win->history = NULL;
+
+       /* Initialise the buffer */
+       win->buffer = g_string_sized_new(1024);
+
+       /* Initialise hyperlink table */
+       win->hyperlinks = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, g_free);
+       
+       return win;
+}
+
+/* Internal function: do all the stuff necessary to close a window. Call only
+ from Glk thread. */
+static void
+window_close_common(winid_t win, gboolean destroy_node)
+{
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+
+       if(glk_data->unregister_obj) 
+       {
+        (*glk_data->unregister_obj)(win, gidisp_Class_Window, win->disprock);
+        win->disprock.ptr = NULL;
+    }
+       
+       if(destroy_node)
+               g_node_destroy(win->window_node);
+       
+       win->magic = MAGIC_FREE;
+       
+       g_list_foreach(win->history, (GFunc)g_free, NULL);
+       g_list_free(win->history);
+       
+       g_string_free(win->buffer, TRUE);
+       g_hash_table_destroy(win->hyperlinks);
+       g_free(win->current_hyperlink);
+
+       if(win->pager_layout)
+               g_object_unref(win->pager_layout);
+
+       g_free(win);
+}
 
 /**
  * glk_window_iterate:
@@ -25,6 +95,7 @@ glk_window_iterate(winid_t win, glui32 *rockptr)
 {
        VALID_WINDOW_OR_NULL(win, return NULL);
        
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
        GNode *retnode;
        
        if(win == NULL)
@@ -73,8 +144,8 @@ glk_window_get_rock(winid_t win)
  * glk_window_get_type:
  * @win: A window.
  *
- * Returns @win's type, one of #wintype_Blank, #wintype_Pair,
- * #wintype_TextBuffer, #wintype_TextGrid, or #wintype_Graphics.
+ * Returns @win's type, one of %wintype_Blank, %wintype_Pair,
+ * %wintype_TextBuffer, %wintype_TextGrid, or %wintype_Graphics.
  *
  * Returns: The window's type.
  */
@@ -100,7 +171,11 @@ winid_t
 glk_window_get_parent(winid_t win)
 {
        VALID_WINDOW(win, return NULL);
+
        /* Value will also be NULL if win is the root window */
+       if(win->window_node->parent == NULL)
+               return NULL;
+
        return (winid_t)win->window_node->parent->data;
 }
 
@@ -135,6 +210,7 @@ glk_window_get_sibling(winid_t win)
 winid_t
 glk_window_get_root()
 {
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
        if(glk_data->root_window == NULL)
                return NULL;
        return (winid_t)glk_data->root_window->data;
@@ -145,15 +221,15 @@ glk_window_get_root()
  * @split: The window to split to create the new window. Must be 0 if there
  * are no windows yet.
  * @method: Position of the new window and method of size computation. One of
- * #winmethod_Above, #winmethod_Below, #winmethod_Left, or #winmethod_Right
- * OR'ed with #winmethod_Fixed or #winmethod_Proportional. If @wintype is
- * #wintype_Blank, then #winmethod_Fixed is not allowed.
+ * %winmethod_Above, %winmethod_Below, %winmethod_Left, or %winmethod_Right
+ * OR'ed with %winmethod_Fixed or %winmethod_Proportional. If @wintype is
+ * %wintype_Blank, then %winmethod_Fixed is not allowed.
  * @size: Size of the new window, in percentage points if @method is
- * #winmethod_Proportional, otherwise in characters if @wintype is 
- * #wintype_TextBuffer or #wintype_TextGrid, or pixels if @wintype is
- * #wintype_Graphics.
- * @wintype: Type of the new window. One of #wintype_Blank, #wintype_TextGrid,
- * #wintype_TextBuffer, or #wintype_Graphics.
+ * %winmethod_Proportional, otherwise in characters if @wintype is 
+ * %wintype_TextBuffer or %wintype_TextGrid, or pixels if @wintype is
+ * %wintype_Graphics.
+ * @wintype: Type of the new window. One of %wintype_Blank, %wintype_TextGrid,
+ * %wintype_TextBuffer, or %wintype_Graphics.
  * @rock: The new window's rock value.
  *
  * Creates a new window. If there are no windows, the first three arguments are
@@ -211,19 +287,19 @@ glk_window_get_root()
  * 
  * So to create a text buffer window which takes the top 40% of the original
  * window's space, you would execute
- * |[ newwin = #glk_window_open(win, #winmethod_Above | #winmethod_Proportional, 40, #wintype_TextBuffer, 0); ]|
+ * |[ newwin = glk_window_open(win, winmethod_Above | winmethod_Proportional, 40, wintype_TextBuffer, 0); ]|
  *
  * To create a text grid which is always five lines high, at the bottom of the
  * original window, you would do
- * |[ newwin = #glk_window_open(win, #winmethod_Below | #winmethod_Fixed, 5, #wintype_TextGrid, 0); ]|
+ * |[ newwin = glk_window_open(win, winmethod_Below | winmethod_Fixed, 5, wintype_TextGrid, 0); ]|
  * 
  * Note that the meaning of the @size argument depends on the @method argument.
- * If the method is #winmethod_Fixed, it also depends on the @wintype argument.
+ * If the method is %winmethod_Fixed, it also depends on the @wintype argument.
  * The new window is then called the <quote>key window</quote> of this split,
  * because its window type determines how the split size is computed.
  * 
  * <note><para>
- *   For #winmethod_Proportional splits, you can still call the new window the
+ *   For %winmethod_Proportional splits, you can still call the new window the
  *   <quote>key window</quote>. But the key window is not important for
  *   proportional splits, because the size will always be computed as a simple
  *   ratio of the available space, not a fixed size of one child window.
@@ -260,7 +336,8 @@ glk_window_get_root()
  * <quote>O</quote>. C gets two rows; A gets the rest. All done.
  * 
  * Then the user maliciously starts squeezing the window down, in stages:
- * <informaltable frame="none"><tgroup cols="5"><tbody><row valign="top">
+ * <informaltable xml:id="chimara-Figure-Squeezing-Window" frame="none">
+ * <tgroup cols="5"><tbody><row valign="top">
  * <entry><mediaobject><imageobject><imagedata fileref="fig5-7a.png"/>
  * </imageobject></mediaobject></entry>
  * <entry><mediaobject><imageobject><imagedata fileref="fig7b.png"/>
@@ -377,6 +454,8 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype,
        g_return_val_if_fail(method == (method & (winmethod_DirMask | winmethod_DivisionMask)), NULL);
        g_return_val_if_fail(!(((method & winmethod_DivisionMask) == winmethod_Proportional) && size > 100), NULL);     
 
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       
        if(split == NULL && glk_data->root_window != NULL)
        {
                ILLEGAL("Tried to open a new root window, but there is already a root window");
@@ -386,11 +465,8 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype,
        gdk_threads_enter();
        
        /* Create the new window */
-       winid_t win = g_new0(struct glk_window_struct, 1);
-       win->magic = MAGIC_WINDOW;
-       win->rock = rock;
+       winid_t win = window_new_common(rock);
        win->type = wintype;
-       win->window_node = g_node_new(win);
 
        switch(wintype)
        {
@@ -405,42 +481,41 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype,
                        /* A blank window has no size */
                        win->unit_width = 0;
                        win->unit_height = 0;
-                       /* You can print to a blank window's stream, but it does nothing */
-                       win->window_stream = window_stream_new(win);
-                       win->echo_stream = NULL;
                }
                        break;
                
                case wintype_TextGrid:
                {
                    GtkWidget *textview = gtk_text_view_new();
+                       GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
 
                    gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_NONE );
                    gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
                        gtk_widget_show(textview);
                                
-                       /* Set the window's font */
-                       gtk_widget_modify_font(textview, glk_data->monospace_font_desc);
+                       /* Create the styles available to the window stream */
+                       style_init_textgrid(textbuffer);
+                       gtk_widget_modify_font( textview, get_current_font(wintype) );
                    
                    win->widget = textview;
                    win->frame = textview;
                        
                        /* Determine the size of a "0" character in pixels */
                        PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
-                       pango_layout_set_font_description(zero, glk_data->monospace_font_desc);
+                       pango_layout_set_font_description( zero, get_current_font(wintype) );
                        pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height));
                        g_object_unref(zero);
-                       
-                       /* Set the other parameters (width and height are set later) */
-                       win->window_stream = window_stream_new(win);
-                       win->echo_stream = NULL;
-                       win->input_request_type = INPUT_REQUEST_NONE;
-                       win->line_input_buffer = NULL;
-                       win->line_input_buffer_unicode = NULL;
+                       /* width and height are set later */
                        
                        /* Connect signal handlers */
-                       win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
-                       g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
+                       win->char_input_keypress_handler = g_signal_connect(textview, "key-press-event", G_CALLBACK(on_char_input_key_press_event), win);
+                       g_signal_handler_block(textview, win->char_input_keypress_handler);
+                       win->line_input_keypress_handler = g_signal_connect(textview, "key-press-event", G_CALLBACK(on_line_input_key_press_event), win);
+                       g_signal_handler_block(textview, win->line_input_keypress_handler);
+                       win->shutdown_keypress_handler = g_signal_connect(textview, "key-press-event", G_CALLBACK(on_shutdown_key_press_event), win);
+                       g_signal_handler_block(textview, win->shutdown_keypress_handler);
+                       win->button_press_event_handler = g_signal_connect( textview, "button-press-event", G_CALLBACK(on_window_button_press), win );
+                       g_signal_handler_block(textview, win->button_press_event_handler);
                }
                    break;
                
@@ -454,49 +529,87 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype,
                        
                        gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR );
                        gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
+                       gtk_text_view_set_pixels_inside_wrap( GTK_TEXT_VIEW(textview), 3 );
+                       gtk_text_view_set_left_margin( GTK_TEXT_VIEW(textview), 20 );
+                       gtk_text_view_set_right_margin( GTK_TEXT_VIEW(textview), 20 );
 
                        gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
                        gtk_widget_show_all(scrolledwindow);
 
-                       /* Set the window's font */
-                       gtk_widget_modify_font(textview, glk_data->default_font_desc);
-                       
                        win->widget = textview;
                        win->frame = scrolledwindow;
                        
+                       /* Create the styles available to the window stream */
+                       style_init_textbuffer(textbuffer);
+                       style_init_more_prompt(win);
+                       gtk_widget_modify_font( textview, get_current_font(wintype) );
+                       
                        /* Determine the size of a "0" character in pixels */
                        PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
-                       pango_layout_set_font_description(zero, glk_data->default_font_desc);
+                       pango_layout_set_font_description( zero, get_current_font(wintype) );
                        pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height));
                        g_object_unref(zero);
-                       
-                       /* Set the other parameters */
-                       win->window_stream = window_stream_new(win);
-                       win->echo_stream = NULL;
-                       win->input_request_type = INPUT_REQUEST_NONE;
-                       win->line_input_buffer = NULL;
-                       win->line_input_buffer_unicode = NULL;
 
                        /* Connect signal handlers */
-                       win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
-                       g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
+                       
+                       /* Pager */
+                       //g_signal_connect_after( textview, "expose-event", G_CALLBACK(pager_after_expose_event), win );
+                       g_signal_connect_after( textview, "size-request", G_CALLBACK(pager_after_size_request), win );
+                       win->pager_expose_handler = g_signal_connect_after( textview, "expose-event", G_CALLBACK(pager_on_expose), win );
+                       g_signal_handler_block(textview, win->pager_expose_handler);
+                       win->pager_keypress_handler = g_signal_connect( textview, "key-press-event", G_CALLBACK(pager_on_key_press_event), win );
+                       g_signal_handler_block(textview, win->pager_keypress_handler);
+                       GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scrolledwindow));
+                       win->pager_adjustment_handler = g_signal_connect_after(adj, "value-changed", G_CALLBACK(pager_after_adjustment_changed), win);
 
-                       win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win );
-                       g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler );
+                       /* Char and line input */
+                       win->char_input_keypress_handler = g_signal_connect( textview, "key-press-event", G_CALLBACK(on_char_input_key_press_event), win );
+                       g_signal_handler_block(textview, win->char_input_keypress_handler);
+                       win->line_input_keypress_handler = g_signal_connect( textview, "key-press-event", G_CALLBACK(on_line_input_key_press_event), win );
+                       g_signal_handler_block(textview, win->line_input_keypress_handler);
+                       win->insert_text_handler = g_signal_connect_after( textbuffer, "insert-text", G_CALLBACK(after_window_insert_text), win );
+                       g_signal_handler_block(textbuffer, win->insert_text_handler);
+
+                       /* Shutdown key press */
+                       win->shutdown_keypress_handler = g_signal_connect( textview, "key-press-event", G_CALLBACK(on_shutdown_key_press_event), win );
+                       g_signal_handler_block(textview, win->shutdown_keypress_handler);                       
 
                        /* Create an editable tag to indicate uneditable parts of the window
                        (for line input) */
                        gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
 
-                       /* Create the default styles available to the window stream */
-                       style_init_textbuffer(textbuffer);
-
-                       /* Mark the position where the user will input text */
+                       /* Mark the position where the user will input text and the end mark */
                        GtkTextIter end;
                        gtk_text_buffer_get_end_iter(textbuffer, &end);
                        gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE);
+                       gtk_text_buffer_create_mark(textbuffer, "end_position", &end, FALSE);
+
+                       /* Create the pager position mark; it stands for the last character in the buffer
+                        that has been on-screen */
+                       GtkTextMark *pager_position = gtk_text_buffer_create_mark(textbuffer, "pager_position", &end, TRUE);
+                       gtk_text_mark_set_visible(pager_position, TRUE);
                }
                        break;
+
+               case wintype_Graphics:
+               {
+                   GtkWidget *image = gtk_image_new_from_pixmap(NULL, NULL);
+                       gtk_widget_show(image);
+
+                       win->unit_width = 1;
+                       win->unit_height = 1;
+                   win->widget = image;
+                   win->frame = image;
+                       win->background_color = 0x00FFFFFF;
+                               
+                       /* Connect signal handlers */
+                       win->button_press_event_handler = g_signal_connect(image, "button-press-event", G_CALLBACK(on_window_button_press), win);
+                       g_signal_handler_block(image, win->button_press_event_handler);
+                       win->shutdown_keypress_handler = g_signal_connect(image, "key-press-event", G_CALLBACK(on_shutdown_key_press_event), win);
+                       g_signal_handler_block(image, win->shutdown_keypress_handler);                  
+                       win->size_allocate_handler = g_signal_connect(image, "size-allocate", G_CALLBACK(on_graphics_size_allocate), win);
+               }
+                   break;
                        
                default:
                        gdk_threads_leave();
@@ -516,14 +629,8 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype,
        {
                /* When splitting, construct a new parent window
                 * copying most characteristics from the window that is being split */
-               winid_t pair = g_new0(struct glk_window_struct, 1);
-               pair->magic = MAGIC_WINDOW;
-               pair->rock = 0;
+               winid_t pair = window_new_common(0);
                pair->type = wintype_Pair;
-               pair->window_node = g_node_new(pair);
-               /* You can print to a pair window's window stream, but it has no effect */
-               pair->window_stream = window_stream_new(pair);
-               pair->echo_stream = NULL;
 
                /* The pair window must know about its children's split method */
                pair->key_window = win;
@@ -563,34 +670,24 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype,
 
        /* Set the window as a child of the Glk widget, don't trigger an arrange event */
        g_mutex_lock(glk_data->arrange_lock);
+       glk_data->needs_rearrange = TRUE;
        glk_data->ignore_next_arrange_event = TRUE;
        g_mutex_unlock(glk_data->arrange_lock);
        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);
-    }
+       }
 
+       gdk_threads_leave();
+    glk_window_clear(win);
        return win;
 }
 
@@ -615,6 +712,7 @@ destroy_windows_below(winid_t win, stream_result_t *result)
                case wintype_Blank:
            case wintype_TextGrid:
                case wintype_TextBuffer:
+               case wintype_Graphics:
                        gtk_widget_unparent(win->frame);
                        break;
 
@@ -638,8 +736,7 @@ free_winids_below(winid_t win)
                free_winids_below(win->window_node->children->data);
                free_winids_below(win->window_node->children->next->data);
        }
-       win->magic = MAGIC_FREE;
-       g_free(win);
+       window_close_common(win, FALSE);
 }
 
 /**
@@ -708,6 +805,8 @@ void
 glk_window_close(winid_t win, stream_result_t *result)
 {
        VALID_WINDOW(win, return);
+
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
        
        gdk_threads_enter(); /* Prevent redraw while we're trashing the window */
        
@@ -729,12 +828,16 @@ glk_window_close(winid_t win, stream_result_t *result)
        /* 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 */
+
+               /* Lookup our sibling */
+               GNode *sibling_node = pair_node->children;
+               if(sibling_node == win->window_node)
+                       sibling_node = sibling_node->next;
+
                GNode *new_parent_node = pair_node->parent;
                g_node_unlink(pair_node);
                g_node_unlink(sibling_node);
@@ -752,22 +855,19 @@ glk_window_close(winid_t win, stream_result_t *result)
                                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);
+               stream_close_common( ((winid_t) pair_node->data)->window_stream, NULL );
+               window_close_common( (winid_t) pair_node->data, TRUE);
        } 
        else /* it was the root window */
        {
                glk_data->root_window = NULL;
        }
 
-       win->magic = MAGIC_FREE;
-       g_free(win);
+       window_close_common(win, FALSE);
 
        /* Schedule a redraw */
        g_mutex_lock(glk_data->arrange_lock);
+       glk_data->needs_rearrange = TRUE;
        glk_data->ignore_next_arrange_event = TRUE;
        g_mutex_unlock(glk_data->arrange_lock);
        gtk_widget_queue_resize( GTK_WIDGET(glk_data->self) );
@@ -800,7 +900,8 @@ glk_window_close(winid_t win, stream_result_t *result)
  *  <term>Graphics</term>
  *  <listitem><para>
  *   Clears the entire window to its current background color. See <link
- *   linkend="chimara-Graphics-Windows">Graphics Windows</link>.
+ *   linkend="chimara-The-Types-of-Windows&num;wintype-Graphics">Graphics 
+ *   Windows</link>.
  *  </para></listitem>
  * </varlistentry>
  * <varlistentry>
@@ -816,6 +917,8 @@ 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);
+
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
        
        switch(win->type)
        {
@@ -827,6 +930,12 @@ glk_window_clear(winid_t win)
                case wintype_TextGrid:
                    /* fill the buffer with blanks */
                {
+                       /* Wait for the window's size to be updated */
+                       g_mutex_lock(glk_data->arrange_lock);
+                       if(glk_data->needs_rearrange)
+                               g_cond_wait(glk_data->rearranged, glk_data->arrange_lock);
+                       g_mutex_unlock(glk_data->arrange_lock);
+                       
                    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. */
@@ -865,6 +974,18 @@ glk_window_clear(winid_t win)
                        gdk_threads_leave();
                }
                        break;
+
+               case wintype_Graphics:
+               {
+                       /* Wait for the window's size to be updated */
+                       g_mutex_lock(glk_data->arrange_lock);
+                       if(glk_data->needs_rearrange)
+                               g_cond_wait(glk_data->rearranged, glk_data->arrange_lock);
+                       g_mutex_unlock(glk_data->arrange_lock);
+
+                       glk_window_erase_rect(win, 0, 0, win->widget->allocation.width, win->widget->allocation.height);
+               }
+                       break;
                
                default:
                        ILLEGAL_PARAM("Unknown window type: %d", win->type);
@@ -873,16 +994,26 @@ glk_window_clear(winid_t win)
 
 /**
  * glk_set_window:
- * @win: A window.
+ * @win: A window, or %NULL.
  *
  * Sets the current stream to @win's window stream. It is exactly equivalent to
- * <code>#glk_stream_set_current(#glk_window_get_stream(@win))</code>.
+ * |[ glk_stream_set_current(glk_window_get_stream(win)) ]| 
+ * See <link linkend="chimara-Streams">Streams</link>.
+ *
+ * <note><title>Chimara</title>
+ * <para>
+ *   Although this is not mentioned in the specification, @win may also be 
+ *   %NULL, in which case the current stream is also set to %NULL.
+ * </para></note>
  */
 void
 glk_set_window(winid_t win)
 {
        VALID_WINDOW_OR_NULL(win, return);
-       glk_stream_set_current( glk_window_get_stream(win) );
+       if(win)
+               glk_stream_set_current( glk_window_get_stream(win) );
+       else
+               glk_stream_set_current(NULL);
 }
 
 /**
@@ -975,6 +1106,8 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
 {
        VALID_WINDOW(win, return);
 
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
+       
     switch(win->type)
     {
         case wintype_Blank:
@@ -986,18 +1119,14 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
             break;
             
         case wintype_TextGrid:
+                       /* Wait until the window's size is current */
+                       g_mutex_lock(glk_data->arrange_lock);
+                       if(glk_data->needs_rearrange)
+                               g_cond_wait(glk_data->rearranged, glk_data->arrange_lock);
+                       g_mutex_unlock(glk_data->arrange_lock);
+                       
                        gdk_threads_enter();
-                       /* Wait for the window to be drawn, and then cache the width and height */
-                       gdk_window_process_all_updates();
-                       while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
-                   {
-                       /* Release the GDK lock momentarily */
-                       gdk_threads_leave();
-                       gdk_threads_enter();
-                       while(gtk_events_pending())
-                           gtk_main_iteration();
-                   }
-                   
+                       /* Cache the width and height */
                        win->width = (glui32)(win->widget->allocation.width / win->unit_width);
                    win->height = (glui32)(win->widget->allocation.height / win->unit_height);
             gdk_threads_leave();
@@ -1009,32 +1138,34 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
             break;
             
         case wintype_TextBuffer:
-            /* 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. */
+            /* Wait until the window's size is current */
+                       g_mutex_lock(glk_data->arrange_lock);
+                       if(glk_data->needs_rearrange)
+                               g_cond_wait(glk_data->rearranged, glk_data->arrange_lock);
+                       g_mutex_unlock(glk_data->arrange_lock);
+                       
             gdk_threads_enter();
-            /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
-            {
-                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.");
-                guess the size from the parent window;
-                break;
-            } */
-            
-            /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */
-            gdk_window_process_all_updates();
-                       while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
-            {
-                /* Release the GDK lock momentarily */
-                gdk_threads_leave();
-                gdk_threads_enter();
-                while(gtk_events_pending())
-                    gtk_main_iteration();
-            }
-                
             if(widthptr != NULL)
                 *widthptr = (glui32)(win->widget->allocation.width / win->unit_width);
             if(heightptr != NULL)
                 *heightptr = (glui32)(win->widget->allocation.height / win->unit_height);
             gdk_threads_leave();
             
+            break;
+
+               case wintype_Graphics:
+                       g_mutex_lock(glk_data->arrange_lock);
+                       if(glk_data->needs_rearrange)
+                               g_cond_wait(glk_data->rearranged, glk_data->arrange_lock);
+                       g_mutex_unlock(glk_data->arrange_lock);
+                       
+            gdk_threads_enter();
+            if(widthptr != NULL)
+                *widthptr = (glui32)(win->widget->allocation.width);
+            if(heightptr != NULL)
+                *heightptr = (glui32)(win->widget->allocation.height);
+            gdk_threads_leave();
+            
             break;
             
         default:
@@ -1045,13 +1176,13 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
 /**
  * glk_window_set_arrangement:
  * @win: a pair window to rearrange.
- * @method: new method of size computation. One of #winmethod_Above, 
- * #winmethod_Below, #winmethod_Left, or #winmethod_Right OR'ed with 
- * #winmethod_Fixed or #winmethod_Proportional.
+ * @method: new method of size computation. One of %winmethod_Above, 
+ * %winmethod_Below, %winmethod_Left, or %winmethod_Right OR'ed with 
+ * %winmethod_Fixed or %winmethod_Proportional.
  * @size: new size constraint, in percentage points if @method is
- * #winmethod_Proportional, otherwise in characters if @win's type is 
- * #wintype_TextBuffer or #wintype_TextGrid, or pixels if @win's type is
- * #wintype_Graphics.
+ * %winmethod_Proportional, otherwise in characters if @win's type is 
+ * %wintype_TextBuffer or %wintype_TextGrid, or pixels if @win's type is
+ * %wintype_Graphics.
  * @keywin: new key window, or %NULL to leave the key window unchanged.
  *
  * Changes the size of an existing split &mdash; that is, it changes the 
@@ -1060,15 +1191,15 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
  * Consider the example above, where D has collapsed to zero height. Say D was a
  * text buffer window. You could make a more useful layout by doing
  * |[
- * #winid_t o2;
- * o2 = #glk_window_get_parent(d);
- * glk_window_set_arrangement(o2, #winmethod_Above | #winmethod_Fixed, 3, d);
+ * winid_t o2;
+ * o2 = glk_window_get_parent(d);
+ * glk_window_set_arrangement(o2, winmethod_Above | winmethod_Fixed, 3, d);
  * ]|
  * That would set D (the upper child of O2) to be O2's key window, and give it a
  * fixed size of 3 rows.
  * 
  * If you later wanted to expand D, you could do
- * |[ glk_window_set_arrangement(o2, #winmethod_Above | #winmethod_Fixed, 5, NULL); ]|
+ * |[ glk_window_set_arrangement(o2, winmethod_Above | winmethod_Fixed, 5, NULL); ]|
  * That expands D to five rows. Note that, since O2's key window is already set 
  * to D, it is not necessary to provide the @keywin argument; you can pass %NULL
  * to mean <quote>leave the key window unchanged.</quote>
@@ -1078,18 +1209,18 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
  * example, you could change O2's key window to be A, but not B. The key window
  * also cannot be a pair window itself.
  * 
- * |[ glk_window_set_arrangement(o2, #winmethod_Below | #winmethod_Fixed, 3, NULL); ]|
+ * |[ glk_window_set_arrangement(o2, winmethod_Below | winmethod_Fixed, 3, NULL); ]|
  * This changes the constraint to be on the <emphasis>lower</emphasis> child of 
  * O2, which is A. The key window is still D; so A would then be three rows high
  * as measured in D's font, and D would get the rest of O2's space. That may not
  * be what you want. To set A to be three rows high as measured in A's font, you
  * would do
- * |[ glk_window_set_arrangement(o2, #winmethod_Below | #winmethod_Fixed, 3, a); ]|
+ * |[ glk_window_set_arrangement(o2, winmethod_Below | winmethod_Fixed, 3, a); ]|
  * 
  * Or you could change O2 to a proportional split:
- * |[ glk_window_set_arrangement(o2, #winmethod_Below | #winmethod_Proportional, 30, NULL); ]|
+ * |[ glk_window_set_arrangement(o2, winmethod_Below | winmethod_Proportional, 30, NULL); ]|
  * or
- * |[ glk_window_set_arrangement(o2, #winmethod_Above | #winmethod_Proportional, 70, NULL); ]|
+ * |[ glk_window_set_arrangement(o2, winmethod_Above | winmethod_Proportional, 70, NULL); ]|
  * These do exactly the same thing, since 30&percnt; above is the same as 
  * 70&percnt; below. You don't need to specify a key window with a proportional
  * split, so the @keywin argument is %NULL. (You could actually specify either A
@@ -1119,6 +1250,8 @@ glk_window_set_arrangement(winid_t win, glui32 method, glui32 size, winid_t keyw
        }
        g_return_if_fail(method == (method & (winmethod_DirMask | winmethod_DivisionMask)));
        g_return_if_fail(!(((method & winmethod_DivisionMask) == winmethod_Proportional) && size > 100));
+
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
        
        win->split_method = method;
        win->constraint_size = size;
@@ -1128,6 +1261,7 @@ glk_window_set_arrangement(winid_t win, glui32 method, glui32 size, winid_t keyw
        /* Tell GTK to rearrange the windows */
        gdk_threads_enter();
        g_mutex_lock(glk_data->arrange_lock);
+       glk_data->needs_rearrange = TRUE;
        glk_data->ignore_next_arrange_event = TRUE;
        g_mutex_unlock(glk_data->arrange_lock);
        gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
@@ -1192,7 +1326,17 @@ glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
 {
        VALID_WINDOW(win, return);
        g_return_if_fail(win->type == wintype_TextGrid);
+
+       flush_window_buffer(win);
+
+       ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
        
+       /* Wait until the window's size is current */
+       g_mutex_lock(glk_data->arrange_lock);
+       if(glk_data->needs_rearrange)
+               g_cond_wait(glk_data->rearranged, glk_data->arrange_lock);
+       g_mutex_unlock(glk_data->arrange_lock);
+
        /* Don't do anything if the window is shrunk down to nothing */
        if(win->width == 0 || win->height == 0)
                return;
@@ -1203,6 +1347,7 @@ glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
            ypos += xpos / win->width;
            xpos %= win->width;
        }
+
        /* Go to the end if the cursor is moved off the bottom edge */
        if(ypos >= win->height)
        {
@@ -1220,4 +1365,3 @@ glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
        
        gdk_threads_leave();
 }
-