Changed build system to Automake. Split Glk code off into a GTK widget.
[projects/chimara/chimara.git] / src / window.c
index 22cb2e3923343b9de5c37de193570598fa50403a..16927ca9fd0ee2944748a7b4a58ead4d3502d71e 100644 (file)
@@ -1,23 +1,23 @@
 #include "window.h"
+#include "chimara-glk-private.h"
 
-/* Global tree of all windows */
-static GNode *root_window = NULL;
+extern ChimaraGlkPrivate *glk_data;
 
 /**
  * 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)
@@ -25,7 +25,7 @@ glk_window_iterate(winid_t win, glui32 *rockptr)
        GNode *retnode;
        
        if(win == NULL)
-               retnode = root_window;
+               retnode = glk_data->root_window;
        else
        {
                GNode *node = win->window_node;
@@ -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.
  */
@@ -69,7 +70,7 @@ glk_window_get_rock(winid_t win)
  * glk_window_get_type:
  * @win: A window.
  *
- * Returns the window @win's type, one of #wintype_Blank, #wintype_Pair,
+ * Returns @win's type, one of #wintype_Blank, #wintype_Pair,
  * #wintype_TextBuffer, #wintype_TextGrid, or #wintype_Graphics.
  *
  * Returns: The window's type.
@@ -78,7 +79,7 @@ glui32
 glk_window_get_type(winid_t win)
 {
        g_return_val_if_fail(win != NULL, 0);
-       return win->wintype;
+       return win->type;
 }
 
 /**
@@ -86,7 +87,7 @@ glk_window_get_type(winid_t win)
  * @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
+ * 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.
@@ -104,9 +105,9 @@ glk_window_get_parent(winid_t win)
  * @win: A window.
  *
  * Returns the other child of the window @win's parent. If @win is the
- * root window, this returns #NULL.
+ * root window, this returns %NULL.
  *
- * Returns: A window, or NULL.
+ * Returns: A window, or %NULL.
  */
 winid_t
 glk_window_get_sibling(winid_t win)
@@ -130,9 +131,9 @@ glk_window_get_sibling(winid_t win)
 winid_t
 glk_window_get_root()
 {
-       if(root_window == NULL)
+       if(glk_data->root_window == NULL)
                return NULL;
-       return (winid_t)root_window->data;
+       return (winid_t)glk_data->root_window->data;
 }
 
 /**
@@ -162,89 +163,169 @@ winid_t
 glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, 
                 glui32 rock)
 {
-       extern GtkBuilder *builder;
-
        if(split)
        {
                g_warning("glk_window_open: splitting of windows not implemented");
                return NULL;
        }
 
-       if(root_window != NULL)
+       if(glk_data->root_window != NULL)
        {
                g_warning("glk_window_open: there is already a window");
                return NULL;
        }
+       
+       gdk_threads_enter();
+       
        /* 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);
-
-       new_window->rock = rock;
-       new_window->window_type = wintype;
+       winid_t win = g_new0(struct glk_window_struct, 1);
+       glk_data->root_window = g_node_new(win);
 
-       GtkBox *vbox = GTK_BOX( gtk_builder_get_object(builder, "vbox") );                      
-       if(vbox == NULL)
-       {
-               error_dialog(NULL, NULL, "Could not find vbox");
-               return NULL;
-       }
+       win->rock = rock;
+       win->type = wintype;
+    win->window_node = glk_data->root_window;
 
        switch(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_widget_show(label);
                        
-                       new_window->widget = window;
+                       win->widget = label;
+                       win->frame = 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;
                        
                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_widget_show_all(scrolledwindow);
+
+                       win->widget = textview;
+                       win->frame = scrolledwindow;
+                       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;
+    /* Put the frame widget into our container */
+    gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self));
+    gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
+
+       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)
+{
+       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);
 
-       return new_window;
+       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.
+ * Erases the window @win. The meaning of this depends on the window type.
+ *
+ * <itemizedlist>
+ *  <listitem><para>
+ *   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.
+ *  </para></listitem>
+ *  <listitem><para>
+ *   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).
+ *  </para></listitem>
+ *  <listitem><para>
+ *   Graphics: Clears the entire window to its current background color.
+ *  </para></listitem>
+ *  <listitem><para>
+ *   Other window types: No effect. 
+ *  </para></listitem>
+ * </itemizedlist>
+ *
+ * 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->wintype)
+       switch(win->type)
        {
                case wintype_Blank:
                        /* do nothing */
@@ -253,11 +334,14 @@ glk_window_clear(winid_t win)
                case wintype_TextBuffer:
                        /* delete all text in the window */
                {
-                       GtkTextBuffer *buffer = gtk_text_view_get_buffer( 
-                               GTK_TEXT_VIEW(current_stream->window->widget) );
+                       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;
                        
@@ -270,7 +354,10 @@ glk_window_clear(winid_t win)
  * 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
+ * <informalexample><programlisting>
+ *  glk_stream_set_current(glk_window_get_stream(win))
+ * </programlisting></informalexample>
  */
 void
 glk_set_window(winid_t win)
@@ -282,7 +369,9 @@ 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.
  */
@@ -295,7 +384,7 @@ strid_t glk_window_get_stream(winid_t win)
 /**
  * glk_window_set_echo_stream:
  * @win: A window.
- * @str: A stream to attach to the window, or #NULL.
+ * @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
@@ -313,7 +402,7 @@ strid_t glk_window_get_stream(winid_t win)
  * 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.
+ * 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)
@@ -321,20 +410,18 @@ 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_str = str;
-               next_str != NULL && next_str->stream_type == STREAM_TYPE_WINDOW;
-               next_str = next_str->window->echo_stream)
+       strid_t next = str;
+       for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream)
        {
-               if(next_str == win->window_stream)
+               if(next == win->window_stream)
                {
-                       g_warning("glk_window_set_echo_stream: Infinite loop detected");
+                       g_warning("%s: Infinite loop detected", __func__);
                        win->echo_stream = NULL;
                        return;
                }
        }
        
-       win->echo_stream = str; 
+       win->echo_stream = str;
 }
 
 /**
@@ -342,9 +429,9 @@ glk_window_set_echo_stream(winid_t win, strid_t str)
  * @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.
+ * is initially the case) then this returns %NULL.
  *
- * Returns: A stream, or #NULL.
+ * Returns: A stream, or %NULL.
  */
 strid_t
 glk_window_get_echo_stream(winid_t win)
@@ -353,3 +440,68 @@ glk_window_get_echo_stream(winid_t win)
        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><para>
+ *  Note that the arguments of glk_window_move_cursor() are <type>unsigned 
+ *  int</type>s. 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.
+ * </para></note>
+ *
+ * <note><para>
+ *  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.
+ * </para></note>
+ */
+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 */
+}