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.
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;
141 * @split: The window to split to create the new window. Must be 0 if there
142 * are no windows yet.
143 * @method: Position of the new window and method of size computation. One of
144 * #winmethod_Above, #winmethod_Below, #winmethod_Left, or #winmethod_Right
145 * OR'ed with #winmethod_Fixed or #winmethod_Proportional. If @wintype is
146 * #wintype_Blank, then #winmethod_Fixed is not allowed.
147 * @size: Size of the new window, in percentage points if @method is
148 * #winmethod_Proportional, otherwise in characters if @wintype is
149 * #wintype_TextBuffer or #wintype_TextGrid, or pixels if @wintype is
151 * @wintype: Type of the new window. One of #wintype_Blank, #wintype_TextGrid,
152 * #wintype_TextBuffer, or #wintype_Graphics.
153 * @rock: The new window's rock value.
155 * If there are no windows, create a new root window. @split must be 0, and
156 * @method and @size are ignored. Otherwise, split window @split into two, with
157 * position, size, and type specified by @method, @size, and @wintype. See the
158 * Glk documentation for the window placement algorithm.
160 * Returns: the new window.
163 glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype,
169 g_warning("glk_window_open: splitting of windows not implemented");
174 if(split == NULL && glk_data->root_window != NULL)
176 g_warning("glk_window_open: there is already a root window");
182 /* We only create one window and don't support any more than that */
183 winid_t win = g_new0(struct glk_window_struct, 1);
186 win->window_node = g_node_new(win);
192 /* A blank window will be a label without any text */
193 GtkWidget *label = gtk_label_new("");
194 gtk_widget_show(label);
198 /* A blank window has no size */
200 win->unit_height = 0;
201 /* You can print to a blank window's stream, but it does nothing */
202 win->window_stream = window_stream_new(win);
203 win->echo_stream = NULL;
207 case wintype_TextBuffer:
209 GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
210 GtkWidget *textview = gtk_text_view_new();
211 GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
213 gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR );
214 gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
216 gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
217 gtk_widget_show_all(scrolledwindow);
219 win->widget = textview;
220 win->frame = scrolledwindow;
221 /* Determine the size of a "0" character in pixels" */
222 PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
223 pango_layout_get_pixel_size( zero, &(win->unit_width), &(win->unit_height) );
224 g_object_unref(zero);
226 /* Set the other parameters */
227 win->window_stream = window_stream_new(win);
228 win->echo_stream = NULL;
229 win->input_request_type = INPUT_REQUEST_NONE;
230 win->line_input_buffer = NULL;
231 win->line_input_buffer_unicode = NULL;
233 /* Connect signal handlers */
234 win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
235 g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
237 win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win );
238 g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler );
240 /* Create an editable tag to indicate uneditable parts of the window
242 gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
244 /* Mark the position where the user will input text */
246 gtk_text_buffer_get_end_iter(textbuffer, &end);
247 gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE);
253 g_warning("%s: unsupported window type", __func__);
255 g_node_destroy(glk_data->root_window);
256 glk_data->root_window = NULL;
262 /* When splitting, construct a new parent window
263 * copying most characteristics from the window that is being split */
264 winid_t pair = g_new0(struct glk_window_struct, 1);
266 pair->type = wintype_Pair;
267 pair->window_node = g_node_new(pair);
268 pair->unit_width = split->unit_width;
269 pair->unit_height = split->unit_height;
270 pair->window_stream = NULL;
271 pair->echo_stream = NULL;
273 /* Insert the new window into the window tree */
274 if(split->window_node->parent == NULL)
276 glk_data->root_window = pair->window_node;
278 g_node_append(split->window_node->parent, pair->window_node);
279 g_node_unlink(split->window_node);
282 /* Keep track of the parent widget of the window that is being split */
283 GtkWidget* old_parent = gtk_widget_get_parent(split->frame);
284 gtk_widget_ref(split->frame);
285 gtk_widget_unparent(split->frame);
287 /* Place the windows in the correct order */
288 switch(method & winmethod_DirMask)
291 pair->widget = gtk_hbox_new(FALSE, 0);
292 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
293 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
294 g_node_append(pair->window_node, split->window_node);
295 g_node_append(pair->window_node, win->window_node);
297 case winmethod_Right:
298 pair->widget = gtk_hbox_new(FALSE, 0);
299 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
300 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
301 g_node_append(pair->window_node, win->window_node);
302 g_node_append(pair->window_node, split->window_node);
304 case winmethod_Above:
305 pair->widget = gtk_vbox_new(FALSE, 0);
306 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
307 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
308 g_node_append(pair->window_node, split->window_node);
309 g_node_append(pair->window_node, win->window_node);
311 case winmethod_Below:
312 pair->widget = gtk_vbox_new(FALSE, 0);
313 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
314 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
315 g_node_append(pair->window_node, win->window_node);
316 g_node_append(pair->window_node, split->window_node);
319 gtk_widget_unref(split->frame);
321 /* TODO: set the new size of the windows */
323 pair->frame = pair->widget;
324 gtk_widget_set_parent(pair->widget, old_parent);
325 gtk_widget_show(pair->widget);
327 /* Set the window as root window */
328 glk_data->root_window = win->window_node;
329 gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self));
330 gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
340 * @win: Window to close.
341 * @result: Pointer to a #stream_result_t in which to store the write count.
343 * Closes @win, which is pretty much exactly the opposite of opening a window.
344 * It is legal to close all your windows, or to close the root window (which is
347 * The @result argument is filled with the output character count of the window
351 glk_window_close(winid_t win, stream_result_t *result)
355 g_return_if_fail(win != NULL);
361 case wintype_TextBuffer:
362 gtk_widget_destroy(win->frame);
364 /* TODO: Cancel all input requests */
368 gtk_widget_destroy(win->widget);
373 GNode* left_child = g_node_first_child(win->window_node);
374 GNode* right_child = g_node_last_child(win->window_node);
376 glk_window_close((winid_t) left_child->data, result);
377 glk_window_close((winid_t) right_child->data, result);
379 gtk_widget_destroy(win->widget);
384 g_warning("%s: unsupported window type", __func__);
389 stream_close_common(win->window_stream, result);
391 /* Parent window changes from a split window into the sibling window */
392 if( (parent_node = win->window_node->parent) != NULL )
394 winid_t pair = (winid_t) parent_node->data;
395 if(parent_node->parent == NULL)
397 if(parent_node->next)
398 glk_data->root_window = parent_node->next;
399 else if(parent_node->prev)
400 glk_data->root_window = parent_node->prev;
402 if(parent_node->next)
403 g_node_append(parent_node->parent, parent_node->next);
404 else if(parent_node->prev)
405 g_node_append(parent_node->parent, parent_node->prev);
408 g_node_unlink(parent_node);
412 g_node_destroy(win->window_node);
422 * Erases the window @win. The meaning of this depends on the window type.
426 * Text buffer: This may do any number of things, such as delete all text in
427 * the window, or print enough blank lines to scroll all text beyond
428 * visibility, or insert a page-break marker which is treated specially by the
429 * display part of the library.
432 * Text grid: This will clear the window, filling all positions with blanks.
433 * The window cursor is moved to the top left corner (position 0,0).
436 * Graphics: Clears the entire window to its current background color.
439 * Other window types: No effect.
443 * It is illegal to erase a window which has line input pending.
446 glk_window_clear(winid_t win)
448 g_return_if_fail(win != NULL);
449 g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE);
458 case wintype_TextBuffer:
459 /* delete all text in the window */
463 GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
464 GtkTextIter start, end;
465 gtk_text_buffer_get_bounds(buffer, &start, &end);
466 gtk_text_buffer_delete(buffer, &start, &end);
473 g_warning("glk_window_clear: unsupported window type");
481 * Sets the current stream to @win's window stream. It is exactly equivalent to
482 * <informalexample><programlisting>
483 * glk_stream_set_current(glk_window_get_stream(win))
484 * </programlisting></informalexample>
487 glk_set_window(winid_t win)
489 glk_stream_set_current( glk_window_get_stream(win) );
493 * glk_window_get_stream:
496 * Returns the stream which is associated with @win. Every window has a stream
497 * which can be printed to, but this may not be useful, depending on the window
498 * type. (For example, printing to a blank window's stream has no effect.)
500 * Returns: The window stream.
502 strid_t glk_window_get_stream(winid_t win)
504 g_return_val_if_fail(win != NULL, NULL);
505 return win->window_stream;
509 * glk_window_set_echo_stream:
511 * @str: A stream to attach to the window, or %NULL.
513 * Attaches the stream @str to @win as a second stream. Any text printed to the
514 * window is also echoed to this second stream, which is called the window's
517 * Effectively, any call to glk_put_char() (or the other output commands) which
518 * is directed to @win's window stream, is replicated to @win's echo stream.
519 * This also goes for the style commands such as glk_set_style().
521 * Note that the echoing is one-way. You can still print text directly to the
522 * echo stream, and it will go wherever the stream is bound, but it does not
523 * back up and appear in the window.
525 * It is illegal to set a window's echo stream to be its own window stream,
526 * which would create an infinite loop. It is similarly illegal to create a
527 * longer loop (two or more windows echoing to each other.)
529 * You can reset a window to stop echoing by setting @str to %NULL.
532 glk_window_set_echo_stream(winid_t win, strid_t str)
534 g_return_if_fail(win != NULL);
536 /* Test for an infinite loop */
538 for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream)
540 if(next == win->window_stream)
542 g_warning("%s: Infinite loop detected", __func__);
543 win->echo_stream = NULL;
548 win->echo_stream = str;
552 * glk_window_get_echo_stream:
555 * Returns the echo stream of window @win. If the window has no echo stream (as
556 * is initially the case) then this returns %NULL.
558 * Returns: A stream, or %NULL.
561 glk_window_get_echo_stream(winid_t win)
563 g_return_val_if_fail(win != NULL, NULL);
564 return win->echo_stream;
568 * glk_window_get_size:
570 * @widthptr: Pointer to a location to store the window's width, or %NULL.
571 * @heightptr: Pointer to a location to store the window's height, or %NULL.
573 * Simply returns the actual size of the window, in its measurement system.
574 * Either @widthptr or @heightptr can be %NULL, if you only want one
575 * measurement. (Or, in fact, both, if you want to waste time.)
578 glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
580 g_return_if_fail(win != NULL);
587 if(heightptr != NULL)
591 case wintype_TextBuffer:
592 /* 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. */
594 /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
596 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.");
597 guess the size from the parent window;
601 /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */
602 while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
604 /* Release the GDK lock momentarily */
607 while(gtk_events_pending())
608 gtk_main_iteration();
612 *widthptr = (glui32)(win->widget->allocation.width / win->unit_width);
613 if(heightptr != NULL)
614 *heightptr = (glui32)(win->widget->allocation.height / win->unit_height);
620 g_warning("glk_window_get_size: Unsupported window type");
625 * glk_window_move_cursor:
626 * @win: A text grid window.
627 * @xpos: Horizontal cursor position.
628 * @ypos: Vertical cursor position.
630 * Sets the cursor position. If you move the cursor right past the end of a
631 * line, it wraps; the next character which is printed will appear at the
632 * beginning of the next line.
634 * If you move the cursor below the last line, or when the cursor reaches the
635 * end of the last line, it goes "off the screen" and further output has no
636 * effect. You must call glk_window_move_cursor() or glk_window_clear() to move
637 * the cursor back into the visible region.
640 * Note that the arguments of glk_window_move_cursor() are <type>unsigned
641 * int</type>s. This is okay, since there are no negative positions. If you try
642 * to pass a negative value, Glk will interpret it as a huge positive value,
643 * and it will wrap or go off the last line.
647 * Also note that the output cursor is not necessarily visible. In particular,
648 * when you are requesting line or character input in a grid window, you cannot
649 * rely on the cursor position to prompt the player where input is indicated.
650 * You should print some character prompt at that spot -- a ">" character, for
655 glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
657 g_return_if_fail(win != NULL);
658 g_return_if_fail(win->type == wintype_TextGrid);
659 /* TODO: write this function */