X-Git-Url: https://git.stderr.nl/gitweb?a=blobdiff_plain;f=src%2Fwindow.c;h=5f2bd5b349673bb2b46a755e46c3da8906c93240;hb=5a06246277a255fe6e0e465ce0f190541b7b0e16;hp=8351378363e343a1450b40469a6647958e531ad7;hpb=fb6cf2af0f2318aceaea82d02acc961df0f9811d;p=rodin%2Fchimara.git diff --git a/src/window.c b/src/window.c index 8351378..5f2bd5b 100644 --- a/src/window.c +++ b/src/window.c @@ -1,4 +1,5 @@ #include "window.h" +#include "magic.h" #include "chimara-glk-private.h" extern ChimaraGlkPrivate *glk_data; @@ -8,20 +9,22 @@ extern ChimaraGlkPrivate *glk_data; * @win: A window, or %NULL. * @rockptr: Return location for the next window's rock, or %NULL. * - * Iterates over the list of windows; if @win is %NULL, it returns the first - * window, otherwise the next window after @win. If there are no more, it - * returns #NULL. The window's rock is stored in @rockptr. If you don't want - * the rocks to be returned, you may set @rockptr to %NULL. + * This function can be used to iterate through the list of all open windows + * (including pair windows.) See Iterating Through Opaque + * Objects. * - * The order in which windows are returned is arbitrary. The root window is - * not necessarily first, nor is it necessarily last. The order may change - * every time you create or destroy a window, invalidating the iteration. + * As that section describes, the order in which windows are returned is + * arbitrary. The root window is not necessarily first, nor is it necessarily + * last. * * Returns: the next window, or %NULL if there are no more. */ winid_t glk_window_iterate(winid_t win, glui32 *rockptr) { + VALID_WINDOW_OR_NULL(win, return NULL); + GNode *retnode; if(win == NULL) @@ -55,14 +58,14 @@ glk_window_iterate(winid_t win, glui32 *rockptr) * @win: A window. * * Returns @win's rock value. Pair windows always have rock 0; all other windows - * have the rock value you created them with. + * return whatever rock value you created them with. * * Returns: A rock value. */ glui32 glk_window_get_rock(winid_t win) { - g_return_val_if_fail(win != NULL, 0); + VALID_WINDOW(win, return 0); return win->rock; } @@ -78,7 +81,7 @@ glk_window_get_rock(winid_t win) glui32 glk_window_get_type(winid_t win) { - g_return_val_if_fail(win != NULL, 0); + VALID_WINDOW(win, return 0); return win->type; } @@ -86,16 +89,17 @@ glk_window_get_type(winid_t win) * glk_window_get_parent: * @win: A window. * - * Returns the window @win's parent window. If @win is the root window, this - * returns %NULL, since the root window has no parent. Remember that the parent - * of every window is a pair window; other window types are always childless. + * Returns the window which is the parent of @win. If @win is the root window, + * this returns %NULL, since the root window has no parent. Remember that the + * parent of every window is a pair window; other window types are always + * childless. * * Returns: A window, or %NULL. */ winid_t glk_window_get_parent(winid_t win) { - g_return_val_if_fail(win != NULL, NULL); + VALID_WINDOW(win, return NULL); /* Value will also be NULL if win is the root window */ return (winid_t)win->window_node->parent->data; } @@ -104,15 +108,15 @@ glk_window_get_parent(winid_t win) * glk_window_get_sibling: * @win: A window. * - * Returns the other child of the window @win's parent. If @win is the - * root window, this returns %NULL. + * Returns the other child of @win's parent. If @win is the root window, this + * returns %NULL. * * Returns: A window, or %NULL. */ winid_t glk_window_get_sibling(winid_t win) { - g_return_val_if_fail(win != NULL, NULL); + VALID_WINDOW(win, return NULL); if(G_NODE_IS_ROOT(win->window_node)) return NULL; @@ -136,15 +140,6 @@ glk_window_get_root() return (winid_t)glk_data->root_window->data; } -/* Determine the size of a "0" character in pixels */ -static void -text_window_get_char_size(GtkWidget *textview, int *width, int *height) -{ - PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0"); - pango_layout_get_pixel_size(zero, width, height); - g_object_unref(zero); -} - /** * glk_window_open: * @split: The window to split to create the new window. Must be 0 if there @@ -161,10 +156,216 @@ text_window_get_char_size(GtkWidget *textview, int *width, int *height) * #wintype_TextBuffer, or #wintype_Graphics. * @rock: The new window's rock value. * - * If there are no windows, create a new root window. @split must be 0, and - * @method and @size are ignored. Otherwise, split window @split into two, with - * position, size, and type specified by @method, @size, and @wintype. See the - * Glk documentation for the window placement algorithm. + * Creates a new window. If there are no windows, the first three arguments are + * meaningless. @split must be 0, and @method and @size + * are ignored. @wintype is the type of window you're creating, and @rock is + * the rock (see Rocks). + * + * If any windows exist, new windows must be created by splitting existing + * ones. @split is the window you want to split; this must + * not be zero. @method is a mask of constants to specify the + * direction and the split method (see below). @size is the size of the split. + * @wintype is the type of window you're creating, and @rock is the rock. + * + * Remember that it is possible that the library will be unable to create a new + * window, in which case glk_window_open() will return %NULL. + * + * + * It is acceptable to gracefully exit, if the window you are creating is an + * important one — such as your first window. But you should not try to + * perform any window operation on the id until you have tested to make sure + * it is non-zero. + * + * + * The examples we've seen so far have the simplest kind of size control. (Yes, + * this is below.) Every pair is a percentage split, with + * + * X + * X + * + * percent going to one side, and + * + * (100-X) + * (100 - X) + * + * percent going to the other side. If the player resizes the window, the whole + * mess expands, contracts, or stretches in a uniform way. + * + * As I said above, you can also make fixed-size splits. This is a little more + * complicated, because you have to know how this fixed size is measured. + * + * Sizes are measured in a way which is different for each window type. For + * example, a text grid window is measured by the size of its fixed-width font. + * You can make a text grid window which is fixed at a height of four rows, or + * ten columns. A text buffer window is measured by the size of its font. + * + * + * Remember that different windows may use different size fonts. Even two + * text grid windows may use fixed-size fonts of different sizes. + * + * + * Graphics windows are measured in pixels, not characters. Blank windows + * aren't measured at all; there's no meaningful way to measure them, and + * therefore you can't create a blank window of a fixed size, only of a + * proportional (percentage) size. + * + * 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); ]| + * + * 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); ]| + * + * 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. + * The new window is then called the key window of this split, + * because its window type determines how the split size is computed. + * + * + * For #winmethod_Proportional splits, you can still call the new window the + * key window. 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. + * + * + * This system is more or less peachy as long as all the constraints work out. + * What happens when there is a conflict? The rules are simple. Size control + * always flows down the tree, and the player is at the top. Let's bring out an + * example: + * + * + * + * + * O + * / \ + * O B + * / \ + * A C + * + * + * + * First we split A into A and B, with a 50% proportional split. Then we split + * A into A and C, with C above, C being a text grid window, and C gets a fixed + * size of two rows (as measured in its own font size). A gets whatever remains + * of the 50% it had before. + * + * Now the player stretches the window vertically. + * + * + * + * The library figures: the topmost split, the original A/B split, is 50-50. So + * B gets half the screen space, and the pair window next to it (the lower + * O) gets the other half. Then it looks at the lower + * O. C gets two rows; A gets the rest. All done. + * + * Then the user maliciously starts squeezing the window down, in stages: + * + * + * + * + * + * + * + * + * + * + * + * + * + * The logic remains the same. B always gets half the space. At stage 3, + * there's no room left for A, so it winds up with zero height. Nothing + * displayed in A will be visible. At stage 4, there isn't even room in the + * upper 50% to give C its two rows; so it only gets one. Finally, C is + * squashed out of existence as well. + * + * When a window winds up undersized, it remembers what size it should be. In + * the example above, A remembers that it should be two rows; if the user + * expands the window to the original size, it would return to the original + * layout. + * + * The downward flow of control is a bit harsh. After all, in stage 4, there's + * room for C to have its two rows if only B would give up some of its 50%. But + * this does not happen. + * + * + * This makes life much easier for the Glk library. To determine the + * configuration of a window, it only needs to look at the window's + * ancestors, never at its descendants. So window layout is a simple + * recursive algorithm, no backtracking. + * + * + * What happens when you split a fixed-size window? The resulting pair window + * — that is, the two new parts together — retain the same size + * constraint as the original window that was split. The key window for the + * original split is still the key window for that split, even though it's now + * a grandchild instead of a child. + * + * The easy, and correct, way to think about this is that the size constraint + * is stored by a window's parent, not the window itself; and a constraint + * consists of a pointer to a key window plus a size value. + * + * + * + * + * + * A + * + * + * + * + * O1 + * / \ + * A B + * + * + * + * + * O1 + * / \ + * O2 B + * / \ + * A C + * + * + * After the first split, the new pair window (O1, which covers the whole + * screen) knows that its first child (A) is above the second, and gets 50% of + * its own area. (A is the key window for this split, but a proportional split + * doesn't care about key windows.) + * + * After the second split, all this remains true; O1 knows that its first child + * gets 50% of its space, and A is O1's key window. But now O1's first child is + * O2 instead of A. The newer pair window (O2) knows that its first child (C) + * is above the second, and gets a fixed size of two rows. (As measured in C's + * font, because C is O2's key window.) + * + * If we split C, now, the resulting pair will still be two C-font rows high + * — that is, tall enough for two lines of whatever font C displays. For + * the sake of example, we'll do this vertically. + * + * + * + * + * O1 + * / \ + * O2 B + * / \ + * A O3 + * / \ + * C D + * + * + * + * O3 now knows that its children have a 50-50 left-right split. O2 is still + * committed to giving its upper child, O3, two C-font rows. Again, this is + * because C is O2's key window. + * + * + * This turns out to be a good idea, because it means that C, the text grid + * window, is still two rows high. If O3 had been a upper-lower split, things + * wouldn't work out so neatly. But the rules would still apply. If you don't + * like this, don't do it. + * * * Returns: the new window, or %NULL on error. */ @@ -172,24 +373,19 @@ winid_t glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, glui32 rock) { - /* - if(split) - { - g_warning("glk_window_open: splitting of windows not implemented"); - return NULL; - } - */ + VALID_WINDOW_OR_NULL(split, return NULL); if(split == NULL && glk_data->root_window != NULL) { - g_warning("glk_window_open: there is already a root window"); + ILLEGAL("Tried to open a new root window, but there is already a root window"); return NULL; } gdk_threads_enter(); - /* We only create one window and don't support any more than that */ + /* Create the new window */ winid_t win = g_new0(struct glk_window_struct, 1); + win->magic = MAGIC_WINDOW; win->rock = rock; win->type = wintype; win->window_node = g_node_new(win); @@ -215,26 +411,23 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, case wintype_TextGrid: { - GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL); GtkWidget *textview = gtk_text_view_new(); - - gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_NEVER ); - - gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_CHAR ); + + gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_NONE ); gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE ); - - gtk_container_add( GTK_CONTAINER(scrolledwindow), textview ); - gtk_widget_show_all(scrolledwindow); + gtk_widget_show(textview); /* Set the window's font */ - /* TODO: Use Pango to pick out a monospace font on the system */ - PangoFontDescription *font = pango_font_description_from_string("Monospace"); - gtk_widget_modify_font(textview, font); - pango_font_description_free(font); + gtk_widget_modify_font(textview, glk_data->monospace_font_desc); win->widget = textview; - win->frame = scrolledwindow; - text_window_get_char_size( textview, &(win->unit_width), &(win->unit_height) ); + 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_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); @@ -255,15 +448,25 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, GtkWidget *textview = gtk_text_view_new(); GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) ); + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC ); + 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_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; - text_window_get_char_size( textview, &(win->unit_width), &(win->unit_height) ); + + /* 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_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); @@ -283,6 +486,9 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, (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 */ GtkTextIter end; gtk_text_buffer_get_end_iter(textbuffer, &end); @@ -292,197 +498,279 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, default: gdk_threads_leave(); - g_warning("%s: unsupported window type", __func__); + ILLEGAL_PARAM("Unknown window type: %u", wintype); g_free(win); g_node_destroy(glk_data->root_window); glk_data->root_window = NULL; return NULL; } + /* Set the minimum size to "as small as possible" so it doesn't depend on + the size of the window contents */ + gtk_widget_set_size_request(win->widget, 0, 0); + gtk_widget_set_size_request(win->frame, 0, 0); + if(split) { /* 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; pair->type = wintype_Pair; pair->window_node = g_node_new(pair); - pair->unit_width = split->unit_width; - pair->unit_height = split->unit_height; - pair->window_stream = NULL; + /* 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; + 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 { - g_node_append(split->window_node->parent, 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); } - - /* Keep track of the parent widget of the window that is being split */ - GtkWidget* old_parent = gtk_widget_get_parent(split->frame); - gtk_widget_ref(split->frame); - gtk_widget_unparent(split->frame); - /* Place the windows in the correct order */ switch(method & winmethod_DirMask) { case winmethod_Left: - pair->widget = gtk_hbox_new(FALSE, 0); - gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0); - gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0); - g_node_append(pair->window_node, split->window_node); - g_node_append(pair->window_node, win->window_node); - break; - case winmethod_Right: - pair->widget = gtk_hbox_new(FALSE, 0); - gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0); - gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0); - g_node_append(pair->window_node, win->window_node); - g_node_append(pair->window_node, split->window_node); - break; case winmethod_Above: - pair->widget = gtk_vbox_new(FALSE, 0); - gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0); - gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0); - g_node_append(pair->window_node, split->window_node); 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: - pair->widget = gtk_vbox_new(FALSE, 0); - gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0); - gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0); - g_node_append(pair->window_node, win->window_node); g_node_append(pair->window_node, split->window_node); + g_node_append(pair->window_node, win->window_node); break; } - gtk_widget_unref(split->frame); - - /* TODO: set the new size of the windows */ - pair->frame = pair->widget; - gtk_widget_set_parent(pair->widget, old_parent); - gtk_widget_show(pair->widget); } else { /* Set the window as root window */ glk_data->root_window = win->window_node; - gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self)); - gtk_widget_queue_resize(GTK_WIDGET(glk_data->self)); } - /* For text grid windows, wait until GTK draws the window (see note in glk_window_get_size() ), calculate the size and fill the buffer with blanks. */ + /* 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) { - 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(); - } - win->width = (glui32)(win->widget->allocation.width / win->unit_width); - win->height = (glui32)(win->widget->allocation.height / win->unit_height); - - /* Mark the cursor position */ + /* 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); - - /* Fill the buffer with blanks and move the cursor to the upper left */ gdk_threads_leave(); + + /* Fill the buffer with blanks and move the cursor to the upper left */ glk_window_clear(win); - gdk_threads_enter(); - - /* Apparently this only works after the window has been realized */ - gtk_text_view_set_overwrite( GTK_TEXT_VIEW(win->widget), TRUE ); } - gdk_threads_leave(); - return 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. - */ -void -glk_window_close(winid_t win, stream_result_t *result) +/* 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) { - GNode* parent_node; - - g_return_if_fail(win != NULL); - - gdk_threads_enter(); + 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: - gtk_widget_destroy(win->widget); + gdk_threads_enter(); + gtk_widget_unparent(win->widget); + gdk_threads_leave(); break; case wintype_TextGrid: case wintype_TextBuffer: - gtk_widget_destroy(win->frame); + gdk_threads_enter(); + gtk_widget_unparent(win->frame); + gdk_threads_leave(); /* TODO: Cancel all input requests */ break; case wintype_Pair: - { - GNode* left_child = g_node_first_child(win->window_node); - GNode* right_child = g_node_last_child(win->window_node); - - glk_window_close((winid_t) left_child->data, result); - glk_window_close((winid_t) right_child->data, result); - - gtk_widget_destroy(win->widget); - } + destroy_windows_below(win->window_node->children->data, NULL); + destroy_windows_below(win->window_node->children->next->data, NULL); break; default: - g_warning("%s: unsupported window type", __func__); - gdk_threads_leave(); + 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 Streams and Closing Streams. + * + * 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: + * + * + * + * + * O1 + * / \ + * O2 B + * / \ + * A C + * + * + * + * 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: + * + * + * + * + * O1 + * / \ + * O2 B + * / \ + * A D + * + * + * + * 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. + * + * + * 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 Changing Window + * Constraints. + * + */ +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 */ - if( (parent_node = win->window_node->parent) != NULL ) + /* 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) { - winid_t pair = (winid_t) parent_node->data; - if(parent_node->parent == 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(parent_node->next) - glk_data->root_window = parent_node->next; - else if(parent_node->prev) - glk_data->root_window = parent_node->prev; - } else { - if(parent_node->next) - g_node_append(parent_node->parent, parent_node->next); - else if(parent_node->prev) - g_node_append(parent_node->parent, parent_node->prev); + if(new_child_on_left) + g_node_prepend(new_parent_node, sibling_node); + else + g_node_append(new_parent_node, sibling_node); } - g_node_unlink(parent_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; } - g_node_destroy(win->window_node); + 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(); } @@ -490,33 +778,43 @@ glk_window_close(winid_t win, stream_result_t *result) * glk_window_clear: * @win: A window. * - * Erases the window @win. The meaning of this depends on the window type. - * - * + * Erases @win. The meaning of this depends on the window type. + * + * + * Text buffer * - * Text buffer: 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. + * 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. * + * + * + * Text grid * - * Text grid: This will clear the window, filling all positions with blanks. - * The window cursor is moved to the top left corner (position 0,0). + * This will clear the window, filling all positions with blanks. The window + * cursor is moved to the top left corner (position 0,0). * + * + * + * Graphics * - * Graphics: Clears the entire window to its current background color. + * Clears the entire window to its current background color. See Graphics Windows. * - * - * Other window types: No effect. - * - * + * + * + * Other window types + * No effect. + * + * * * It is illegal to erase a window which has line input pending. */ void glk_window_clear(winid_t win) { - g_return_if_fail(win != NULL); + 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) @@ -569,7 +867,7 @@ glk_window_clear(winid_t win) break; default: - g_warning("glk_window_clear: unsupported window type"); + ILLEGAL_PARAM("Unknown window type: %d", win->type); } } @@ -578,13 +876,12 @@ glk_window_clear(winid_t win) * @win: A window. * * Sets the current stream to @win's window stream. It is exactly equivalent to - * - * glk_stream_set_current(glk_window_get_stream(win)) - * + * #glk_stream_set_current(#glk_window_get_stream(@win)). */ void glk_set_window(winid_t win) { + VALID_WINDOW_OR_NULL(win, return); glk_stream_set_current( glk_window_get_stream(win) ); } @@ -592,15 +889,20 @@ glk_set_window(winid_t win) * glk_window_get_stream: * @win: A window. * - * Returns the stream which is associated with @win. Every window has a stream - * which can be printed to, but this may not be useful, depending on the window - * type. (For example, printing to a blank window's stream has no effect.) + * Returns the stream which is associated with @win. (See Window Streams.) Every window has a + * stream which can be printed to, but this may not be useful, depending on the + * window type. + * + * + * For example, printing to a blank window's stream has no effect. + * * - * Returns: The window stream. + * Returns: A window stream. */ strid_t glk_window_get_stream(winid_t win) { - g_return_val_if_fail(win != NULL, NULL); + VALID_WINDOW(win, return NULL); return win->window_stream; } @@ -609,28 +911,20 @@ strid_t glk_window_get_stream(winid_t win) * @win: A window. * @str: A stream to attach to the window, or %NULL. * - * Attaches the stream @str to @win as a second stream. Any text printed to the - * window is also echoed to this second stream, which is called the window's - * "echo stream." + * Sets @win's echo stream to @str, which can be any valid output stream. You + * can reset a window to stop echoing by calling + * #glk_window_set_echo_stream(@win, %NULL). * - * Effectively, any call to glk_put_char() (or the other output commands) which - * is directed to @win's window stream, is replicated to @win's echo stream. - * This also goes for the style commands such as glk_set_style(). - * - * Note that the echoing is one-way. You can still print text directly to the - * echo stream, and it will go wherever the stream is bound, but it does not - * back up and appear in the window. - * - * It is illegal to set a window's echo stream to be its own window stream, - * which would create an infinite loop. It is similarly illegal to create a - * longer loop (two or more windows echoing to each other.) - * - * You can reset a window to stop echoing by setting @str to %NULL. + * It is illegal to set a window's echo stream to be its + * own window stream. That would create an infinite loop, + * and is nearly certain to crash the Glk library. It is similarly illegal to + * create a longer loop (two or more windows echoing to each other.) */ void glk_window_set_echo_stream(winid_t win, strid_t str) { - g_return_if_fail(win != NULL); + VALID_WINDOW(win, return); + VALID_STREAM_OR_NULL(str, return); /* Test for an infinite loop */ strid_t next = str; @@ -638,7 +932,7 @@ glk_window_set_echo_stream(winid_t win, strid_t str) { if(next == win->window_stream) { - g_warning("%s: Infinite loop detected", __func__); + ILLEGAL("Infinite loop detected"); win->echo_stream = NULL; return; } @@ -651,15 +945,15 @@ glk_window_set_echo_stream(winid_t win, strid_t str) * glk_window_get_echo_stream: * @win: A window. * - * Returns the echo stream of window @win. If the window has no echo stream (as - * is initially the case) then this returns %NULL. + * Returns the echo stream of window @win. Initially, a window has no echo + * stream, so #glk_window_get_echo_stream(@win) will return %NULL. * * Returns: A stream, or %NULL. */ strid_t glk_window_get_echo_stream(winid_t win) { - g_return_val_if_fail(win != NULL, NULL); + VALID_WINDOW(win, return NULL); return win->echo_stream; } @@ -670,17 +964,21 @@ glk_window_get_echo_stream(winid_t win) * @heightptr: Pointer to a location to store the window's height, or %NULL. * * Simply returns the actual size of the window, in its measurement system. - * Either @widthptr or @heightptr can be %NULL, if you only want one - * measurement. (Or, in fact, both, if you want to waste time.) + * As described in Other API + * Conventions, either @widthptr or @heightptr can be %NULL, if you + * only want one measurement. + * + * Or, in fact, both, if you want to waste time. */ void glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr) { - g_return_if_fail(win != NULL); + VALID_WINDOW(win, return); switch(win->type) { case wintype_Blank: + case wintype_Pair: if(widthptr != NULL) *widthptr = 0; if(heightptr != NULL) @@ -688,7 +986,22 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr) break; case wintype_TextGrid: - /* The text grid caches its width and height */ + 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(); + } + + win->width = (glui32)(win->widget->allocation.width / win->unit_width); + win->height = (glui32)(win->widget->allocation.height / win->unit_height); + gdk_threads_leave(); + if(widthptr != NULL) *widthptr = win->width; if(heightptr != NULL) @@ -706,7 +1019,8 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr) } */ /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */ - while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1) + gdk_window_process_all_updates(); + while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1) { /* Release the GDK lock momentarily */ gdk_threads_leave(); @@ -724,10 +1038,10 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr) break; default: - g_warning("glk_window_get_size: Unsupported window type"); + ILLEGAL_PARAM("Unknown window type: %u", win->type); } } - + /** * glk_window_move_cursor: * @win: A text grid window. @@ -739,9 +1053,9 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr) * beginning of the next line. * * If you move the cursor below the last line, or when the cursor reaches the - * end of the last line, it goes "off the screen" and further output has no - * effect. You must call glk_window_move_cursor() or glk_window_clear() to move - * the cursor back into the visible region. + * end of the last line, it goes off the screen and further + * output has no effect. You must call glk_window_move_cursor() or + * glk_window_clear() to move the cursor back into the visible region. * * * Note that the arguments of glk_window_move_cursor() are unsigned @@ -754,14 +1068,14 @@ glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr) * Also note that the output cursor is not necessarily visible. In particular, * when you are requesting line or character input in a grid window, you cannot * rely on the cursor position to prompt the player where input is indicated. - * You should print some character prompt at that spot -- a ">" character, for - * example. + * You should print some character prompt at that spot — a + * > character, for example. * */ void glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos) { - g_return_if_fail(win != NULL); + VALID_WINDOW(win, return); g_return_if_fail(win->type == wintype_TextGrid); /* Calculate actual position if cursor is moved past the right edge */ @@ -787,3 +1101,4 @@ glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos) gdk_threads_leave(); } +