Hopefully solved the problem of waiting for windows to draw themselves once and for...
[rodin/chimara.git] / libchimara / window.c
index 5f2bd5b349673bb2b46a755e46c3da8906c93240..5fd2447421d0d60d7af4d19842ad083b9486e659 100644 (file)
@@ -374,6 +374,8 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype,
                 glui32 rock)
 {
        VALID_WINDOW_OR_NULL(split, return NULL);
+       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);     
 
        if(split == NULL && glk_data->root_window != NULL)
        {
@@ -559,33 +561,26 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype,
                glk_data->root_window = win->window_node;
        }
 
-       /* Set the window as a child of the Glk widget */
+       /* 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;
 }
 
@@ -601,24 +596,16 @@ remove_key_windows(GNode *node, winid_t closing_win)
 }
 
 /* Internal function: destroy this window's GTK widgets, window streams, 
- and those of all its children */
+ and those of all its children. GDK threads must be locked. */
 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:
@@ -712,6 +699,8 @@ glk_window_close(winid_t win, stream_result_t *result)
 {
        VALID_WINDOW(win, return);
        
+       gdk_threads_enter(); /* Prevent redraw while we're trashing the window */
+       
        /* 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);
@@ -768,9 +757,11 @@ glk_window_close(winid_t win, stream_result_t *result)
        g_free(win);
 
        /* Schedule a redraw */
-       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) );
-       gdk_window_process_all_updates();
        gdk_threads_leave();
 }
 
@@ -827,6 +818,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. */
@@ -986,18 +983,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,26 +1002,13 @@ 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)
@@ -1041,7 +1021,123 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
             ILLEGAL_PARAM("Unknown window type: %u", win->type);
     }
 }
+
+/**
+ * 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.
+ * @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.
+ * @keywin: new key window, or %NULL to leave the key window unchanged.
+ *
+ * Changes the size of an existing split — that is, it changes the 
+ * constraint of a given pair window.
+ * 
+ * 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);
+ * ]|
+ * 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); ]|
+ * 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>
+ * 
+ * If you do change the key window of a pair window, the new key window 
+ * <emphasis>must</emphasis> be a descendant of that pair window. In the current
+ * 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); ]|
+ * 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); ]|
+ * 
+ * Or you could change O2 to a proportional split:
+ * |[ glk_window_set_arrangement(o2, #winmethod_Below | #winmethod_Proportional, 30, NULL); ]|
+ * or
+ * |[ 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
+ * or D as the key window, but it wouldn't affect the result.)
+ * 
+ * Whatever constraint you set, glk_window_get_size() will tell you the actual 
+ * window size you got.
+ * 
+ * Note that you can resize windows, but you can't flip or rotate them. You 
+ * can't move A above D, or change O2 to a vertical split where A is left or 
+ * right of D. 
+ * <note><para>
+ *   To get this effect you could close one of the windows, and re-split the 
+ *   other one with glk_window_open().
+ * </para></note>
+ */
+void
+glk_window_set_arrangement(winid_t win, glui32 method, glui32 size, winid_t keywin)
+{
+       VALID_WINDOW(win, return);
+       VALID_WINDOW_OR_NULL(keywin, return);
+       g_return_if_fail(win->type == wintype_Pair);
+       if(keywin)
+       {
+               g_return_if_fail(keywin->type != wintype_Pair);
+               g_return_if_fail(g_node_is_ancestor(win->window_node, keywin->window_node));
+       }
+       g_return_if_fail(method == (method & (winmethod_DirMask | winmethod_DivisionMask)));
+       g_return_if_fail(!(((method & winmethod_DivisionMask) == winmethod_Proportional) && size > 100));
+       
+       win->split_method = method;
+       win->constraint_size = size;
+       if(keywin)
+               win->key_window = keywin;
+
+       /* 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));
+       gdk_threads_leave();
+}
+
+/**
+ * glk_window_get_arrangement:
+ * @win: a pair window.
+ * @methodptr: return location for the constraint flags of @win, or %NULL.
+ * @sizeptr: return location for the constraint size of @win, or %NULL.
+ * @keywinptr: return location for the key window of @win, or %NULL.
+ *
+ * Queries the constraint of a given pair window.
+ */
+void
+glk_window_get_arrangement(winid_t win, glui32 *methodptr, glui32 *sizeptr, winid_t *keywinptr)
+{
+       VALID_WINDOW(win, return);
+       g_return_if_fail(win->type == wintype_Pair);
+       
+       if(methodptr)
+               *methodptr = win->split_method;
+       if(sizeptr)
+               *sizeptr = win->constraint_size;
+       if(keywinptr)
+               *keywinptr = win->key_window;
+}
+
 /**
  * glk_window_move_cursor:
  * @win: A text grid window.
@@ -1078,6 +1174,16 @@ glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
        VALID_WINDOW(win, return);
        g_return_if_fail(win->type == 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);
+
+       /* Don't do anything if the window is shrunk down to nothing */
+       if(win->width == 0 || win->height == 0)
+               return;
+       
        /* Calculate actual position if cursor is moved past the right edge */
        if(xpos >= win->width)
        {