3 #include "chimara-glk-private.h"
5 extern ChimaraGlkPrivate *glk_data;
9 * @win: A window, or %NULL.
10 * @rockptr: Return location for the next window's rock, or %NULL.
12 * This function can be used to iterate through the list of all open windows
13 * (including pair windows.) See <link
14 * linkend="chimara-Iterating-Through-Opaque-Objects">Iterating Through Opaque
17 * As that section describes, the order in which windows are returned is
18 * arbitrary. The root window is not necessarily first, nor is it necessarily
21 * Returns: the next window, or %NULL if there are no more.
24 glk_window_iterate(winid_t win, glui32 *rockptr)
26 VALID_WINDOW_OR_NULL(win, return NULL);
31 retnode = glk_data->root_window;
34 GNode *node = win->window_node;
35 if( G_NODE_IS_LEAF(node) )
37 while(node && node->next == NULL)
45 retnode = g_node_first_child(node);
47 winid_t retval = retnode? (winid_t)retnode->data : NULL;
49 /* Store the window's rock in rockptr */
51 *rockptr = glk_window_get_rock(retval);
57 * glk_window_get_rock:
60 * Returns @win's rock value. Pair windows always have rock 0; all other windows
61 * return whatever rock value you created them with.
63 * Returns: A rock value.
66 glk_window_get_rock(winid_t win)
68 VALID_WINDOW(win, return 0);
73 * glk_window_get_type:
76 * Returns @win's type, one of #wintype_Blank, #wintype_Pair,
77 * #wintype_TextBuffer, #wintype_TextGrid, or #wintype_Graphics.
79 * Returns: The window's type.
82 glk_window_get_type(winid_t win)
84 VALID_WINDOW(win, return 0);
89 * glk_window_get_parent:
92 * Returns the window which is the parent of @win. If @win is the root window,
93 * this returns %NULL, since the root window has no parent. Remember that the
94 * parent of every window is a pair window; other window types are always
97 * Returns: A window, or %NULL.
100 glk_window_get_parent(winid_t win)
102 VALID_WINDOW(win, return NULL);
103 /* Value will also be NULL if win is the root window */
104 return (winid_t)win->window_node->parent->data;
108 * glk_window_get_sibling:
111 * Returns the other child of @win's parent. If @win is the root window, this
114 * Returns: A window, or %NULL.
117 glk_window_get_sibling(winid_t win)
119 VALID_WINDOW(win, return NULL);
121 if(G_NODE_IS_ROOT(win->window_node))
123 if(win->window_node->next)
124 return (winid_t)win->window_node->next;
125 return (winid_t)win->window_node->prev;
129 * glk_window_get_root:
131 * Returns the root window. If there are no windows, this returns %NULL.
133 * Returns: A window, or %NULL.
136 glk_window_get_root()
138 if(glk_data->root_window == NULL)
140 return (winid_t)glk_data->root_window->data;
145 * @split: The window to split to create the new window. Must be 0 if there
146 * are no windows yet.
147 * @method: Position of the new window and method of size computation. One of
148 * #winmethod_Above, #winmethod_Below, #winmethod_Left, or #winmethod_Right
149 * OR'ed with #winmethod_Fixed or #winmethod_Proportional. If @wintype is
150 * #wintype_Blank, then #winmethod_Fixed is not allowed.
151 * @size: Size of the new window, in percentage points if @method is
152 * #winmethod_Proportional, otherwise in characters if @wintype is
153 * #wintype_TextBuffer or #wintype_TextGrid, or pixels if @wintype is
155 * @wintype: Type of the new window. One of #wintype_Blank, #wintype_TextGrid,
156 * #wintype_TextBuffer, or #wintype_Graphics.
157 * @rock: The new window's rock value.
159 * Creates a new window. If there are no windows, the first three arguments are
160 * meaningless. @split <emphasis>must</emphasis> be 0, and @method and @size
161 * are ignored. @wintype is the type of window you're creating, and @rock is
162 * the rock (see <link linkend="chimara-Rocks">Rocks</link>).
164 * If any windows exist, new windows must be created by splitting existing
165 * ones. @split is the window you want to split; this <emphasis>must
166 * not</emphasis> be zero. @method is a mask of constants to specify the
167 * direction and the split method (see below). @size is the size of the split.
168 * @wintype is the type of window you're creating, and @rock is the rock.
170 * Remember that it is possible that the library will be unable to create a new
171 * window, in which case glk_window_open() will return %NULL.
174 * It is acceptable to gracefully exit, if the window you are creating is an
175 * important one — such as your first window. But you should not try to
176 * perform any window operation on the id until you have tested to make sure
180 * The examples we've seen so far have the simplest kind of size control. (Yes,
181 * this is <quote>below</quote>.) Every pair is a percentage split, with
184 * <mathphrase>X</mathphrase>
186 * percent going to one side, and
189 * <mathphrase>(100 - X)</mathphrase>
191 * percent going to the other side. If the player resizes the window, the whole
192 * mess expands, contracts, or stretches in a uniform way.
194 * As I said above, you can also make fixed-size splits. This is a little more
195 * complicated, because you have to know how this fixed size is measured.
197 * Sizes are measured in a way which is different for each window type. For
198 * example, a text grid window is measured by the size of its fixed-width font.
199 * You can make a text grid window which is fixed at a height of four rows, or
200 * ten columns. A text buffer window is measured by the size of its font.
203 * Remember that different windows may use different size fonts. Even two
204 * text grid windows may use fixed-size fonts of different sizes.
207 * Graphics windows are measured in pixels, not characters. Blank windows
208 * aren't measured at all; there's no meaningful way to measure them, and
209 * therefore you can't create a blank window of a fixed size, only of a
210 * proportional (percentage) size.
212 * So to create a text buffer window which takes the top 40% of the original
213 * window's space, you would execute
214 * <informalexample><programlisting>
215 * newwin = #glk_window_open(win, #winmethod_Above | #winmethod_Proportional, 40, #wintype_TextBuffer, 0);
216 * </programlisting></informalexample>
218 * To create a text grid which is always five lines high, at the bottom of the
219 * original window, you would do
220 * <informalexample><programlisting>
221 * newwin = #glk_window_open(win, #winmethod_Below | #winmethod_Fixed, 5, #wintype_TextGrid, 0);
222 * </programlisting></informalexample>
224 * Note that the meaning of the @size argument depends on the @method argument.
225 * If the method is #winmethod_Fixed, it also depends on the @wintype argument.
226 * The new window is then called the <quote>key window</quote> of this split,
227 * because its window type determines how the split size is computed.
230 * For #winmethod_Proportional splits, you can still call the new window the
231 * <quote>key window</quote>. But the key window is not important for
232 * proportional splits, because the size will always be computed as a simple
233 * ratio of the available space, not a fixed size of one child window.
236 * This system is more or less peachy as long as all the constraints work out.
237 * What happens when there is a conflict? The rules are simple. Size control
238 * always flows down the tree, and the player is at the top. Let's bring out an
240 * <mediaobject><textobject><phrase>Screen shot 5</phrase></textobject>
243 * First we split A into A and B, with a 50% proportional split. Then we split
244 * A into A and C, with C above, C being a text grid window, and C gets a fixed
245 * size of two rows (as measured in its own font size). A gets whatever remains
246 * of the 50% it had before.
248 * Now the player stretches the window vertically.
249 * <mediaobject><textobject><phrase>Screen shot 6</phrase></textobject>
252 * The library figures: the topmost split, the original A/B split, is 50-50. So
253 * B gets half the screen space, and the pair window next to it (the lower
254 * <quote>O</quote>) gets the other half. Then it looks at the lower
255 * <quote>O</quote>. C gets two rows; A gets the rest. All done.
257 * Then the user maliciously starts squeezing the window down, in stages:
258 * <mediaobject id="chimara-Figure-Squeezing-Window"><textobject><phrase>
259 * Screen shot 7</phrase></textobject></mediaobject>
261 * The logic remains the same. B always gets half the space. At stage 3,
262 * there's no room left for A, so it winds up with zero height. Nothing
263 * displayed in A will be visible. At stage 4, there isn't even room in the
264 * upper 50% to give C its two rows; so it only gets one. Finally, C is
265 * squashed out of existence as well.
267 * When a window winds up undersized, it remembers what size it should be. In
268 * the example above, A remembers that it should be two rows; if the user
269 * expands the window to the original size, it would return to the original
272 * The downward flow of control is a bit harsh. After all, in stage 4, there's
273 * room for C to have its two rows if only B would give up some of its 50%. But
274 * this does not happen.
277 * This makes life much easier for the Glk library. To determine the
278 * configuration of a window, it only needs to look at the window's
279 * ancestors, never at its descendants. So window layout is a simple
280 * recursive algorithm, no backtracking.
283 * What happens when you split a fixed-size window? The resulting pair window
284 * — that is, the two new parts together — retain the same size
285 * constraint as the original window that was split. The key window for the
286 * original split is still the key window for that split, even though it's now
287 * a grandchild instead of a child.
289 * The easy, and correct, way to think about this is that the size constraint
290 * is stored by a window's parent, not the window itself; and a constraint
291 * consists of a pointer to a key window plus a size value.
293 * <mediaobject><textobject><phrase>Screen shot 8</phrase></textobject>
295 * After the first split, the new pair window (O1, which covers the whole
296 * screen) knows that its first child (A) is above the second, and gets 50% of
297 * its own area. (A is the key window for this split, but a proportional split
298 * doesn't care about key windows.)
300 * After the second split, all this remains true; O1 knows that its first child
301 * gets 50% of its space, and A is O1's key window. But now O1's first child is
302 * O2 instead of A. The newer pair window (O2) knows that its first child (C)
303 * is above the second, and gets a fixed size of two rows. (As measured in C's
304 * font, because C is O2's key window.)
306 * If we split C, now, the resulting pair will still be two C-font rows high
307 * — that is, tall enough for two lines of whatever font C displays. For
308 * the sake of example, we'll do this vertically.
309 * <mediaobject><textobject><phrase>Screen shot 9</phrase></textobject>
312 * O3 now knows that its children have a 50-50 left-right split. O2 is still
313 * committed to giving its upper child, O3, two C-font rows. Again, this is
314 * because C is O2's key window.
317 * This turns out to be a good idea, because it means that C, the text grid
318 * window, is still two rows high. If O3 had been a upper-lower split, things
319 * wouldn't work out so neatly. But the rules would still apply. If you don't
320 * like this, don't do it.
323 * Returns: the new window, or %NULL on error.
326 glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype,
329 VALID_WINDOW_OR_NULL(split, return NULL);
331 if(split == NULL && glk_data->root_window != NULL)
333 ILLEGAL("Tried to open a new root window, but there is already a root window");
339 /* We only create one window and don't support any more than that */
340 winid_t win = g_new0(struct glk_window_struct, 1);
341 win->magic = MAGIC_WINDOW;
344 win->window_node = g_node_new(win);
350 /* A blank window will be a label without any text */
351 GtkWidget *label = gtk_label_new("");
352 gtk_widget_show(label);
356 /* A blank window has no size */
358 win->unit_height = 0;
359 /* You can print to a blank window's stream, but it does nothing */
360 win->window_stream = window_stream_new(win);
361 win->echo_stream = NULL;
365 case wintype_TextGrid:
367 GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
368 GtkWidget *textview = gtk_text_view_new();
370 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_NEVER );
372 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_CHAR );
373 gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
375 gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
376 gtk_widget_show_all(scrolledwindow);
378 /* Set the window's font */
379 gtk_widget_modify_font(textview, glk_data->monospace_font_desc);
381 win->widget = textview;
382 win->frame = scrolledwindow;
384 /* Determine the size of a "0" character in pixels */
385 PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
386 pango_layout_set_font_description(zero, glk_data->monospace_font_desc);
387 pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height));
388 g_object_unref(zero);
390 /* Set the other parameters (width and height are set later) */
391 win->window_stream = window_stream_new(win);
392 win->echo_stream = NULL;
393 win->input_request_type = INPUT_REQUEST_NONE;
394 win->line_input_buffer = NULL;
395 win->line_input_buffer_unicode = NULL;
397 /* Connect signal handlers */
398 win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
399 g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
403 case wintype_TextBuffer:
405 GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
406 GtkWidget *textview = gtk_text_view_new();
407 GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
409 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR );
410 gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
412 gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
413 gtk_widget_show_all(scrolledwindow);
415 /* Set the window's font */
416 gtk_widget_modify_font(textview, glk_data->default_font_desc);
418 win->widget = textview;
419 win->frame = scrolledwindow;
421 /* Determine the size of a "0" character in pixels */
422 PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
423 pango_layout_set_font_description(zero, glk_data->default_font_desc);
424 pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height));
425 g_object_unref(zero);
427 /* Set the other parameters */
428 win->window_stream = window_stream_new(win);
429 win->echo_stream = NULL;
430 win->input_request_type = INPUT_REQUEST_NONE;
431 win->line_input_buffer = NULL;
432 win->line_input_buffer_unicode = NULL;
434 /* Connect signal handlers */
435 win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
436 g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
438 win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win );
439 g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler );
441 /* Create an editable tag to indicate uneditable parts of the window
443 gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
445 /* Mark the position where the user will input text */
447 gtk_text_buffer_get_end_iter(textbuffer, &end);
448 gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE);
454 ILLEGAL_PARAM("Unknown window type: %u", wintype);
456 g_node_destroy(glk_data->root_window);
457 glk_data->root_window = NULL;
463 /* When splitting, construct a new parent window
464 * copying most characteristics from the window that is being split */
465 winid_t pair = g_new0(struct glk_window_struct, 1);
467 pair->type = wintype_Pair;
468 pair->window_node = g_node_new(pair);
469 pair->unit_width = split->unit_width;
470 pair->unit_height = split->unit_height;
471 pair->window_stream = NULL;
472 pair->echo_stream = NULL;
474 /* Insert the new window into the window tree */
475 if(split->window_node->parent == NULL)
477 glk_data->root_window = pair->window_node;
479 g_node_append(split->window_node->parent, pair->window_node);
480 g_node_unlink(split->window_node);
483 /* Keep track of the parent widget of the window that is being split */
484 GtkWidget* old_parent = gtk_widget_get_parent(split->frame);
485 gtk_widget_ref(split->frame);
486 gtk_widget_unparent(split->frame);
488 /* Place the windows in the correct order */
489 switch(method & winmethod_DirMask)
492 pair->widget = gtk_hbox_new(FALSE, 0);
493 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
494 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
495 g_node_append(pair->window_node, split->window_node);
496 g_node_append(pair->window_node, win->window_node);
498 case winmethod_Right:
499 pair->widget = gtk_hbox_new(FALSE, 0);
500 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
501 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
502 g_node_append(pair->window_node, win->window_node);
503 g_node_append(pair->window_node, split->window_node);
505 case winmethod_Above:
506 pair->widget = gtk_vbox_new(FALSE, 0);
507 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
508 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
509 g_node_append(pair->window_node, split->window_node);
510 g_node_append(pair->window_node, win->window_node);
512 case winmethod_Below:
513 pair->widget = gtk_vbox_new(FALSE, 0);
514 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
515 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
516 g_node_append(pair->window_node, win->window_node);
517 g_node_append(pair->window_node, split->window_node);
520 gtk_widget_unref(split->frame);
522 /* TODO: set the new size of the windows */
524 pair->frame = pair->widget;
525 gtk_widget_set_parent(pair->widget, old_parent);
526 gtk_widget_show(pair->widget);
528 /* Set the window as root window */
529 glk_data->root_window = win->window_node;
530 gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self));
531 gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
534 /* 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. */
535 if(wintype == wintype_TextGrid)
537 while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
539 /* Release the GDK lock momentarily */
542 while(gtk_events_pending())
543 gtk_main_iteration();
545 win->width = (glui32)(win->widget->allocation.width / win->unit_width);
546 win->height = (glui32)(win->widget->allocation.height / win->unit_height);
548 /* Mark the cursor position */
550 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
551 gtk_text_buffer_get_start_iter(buffer, &begin);
552 gtk_text_buffer_create_mark(buffer, "cursor_position", &begin, TRUE);
554 /* Fill the buffer with blanks and move the cursor to the upper left */
556 glk_window_clear(win);
559 /* Apparently this only works after the window has been realized */
560 gtk_text_view_set_overwrite( GTK_TEXT_VIEW(win->widget), TRUE );
570 * @win: Window to close.
571 * @result: Pointer to a #stream_result_t in which to store the write count.
573 * Closes @win, which is pretty much exactly the opposite of opening a window.
574 * It is legal to close all your windows, or to close the root window (which is
577 * The @result argument is filled with the output character count of the window
578 * stream. See <link linkend="chimara-Streams">Streams</link> and <link
579 * linkend="chimara-Closing-Streams">Closing Streams</link>.
581 * When you close a window (and it is not the root window), the other window
582 * in its pair takes over all the freed-up area. Let's close D, in the current
584 * <mediaobject><textobject><phrase>Screen shot 10</phrase></textobject>
587 * Notice what has happened. D is gone. O3 is gone, and its 50-50 left-right
588 * split has gone with it. The other size constraints are unchanged; O2 is
589 * still committed to giving its upper child two rows, as measured in the font
590 * of O2's key window, which is C. Conveniently, O2's upper child is C, just as
591 * it was before we created D. In fact, now that D is gone, everything is back
592 * to the way it was before we created D.
594 * But what if we had closed C instead of D? We would have gotten this:
595 * <mediaobject><textobject><phrase>Screen shot 11</phrase></textobject>
598 * Again, O3 is gone. But D has collapsed to zero height. This is because its
599 * height is controlled by O2, and O2's key window was C, and C is now gone. O2
600 * no longer has a key window at all, so it cannot compute a height for its
601 * upper child, so it defaults to zero.
604 * This may seem to be an inconvenient choice. That is deliberate. You should
605 * not leave a pair window with no key, and the zero-height default reminds
606 * you not to. You can use glk_window_set_arrangement() to set a new split
607 * measurement and key window. See <link
608 * linkend="chimara-Changing-Window-Constraints">Changing Window
609 * Constraints</link>.
613 glk_window_close(winid_t win, stream_result_t *result)
615 VALID_WINDOW(win, return);
624 gtk_widget_destroy(win->widget);
627 case wintype_TextGrid:
628 case wintype_TextBuffer:
629 gtk_widget_destroy(win->frame);
630 /* TODO: Cancel all input requests */
635 GNode* left_child = g_node_first_child(win->window_node);
636 GNode* right_child = g_node_last_child(win->window_node);
638 glk_window_close((winid_t) left_child->data, result);
639 glk_window_close((winid_t) right_child->data, result);
641 gtk_widget_destroy(win->widget);
646 ILLEGAL_PARAM("Unknown window type: %u", win->type);
651 stream_close_common(win->window_stream, result);
653 /* Parent window changes from a split window into the sibling window */
654 if( (parent_node = win->window_node->parent) != NULL )
656 winid_t pair = (winid_t) parent_node->data;
657 if(parent_node->parent == NULL)
659 if(parent_node->next)
660 glk_data->root_window = parent_node->next;
661 else if(parent_node->prev)
662 glk_data->root_window = parent_node->prev;
666 if(parent_node->next)
667 g_node_append(parent_node->parent, parent_node->next);
668 else if(parent_node->prev)
669 g_node_append(parent_node->parent, parent_node->prev);
672 g_node_unlink(parent_node);
676 g_node_destroy(win->window_node);
677 win->magic = MAGIC_FREE;
687 * Erases @win. The meaning of this depends on the window type.
690 * <term>Text buffer</term>
692 * This may do any number of things, such as delete all text in the window, or
693 * print enough blank lines to scroll all text beyond visibility, or insert a
694 * page-break marker which is treated specially by the display part of the
699 * <term>Text grid</term>
701 * This will clear the window, filling all positions with blanks. The window
702 * cursor is moved to the top left corner (position 0,0).
706 * <term>Graphics</term>
708 * Clears the entire window to its current background color. See <link
709 * linkend="chimara-Graphics-Windows">Graphics Windows</link>.
713 * <term>Other window types</term>
714 * <listitem><para>No effect.</para></listitem>
718 * It is illegal to erase a window which has line input pending.
721 glk_window_clear(winid_t win)
723 VALID_WINDOW(win, return);
724 g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE);
733 case wintype_TextGrid:
734 /* fill the buffer with blanks */
738 /* Manually put newlines at the end of each row of characters in the buffer; manual newlines make resizing the window's grid easier. */
739 gchar *blanks = g_strnfill(win->width, ' ');
740 gchar **blanklines = g_new0(gchar *, win->height + 1);
742 for(count = 0; count < win->height; count++)
743 blanklines[count] = blanks;
744 blanklines[win->height] = NULL;
745 gchar *text = g_strjoinv("\n", blanklines);
746 g_free(blanklines); /* not g_strfreev() */
749 GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
750 gtk_text_buffer_set_text(textbuffer, text, -1);
754 gtk_text_buffer_get_start_iter(textbuffer, &begin);
755 gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &begin);
761 case wintype_TextBuffer:
762 /* delete all text in the window */
766 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
767 GtkTextIter start, end;
768 gtk_text_buffer_get_bounds(buffer, &start, &end);
769 gtk_text_buffer_delete(buffer, &start, &end);
776 ILLEGAL_PARAM("Unknown window type: %d", win->type);
784 * Sets the current stream to @win's window stream. It is exactly equivalent to
785 * <code>#glk_stream_set_current(#glk_window_get_stream(@win))</code>.
788 glk_set_window(winid_t win)
790 VALID_WINDOW_OR_NULL(win, return);
791 glk_stream_set_current( glk_window_get_stream(win) );
795 * glk_window_get_stream:
798 * Returns the stream which is associated with @win. (See <link
799 * linkend="chimara-Window-Streams">Window Streams</link>.) Every window has a
800 * stream which can be printed to, but this may not be useful, depending on the
804 * For example, printing to a blank window's stream has no effect.
807 * Returns: A window stream.
809 strid_t glk_window_get_stream(winid_t win)
811 VALID_WINDOW(win, return NULL);
812 return win->window_stream;
816 * glk_window_set_echo_stream:
818 * @str: A stream to attach to the window, or %NULL.
820 * Sets @win's echo stream to @str, which can be any valid output stream. You
821 * can reset a window to stop echoing by calling
822 * <code>#glk_window_set_echo_stream(@win, %NULL)</code>.
824 * It is illegal to set a window's echo stream to be its
825 * <emphasis>own</emphasis> window stream. That would create an infinite loop,
826 * and is nearly certain to crash the Glk library. It is similarly illegal to
827 * create a longer loop (two or more windows echoing to each other.)
830 glk_window_set_echo_stream(winid_t win, strid_t str)
832 VALID_WINDOW(win, return);
833 VALID_STREAM_OR_NULL(str, return);
835 /* Test for an infinite loop */
837 for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream)
839 if(next == win->window_stream)
841 ILLEGAL("Infinite loop detected");
842 win->echo_stream = NULL;
847 win->echo_stream = str;
851 * glk_window_get_echo_stream:
854 * Returns the echo stream of window @win. Initially, a window has no echo
855 * stream, so <code>#glk_window_get_echo_stream(@win)</code> will return %NULL.
857 * Returns: A stream, or %NULL.
860 glk_window_get_echo_stream(winid_t win)
862 VALID_WINDOW(win, return NULL);
863 return win->echo_stream;
867 * glk_window_get_size:
869 * @widthptr: Pointer to a location to store the window's width, or %NULL.
870 * @heightptr: Pointer to a location to store the window's height, or %NULL.
872 * Simply returns the actual size of the window, in its measurement system.
873 * As described in <link linkend="chimara-Other-API-Conventions">Other API
874 * Conventions</link>, either @widthptr or @heightptr can be %NULL, if you
875 * only want one measurement.
877 * <note><para>Or, in fact, both, if you want to waste time.</para></note>
880 glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
882 VALID_WINDOW(win, return);
889 if(heightptr != NULL)
893 case wintype_TextGrid:
894 /* The text grid caches its width and height */
896 *widthptr = win->width;
897 if(heightptr != NULL)
898 *heightptr = win->height;
901 case wintype_TextBuffer:
902 /* 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. */
904 /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
906 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.");
907 guess the size from the parent window;
911 /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */
912 while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
914 /* Release the GDK lock momentarily */
917 while(gtk_events_pending())
918 gtk_main_iteration();
922 *widthptr = (glui32)(win->widget->allocation.width / win->unit_width);
923 if(heightptr != NULL)
924 *heightptr = (glui32)(win->widget->allocation.height / win->unit_height);
930 ILLEGAL_PARAM("Unknown window type: %u", win->type);
935 * glk_window_move_cursor:
936 * @win: A text grid window.
937 * @xpos: Horizontal cursor position.
938 * @ypos: Vertical cursor position.
940 * Sets the cursor position. If you move the cursor right past the end of a
941 * line, it wraps; the next character which is printed will appear at the
942 * beginning of the next line.
944 * If you move the cursor below the last line, or when the cursor reaches the
945 * end of the last line, it goes <quote>off the screen</quote> and further
946 * output has no effect. You must call glk_window_move_cursor() or
947 * glk_window_clear() to move the cursor back into the visible region.
950 * Note that the arguments of glk_window_move_cursor() are <type>unsigned
951 * int</type>s. This is okay, since there are no negative positions. If you try
952 * to pass a negative value, Glk will interpret it as a huge positive value,
953 * and it will wrap or go off the last line.
957 * Also note that the output cursor is not necessarily visible. In particular,
958 * when you are requesting line or character input in a grid window, you cannot
959 * rely on the cursor position to prompt the player where input is indicated.
960 * You should print some character prompt at that spot — a
961 * <quote>></quote> character, for example.
965 glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
967 VALID_WINDOW(win, return);
968 g_return_if_fail(win->type == wintype_TextGrid);
970 /* Calculate actual position if cursor is moved past the right edge */
971 if(xpos >= win->width)
973 ypos += xpos / win->width;
976 /* Go to the end if the cursor is moved off the bottom edge */
977 if(ypos >= win->height)
979 xpos = win->width - 1;
980 ypos = win->height - 1;
985 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
987 /* There must actually be a character at xpos, or the following function will choke */
988 gtk_text_buffer_get_iter_at_line_offset(buffer, &newpos, ypos, xpos);
989 gtk_text_buffer_move_mark_by_name(buffer, "cursor_position", &newpos);