Added text grid windows. Output, cursor placement, and character input work. Line...
[projects/chimara/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 uneditable parts of the window (for line input) */
255                         gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable",    FALSE, "editable-set", TRUE, NULL);
256                         
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,
260                             NULL);
261                 }
262                     break;
263                 
264                 case wintype_TextBuffer:
265                 {
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) );
269
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 );
272
273                         gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
274                         gtk_widget_show_all(scrolledwindow);
275
276                         win->widget = textview;
277                         win->frame = scrolledwindow;
278             text_window_get_char_size( textview, &(win->unit_width), &(win->unit_height) );
279                         
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;
286
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 );
290
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 );
293
294                         /* Create an editable tag to indicate uneditable parts of the window
295                         (for line input) */
296                         gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
297
298                         /* Mark the position where the user will input text */
299                         GtkTextIter end;
300                         gtk_text_buffer_get_end_iter(textbuffer, &end);
301                         gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE);
302                 }
303                         break;
304                         
305                 default:
306                         gdk_threads_leave();
307                         g_warning("%s: unsupported window type", __func__);
308                         g_free(win);
309                         g_node_destroy(glk_data->root_window);
310                         glk_data->root_window = NULL;
311                         return NULL;
312         }
313
314         if(split)
315         {
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);
319                 pair->rock = 0;
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;
326
327                 /* Insert the new window into the window tree */
328                 if(split->window_node->parent == NULL)
329                 {
330                         glk_data->root_window = pair->window_node;
331                 } else {
332                         g_node_append(split->window_node->parent, pair->window_node);
333                         g_node_unlink(split->window_node);
334                 }
335
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);
340
341                 /* Place the windows in the correct order */
342                 switch(method & winmethod_DirMask)
343                 {
344                         case winmethod_Left:
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);
350                                 break;
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);
357                                 break;
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);
364                                 break;
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);
371                                 break;
372                 }
373                 gtk_widget_unref(split->frame);
374
375                 /* TODO: set the new size of the windows */
376
377                 pair->frame = pair->widget;
378                 gtk_widget_set_parent(pair->widget, old_parent);
379                 gtk_widget_show(pair->widget);
380         } else {
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));
385         }
386
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)
389     {
390         while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
391         {
392             /* Release the GDK lock momentarily */
393             gdk_threads_leave();
394             gdk_threads_enter();
395             while(gtk_events_pending())
396                 gtk_main_iteration();
397         }
398         win->width = (glui32)(win->widget->allocation.width / win->unit_width);
399         win->height = (glui32)(win->widget->allocation.height / win->unit_height);
400                 
401         /* Mark the cursor position */
402         GtkTextIter begin;
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);
406         
407         /* Fill the buffer with blanks and move the cursor to the upper left */
408         gdk_threads_leave();
409         glk_window_clear(win);
410         gdk_threads_enter();
411         
412         /* Apparently this only works after the window has been realized */
413         gtk_text_view_set_overwrite( GTK_TEXT_VIEW(win->widget), TRUE );
414     }
415
416         gdk_threads_leave();
417
418         return win;
419 }
420
421 /**
422  * glk_window_close:
423  * @win: Window to close.
424  * @result: Pointer to a #stream_result_t in which to store the write count.
425  *
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
428  * the same thing.) 
429  *
430  * The @result argument is filled with the output character count of the window
431  * stream.
432  */
433 void
434 glk_window_close(winid_t win, stream_result_t *result)
435 {
436         GNode* parent_node;
437
438         g_return_if_fail(win != NULL);
439
440         gdk_threads_enter();
441
442         switch(win->type)
443         {
444                 case wintype_Blank:
445                         gtk_widget_destroy(win->widget);
446                         break;
447         
448             case wintype_TextGrid:
449                 case wintype_TextBuffer:
450                         gtk_widget_destroy(win->frame);
451                         /* TODO: Cancel all input requests */
452                         break;
453
454                 case wintype_Pair:
455                 {
456                         GNode* left_child = g_node_first_child(win->window_node);
457                         GNode* right_child = g_node_last_child(win->window_node);
458
459                         glk_window_close((winid_t) left_child->data, result);
460                         glk_window_close((winid_t) right_child->data, result);
461
462                         gtk_widget_destroy(win->widget);
463                 }
464                         break;
465
466                 default:
467                         g_warning("%s: unsupported window type", __func__);
468                         gdk_threads_leave();
469                         return;
470         }
471
472         stream_close_common(win->window_stream, result);
473
474         /* Parent window changes from a split window into the sibling window */
475         if( (parent_node = win->window_node->parent) != NULL )
476         {
477                 winid_t pair = (winid_t) parent_node->data;
478                 if(parent_node->parent == NULL)
479                 {
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;
484                 } else {
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);
489                 }
490
491                 g_node_unlink(parent_node);
492                 g_free(pair);
493         }
494
495         g_node_destroy(win->window_node);
496         g_free(win);
497
498         gdk_threads_leave();
499 }
500
501 /**
502  * glk_window_clear:
503  * @win: A window.
504  *
505  * Erases the window @win. The meaning of this depends on the window type.
506  *
507  * <itemizedlist>
508  *  <listitem><para>
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.
513  *  </para></listitem>
514  *  <listitem><para>
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).
517  *  </para></listitem>
518  *  <listitem><para>
519  *   Graphics: Clears the entire window to its current background color.
520  *  </para></listitem>
521  *  <listitem><para>
522  *   Other window types: No effect. 
523  *  </para></listitem>
524  * </itemizedlist>
525  *
526  * It is illegal to erase a window which has line input pending. 
527  */
528 void
529 glk_window_clear(winid_t win)
530 {
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);
533         
534         switch(win->type)
535         {
536                 case wintype_Blank:
537                 case wintype_Pair:
538                         /* do nothing */
539                         break;
540                 
541                 case wintype_TextGrid:
542                     /* fill the buffer with blanks */
543                 {
544                     gdk_threads_enter();
545                     
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);
549             int count;
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() */
555             g_free(blanks);
556             
557             GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
558             gtk_text_buffer_set_text(textbuffer, text, -1);
559             g_free(text);
560             
561             GtkTextIter begin;
562             gtk_text_buffer_get_start_iter(textbuffer, &begin);
563             gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &begin);
564                     
565                     gdk_threads_leave();
566                 }
567                     break;
568                 
569                 case wintype_TextBuffer:
570                         /* delete all text in the window */
571                 {
572                         gdk_threads_enter();
573
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);
578
579                         gdk_threads_leave();
580                 }
581                         break;
582                 
583                 default:
584                         g_warning("glk_window_clear: unsupported window type");
585         }
586 }
587
588 /**
589  * glk_set_window:
590  * @win: A window.
591  *
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>
596  */
597 void
598 glk_set_window(winid_t win)
599 {
600         glk_stream_set_current( glk_window_get_stream(win) );
601 }
602
603 /**
604  * glk_window_get_stream:
605  * @win: A window.
606  *
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.)
610  *
611  * Returns: The window stream.
612  */
613 strid_t glk_window_get_stream(winid_t win)
614 {
615         g_return_val_if_fail(win != NULL, NULL);
616         return win->window_stream;
617 }
618
619 /**
620  * glk_window_set_echo_stream:
621  * @win: A window.
622  * @str: A stream to attach to the window, or %NULL.
623  *
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
626  * "echo stream."
627  *
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().
631  *
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.
635  *
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.)
639  *
640  * You can reset a window to stop echoing by setting @str to %NULL.
641  */
642 void
643 glk_window_set_echo_stream(winid_t win, strid_t str)
644 {
645         g_return_if_fail(win != NULL);
646         
647         /* Test for an infinite loop */
648         strid_t next = str;
649         for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream)
650         {
651                 if(next == win->window_stream)
652                 {
653                         g_warning("%s: Infinite loop detected", __func__);
654                         win->echo_stream = NULL;
655                         return;
656                 }
657         }
658         
659         win->echo_stream = str;
660 }
661
662 /**
663  * glk_window_get_echo_stream:
664  * @win: A window.
665  *
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.
668  *
669  * Returns: A stream, or %NULL.
670  */
671 strid_t
672 glk_window_get_echo_stream(winid_t win)
673 {
674         g_return_val_if_fail(win != NULL, NULL);
675         return win->echo_stream;
676 }
677
678 /**
679  * glk_window_get_size:
680  * @win: A window.
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.
683  *
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.)
687  */
688 void
689 glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
690 {
691         g_return_if_fail(win != NULL);
692
693     switch(win->type)
694     {
695         case wintype_Blank:
696             if(widthptr != NULL)
697                 *widthptr = 0;
698             if(heightptr != NULL)
699                 *heightptr = 0;
700             break;
701             
702         case wintype_TextGrid:
703             /* The text grid caches its width and height */
704             if(widthptr != NULL)
705                 *widthptr = win->width;
706             if(heightptr != NULL)
707                 *heightptr = win->height;
708             break;
709             
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. */
712             gdk_threads_enter();
713             /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1)
714             {
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;
717                 break;
718             } */
719             
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)
722             {
723                 /* Release the GDK lock momentarily */
724                 gdk_threads_leave();
725                 gdk_threads_enter();
726                 while(gtk_events_pending())
727                     gtk_main_iteration();
728             }
729                 
730             if(widthptr != NULL)
731                 *widthptr = (glui32)(win->widget->allocation.width / win->unit_width);
732             if(heightptr != NULL)
733                 *heightptr = (glui32)(win->widget->allocation.height / win->unit_height);
734             gdk_threads_leave();
735             
736             break;
737             
738         default:
739             g_warning("glk_window_get_size: Unsupported window type");
740     }
741 }
742
743 /**
744  * glk_window_move_cursor:
745  * @win: A text grid window.
746  * @xpos: Horizontal cursor position.
747  * @ypos: Vertical cursor position.
748  * 
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.
752  * 
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.
757  * 
758  * <note><para>
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.
763  * </para></note>
764  *
765  * <note><para>
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
770  *  example.
771  * </para></note>
772  */
773 void
774 glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
775 {
776         g_return_if_fail(win != NULL);
777         g_return_if_fail(win->type == wintype_TextGrid);
778         
779         /* Calculate actual position if cursor is moved past the right edge */
780         if(xpos >= win->width)
781         {
782             ypos += xpos / win->width;
783             xpos %= win->width;
784         }
785         /* Go to the end if the cursor is moved off the bottom edge */
786         if(ypos >= win->height)
787         {
788             xpos = win->width - 1;
789             ypos = win->height - 1;
790         }
791         
792         gdk_threads_enter();
793         
794         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
795         GtkTextIter newpos;
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);
799         
800         gdk_threads_leave();
801 }