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 * Iterates over the list of windows; if @win is %NULL, it returns the first
12 * window, otherwise the next window after @win. If there are no more, it
13 * returns #NULL. The window's rock is stored in @rockptr. If you don't want
14 * the rocks to be returned, you may set @rockptr to %NULL.
16 * The order in which windows are returned is arbitrary. The root window is
17 * not necessarily first, nor is it necessarily last. The order may change
18 * every time you create or destroy a window, invalidating the iteration.
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 * have the 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 @win's parent window. If @win is the root window, this
90 * returns %NULL, since the root window has no parent. Remember that the parent
91 * of every window is a pair window; other window types are always childless.
93 * Returns: A window, or %NULL.
96 glk_window_get_parent(winid_t win)
98 g_return_val_if_fail(win != NULL, NULL);
99 /* Value will also be NULL if win is the root window */
100 return (winid_t)win->window_node->parent->data;
104 * glk_window_get_sibling:
107 * Returns the other child of the window @win's parent. If @win is the
108 * root window, this returns %NULL.
110 * Returns: A window, or %NULL.
113 glk_window_get_sibling(winid_t win)
115 g_return_val_if_fail(win != NULL, NULL);
117 if(G_NODE_IS_ROOT(win->window_node))
119 if(win->window_node->next)
120 return (winid_t)win->window_node->next;
121 return (winid_t)win->window_node->prev;
125 * glk_window_get_root:
127 * Returns the root window. If there are no windows, this returns %NULL.
129 * Returns: A window, or %NULL.
132 glk_window_get_root()
134 if(glk_data->root_window == NULL)
136 return (winid_t)glk_data->root_window->data;
139 /* Determine the size of a "0" character in pixels */
141 text_window_get_char_size(GtkWidget *textview, int *width, int *height)
143 PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
144 pango_layout_get_pixel_size(zero, width, height);
145 g_object_unref(zero);
150 * @split: The window to split to create the new window. Must be 0 if there
151 * are no windows yet.
152 * @method: Position of the new window and method of size computation. One of
153 * #winmethod_Above, #winmethod_Below, #winmethod_Left, or #winmethod_Right
154 * OR'ed with #winmethod_Fixed or #winmethod_Proportional. If @wintype is
155 * #wintype_Blank, then #winmethod_Fixed is not allowed.
156 * @size: Size of the new window, in percentage points if @method is
157 * #winmethod_Proportional, otherwise in characters if @wintype is
158 * #wintype_TextBuffer or #wintype_TextGrid, or pixels if @wintype is
160 * @wintype: Type of the new window. One of #wintype_Blank, #wintype_TextGrid,
161 * #wintype_TextBuffer, or #wintype_Graphics.
162 * @rock: The new window's rock value.
164 * If there are no windows, create a new root window. @split must be 0, and
165 * @method and @size are ignored. Otherwise, split window @split into two, with
166 * position, size, and type specified by @method, @size, and @wintype. See the
167 * Glk documentation for the window placement algorithm.
169 * Returns: the new window, or %NULL on error.
172 glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype,
178 g_warning("glk_window_open: splitting of windows not implemented");
183 if(split == NULL && glk_data->root_window != NULL)
185 g_warning("glk_window_open: there is already a root window");
191 /* We only create one window and don't support any more than that */
192 winid_t win = g_new0(struct glk_window_struct, 1);
195 win->window_node = g_node_new(win);
201 /* A blank window will be a label without any text */
202 GtkWidget *label = gtk_label_new("");
203 gtk_widget_show(label);
207 /* A blank window has no size */
209 win->unit_height = 0;
210 /* You can print to a blank window's stream, but it does nothing */
211 win->window_stream = window_stream_new(win);
212 win->echo_stream = NULL;
216 case wintype_TextGrid:
218 GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
219 GtkWidget *textview = gtk_text_view_new();
220 GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
222 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_NEVER );
224 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_CHAR );
225 gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
227 gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
228 gtk_widget_show_all(scrolledwindow);
230 /* Set the window's font */
231 /* TODO: Use Pango to pick out a monospace font on the system */
232 PangoFontDescription *font = pango_font_description_from_string("Monospace");
233 gtk_widget_modify_font(textview, font);
234 pango_font_description_free(font);
236 win->widget = textview;
237 win->frame = scrolledwindow;
238 text_window_get_char_size( textview, &(win->unit_width), &(win->unit_height) );
240 /* Set the other parameters */
241 win->window_stream = window_stream_new(win);
242 win->echo_stream = NULL;
243 win->input_request_type = INPUT_REQUEST_NONE;
244 win->line_input_buffer = NULL;
245 win->line_input_buffer_unicode = NULL;
247 /* Connect signal handlers */
248 win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
249 g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
251 win->insert_text_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_text_grid_key_press_event), win );
252 g_signal_handler_block( G_OBJECT(textview), win->insert_text_handler );
254 /* Create a tag to indicate uneditable parts of the window (for line input) */
255 gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
257 /* Create a tag to indicate an editable field in the window (for line input) */
258 gtk_text_buffer_create_tag(textbuffer, "input_field",
259 "background", "grey", "background-set", TRUE,
264 case wintype_TextBuffer:
266 GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
267 GtkWidget *textview = gtk_text_view_new();
268 GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
270 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR );
271 gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
273 gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
274 gtk_widget_show_all(scrolledwindow);
276 win->widget = textview;
277 win->frame = scrolledwindow;
278 text_window_get_char_size( textview, &(win->unit_width), &(win->unit_height) );
280 /* Set the other parameters */
281 win->window_stream = window_stream_new(win);
282 win->echo_stream = NULL;
283 win->input_request_type = INPUT_REQUEST_NONE;
284 win->line_input_buffer = NULL;
285 win->line_input_buffer_unicode = NULL;
287 /* Connect signal handlers */
288 win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
289 g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
291 win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win );
292 g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler );
294 /* Create an editable tag to indicate uneditable parts of the window
296 gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
298 /* Mark the position where the user will input text */
300 gtk_text_buffer_get_end_iter(textbuffer, &end);
301 gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE);
307 g_warning("%s: unsupported window type", __func__);
309 g_node_destroy(glk_data->root_window);
310 glk_data->root_window = NULL;
316 /* When splitting, construct a new parent window
317 * copying most characteristics from the window that is being split */
318 winid_t pair = g_new0(struct glk_window_struct, 1);
320 pair->type = wintype_Pair;
321 pair->window_node = g_node_new(pair);
322 pair->unit_width = split->unit_width;
323 pair->unit_height = split->unit_height;
324 pair->window_stream = NULL;
325 pair->echo_stream = NULL;
327 /* Insert the new window into the window tree */
328 if(split->window_node->parent == NULL)
330 glk_data->root_window = pair->window_node;
332 g_node_append(split->window_node->parent, pair->window_node);
333 g_node_unlink(split->window_node);
336 /* Keep track of the parent widget of the window that is being split */
337 GtkWidget* old_parent = gtk_widget_get_parent(split->frame);
338 gtk_widget_ref(split->frame);
339 gtk_widget_unparent(split->frame);
341 /* Place the windows in the correct order */
342 switch(method & winmethod_DirMask)
345 pair->widget = gtk_hbox_new(FALSE, 0);
346 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
347 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
348 g_node_append(pair->window_node, split->window_node);
349 g_node_append(pair->window_node, win->window_node);
351 case winmethod_Right:
352 pair->widget = gtk_hbox_new(FALSE, 0);
353 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
354 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
355 g_node_append(pair->window_node, win->window_node);
356 g_node_append(pair->window_node, split->window_node);
358 case winmethod_Above:
359 pair->widget = gtk_vbox_new(FALSE, 0);
360 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
361 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
362 g_node_append(pair->window_node, split->window_node);
363 g_node_append(pair->window_node, win->window_node);
365 case winmethod_Below:
366 pair->widget = gtk_vbox_new(FALSE, 0);
367 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
368 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
369 g_node_append(pair->window_node, win->window_node);
370 g_node_append(pair->window_node, split->window_node);
373 gtk_widget_unref(split->frame);
375 /* TODO: set the new size of the windows */
377 pair->frame = pair->widget;
378 gtk_widget_set_parent(pair->widget, old_parent);
379 gtk_widget_show(pair->widget);
381 /* Set the window as root window */
382 glk_data->root_window = win->window_node;
383 gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self));
384 gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
387 /* 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. */
388 if(wintype == wintype_TextGrid)
390 while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
392 /* Release the GDK lock momentarily */
395 while(gtk_events_pending())
396 gtk_main_iteration();
398 win->width = (glui32)(win->widget->allocation.width / win->unit_width);
399 win->height = (glui32)(win->widget->allocation.height / win->unit_height);
401 /* Mark the cursor position */
403 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
404 gtk_text_buffer_get_start_iter(buffer, &begin);
405 gtk_text_buffer_create_mark(buffer, "cursor_position", &begin, TRUE);
407 /* Fill the buffer with blanks and move the cursor to the upper left */
409 glk_window_clear(win);
412 /* Apparently this only works after the window has been realized */
413 gtk_text_view_set_overwrite( GTK_TEXT_VIEW(win->widget), TRUE );
423 * @win: Window to close.
424 * @result: Pointer to a #stream_result_t in which to store the write count.
426 * Closes @win, which is pretty much exactly the opposite of opening a window.
427 * It is legal to close all your windows, or to close the root window (which is
430 * The @result argument is filled with the output character count of the window
434 glk_window_close(winid_t win, stream_result_t *result)
438 g_return_if_fail(win != NULL);
445 gtk_widget_destroy(win->widget);
448 case wintype_TextGrid:
449 case wintype_TextBuffer:
450 gtk_widget_destroy(win->frame);
451 /* TODO: Cancel all input requests */
456 GNode* left_child = g_node_first_child(win->window_node);
457 GNode* right_child = g_node_last_child(win->window_node);
459 glk_window_close((winid_t) left_child->data, result);
460 glk_window_close((winid_t) right_child->data, result);
462 gtk_widget_destroy(win->widget);
467 g_warning("%s: unsupported window type", __func__);
472 stream_close_common(win->window_stream, result);
474 /* Parent window changes from a split window into the sibling window */
475 if( (parent_node = win->window_node->parent) != NULL )
477 winid_t pair = (winid_t) parent_node->data;
478 if(parent_node->parent == NULL)
480 if(parent_node->next)
481 glk_data->root_window = parent_node->next;
482 else if(parent_node->prev)
483 glk_data->root_window = parent_node->prev;
485 if(parent_node->next)
486 g_node_append(parent_node->parent, parent_node->next);
487 else if(parent_node->prev)
488 g_node_append(parent_node->parent, parent_node->prev);
491 g_node_unlink(parent_node);
495 g_node_destroy(win->window_node);
505 * Erases the window @win. The meaning of this depends on the window type.
509 * Text buffer: This may do any number of things, such as delete all text in
510 * the window, or print enough blank lines to scroll all text beyond
511 * visibility, or insert a page-break marker which is treated specially by the
512 * display part of the library.
515 * Text grid: This will clear the window, filling all positions with blanks.
516 * The window cursor is moved to the top left corner (position 0,0).
519 * Graphics: Clears the entire window to its current background color.
522 * Other window types: No effect.
526 * It is illegal to erase a window which has line input pending.
529 glk_window_clear(winid_t win)
531 g_return_if_fail(win != NULL);
532 g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE);
541 case wintype_TextGrid:
542 /* fill the buffer with blanks */
546 /* Manually put newlines at the end of each row of characters in the buffer; manual newlines make resizing the window's grid easier. */
547 gchar *blanks = g_strnfill(win->width, ' ');
548 gchar **blanklines = g_new0(gchar *, win->height + 1);
550 for(count = 0; count < win->height; count++)
551 blanklines[count] = blanks;
552 blanklines[win->height] = NULL;
553 gchar *text = g_strjoinv("\n", blanklines);
554 g_free(blanklines); /* not g_strfreev() */
557 GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
558 gtk_text_buffer_set_text(textbuffer, text, -1);
562 gtk_text_buffer_get_start_iter(textbuffer, &begin);
563 gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &begin);
569 case wintype_TextBuffer:
570 /* delete all text in the window */
574 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
575 GtkTextIter start, end;
576 gtk_text_buffer_get_bounds(buffer, &start, &end);
577 gtk_text_buffer_delete(buffer, &start, &end);
584 g_warning("glk_window_clear: unsupported window type");
592 * Sets the current stream to @win's window stream. It is exactly equivalent to
593 * <informalexample><programlisting>
594 * glk_stream_set_current(glk_window_get_stream(win))
595 * </programlisting></informalexample>
598 glk_set_window(winid_t win)
600 glk_stream_set_current( glk_window_get_stream(win) );
604 * glk_window_get_stream:
607 * Returns the stream which is associated with @win. Every window has a stream
608 * which can be printed to, but this may not be useful, depending on the window
609 * type. (For example, printing to a blank window's stream has no effect.)
611 * Returns: The window stream.
613 strid_t glk_window_get_stream(winid_t win)
615 g_return_val_if_fail(win != NULL, NULL);
616 return win->window_stream;
620 * glk_window_set_echo_stream:
622 * @str: A stream to attach to the window, or %NULL.
624 * Attaches the stream @str to @win as a second stream. Any text printed to the
625 * window is also echoed to this second stream, which is called the window's
628 * Effectively, any call to glk_put_char() (or the other output commands) which
629 * is directed to @win's window stream, is replicated to @win's echo stream.
630 * This also goes for the style commands such as glk_set_style().
632 * Note that the echoing is one-way. You can still print text directly to the
633 * echo stream, and it will go wherever the stream is bound, but it does not
634 * back up and appear in the window.
636 * It is illegal to set a window's echo stream to be its own window stream,
637 * which would create an infinite loop. It is similarly illegal to create a
638 * longer loop (two or more windows echoing to each other.)
640 * You can reset a window to stop echoing by setting @str to %NULL.
643 glk_window_set_echo_stream(winid_t win, strid_t str)
645 g_return_if_fail(win != NULL);
647 /* Test for an infinite loop */
649 for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream)
651 if(next == win->window_stream)
653 g_warning("%s: Infinite loop detected", __func__);
654 win->echo_stream = NULL;
659 win->echo_stream = str;
663 * glk_window_get_echo_stream:
666 * Returns the echo stream of window @win. If the window has no echo stream (as
667 * is initially the case) then this returns %NULL.
669 * Returns: A stream, or %NULL.
672 glk_window_get_echo_stream(winid_t win)
674 g_return_val_if_fail(win != NULL, NULL);
675 return win->echo_stream;
679 * glk_window_get_size:
681 * @widthptr: Pointer to a location to store the window's width, or %NULL.
682 * @heightptr: Pointer to a location to store the window's height, or %NULL.
684 * Simply returns the actual size of the window, in its measurement system.
685 * Either @widthptr or @heightptr can be %NULL, if you only want one
686 * measurement. (Or, in fact, both, if you want to waste time.)
689 glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
691 g_return_if_fail(win != NULL);
698 if(heightptr != NULL)
702 case wintype_TextGrid:
703 /* The text grid caches its width and height */
705 *widthptr = win->width;
706 if(heightptr != NULL)
707 *heightptr = win->height;
710 case wintype_TextBuffer:
711 /* 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. */
713 /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
715 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.");
716 guess the size from the parent window;
720 /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */
721 while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
723 /* Release the GDK lock momentarily */
726 while(gtk_events_pending())
727 gtk_main_iteration();
731 *widthptr = (glui32)(win->widget->allocation.width / win->unit_width);
732 if(heightptr != NULL)
733 *heightptr = (glui32)(win->widget->allocation.height / win->unit_height);
739 g_warning("glk_window_get_size: Unsupported window type");
744 * glk_window_move_cursor:
745 * @win: A text grid window.
746 * @xpos: Horizontal cursor position.
747 * @ypos: Vertical cursor position.
749 * Sets the cursor position. If you move the cursor right past the end of a
750 * line, it wraps; the next character which is printed will appear at the
751 * beginning of the next line.
753 * If you move the cursor below the last line, or when the cursor reaches the
754 * end of the last line, it goes "off the screen" and further output has no
755 * effect. You must call glk_window_move_cursor() or glk_window_clear() to move
756 * the cursor back into the visible region.
759 * Note that the arguments of glk_window_move_cursor() are <type>unsigned
760 * int</type>s. This is okay, since there are no negative positions. If you try
761 * to pass a negative value, Glk will interpret it as a huge positive value,
762 * and it will wrap or go off the last line.
766 * Also note that the output cursor is not necessarily visible. In particular,
767 * when you are requesting line or character input in a grid window, you cannot
768 * rely on the cursor position to prompt the player where input is indicated.
769 * You should print some character prompt at that spot -- a ">" character, for
774 glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
776 g_return_if_fail(win != NULL);
777 g_return_if_fail(win->type == wintype_TextGrid);
779 /* Calculate actual position if cursor is moved past the right edge */
780 if(xpos >= win->width)
782 ypos += xpos / win->width;
785 /* Go to the end if the cursor is moved off the bottom edge */
786 if(ypos >= win->height)
788 xpos = win->width - 1;
789 ypos = win->height - 1;
794 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
796 /* There must actually be a character at xpos, or the following function will choke */
797 gtk_text_buffer_get_iter_at_line_offset(buffer, &newpos, ypos, xpos);
798 gtk_text_buffer_move_mark_by_name(buffer, "cursor_position", &newpos);