66d708bfc2b722503281e1ec335ab70f7ff7ae32
[rodin/chimara.git] / src / window.c
1 #include "window.h"
2 #include "chimara-glk-private.h"
3
4 extern ChimaraGlkPrivate *glk_data;
5
6 /**
7  * glk_window_iterate:
8  * @win: A window, or %NULL.
9  * @rockptr: Return location for the next window's rock, or %NULL.
10  *
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.
15  *
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.
19  *
20  * Returns: the next window, or %NULL if there are no more.
21  */
22 winid_t
23 glk_window_iterate(winid_t win, glui32 *rockptr)
24 {
25         GNode *retnode;
26         
27         if(win == NULL)
28                 retnode = glk_data->root_window;
29         else
30         {
31                 GNode *node = win->window_node;
32                 if( G_NODE_IS_LEAF(node) )
33                 {
34                         while(node && node->next == NULL)
35                                 node = node->parent;
36                         if(node)
37                                 retnode = node->next;
38                         else
39                                 retnode = NULL;
40                 }
41                 else
42                         retnode = g_node_first_child(node);
43         }
44         winid_t retval = retnode? (winid_t)retnode->data : NULL;
45                 
46         /* Store the window's rock in rockptr */
47         if(retval && rockptr)
48                 *rockptr = glk_window_get_rock(retval);
49                 
50         return retval;
51 }
52
53 /**
54  * glk_window_get_rock:
55  * @win: A window.
56  * 
57  * Returns @win's rock value. Pair windows always have rock 0; all other windows
58  * have the rock value you created them with.
59  *
60  * Returns: A rock value.
61  */
62 glui32
63 glk_window_get_rock(winid_t win)
64 {
65         g_return_val_if_fail(win != NULL, 0);
66         return win->rock;
67 }
68
69 /**
70  * glk_window_get_type:
71  * @win: A window.
72  *
73  * Returns @win's type, one of #wintype_Blank, #wintype_Pair,
74  * #wintype_TextBuffer, #wintype_TextGrid, or #wintype_Graphics.
75  *
76  * Returns: The window's type.
77  */
78 glui32
79 glk_window_get_type(winid_t win)
80 {
81         g_return_val_if_fail(win != NULL, 0);
82         return win->type;
83 }
84
85 /**
86  * glk_window_get_parent:
87  * @win: A window.
88  *
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.
92  *
93  * Returns: A window, or %NULL.
94  */
95 winid_t
96 glk_window_get_parent(winid_t win)
97 {
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;
101 }
102
103 /**
104  * glk_window_get_sibling:
105  * @win: A window.
106  *
107  * Returns the other child of the window @win's parent. If @win is the
108  * root window, this returns %NULL.
109  *
110  * Returns: A window, or %NULL.
111  */
112 winid_t
113 glk_window_get_sibling(winid_t win)
114 {
115         g_return_val_if_fail(win != NULL, NULL);
116         
117         if(G_NODE_IS_ROOT(win->window_node))
118                 return NULL;
119         if(win->window_node->next)
120                 return (winid_t)win->window_node->next;
121         return (winid_t)win->window_node->prev;
122 }
123
124 /**
125  * glk_window_get_root:
126  * 
127  * Returns the root window. If there are no windows, this returns %NULL.
128  *
129  * Returns: A window, or %NULL.
130  */
131 winid_t
132 glk_window_get_root()
133 {
134         if(glk_data->root_window == NULL)
135                 return NULL;
136         return (winid_t)glk_data->root_window->data;
137 }
138
139 /* Determine the size of a "0" character in pixels */
140 static void
141 text_window_get_char_size(GtkWidget *textview, int *width, int *height)
142 {
143     PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0");
144     pango_layout_get_pixel_size(zero, width, height);
145     g_object_unref(zero);
146 }
147
148 /**
149  * glk_window_open:
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
159  * #wintype_Graphics.
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.
163  *
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.
168  *
169  * Returns: the new window, or %NULL on error.
170  */
171 winid_t
172 glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, 
173                 glui32 rock)
174 {
175         /*
176         if(split)
177         {
178                 g_warning("glk_window_open: splitting of windows not implemented");
179                 return NULL;
180         }
181         */
182
183         if(split == NULL && glk_data->root_window != NULL)
184         {
185                 g_warning("glk_window_open: there is already a root window");
186                 return NULL;
187         }
188         
189         gdk_threads_enter();
190         
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);
193         win->rock = rock;
194         win->type = wintype;
195         win->window_node = g_node_new(win);
196
197         switch(wintype)
198         {
199                 case wintype_Blank:
200                 {
201                         /* A blank window will be a label without any text */
202                         GtkWidget *label = gtk_label_new("");
203                         gtk_widget_show(label);
204                         
205                         win->widget = label;
206                         win->frame = label;
207                         /* A blank window has no size */
208                         win->unit_width = 0;
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;
213                 }
214                         break;
215                 
216                 case wintype_TextGrid:
217                 {
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) );
221                     
222                     gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_NEVER );
223                     
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 );
226                     
227                     gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
228                     gtk_widget_show_all(scrolledwindow);
229                                 
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);
235                     
236                     win->widget = textview;
237                     win->frame = scrolledwindow;
238                     text_window_get_char_size( textview, &(win->unit_width), &(win->unit_height) );
239                         
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;
246                         
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 );
250                         
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 );
253                         
254                         /* Create a tag to indicate an editable field in the window (for line input) */
255                         gtk_text_buffer_create_tag(textbuffer, "input_field",
256                             "background", "grey", "background-set", TRUE,
257                             "editable", TRUE, "editable-set", TRUE,
258                             NULL);
259                 }
260                     break;
261                 
262                 case wintype_TextBuffer:
263                 {
264                         GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
265                         GtkWidget *textview = gtk_text_view_new();
266                         GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
267
268                         gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR );
269                         gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
270
271                         gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
272                         gtk_widget_show_all(scrolledwindow);
273
274                         win->widget = textview;
275                         win->frame = scrolledwindow;
276             text_window_get_char_size( textview, &(win->unit_width), &(win->unit_height) );
277                         
278                         /* Set the other parameters */
279                         win->window_stream = window_stream_new(win);
280                         win->echo_stream = NULL;
281                         win->input_request_type = INPUT_REQUEST_NONE;
282                         win->line_input_buffer = NULL;
283                         win->line_input_buffer_unicode = NULL;
284
285                         /* Connect signal handlers */
286                         win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
287                         g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
288
289                         win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win );
290                         g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler );
291
292                         /* Create an editable tag to indicate uneditable parts of the window
293                         (for line input) */
294                         gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
295
296                         /* Mark the position where the user will input text */
297                         GtkTextIter end;
298                         gtk_text_buffer_get_end_iter(textbuffer, &end);
299                         gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE);
300                 }
301                         break;
302                         
303                 default:
304                         gdk_threads_leave();
305                         g_warning("%s: unsupported window type", __func__);
306                         g_free(win);
307                         g_node_destroy(glk_data->root_window);
308                         glk_data->root_window = NULL;
309                         return NULL;
310         }
311
312         if(split)
313         {
314                 /* When splitting, construct a new parent window
315                  * copying most characteristics from the window that is being split */
316                 winid_t pair = g_new0(struct glk_window_struct, 1);
317                 pair->rock = 0;
318                 pair->type = wintype_Pair;
319                 pair->window_node = g_node_new(pair);
320                 pair->unit_width = split->unit_width;
321                 pair->unit_height = split->unit_height;
322                 pair->window_stream = NULL;
323                 pair->echo_stream = NULL;
324
325                 /* Insert the new window into the window tree */
326                 if(split->window_node->parent == NULL)
327                 {
328                         glk_data->root_window = pair->window_node;
329                 } else {
330                         g_node_append(split->window_node->parent, pair->window_node);
331                         g_node_unlink(split->window_node);
332                 }
333
334                 /* Keep track of the parent widget of the window that is being split */
335                 GtkWidget* old_parent = gtk_widget_get_parent(split->frame);
336                 gtk_widget_ref(split->frame);
337                 gtk_widget_unparent(split->frame);
338
339                 /* Place the windows in the correct order */
340                 switch(method & winmethod_DirMask)
341                 {
342                         case winmethod_Left:
343                                 pair->widget = gtk_hbox_new(FALSE, 0);
344                                 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
345                                 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
346                                 g_node_append(pair->window_node, split->window_node);
347                                 g_node_append(pair->window_node, win->window_node);
348                                 break;
349                         case winmethod_Right:
350                                 pair->widget = gtk_hbox_new(FALSE, 0);
351                                 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
352                                 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
353                                 g_node_append(pair->window_node, win->window_node);
354                                 g_node_append(pair->window_node, split->window_node);
355                                 break;
356                         case winmethod_Above:
357                                 pair->widget = gtk_vbox_new(FALSE, 0);
358                                 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
359                                 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
360                                 g_node_append(pair->window_node, split->window_node);
361                                 g_node_append(pair->window_node, win->window_node);
362                                 break;
363                         case winmethod_Below:
364                                 pair->widget = gtk_vbox_new(FALSE, 0);
365                                 gtk_box_pack_end(GTK_BOX(pair->widget), win->frame, TRUE, TRUE, 0);
366                                 gtk_box_pack_end(GTK_BOX(pair->widget), split->frame, TRUE, TRUE, 0);
367                                 g_node_append(pair->window_node, win->window_node);
368                                 g_node_append(pair->window_node, split->window_node);
369                                 break;
370                 }
371                 gtk_widget_unref(split->frame);
372
373                 /* TODO: set the new size of the windows */
374
375                 pair->frame = pair->widget;
376                 gtk_widget_set_parent(pair->widget, old_parent);
377                 gtk_widget_show(pair->widget);
378         } else {
379                 /* Set the window as root window */
380                 glk_data->root_window = win->window_node;
381                 gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self));
382                 gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
383         }
384
385     /* 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. */
386     if(wintype == wintype_TextGrid)
387     {
388         while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
389         {
390             /* Release the GDK lock momentarily */
391             gdk_threads_leave();
392             gdk_threads_enter();
393             while(gtk_events_pending())
394                 gtk_main_iteration();
395         }
396         win->width = (glui32)(win->widget->allocation.width / win->unit_width);
397         win->height = (glui32)(win->widget->allocation.height / win->unit_height);
398                 
399         /* Mark the cursor position */
400         GtkTextIter begin;
401         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
402         gtk_text_buffer_get_start_iter(buffer, &begin);
403         gtk_text_buffer_create_mark(buffer, "cursor_position", &begin, TRUE);
404         
405         /* Fill the buffer with blanks and move the cursor to the upper left */
406         gdk_threads_leave();
407         glk_window_clear(win);
408         gdk_threads_enter();
409         
410         /* Apparently this only works after the window has been realized */
411         gtk_text_view_set_overwrite( GTK_TEXT_VIEW(win->widget), TRUE );
412     }
413
414         gdk_threads_leave();
415
416         return win;
417 }
418
419 /**
420  * glk_window_close:
421  * @win: Window to close.
422  * @result: Pointer to a #stream_result_t in which to store the write count.
423  *
424  * Closes @win, which is pretty much exactly the opposite of opening a window.
425  * It is legal to close all your windows, or to close the root window (which is
426  * the same thing.) 
427  *
428  * The @result argument is filled with the output character count of the window
429  * stream.
430  */
431 void
432 glk_window_close(winid_t win, stream_result_t *result)
433 {
434         GNode* parent_node;
435
436         g_return_if_fail(win != NULL);
437
438         gdk_threads_enter();
439
440         switch(win->type)
441         {
442                 case wintype_Blank:
443                         gtk_widget_destroy(win->widget);
444                         break;
445         
446             case wintype_TextGrid:
447                 case wintype_TextBuffer:
448                         gtk_widget_destroy(win->frame);
449                         /* TODO: Cancel all input requests */
450                         break;
451
452                 case wintype_Pair:
453                 {
454                         GNode* left_child = g_node_first_child(win->window_node);
455                         GNode* right_child = g_node_last_child(win->window_node);
456
457                         glk_window_close((winid_t) left_child->data, result);
458                         glk_window_close((winid_t) right_child->data, result);
459
460                         gtk_widget_destroy(win->widget);
461                 }
462                         break;
463
464                 default:
465                         g_warning("%s: unsupported window type", __func__);
466                         gdk_threads_leave();
467                         return;
468         }
469
470         stream_close_common(win->window_stream, result);
471
472         /* Parent window changes from a split window into the sibling window */
473         if( (parent_node = win->window_node->parent) != NULL )
474         {
475                 winid_t pair = (winid_t) parent_node->data;
476                 if(parent_node->parent == NULL)
477                 {
478                         if(parent_node->next)
479                                 glk_data->root_window = parent_node->next;
480                         else if(parent_node->prev)
481                                 glk_data->root_window = parent_node->prev;
482                 } else {
483                         if(parent_node->next)
484                                 g_node_append(parent_node->parent, parent_node->next);
485                         else if(parent_node->prev)
486                                 g_node_append(parent_node->parent, parent_node->prev);
487                 }
488
489                 g_node_unlink(parent_node);
490                 g_free(pair);
491         }
492
493         g_node_destroy(win->window_node);
494         g_free(win);
495
496         gdk_threads_leave();
497 }
498
499 /**
500  * glk_window_clear:
501  * @win: A window.
502  *
503  * Erases the window @win. The meaning of this depends on the window type.
504  *
505  * <itemizedlist>
506  *  <listitem><para>
507  *   Text buffer: This may do any number of things, such as delete all text in 
508  *   the window, or print enough blank lines to scroll all text beyond 
509  *   visibility, or insert a page-break marker which is treated specially by the
510  *   display part of the library.
511  *  </para></listitem>
512  *  <listitem><para>
513  *   Text grid: This will clear the window, filling all positions with blanks.
514  *   The window cursor is moved to the top left corner (position 0,0).
515  *  </para></listitem>
516  *  <listitem><para>
517  *   Graphics: Clears the entire window to its current background color.
518  *  </para></listitem>
519  *  <listitem><para>
520  *   Other window types: No effect. 
521  *  </para></listitem>
522  * </itemizedlist>
523  *
524  * It is illegal to erase a window which has line input pending. 
525  */
526 void
527 glk_window_clear(winid_t win)
528 {
529         g_return_if_fail(win != NULL);
530         g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE);
531         
532         switch(win->type)
533         {
534                 case wintype_Blank:
535                 case wintype_Pair:
536                         /* do nothing */
537                         break;
538                 
539                 case wintype_TextGrid:
540                     /* fill the buffer with blanks */
541                 {
542                     gdk_threads_enter();
543                     
544             /* Manually put newlines at the end of each row of characters in the buffer; manual newlines make resizing the window's grid easier. */
545             gchar *blanks = g_strnfill(win->width, ' ');
546             gchar **blanklines = g_new0(gchar *, win->height + 1);
547             int count;
548             for(count = 0; count < win->height; count++)
549                 blanklines[count] = blanks;
550             blanklines[win->height] = NULL;
551             gchar *text = g_strjoinv("\n", blanklines);
552             g_free(blanklines); /* not g_strfreev() */
553             g_free(blanks);
554             
555             GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
556             gtk_text_buffer_set_text(textbuffer, text, -1);
557             g_free(text);
558             
559             GtkTextIter begin;
560             gtk_text_buffer_get_start_iter(textbuffer, &begin);
561             gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &begin);
562                     
563                     gdk_threads_leave();
564                 }
565                     break;
566                 
567                 case wintype_TextBuffer:
568                         /* delete all text in the window */
569                 {
570                         gdk_threads_enter();
571
572                         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
573                         GtkTextIter start, end;
574                         gtk_text_buffer_get_bounds(buffer, &start, &end);
575                         gtk_text_buffer_delete(buffer, &start, &end);
576
577                         gdk_threads_leave();
578                 }
579                         break;
580                 
581                 default:
582                         g_warning("glk_window_clear: unsupported window type");
583         }
584 }
585
586 /**
587  * glk_set_window:
588  * @win: A window.
589  *
590  * Sets the current stream to @win's window stream. It is exactly equivalent to
591  * <informalexample><programlisting>
592  *  glk_stream_set_current(glk_window_get_stream(win))
593  * </programlisting></informalexample>
594  */
595 void
596 glk_set_window(winid_t win)
597 {
598         glk_stream_set_current( glk_window_get_stream(win) );
599 }
600
601 /**
602  * glk_window_get_stream:
603  * @win: A window.
604  *
605  * Returns the stream which is associated with @win. Every window has a stream
606  * which can be printed to, but this may not be useful, depending on the window
607  * type. (For example, printing to a blank window's stream has no effect.)
608  *
609  * Returns: The window stream.
610  */
611 strid_t glk_window_get_stream(winid_t win)
612 {
613         g_return_val_if_fail(win != NULL, NULL);
614         return win->window_stream;
615 }
616
617 /**
618  * glk_window_set_echo_stream:
619  * @win: A window.
620  * @str: A stream to attach to the window, or %NULL.
621  *
622  * Attaches the stream @str to @win as a second stream. Any text printed to the
623  * window is also echoed to this second stream, which is called the window's
624  * "echo stream."
625  *
626  * Effectively, any call to glk_put_char() (or the other output commands) which
627  * is directed to @win's window stream, is replicated to @win's echo stream.
628  * This also goes for the style commands such as glk_set_style().
629  *
630  * Note that the echoing is one-way. You can still print text directly to the
631  * echo stream, and it will go wherever the stream is bound, but it does not
632  * back up and appear in the window.
633  *
634  * It is illegal to set a window's echo stream to be its own window stream,
635  * which would create an infinite loop. It is similarly illegal to create a
636  * longer loop (two or more windows echoing to each other.)
637  *
638  * You can reset a window to stop echoing by setting @str to %NULL.
639  */
640 void
641 glk_window_set_echo_stream(winid_t win, strid_t str)
642 {
643         g_return_if_fail(win != NULL);
644         
645         /* Test for an infinite loop */
646         strid_t next = str;
647         for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream)
648         {
649                 if(next == win->window_stream)
650                 {
651                         g_warning("%s: Infinite loop detected", __func__);
652                         win->echo_stream = NULL;
653                         return;
654                 }
655         }
656         
657         win->echo_stream = str;
658 }
659
660 /**
661  * glk_window_get_echo_stream:
662  * @win: A window.
663  *
664  * Returns the echo stream of window @win. If the window has no echo stream (as
665  * is initially the case) then this returns %NULL.
666  *
667  * Returns: A stream, or %NULL.
668  */
669 strid_t
670 glk_window_get_echo_stream(winid_t win)
671 {
672         g_return_val_if_fail(win != NULL, NULL);
673         return win->echo_stream;
674 }
675
676 /**
677  * glk_window_get_size:
678  * @win: A window.
679  * @widthptr: Pointer to a location to store the window's width, or %NULL.
680  * @heightptr: Pointer to a location to store the window's height, or %NULL.
681  *
682  * Simply returns the actual size of the window, in its measurement system.
683  * Either @widthptr or @heightptr can be %NULL, if you only want one 
684  * measurement. (Or, in fact, both, if you want to waste time.)
685  */
686 void
687 glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
688 {
689         g_return_if_fail(win != NULL);
690
691     switch(win->type)
692     {
693         case wintype_Blank:
694             if(widthptr != NULL)
695                 *widthptr = 0;
696             if(heightptr != NULL)
697                 *heightptr = 0;
698             break;
699             
700         case wintype_TextGrid:
701             /* The text grid caches its width and height */
702             if(widthptr != NULL)
703                 *widthptr = win->width;
704             if(heightptr != NULL)
705                 *heightptr = win->height;
706             break;
707             
708         case wintype_TextBuffer:
709             /* 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. */
710             gdk_threads_enter();
711             /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
712             {
713                 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.");
714                 guess the size from the parent window;
715                 break;
716             } */
717             
718             /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */
719             while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
720             {
721                 /* Release the GDK lock momentarily */
722                 gdk_threads_leave();
723                 gdk_threads_enter();
724                 while(gtk_events_pending())
725                     gtk_main_iteration();
726             }
727                 
728             if(widthptr != NULL)
729                 *widthptr = (glui32)(win->widget->allocation.width / win->unit_width);
730             if(heightptr != NULL)
731                 *heightptr = (glui32)(win->widget->allocation.height / win->unit_height);
732             gdk_threads_leave();
733             
734             break;
735             
736         default:
737             g_warning("glk_window_get_size: Unsupported window type");
738     }
739 }
740
741 /**
742  * glk_window_move_cursor:
743  * @win: A text grid window.
744  * @xpos: Horizontal cursor position.
745  * @ypos: Vertical cursor position.
746  * 
747  * Sets the cursor position. If you move the cursor right past the end of a 
748  * line, it wraps; the next character which is printed will appear at the
749  * beginning of the next line.
750  * 
751  * If you move the cursor below the last line, or when the cursor reaches the
752  * end of the last line, it goes "off the screen" and further output has no
753  * effect. You must call glk_window_move_cursor() or glk_window_clear() to move
754  * the cursor back into the visible region.
755  * 
756  * <note><para>
757  *  Note that the arguments of glk_window_move_cursor() are <type>unsigned 
758  *  int</type>s. This is okay, since there are no negative positions. If you try
759  *  to pass a negative value, Glk will interpret it as a huge positive value,
760  *  and it will wrap or go off the last line.
761  * </para></note>
762  *
763  * <note><para>
764  *  Also note that the output cursor is not necessarily visible. In particular,
765  *  when you are requesting line or character input in a grid window, you cannot
766  *  rely on the cursor position to prompt the player where input is indicated.
767  *  You should print some character prompt at that spot -- a ">" character, for
768  *  example.
769  * </para></note>
770  */
771 void
772 glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
773 {
774         g_return_if_fail(win != NULL);
775         g_return_if_fail(win->type == wintype_TextGrid);
776         
777         /* Calculate actual position if cursor is moved past the right edge */
778         if(xpos >= win->width)
779         {
780             ypos += xpos / win->width;
781             xpos %= win->width;
782         }
783         /* Go to the end if the cursor is moved off the bottom edge */
784         if(ypos >= win->height)
785         {
786             xpos = win->width - 1;
787             ypos = win->height - 1;
788         }
789         
790         gdk_threads_enter();
791         
792         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
793         GtkTextIter newpos;
794         /* There must actually be a character at xpos, or the following function will choke */
795         gtk_text_buffer_get_iter_at_line_offset(buffer, &newpos, ypos, xpos);
796         gtk_text_buffer_move_mark_by_name(buffer, "cursor_position", &newpos);
797         
798         gdk_threads_leave();
799 }