X-Git-Url: https://git.stderr.nl/gitweb?a=blobdiff_plain;f=src%2Fwindow.c;h=cd2ff7aa04e8886c6e3417020452a71222286934;hb=5292406311d31682a850baf059cab01a6f0262b2;hp=be7c20d97496224b207852a989d6f600666dc37d;hpb=a706d1eab45506c099bd2b93d301969754ccebf9;p=rodin%2Fchimara.git diff --git a/src/window.c b/src/window.c index be7c20d..cd2ff7a 100644 --- a/src/window.c +++ b/src/window.c @@ -5,19 +5,19 @@ static GNode *root_window = NULL; /** * glk_window_iterate: - * @win: A window, or #NULL. - * @rockptr: Return location for the next window's rock, or #NULL. + * @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 + * 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. + * the rocks to be returned, you may set @rockptr to %NULL. * * 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. * - * Returns: the next window, or #NULL if there are no more. + * Returns: the next window, or %NULL if there are no more. */ winid_t glk_window_iterate(winid_t win, glui32 *rockptr) @@ -54,7 +54,8 @@ glk_window_iterate(winid_t win, glui32 *rockptr) * glk_window_get_rock: * @win: A window. * - * Returns the window @win's rock value. Pair windows always have rock 0. + * Returns @win's rock value. Pair windows always have rock 0; all other windows + * have the rock value you created them with. * * Returns: A rock value. */ @@ -65,6 +66,76 @@ glk_window_get_rock(winid_t win) return win->rock; } +/** + * 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: The window's type. + */ +glui32 +glk_window_get_type(winid_t win) +{ + g_return_val_if_fail(win != NULL, 0); + return win->type; +} + +/** + * 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: A window. + */ +winid_t +glk_window_get_parent(winid_t win) +{ + g_return_val_if_fail(win != NULL, NULL); + /* Value will also be NULL if win is the root window */ + return (winid_t)win->window_node->parent->data; +} + +/** + * 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: A window, or %NULL. + */ +winid_t +glk_window_get_sibling(winid_t win) +{ + g_return_val_if_fail(win != NULL, NULL); + + if(G_NODE_IS_ROOT(win->window_node)) + return NULL; + if(win->window_node->next) + return (winid_t)win->window_node->next; + return (winid_t)win->window_node->prev; +} + +/** + * glk_window_get_root: + * + * Returns the root window. If there are no windows, this returns #NULL. + * + * Returns: A window, or #NULL. + */ +winid_t +glk_window_get_root() +{ + if(root_window == NULL) + return NULL; + return (winid_t)root_window->data; +} + /** * glk_window_open: * @split: The window to split to create the new window. Must be 0 if there @@ -106,16 +177,19 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, return NULL; } /* We only create one window and don't support any more than that */ - winid_t new_window = g_new0(struct glk_window_struct, 1); - root_window = g_node_new(new_window); + winid_t win = g_new0(struct glk_window_struct, 1); + root_window = g_node_new(win); - new_window->rock = rock; - new_window->window_type = wintype; + win->rock = rock; + win->type = wintype; + + gdk_threads_enter(); GtkBox *vbox = GTK_BOX( gtk_builder_get_object(builder, "vbox") ); if(vbox == NULL) { error_dialog(NULL, NULL, "Could not find vbox"); + gdk_threads_leave(); return NULL; } @@ -124,48 +198,172 @@ glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, case wintype_Blank: { /* A blank window will be a label without any text */ - GtkWidget *window = gtk_label_new(""); - gtk_box_pack_end(vbox, window, TRUE, TRUE, 0); - gtk_widget_show(window); + GtkWidget *label = gtk_label_new(""); + gtk_box_pack_end(vbox, label, TRUE, TRUE, 0); + gtk_widget_show(label); - new_window->widget = window; + win->widget = label; /* You can print to a blank window's stream, but it does nothing */ - new_window->window_stream = window_stream_new(new_window); - new_window->echo_stream = NULL; + win->window_stream = window_stream_new(win); + win->echo_stream = NULL; } - break; + break; + case wintype_TextBuffer: { - GtkWidget *scroll_window = gtk_scrolled_window_new(NULL, NULL); - GtkWidget *window = gtk_text_view_new(); - gtk_container_add( GTK_CONTAINER(scroll_window), window ); - gtk_box_pack_end(vbox, scroll_window, TRUE, TRUE, 0); - gtk_widget_show_all(scroll_window); - - new_window->widget = window; - new_window->window_stream = window_stream_new(new_window); - new_window->echo_stream = NULL; - new_window->input_request_type = INPUT_REQUEST_NONE; - new_window->line_input_buffer = NULL; - new_window->line_input_buffer_unicode = NULL; + GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL); + 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_WORD_CHAR ); + gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE ); + + gtk_container_add( GTK_CONTAINER(scrolledwindow), textview ); + gtk_box_pack_end(vbox, scrolledwindow, TRUE, TRUE, 0); + gtk_widget_show_all(scrolledwindow); + + win->widget = textview; + 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 ); + + 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 ); + + /* 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); + + /* Mark the position where the user will input text */ + GtkTextIter end; + gtk_text_buffer_get_end_iter(textbuffer, &end); + gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE); } break; + default: - g_warning("glk_window_open: unsupported window type"); - g_free(new_window); + gdk_threads_leave(); + g_warning("%s: unsupported window type", __func__); + g_free(win); return NULL; } - new_window->window_node = root_window; + gdk_threads_leave(); - return new_window; + win->window_node = root_window; + + 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) +{ + g_return_if_fail(win != NULL); + + switch(win->type) + { + case wintype_TextBuffer: + gtk_widget_destroy( gtk_widget_get_parent(win->widget) ); + /* TODO: Cancel all input requests */ + break; + + case wintype_Blank: + gtk_widget_destroy(win->widget); + break; + } + + stream_close_common(win->window_stream, result); + + g_node_destroy(win->window_node); + /* TODO: iterate over child windows, closing them */ + + g_free(win); +} + +/** + * glk_window_clear: + * @win: A window. + * + * Erases the window @win. The meaning of this depends on the window type. + * + * + * + * 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. + * + * + * 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). + * + * + * Graphics: Clears the entire window to its current background color. + * + * + * 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); + g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE); + + switch(win->type) + { + case wintype_Blank: + /* do nothing */ + break; + + case wintype_TextBuffer: + /* delete all text in the window */ + { + gdk_threads_enter(); + + GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + GtkTextIter start, end; + gtk_text_buffer_get_bounds(buffer, &start, &end); + gtk_text_buffer_delete(buffer, &start, &end); + + gdk_threads_leave(); + } + break; + + default: + g_warning("glk_window_clear: unsupported window type"); + } } /** * glk_set_window: * @win: A window. * - * Sets the current stream to @win's window stream. + * Sets the current stream to @win's window stream. It is exactly equivalent to + * + * glk_stream_set_current(glk_window_get_stream(win)) + * */ void glk_set_window(winid_t win) @@ -177,12 +375,139 @@ glk_set_window(winid_t win) * glk_window_get_stream: * @win: A window. * - * Gets the stream associated with @win. + * 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 window stream. */ strid_t glk_window_get_stream(winid_t win) { + g_return_val_if_fail(win != NULL, NULL); return win->window_stream; } +/** + * glk_window_set_echo_stream: + * @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." + * + * 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. + */ +void +glk_window_set_echo_stream(winid_t win, strid_t str) +{ + g_return_if_fail(win != NULL); + + /* Test for an infinite loop */ + strid_t next = str; + for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream) + { + if(next == win->window_stream) + { + g_warning("%s: Infinite loop detected", __func__); + win->echo_stream = NULL; + return; + } + } + + win->echo_stream = 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: A stream, or %NULL. + */ +strid_t +glk_window_get_echo_stream(winid_t win) +{ + g_return_val_if_fail(win != NULL, NULL); + return win->echo_stream; +} + +/** + * glk_window_get_size: + * @win: A window. + * @widthptr: Pointer to a location to store the window's width, or %NULL. + * @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.) + */ +void +glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr) +{ + g_return_if_fail(win != NULL); + + /* TODO: Write this function */ + /* For a text buffer window: Return the number of rows and columns which + would be available _if_ the window was filled with "0" (zero) characters in + the "normal" font. */ + if(widthptr != NULL) { + *widthptr = 0; + } + + if(heightptr != NULL) { + *heightptr = 0; + } +} + +/** + * glk_window_move_cursor: + * @win: A text grid window. + * @xpos: Horizontal cursor position. + * @ypos: Vertical cursor position. + * + * Sets the cursor position. If you move the cursor right past the end of a + * line, it wraps; the next character which is printed will appear at the + * 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. + * + * + * Note that the arguments of glk_window_move_cursor() are unsigned + * ints. This is okay, since there are no negative positions. If you try + * to pass a negative value, Glk will interpret it as a huge positive value, + * and it will wrap or go off the last line. + * + * + * + * 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. + * + */ +void +glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos) +{ + g_return_if_fail(win != NULL); + g_return_if_fail(win->type == wintype_TextGrid); + /* TODO: write this function */ +}