2 #include "chimara-glk-private.h"
4 extern ChimaraGlkPrivate *glk_data;
8 * @win: A window, or %NULL.
9 * @rockptr: Return location for the next window's rock, or %NULL.
11 * This function can be used to iterate through the list of all open windows
12 * (including pair windows.) See <link
13 * linkend="chimara-Iterating-Through-Opaque-Objects">Iterating Through Opaque
16 * As that section describes, the order in which windows are returned is
17 * arbitrary. The root window is not necessarily first, nor is it necessarily
20 * Returns: the next window, or %NULL if there are no more.
23 glk_window_iterate(winid_t win, glui32 *rockptr)
28 retnode = glk_data->root_window;
31 GNode *node = win->window_node;
32 if( G_NODE_IS_LEAF(node) )
34 while(node && node->next == NULL)
42 retnode = g_node_first_child(node);
44 winid_t retval = retnode? (winid_t)retnode->data : NULL;
46 /* Store the window's rock in rockptr */
48 *rockptr = glk_window_get_rock(retval);
54 * glk_window_get_rock:
57 * Returns @win's rock value. Pair windows always have rock 0; all other windows
58 * return whatever rock value you created them with.
60 * Returns: A rock value.
63 glk_window_get_rock(winid_t win)
65 g_return_val_if_fail(win != NULL, 0);
70 * glk_window_get_type:
73 * Returns @win's type, one of #wintype_Blank, #wintype_Pair,
74 * #wintype_TextBuffer, #wintype_TextGrid, or #wintype_Graphics.
76 * Returns: The window's type.
79 glk_window_get_type(winid_t win)
81 g_return_val_if_fail(win != NULL, 0);
86 * glk_window_get_parent:
89 * Returns the window which is the parent of @win. If @win is the root window,
90 * this returns %NULL, since the root window has no parent. Remember that the
91 * parent of every window is a pair window; other window types are always
94 * Returns: A window, or %NULL.
97 glk_window_get_parent(winid_t win)
99 g_return_val_if_fail(win != NULL, NULL);
100 /* Value will also be NULL if win is the root window */
101 return (winid_t)win->window_node->parent->data;
105 * glk_window_get_sibling:
108 * Returns the other child of @win's parent. If @win is the root window, this
111 * Returns: A window, or %NULL.
114 glk_window_get_sibling(winid_t win)
116 g_return_val_if_fail(win != NULL, NULL);
118 if(G_NODE_IS_ROOT(win->window_node))
120 if(win->window_node->next)
121 return (winid_t)win->window_node->next;
122 return (winid_t)win->window_node->prev;
126 * glk_window_get_root:
128 * Returns the root window. If there are no windows, this returns %NULL.
130 * Returns: A window, or %NULL.
133 glk_window_get_root()
135 if(glk_data->root_window == NULL)
137 return (winid_t)glk_data->root_window->data;
140 /* Determine the size of a "0" character in pixels */
142 text_window_get_char_size(GtkWidget *textview, int *width, int *height)
144 PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
145 pango_layout_get_pixel_size(zero, width, height);
146 g_object_unref(zero);
151 * @split: The window to split to create the new window. Must be 0 if there
152 * are no windows yet.
153 * @method: Position of the new window and method of size computation. One of
154 * #winmethod_Above, #winmethod_Below, #winmethod_Left, or #winmethod_Right
155 * OR'ed with #winmethod_Fixed or #winmethod_Proportional. If @wintype is
156 * #wintype_Blank, then #winmethod_Fixed is not allowed.
157 * @size: Size of the new window, in percentage points if @method is
158 * #winmethod_Proportional, otherwise in characters if @wintype is
159 * #wintype_TextBuffer or #wintype_TextGrid, or pixels if @wintype is
161 * @wintype: Type of the new window. One of #wintype_Blank, #wintype_TextGrid,
162 * #wintype_TextBuffer, or #wintype_Graphics.
163 * @rock: The new window's rock value.
165 * Creates a new window. If there are no windows, the first three arguments are
166 * meaningless. @split <emphasis>must</emphasis> be 0, and @method and @size
167 * are ignored. @wintype is the type of window you're creating, and @rock is
168 * the rock (see <link linkend="chimara-Rocks">Rocks</link>).
170 * If any windows exist, new windows must be created by splitting existing
171 * ones. @split is the window you want to split; this <emphasis>must
172 * not</emphasis> be zero. @method is a mask of constants to specify the
173 * direction and the split method (see below). @size is the size of the split.
174 * @wintype is the type of window you're creating, and @rock is the rock.
176 * Remember that it is possible that the library will be unable to create a new
177 * window, in which case glk_window_open() will return %NULL.
180 * It is acceptable to gracefully exit, if the window you are creating is an
181 * important one — such as your first window. But you should not try to
182 * perform any window operation on the id until you have tested to make sure
186 * The examples we've seen so far have the simplest kind of size control. (Yes,
187 * this is <quote>below</quote>.) Every pair is a percentage split, with
190 * <mathphrase>X</mathphrase>
192 * percent going to one side, and
195 * <mathphrase>(100 - X)</mathphrase>
197 * percent going to the other side. If the player resizes the window, the whole
198 * mess expands, contracts, or stretches in a uniform way.
200 * As I said above, you can also make fixed-size splits. This is a little more
201 * complicated, because you have to know how this fixed size is measured.
203 * Sizes are measured in a way which is different for each window type. For
204 * example, a text grid window is measured by the size of its fixed-width font.
205 * You can make a text grid window which is fixed at a height of four rows, or
206 * ten columns. A text buffer window is measured by the size of its font.
209 * Remember that different windows may use different size fonts. Even two
210 * text grid windows may use fixed-size fonts of different sizes.
213 * Graphics windows are measured in pixels, not characters. Blank windows
214 * aren't measured at all; there's no meaningful way to measure them, and
215 * therefore you can't create a blank window of a fixed size, only of a
216 * proportional (percentage) size.
218 * So to create a text buffer window which takes the top 40% of the original
219 * window's space, you would execute
220 * <informalexample><programlisting>
221 * newwin = #glk_window_open(win, #winmethod_Above | #winmethod_Proportional, 40, #wintype_TextBuffer, 0);
222 * </programlisting></informalexample>
224 * To create a text grid which is always five lines high, at the bottom of the
225 * original window, you would do
226 * <informalexample><programlisting>
227 * newwin = #glk_window_open(win, #winmethod_Below | #winmethod_Fixed, 5, #wintype_TextGrid, 0);
228 * </programlisting></informalexample>
230 * Note that the meaning of the @size argument depends on the @method argument.
231 * If the method is #winmethod_Fixed, it also depends on the @wintype argument.
232 * The new window is then called the <quote>key window</quote> of this split,
233 * because its window type determines how the split size is computed.
236 * For #winmethod_Proportional splits, you can still call the new window the
237 * <quote>key window</quote>. But the key window is not important for
238 * proportional splits, because the size will always be computed as a simple
239 * ratio of the available space, not a fixed size of one child window.
242 * This system is more or less peachy as long as all the constraints work out.
243 * What happens when there is a conflict? The rules are simple. Size control
244 * always flows down the tree, and the player is at the top. Let's bring out an
246 * <mediaobject><textobject><phrase>Screen shot 5</phrase></textobject>
249 * First we split A into A and B, with a 50% proportional split. Then we split
250 * A into A and C, with C above, C being a text grid window, and C gets a fixed
251 * size of two rows (as measured in its own font size). A gets whatever remains
252 * of the 50% it had before.
254 * Now the player stretches the window vertically.
255 * <mediaobject><textobject><phrase>Screen shot 6</phrase></textobject>
258 * The library figures: the topmost split, the original A/B split, is 50-50. So
259 * B gets half the screen space, and the pair window next to it (the lower
260 * <quote>O</quote>) gets the other half. Then it looks at the lower
261 * <quote>O</quote>. C gets two rows; A gets the rest. All done.
263 * Then the user maliciously starts squeezing the window down, in stages:
264 * <mediaobject id="chimara-Figure-Squeezing-Window"><textobject><phrase>
265 * Screen shot 7</phrase></textobject></mediaobject>
267 * The logic remains the same. B always gets half the space. At stage 3,
268 * there's no room left for A, so it winds up with zero height. Nothing
269 * displayed in A will be visible. At stage 4, there isn't even room in the
270 * upper 50% to give C its two rows; so it only gets one. Finally, C is
271 * squashed out of existence as well.
273 * When a window winds up undersized, it remembers what size it should be. In
274 * the example above, A remembers that it should be two rows; if the user
275 * expands the window to the original size, it would return to the original
278 * The downward flow of control is a bit harsh. After all, in stage 4, there's
279 * room for C to have its two rows if only B would give up some of its 50%. But
280 * this does not happen.
283 * This makes life much easier for the Glk library. To determine the
284 * configuration of a window, it only needs to look at the window's
285 * ancestors, never at its descendants. So window layout is a simple
286 * recursive algorithm, no backtracking.
289 * What happens when you split a fixed-size window? The resulting pair window
290 * — that is, the two new parts together — retain the same size
291 * constraint as the original window that was split. The key window for the
292 * original split is still the key window for that split, even though it's now
293 * a grandchild instead of a child.
295 * The easy, and correct, way to think about this is that the size constraint
296 * is stored by a window's parent, not the window itself; and a constraint
297 * consists of a pointer to a key window plus a size value.
299 * <mediaobject><textobject><phrase>Screen shot 8</phrase></textobject>
301 * After the first split, the new pair window (O1, which covers the whole
302 * screen) knows that its first child (A) is above the second, and gets 50% of
303 * its own area. (A is the key window for this split, but a proportional split
304 * doesn't care about key windows.)
306 * After the second split, all this remains true; O1 knows that its first child
307 * gets 50% of its space, and A is O1's key window. But now O1's first child is
308 * O2 instead of A. The newer pair window (O2) knows that its first child (C)
309 * is above the second, and gets a fixed size of two rows. (As measured in C's
310 * font, because C is O2's key window.)
312 * If we split C, now, the resulting pair will still be two C-font rows high
313 * — that is, tall enough for two lines of whatever font C displays. For
314 * the sake of example, we'll do this vertically.
315 * <mediaobject><textobject><phrase>Screen shot 9</phrase></textobject>
318 * O3 now knows that its children have a 50-50 left-right split. O2 is still
319 * committed to giving its upper child, O3, two C-font rows. Again, this is
320 * because C is O2's key window.
323 * This turns out to be a good idea, because it means that C, the text grid
324 * window, is still two rows high. If O3 had been a upper-lower split, things
325 * wouldn't work out so neatly. But the rules would still apply. If you don't
326 * like this, don't do it.
329 * Returns: the new window, or %NULL on error.
332 glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype,
338 g_warning("glk_window_open: splitting of windows not implemented");
343 if(split == NULL && glk_data->root_window != NULL)
345 g_warning("glk_window_open: there is already a root window");
351 /* We only create one window and don't support any more than that */
352 winid_t win = g_new0(struct glk_window_struct, 1);
355 win->window_node = g_node_new(win);
361 /* A blank window will be a label without any text */
362 GtkWidget *label = gtk_label_new("");
363 gtk_widget_show(label);
367 /* A blank window has no size */
369 win->unit_height = 0;
370 /* You can print to a blank window's stream, but it does nothing */
371 win->window_stream = window_stream_new(win);
372 win->echo_stream = NULL;
376 case wintype_TextGrid:
378 GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
379 GtkWidget *textview = gtk_text_view_new();
381 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_NEVER );
383 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_CHAR );
384 gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
386 gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
387 gtk_widget_show_all(scrolledwindow);
389 /* Set the window's font */
390 /* TODO: Use Pango to pick out a monospace font on the system */
391 PangoFontDescription *font = pango_font_description_from_string("Monospace");
392 gtk_widget_modify_font(textview, font);
393 pango_font_description_free(font);
395 win->widget = textview;
396 win->frame = scrolledwindow;
397 text_window_get_char_size( textview, &(win->unit_width), &(win->unit_height) );
399 /* Set the other parameters (width and height are set later) */
400 win->window_stream = window_stream_new(win);
401 win->echo_stream = NULL;
402 win->input_request_type = INPUT_REQUEST_NONE;
403 win->line_input_buffer = NULL;
404 win->line_input_buffer_unicode = NULL;
406 /* Connect signal handlers */
407 win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
408 g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
412 case wintype_TextBuffer:
414 GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
415 GtkWidget *textview = gtk_text_view_new();
416 GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
418 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR );
419 gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
421 gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
422 gtk_widget_show_all(scrolledwindow);
424 win->widget = textview;
425 win->frame = scrolledwindow;
426 text_window_get_char_size( textview, &(win->unit_width), &(win->unit_height) );
428 /* Set the other parameters */
429 win->window_stream = window_stream_new(win);
430 win->echo_stream = NULL;
431 win->input_request_type = INPUT_REQUEST_NONE;
432 win->line_input_buffer = NULL;
433 win->line_input_buffer_unicode = NULL;
435 /* Connect signal handlers */
436 win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
437 g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
439 win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win );
440 g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler );
442 /* Create an editable tag to indicate uneditable parts of the window
444 gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
446 /* Mark the position where the user will input text */
448 gtk_text_buffer_get_end_iter(textbuffer, &end);
449 gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE);
455 g_warning("%s: unsupported window type", __func__);
457 g_node_destroy(glk_data->root_window);
458 glk_data->root_window = NULL;
464 /* When splitting, construct a new parent window
465 * copying most characteristics from the window that is being split */
466 winid_t pair = g_new0(struct glk_window_struct, 1);
468 pair->type = wintype_Pair;
469 pair->window_node = g_node_new(pair);
470 pair->unit_width = split->unit_width;
471 pair->unit_height = split->unit_height;
472 pair->window_stream = NULL;
473 pair->echo_stream = NULL;
475 /* Insert the new window into the window tree */
476 if(split->window_node->parent == NULL)
478 glk_data->root_window = pair->window_node;
480 g_node_append(split->window_node->parent, pair->window_node);
481 g_node_unlink(split->window_node);
484 /* Keep track of the parent widget of the window that is being split */
485 GtkWidget* old_parent = gtk_widget_get_parent(split->frame);
486 gtk_widget_ref(split->frame);
487 gtk_widget_unparent(split->frame);
489 /* Place the windows in the correct order */
490 switch(method & winmethod_DirMask)
493 pair->widget = gtk_hbox_new(FALSE, 0);
494 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
495 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
496 g_node_append(pair->window_node, split->window_node);
497 g_node_append(pair->window_node, win->window_node);
499 case winmethod_Right:
500 pair->widget = gtk_hbox_new(FALSE, 0);
501 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
502 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
503 g_node_append(pair->window_node, win->window_node);
504 g_node_append(pair->window_node, split->window_node);
506 case winmethod_Above:
507 pair->widget = gtk_vbox_new(FALSE, 0);
508 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
509 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
510 g_node_append(pair->window_node, split->window_node);
511 g_node_append(pair->window_node, win->window_node);
513 case winmethod_Below:
514 pair->widget = gtk_vbox_new(FALSE, 0);
515 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
516 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
517 g_node_append(pair->window_node, win->window_node);
518 g_node_append(pair->window_node, split->window_node);
521 gtk_widget_unref(split->frame);
523 /* TODO: set the new size of the windows */
525 pair->frame = pair->widget;
526 gtk_widget_set_parent(pair->widget, old_parent);
527 gtk_widget_show(pair->widget);
529 /* Set the window as root window */
530 glk_data->root_window = win->window_node;
531 gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self));
532 gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
535 /* 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. */
536 if(wintype == wintype_TextGrid)
538 while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
540 /* Release the GDK lock momentarily */
543 while(gtk_events_pending())
544 gtk_main_iteration();
546 win->width = (glui32)(win->widget->allocation.width / win->unit_width);
547 win->height = (glui32)(win->widget->allocation.height / win->unit_height);
549 /* Mark the cursor position */
551 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
552 gtk_text_buffer_get_start_iter(buffer, &begin);
553 gtk_text_buffer_create_mark(buffer, "cursor_position", &begin, TRUE);
555 /* Fill the buffer with blanks and move the cursor to the upper left */
557 glk_window_clear(win);
560 /* Apparently this only works after the window has been realized */
561 gtk_text_view_set_overwrite( GTK_TEXT_VIEW(win->widget), TRUE );
571 * @win: Window to close.
572 * @result: Pointer to a #stream_result_t in which to store the write count.
574 * Closes @win, which is pretty much exactly the opposite of opening a window.
575 * It is legal to close all your windows, or to close the root window (which is
578 * The @result argument is filled with the output character count of the window
579 * stream. See <link linkend="chimara-Streams">Streams</link> and <link
580 * linkend="chimara-Closing-Streams">Closing Streams</link>.
582 * When you close a window (and it is not the root window), the other window
583 * in its pair takes over all the freed-up area. Let's close D, in the current
585 * <mediaobject><textobject><phrase>Screen shot 10</phrase></textobject>
588 * Notice what has happened. D is gone. O3 is gone, and its 50-50 left-right
589 * split has gone with it. The other size constraints are unchanged; O2 is
590 * still committed to giving its upper child two rows, as measured in the font
591 * of O2's key window, which is C. Conveniently, O2's upper child is C, just as
592 * it was before we created D. In fact, now that D is gone, everything is back
593 * to the way it was before we created D.
595 * But what if we had closed C instead of D? We would have gotten this:
596 * <mediaobject><textobject><phrase>Screen shot 11</phrase></textobject>
599 * Again, O3 is gone. But D has collapsed to zero height. This is because its
600 * height is controlled by O2, and O2's key window was C, and C is now gone. O2
601 * no longer has a key window at all, so it cannot compute a height for its
602 * upper child, so it defaults to zero.
605 * This may seem to be an inconvenient choice. That is deliberate. You should
606 * not leave a pair window with no key, and the zero-height default reminds
607 * you not to. You can use glk_window_set_arrangement() to set a new split
608 * measurement and key window. See <link
609 * linkend="chimara-Changing-Window-Constraints">Changing Window
610 * Constraints</link>.
614 glk_window_close(winid_t win, stream_result_t *result)
618 g_return_if_fail(win != NULL);
625 gtk_widget_destroy(win->widget);
628 case wintype_TextGrid:
629 case wintype_TextBuffer:
630 gtk_widget_destroy(win->frame);
631 /* TODO: Cancel all input requests */
636 GNode* left_child = g_node_first_child(win->window_node);
637 GNode* right_child = g_node_last_child(win->window_node);
639 glk_window_close((winid_t) left_child->data, result);
640 glk_window_close((winid_t) right_child->data, result);
642 gtk_widget_destroy(win->widget);
647 g_warning("%s: unsupported window type", __func__);
652 stream_close_common(win->window_stream, result);
654 /* Parent window changes from a split window into the sibling window */
655 if( (parent_node = win->window_node->parent) != NULL )
657 winid_t pair = (winid_t) parent_node->data;
658 if(parent_node->parent == NULL)
660 if(parent_node->next)
661 glk_data->root_window = parent_node->next;
662 else if(parent_node->prev)
663 glk_data->root_window = parent_node->prev;
665 if(parent_node->next)
666 g_node_append(parent_node->parent, parent_node->next);
667 else if(parent_node->prev)
668 g_node_append(parent_node->parent, parent_node->prev);
671 g_node_unlink(parent_node);
675 g_node_destroy(win->window_node);
685 * Erases @win. The meaning of this depends on the window type.
688 * <term>Text buffer</term>
690 * This may do any number of things, such as delete all text in the window, or
691 * print enough blank lines to scroll all text beyond visibility, or insert a
692 * page-break marker which is treated specially by the display part of the
697 * <term>Text grid</term>
699 * This will clear the window, filling all positions with blanks. The window
700 * cursor is moved to the top left corner (position 0,0).
704 * <term>Graphics</term>
706 * Clears the entire window to its current background color. See <link
707 * linkend="chimara-Graphics-Windows">Graphics Windows</link>.
711 * <term>Other window types</term>
712 * <listitem><para>No effect.</para></listitem>
716 * It is illegal to erase a window which has line input pending.
719 glk_window_clear(winid_t win)
721 g_return_if_fail(win != NULL);
722 g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE);
731 case wintype_TextGrid:
732 /* fill the buffer with blanks */
736 /* Manually put newlines at the end of each row of characters in the buffer; manual newlines make resizing the window's grid easier. */
737 gchar *blanks = g_strnfill(win->width, ' ');
738 gchar **blanklines = g_new0(gchar *, win->height + 1);
740 for(count = 0; count < win->height; count++)
741 blanklines[count] = blanks;
742 blanklines[win->height] = NULL;
743 gchar *text = g_strjoinv("\n", blanklines);
744 g_free(blanklines); /* not g_strfreev() */
747 GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
748 gtk_text_buffer_set_text(textbuffer, text, -1);
752 gtk_text_buffer_get_start_iter(textbuffer, &begin);
753 gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &begin);
759 case wintype_TextBuffer:
760 /* delete all text in the window */
764 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
765 GtkTextIter start, end;
766 gtk_text_buffer_get_bounds(buffer, &start, &end);
767 gtk_text_buffer_delete(buffer, &start, &end);
774 g_warning("glk_window_clear: unsupported window type");
782 * Sets the current stream to @win's window stream. It is exactly equivalent to
783 * <code>#glk_stream_set_current(#glk_window_get_stream(@win))</code>.
786 glk_set_window(winid_t win)
788 glk_stream_set_current( glk_window_get_stream(win) );
792 * glk_window_get_stream:
795 * Returns the stream which is associated with @win. (See <link
796 * linkend="chimara-Window-Streams">Window Streams</link>.) Every window has a
797 * stream which can be printed to, but this may not be useful, depending on the
801 * For example, printing to a blank window's stream has no effect.
804 * Returns: A window stream.
806 strid_t glk_window_get_stream(winid_t win)
808 g_return_val_if_fail(win != NULL, NULL);
809 return win->window_stream;
813 * glk_window_set_echo_stream:
815 * @str: A stream to attach to the window, or %NULL.
817 * Sets @win's echo stream to @str, which can be any valid output stream. You
818 * can reset a window to stop echoing by calling
819 * <code>#glk_window_set_echo_stream(@win, %NULL)</code>.
821 * It is illegal to set a window's echo stream to be its
822 * <emphasis>own</emphasis> window stream. That would create an infinite loop,
823 * and is nearly certain to crash the Glk library. It is similarly illegal to
824 * create a longer loop (two or more windows echoing to each other.)
827 glk_window_set_echo_stream(winid_t win, strid_t str)
829 g_return_if_fail(win != NULL);
831 /* Test for an infinite loop */
833 for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream)
835 if(next == win->window_stream)
837 g_warning("%s: Infinite loop detected", __func__);
838 win->echo_stream = NULL;
843 win->echo_stream = str;
847 * glk_window_get_echo_stream:
850 * Returns the echo stream of window @win. Initially, a window has no echo
851 * stream, so <code>#glk_window_get_echo_stream(@win)</code> will return %NULL.
853 * Returns: A stream, or %NULL.
856 glk_window_get_echo_stream(winid_t win)
858 g_return_val_if_fail(win != NULL, NULL);
859 return win->echo_stream;
863 * glk_window_get_size:
865 * @widthptr: Pointer to a location to store the window's width, or %NULL.
866 * @heightptr: Pointer to a location to store the window's height, or %NULL.
868 * Simply returns the actual size of the window, in its measurement system.
869 * As described in <link linkend="chimara-Other-API-Conventions">Other API
870 * Conventions</link>, either @widthptr or @heightptr can be %NULL, if you
871 * only want one measurement.
873 * <note><para>Or, in fact, both, if you want to waste time.</para></note>
876 glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
878 g_return_if_fail(win != NULL);
885 if(heightptr != NULL)
889 case wintype_TextGrid:
890 /* The text grid caches its width and height */
892 *widthptr = win->width;
893 if(heightptr != NULL)
894 *heightptr = win->height;
897 case wintype_TextBuffer:
898 /* 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. */
900 /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
902 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.");
903 guess the size from the parent window;
907 /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */
908 while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
910 /* Release the GDK lock momentarily */
913 while(gtk_events_pending())
914 gtk_main_iteration();
918 *widthptr = (glui32)(win->widget->allocation.width / win->unit_width);
919 if(heightptr != NULL)
920 *heightptr = (glui32)(win->widget->allocation.height / win->unit_height);
926 g_warning("glk_window_get_size: Unsupported window type");
931 * glk_window_move_cursor:
932 * @win: A text grid window.
933 * @xpos: Horizontal cursor position.
934 * @ypos: Vertical cursor position.
936 * Sets the cursor position. If you move the cursor right past the end of a
937 * line, it wraps; the next character which is printed will appear at the
938 * beginning of the next line.
940 * If you move the cursor below the last line, or when the cursor reaches the
941 * end of the last line, it goes <quote>off the screen</quote> and further
942 * output has no effect. You must call glk_window_move_cursor() or
943 * glk_window_clear() to move the cursor back into the visible region.
946 * Note that the arguments of glk_window_move_cursor() are <type>unsigned
947 * int</type>s. This is okay, since there are no negative positions. If you try
948 * to pass a negative value, Glk will interpret it as a huge positive value,
949 * and it will wrap or go off the last line.
953 * Also note that the output cursor is not necessarily visible. In particular,
954 * when you are requesting line or character input in a grid window, you cannot
955 * rely on the cursor position to prompt the player where input is indicated.
956 * You should print some character prompt at that spot — a
957 * <quote>></quote> character, for example.
961 glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
963 g_return_if_fail(win != NULL);
964 g_return_if_fail(win->type == wintype_TextGrid);
966 /* Calculate actual position if cursor is moved past the right edge */
967 if(xpos >= win->width)
969 ypos += xpos / win->width;
972 /* Go to the end if the cursor is moved off the bottom edge */
973 if(ypos >= win->height)
975 xpos = win->width - 1;
976 ypos = win->height - 1;
981 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
983 /* There must actually be a character at xpos, or the following function will choke */
984 gtk_text_buffer_get_iter_at_line_offset(buffer, &newpos, ypos, xpos);
985 gtk_text_buffer_move_mark_by_name(buffer, "cursor_position", &newpos);