+/**
+ * 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);
+
+ switch(win->type)
+ {
+ case wintype_Blank:
+ if(widthptr != NULL)
+ *widthptr = 0;
+ if(heightptr != NULL)
+ *heightptr = 0;
+ break;
+
+ case wintype_TextGrid:
+ /* The text grid caches its width and height */
+ 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:
+ g_warning("glk_window_get_size: Unsupported window 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 "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);
+
+ /* 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();
+}