Fixed a segfault when opening an invalid window.
[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.
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 /**
140  * glk_window_open:
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
150  * #wintype_Graphics.
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.
154  *
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.
159  *
160  * Returns: the new window.
161  */
162 winid_t
163 glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, 
164                 glui32 rock)
165 {
166         if(split)
167         {
168                 g_warning("glk_window_open: splitting of windows not implemented");
169                 return NULL;
170         }
171
172         if(glk_data->root_window != NULL)
173         {
174                 g_warning("glk_window_open: there is already a window");
175                 return NULL;
176         }
177         
178         gdk_threads_enter();
179         
180         /* We only create one window and don't support any more than that */
181         winid_t win = g_new0(struct glk_window_struct, 1);
182         glk_data->root_window = g_node_new(win);
183
184         win->rock = rock;
185         win->type = wintype;
186     win->window_node = glk_data->root_window;
187
188         switch(wintype)
189         {
190                 case wintype_Blank:
191                 {
192                         /* A blank window will be a label without any text */
193                         GtkWidget *label = gtk_label_new("");
194                         gtk_widget_show(label);
195                         
196                         win->widget = label;
197                         win->frame = label;
198                         /* You can print to a blank window's stream, but it does nothing */
199                         win->window_stream = window_stream_new(win);
200                         win->echo_stream = NULL;
201                 }
202                         break;
203                         
204                 case wintype_TextBuffer:
205                 {
206                         GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
207                         GtkWidget *textview = gtk_text_view_new();
208                         GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
209
210                         gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR );
211                         gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE );
212
213                         gtk_container_add( GTK_CONTAINER(scrolledwindow), textview );
214                         gtk_widget_show_all(scrolledwindow);
215
216                         win->widget = textview;
217                         win->frame = scrolledwindow;
218                         win->window_stream = window_stream_new(win);
219                         win->echo_stream = NULL;
220                         win->input_request_type = INPUT_REQUEST_NONE;
221                         win->line_input_buffer = NULL;
222                         win->line_input_buffer_unicode = NULL;
223
224                         /* Connect signal handlers */
225                         win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win );
226                         g_signal_handler_block( G_OBJECT(textview), win->keypress_handler );
227
228                         win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win );
229                         g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler );
230
231                         /* Create an editable tag to indicate uneditable parts of the window
232                         (for line input) */
233                         gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL);
234
235                         /* Mark the position where the user will input text */
236                         GtkTextIter end;
237                         gtk_text_buffer_get_end_iter(textbuffer, &end);
238                         gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE);
239                 }
240                         break;
241                         
242                 default:
243                         gdk_threads_leave();
244                         g_warning("%s: unsupported window type", __func__);
245                         g_free(win);
246                         g_node_destroy(glk_data->root_window);
247                         glk_data->root_window = NULL;
248                         return NULL;
249         }
250
251     /* Put the frame widget into our container */
252     gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self));
253     gtk_widget_queue_resize(GTK_WIDGET(glk_data->self));
254
255         gdk_threads_leave();
256
257         return win;
258 }
259
260 /**
261  * glk_window_close:
262  * @win: Window to close.
263  * @result: Pointer to a #stream_result_t in which to store the write count.
264  *
265  * Closes @win, which is pretty much exactly the opposite of opening a window.
266  * It is legal to close all your windows, or to close the root window (which is
267  * the same thing.) 
268  *
269  * The @result argument is filled with the output character count of the window
270  * stream.
271  */
272 void
273 glk_window_close(winid_t win, stream_result_t *result)
274 {
275         g_return_if_fail(win != NULL);
276
277         switch(win->type)
278         {
279                 case wintype_TextBuffer:
280                         gtk_widget_destroy( gtk_widget_get_parent(win->widget) );
281                         /* TODO: Cancel all input requests */
282                         break;
283
284                 case wintype_Blank:
285                         gtk_widget_destroy(win->widget);
286                         break;
287         }
288
289         stream_close_common(win->window_stream, result);
290
291         g_node_destroy(win->window_node);
292         /* TODO: iterate over child windows, closing them */
293
294         g_free(win);
295 }
296
297 /**
298  * glk_window_clear:
299  * @win: A window.
300  *
301  * Erases the window @win. The meaning of this depends on the window type.
302  *
303  * <itemizedlist>
304  *  <listitem><para>
305  *   Text buffer: This may do any number of things, such as delete all text in 
306  *   the window, or print enough blank lines to scroll all text beyond 
307  *   visibility, or insert a page-break marker which is treated specially by the
308  *   display part of the library.
309  *  </para></listitem>
310  *  <listitem><para>
311  *   Text grid: This will clear the window, filling all positions with blanks.
312  *   The window cursor is moved to the top left corner (position 0,0).
313  *  </para></listitem>
314  *  <listitem><para>
315  *   Graphics: Clears the entire window to its current background color.
316  *  </para></listitem>
317  *  <listitem><para>
318  *   Other window types: No effect. 
319  *  </para></listitem>
320  * </itemizedlist>
321  *
322  * It is illegal to erase a window which has line input pending. 
323  */
324 void
325 glk_window_clear(winid_t win)
326 {
327         g_return_if_fail(win != NULL);
328         g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE);
329         
330         switch(win->type)
331         {
332                 case wintype_Blank:
333                         /* do nothing */
334                         break;
335                         
336                 case wintype_TextBuffer:
337                         /* delete all text in the window */
338                 {
339                         gdk_threads_enter();
340
341                         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
342                         GtkTextIter start, end;
343                         gtk_text_buffer_get_bounds(buffer, &start, &end);
344                         gtk_text_buffer_delete(buffer, &start, &end);
345
346                         gdk_threads_leave();
347                 }
348                         break;
349                         
350                 default:
351                         g_warning("glk_window_clear: unsupported window type");
352         }
353 }
354
355 /**
356  * glk_set_window:
357  * @win: A window.
358  *
359  * Sets the current stream to @win's window stream. It is exactly equivalent to
360  * <informalexample><programlisting>
361  *  glk_stream_set_current(glk_window_get_stream(win))
362  * </programlisting></informalexample>
363  */
364 void
365 glk_set_window(winid_t win)
366 {
367         glk_stream_set_current( glk_window_get_stream(win) );
368 }
369
370 /**
371  * glk_window_get_stream:
372  * @win: A window.
373  *
374  * Returns the stream which is associated with @win. Every window has a stream
375  * which can be printed to, but this may not be useful, depending on the window
376  * type. (For example, printing to a blank window's stream has no effect.)
377  *
378  * Returns: The window stream.
379  */
380 strid_t glk_window_get_stream(winid_t win)
381 {
382         g_return_val_if_fail(win != NULL, NULL);
383         return win->window_stream;
384 }
385
386 /**
387  * glk_window_set_echo_stream:
388  * @win: A window.
389  * @str: A stream to attach to the window, or %NULL.
390  *
391  * Attaches the stream @str to @win as a second stream. Any text printed to the
392  * window is also echoed to this second stream, which is called the window's
393  * "echo stream."
394  *
395  * Effectively, any call to glk_put_char() (or the other output commands) which
396  * is directed to @win's window stream, is replicated to @win's echo stream.
397  * This also goes for the style commands such as glk_set_style().
398  *
399  * Note that the echoing is one-way. You can still print text directly to the
400  * echo stream, and it will go wherever the stream is bound, but it does not
401  * back up and appear in the window.
402  *
403  * It is illegal to set a window's echo stream to be its own window stream,
404  * which would create an infinite loop. It is similarly illegal to create a
405  * longer loop (two or more windows echoing to each other.)
406  *
407  * You can reset a window to stop echoing by setting @str to %NULL.
408  */
409 void
410 glk_window_set_echo_stream(winid_t win, strid_t str)
411 {
412         g_return_if_fail(win != NULL);
413         
414         /* Test for an infinite loop */
415         strid_t next = str;
416         for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream)
417         {
418                 if(next == win->window_stream)
419                 {
420                         g_warning("%s: Infinite loop detected", __func__);
421                         win->echo_stream = NULL;
422                         return;
423                 }
424         }
425         
426         win->echo_stream = str;
427 }
428
429 /**
430  * glk_window_get_echo_stream:
431  * @win: A window.
432  *
433  * Returns the echo stream of window @win. If the window has no echo stream (as
434  * is initially the case) then this returns %NULL.
435  *
436  * Returns: A stream, or %NULL.
437  */
438 strid_t
439 glk_window_get_echo_stream(winid_t win)
440 {
441         g_return_val_if_fail(win != NULL, NULL);
442         return win->echo_stream;
443 }
444
445 /**
446  * glk_window_get_size:
447  * @win: A window.
448  * @widthptr: Pointer to a location to store the window's width, or %NULL.
449  * @heightptr: Pointer to a location to store the window's height, or %NULL.
450  *
451  * Simply returns the actual size of the window, in its measurement system.
452  * Either @widthptr or @heightptr can be %NULL, if you only want one 
453  * measurement. (Or, in fact, both, if you want to waste time.)
454  */
455 void
456 glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr)
457 {
458         g_return_if_fail(win != NULL);
459
460         /* TODO: Write this function */
461         /* For a text buffer window: Return the number of rows and columns which
462         would be available _if_ the window was filled with "0" (zero) characters in
463         the "normal" font. */
464         if(widthptr != NULL) {
465                 *widthptr = 0;
466         }
467
468         if(heightptr != NULL) {
469                 *heightptr = 0;
470         }
471 }
472
473 /**
474  * glk_window_move_cursor:
475  * @win: A text grid window.
476  * @xpos: Horizontal cursor position.
477  * @ypos: Vertical cursor position.
478  * 
479  * Sets the cursor position. If you move the cursor right past the end of a 
480  * line, it wraps; the next character which is printed will appear at the
481  * beginning of the next line.
482  * 
483  * If you move the cursor below the last line, or when the cursor reaches the
484  * end of the last line, it goes "off the screen" and further output has no
485  * effect. You must call glk_window_move_cursor() or glk_window_clear() to move
486  * the cursor back into the visible region.
487  * 
488  * <note><para>
489  *  Note that the arguments of glk_window_move_cursor() are <type>unsigned 
490  *  int</type>s. This is okay, since there are no negative positions. If you try
491  *  to pass a negative value, Glk will interpret it as a huge positive value,
492  *  and it will wrap or go off the last line.
493  * </para></note>
494  *
495  * <note><para>
496  *  Also note that the output cursor is not necessarily visible. In particular,
497  *  when you are requesting line or character input in a grid window, you cannot
498  *  rely on the cursor position to prompt the player where input is indicated.
499  *  You should print some character prompt at that spot -- a ">" character, for
500  *  example.
501  * </para></note>
502  */
503 void
504 glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos)
505 {
506         g_return_if_fail(win != NULL);
507         g_return_if_fail(win->type == wintype_TextGrid);
508         /* TODO: write this function */
509 }