+/**
+ * glk_window_set_echo_stream:
+ * @win: A window.
+ * @str: A stream to attach to the window, or %NULL.
+ *
+ * Sets @win's echo stream to @str, which can be any valid output stream. You
+ * can reset a window to stop echoing by calling
+ * <code>#glk_window_set_echo_stream(@win, %NULL)</code>.
+ *
+ * It is illegal to set a window's echo stream to be its
+ * <emphasis>own</emphasis> 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)
+{
+ VALID_WINDOW(win, return);
+ VALID_STREAM_OR_NULL(str, return);
+
+ /* 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)
+ {
+ ILLEGAL("Infinite loop detected");
+ win->echo_stream = NULL;
+ return;
+ }
+ }
+
+ win->echo_stream = str;
+}
+
+/**
+ * glk_window_get_echo_stream:
+ * @win: A window.
+ *
+ * Returns the echo stream of window @win. Initially, a window has no echo
+ * stream, so <code>#glk_window_get_echo_stream(@win)</code> will return %NULL.
+ *
+ * Returns: A stream, or %NULL.
+ */
+strid_t
+glk_window_get_echo_stream(winid_t win)
+{
+ VALID_WINDOW(win, return 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.
+ * As described in <link linkend="chimara-Other-API-Conventions">Other API
+ * Conventions</link>, either @widthptr or @heightptr can be %NULL, if you
+ * only want one measurement.
+ *
+ * <note><para>Or, in fact, both, if you want to waste time.</para></note>
+ */
+void
+glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
+{
+ VALID_WINDOW(win, return);
+
+ switch(win->type)
+ {
+ case wintype_Blank:
+ case wintype_Pair:
+ if(widthptr != NULL)
+ *widthptr = 0;
+ if(heightptr != NULL)
+ *heightptr = 0;
+ break;
+
+ case wintype_TextGrid:
+ gdk_threads_enter();
+ /* Wait for the window to be drawn, and then cache the width and height */
+ 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)
+ *heightptr = win->height;
+ 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. */
+ 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. */
+ 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)
+ *heightptr = (glui32)(win->widget->allocation.height / win->unit_height);
+ gdk_threads_leave();
+
+ break;
+
+ default:
+ ILLEGAL_PARAM("Unknown window type: %u", win->type);
+ }
+}
+
+/**
+ * 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 <quote>off the screen</quote> 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
+ * <quote>></quote> character, for example.
+ * </para></note>
+ */
+void
+glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
+{
+ VALID_WINDOW(win, return);
+ g_return_if_fail(win->type == wintype_TextGrid);
+
+ /* Calculate actual position if cursor is moved past the right edge */
+ if(xpos >= win->width)
+ {
+ ypos += xpos / win->width;
+ xpos %= win->width;
+ }
+ /* Go to the end if the cursor is moved off the bottom edge */
+ if(ypos >= win->height)
+ {
+ xpos = win->width - 1;
+ ypos = win->height - 1;
+ }
+
+ gdk_threads_enter();
+
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
+ GtkTextIter newpos;
+ /* There must actually be a character at xpos, or the following function will choke */
+ gtk_text_buffer_get_iter_at_line_offset(buffer, &newpos, ypos, xpos);
+ gtk_text_buffer_move_mark_by_name(buffer, "cursor_position", &newpos);
+
+ gdk_threads_leave();
+}
+