X-Git-Url: https://git.stderr.nl/gitweb?a=blobdiff_plain;f=src%2Fwindow.c;h=5f2bd5b349673bb2b46a755e46c3da8906c93240;hb=91214934fbcdfd363202a65c142194506604ff7b;hp=8922d8f3d1e116d168bf45c7ee9655878af750da;hpb=c3e774e7847a994b7c3fb5d0e6a516432981a5b6;p=projects%2Fchimara%2Fchimara.git diff --git a/src/window.c b/src/window.c index 8922d8f..5f2bd5b 100644 --- a/src/window.c +++ b/src/window.c @@ -211,15 +211,11 @@ 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. @@ -237,8 +233,17 @@ glk_window_get_root() * 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: - * Screen shot 5 - * + * + * + * + * + * 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 @@ -246,8 +251,8 @@ glk_window_get_root() * of the 50% it had before. * * Now the player stretches the window vertically. - * Screen shot 6 - * + * + * * * 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 @@ -255,8 +260,18 @@ glk_window_get_root() * O. C gets two rows; A gets the rest. All done. * * Then the user maliciously starts squeezing the window down, in stages: - * - * Screen shot 7 + * + * + * + * + * + * + * + * + * + * + * + * * * 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 @@ -290,8 +305,29 @@ glk_window_get_root() * 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. * - * Screen shot 8 - * + * + * + * + * + * 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 @@ -306,8 +342,19 @@ glk_window_get_root() * 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. - * Screen shot 9 - * + * + * + * + * + * 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 @@ -364,22 +411,17 @@ 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 */ gtk_widget_modify_font(textview, glk_data->monospace_font_desc); win->widget = textview; - win->frame = scrolledwindow; + win->frame = textview; /* Determine the size of a "0" character in pixels */ PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0"); @@ -444,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); @@ -460,6 +505,11 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, 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 @@ -480,10 +530,13 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, /* 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); } /* Place the windows in the correct order */ @@ -509,124 +562,48 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, /* 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)); - - /* 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. */ + + 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(); } - 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. 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: - * Screen shot 10 - * - * - * 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: - * Screen shot 11 - * - * - * 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. - * - */ - -static void -dump_window_tree(GNode *node) +/* 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) { - if(node == NULL) { - g_printerr("NULL"); - return; - } - g_printerr("["); - switch(((winid_t)node->data)->type) { - case wintype_Pair: - g_printerr("Pair "); - dump_window_tree(node->children); - dump_window_tree(node->children->next); - g_printerr("]"); - break; - case wintype_TextBuffer: - g_printerr("Buffer]"); - break; - case wintype_TextGrid: - g_printerr("Grid]"); - break; - case wintype_Blank: - g_printerr("Blank]"); - break; - default: - g_printerr("Fucked up - %d]", ((winid_t)node->data)->type); - } -} - -static void -close_window_streams_below(winid_t win, stream_result_t *result) -{ - if(win->type == wintype_Pair) { - close_window_streams_below(win->window_node->children->data, NULL); - close_window_streams_below(win->window_node->children->next->data, NULL); - } - stream_close_common(win->window_stream, result); + 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_widgets_below(winid_t win) +destroy_windows_below(winid_t win, stream_result_t *result) { switch(win->type) { @@ -645,16 +622,18 @@ destroy_widgets_below(winid_t win) break; case wintype_Pair: - destroy_widgets_below(win->window_node->children->data); - destroy_widgets_below(win->window_node->children->next->data); + 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) { @@ -666,16 +645,80 @@ free_winids_below(winid_t win) 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); - /* First close all the window streams before trashing the window tree */ - close_window_streams_below(win, result); + /* 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); - /* Then destroy the widgets of this window and below */ - destroy_widgets_below(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) { @@ -725,7 +768,9 @@ glk_window_close(winid_t win, stream_result_t *result) 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(); } @@ -941,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) @@ -959,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();