Inspired by #17, compared our Nitfol source to the source from its last
[projects/chimara/chimara.git] / interpreters / nitfol / io.c
1 /*  Nitfol - z-machine interpreter using Glk for output.
2     Copyright (C) 1999  Evin Robertson
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
17
18     The author can be reached at nitfol@deja.com
19 */
20 #include "nitfol.h"
21 #include "io.h"
22
23 #ifdef HEADER
24
25 typedef struct z_window *zwinid;
26
27 #endif
28
29 BOOL is_fixed;       /* If we are forcing output to be fixed-width */
30
31
32 /* descriptions of z-machine colors, in glk 0x00rrggbb style.
33    -1 means to call glk_stylehint_clear instead of glk_stylehint_set.
34    The 'current color' will be overwritten on calls to set_colour.
35
36    Go ahead and customize these (making background colors lighter than
37    foreground colors might be interesting)
38 */
39
40 glsi32 bgcolortable[] = {
41   -1L,          /* current color */
42   -1L,          /* defualt setting */
43   0x00000000L,  /* black */
44   0x00ff0000L,  /* red */
45   0x00008000L,  /* green */
46   0x00ffff00L,  /* yellow */
47   0x000000ffL,  /* blue */
48   0x00ff00ffL,  /* magenta */
49   0x0000ffffL,  /* cyan */
50   0x00ffffffL,  /* white */
51   0x00c0c0c0L,  /* light grey */
52   0x00808080L,  /* medium grey */
53   0x00404040L   /* dark grey */
54 };
55
56 glsi32 fgcolortable[] = {
57   -1L,          /* current color */
58   -1L,          /* defualt setting */
59   0x00000000L,  /* black */
60   0x00ff0000L,  /* red */
61   0x00008000L,  /* green */
62   0x00ffff00L,  /* yellow */
63   0x000000ffL,  /* blue */
64   0x00ff00ffL,  /* magenta */
65   0x0000ffffL,  /* cyan */
66   0x00ffffffL,  /* white */
67   0x00c0c0c0L,  /* light grey */
68   0x00808080L,  /* medium grey */
69   0x00404040L   /* dark grey */
70 };
71
72
73 static void killglkwithcolor(glui32 styl, int fore, int back)
74 {
75   if(fgcolortable[fore] == -1)
76     glk_stylehint_clear(wintype_AllTypes, styl,
77                         stylehint_TextColor);
78   else
79     glk_stylehint_set(wintype_AllTypes, styl,
80                       stylehint_TextColor, fgcolortable[fore]);
81
82   if(bgcolortable[back] == -1)
83     glk_stylehint_clear(wintype_AllTypes, styl,
84                         stylehint_BackColor);
85   else
86     glk_stylehint_set(wintype_AllTypes, styl,
87                       stylehint_BackColor, bgcolortable[back]);
88 }
89
90
91 static void set_stylehints(char fore, char back)
92 {
93   glui32 n;
94   for(n = 0; n < style_NUMSTYLES; n++)
95     killglkwithcolor(n, fore, back);
96
97   /* Subheader will be used for bold */
98   glk_stylehint_set(wintype_TextBuffer, style_Subheader,
99                     stylehint_Weight, 1);
100
101   /* BlockQuote will be used for reverse proportional text */
102   glk_stylehint_set(wintype_TextBuffer, style_BlockQuote,
103                     stylehint_Proportional, 0);
104   glk_stylehint_set(wintype_TextBuffer, style_BlockQuote,
105                     stylehint_Justification, stylehint_just_Centered);
106 #ifdef stylehint_ReverseColor
107   glk_stylehint_set(wintype_TextBuffer, style_BlockQuote,
108                     stylehint_ReverseColor, 1);
109 #endif
110
111   /* User1 will be used for bold italics */
112   glk_stylehint_set(wintype_TextBuffer, style_User1,
113                     stylehint_Weight, 1);
114   glk_stylehint_set(wintype_TextBuffer, style_User1,
115                     stylehint_Oblique, 1);
116
117   /* User2 will be used for proportional bold/italic */
118   glk_stylehint_set(wintype_TextBuffer, style_User2,
119                     stylehint_Proportional, 0);
120   glk_stylehint_set(wintype_TextBuffer, style_User2,
121                     stylehint_Weight, 1);
122 }
123
124 #define sBOLD 1
125 #define sITAL 2
126 #define sFIXE 4
127 #define sREVE 8
128
129 #if 0
130
131 static glui32 bitmap_to_style[16] = {
132   style_Normal,
133   style_Subheader,   /* sBOLD */
134   style_Emphasized,  /* sITAL */
135   style_User1,       /* sBOLD | sITAL */
136   style_Preformatted,/* sFIXE */
137   style_User2,       /* sFIXE | sBOLD */
138   style_User2,       /* sFIXE | sITAL */
139   style_User2,       /* sFIXE | sBOLD | sITAL*/
140   style_BlockQuote,  /* sREVE */
141   style_BlockQuote,  /* sREVE | sBOLD */
142   style_BlockQuote,  /* sREVE | sITAL */
143   style_BlockQuote,  /* sREVE | sBOLD | sITAL */
144   style_BlockQuote,  /* sFIXE | sREVE */
145   style_BlockQuote,  /* sFIXE | sREVE | sBOLD */
146   style_BlockQuote,  /* sFIXE | sREVE | sITAL */
147   style_BlockQuote   /* sFIXE | sREVE | sBOLD | sITAL */
148 };
149
150 #else
151
152 static glui32 bitmap_to_style[16] = {
153   style_Normal,
154   style_Subheader,   /* sBOLD */
155   style_Emphasized,  /* sITAL */
156   style_Subheader,   /* sBOLD | sITAL */
157   style_Preformatted,/* sFIXE */
158   style_Subheader,   /* sFIXE | sBOLD */
159   style_Emphasized,  /* sFIXE | sITAL */
160   style_Subheader,   /* sFIXE | sBOLD | sITAL*/
161   style_Normal,
162   style_Subheader,   /* sBOLD */
163   style_Emphasized,  /* sITAL */
164   style_Subheader,   /* sBOLD | sITAL */
165   style_Preformatted,/* sFIXE */
166   style_Subheader,   /* sFIXE | sBOLD */
167   style_Emphasized,  /* sFIXE | sITAL */
168   style_Subheader    /* sFIXE | sBOLD | sITAL*/
169 };
170
171 #endif
172
173 typedef struct {
174   char fore;
175   char back;
176   unsigned char style;
177 } colorstyle;
178
179 typedef struct {
180   glui32 image_num;
181   glui32 x, y;
182   glui32 width, height;
183 } z_image;
184
185
186 struct z_window {
187   winid_t win;
188   strid_t str;
189   glui32 wintype;
190   glui32 method;
191
192   strid_t transcript;
193
194   BOOL glk_input_pending;
195   glui32 pending_input_type;
196   glui32 pending_input_length;
197
198   /* for upper window of v3 - returns # of lines drawn */
199   glui32 (*draw_callback)(winid_t win, glui32 width, glui32 height);
200   BOOL (*mouse_callback)(BOOL is_char_event, winid_t win, glui32 x, glui32 y);
201   
202   glui32 width, height;
203   glui32 x1, y1, x2, y2;
204
205   glui32 last_height;   /* What the height was last time we got input */
206   glui32 biggest_height;/* The biggest it's been since */
207
208   glui32 curr_offset;   /* offset into text_buffer/color_buffer */
209   glui32 max_offset;    /* curr_offset must stay < max_offset */
210   glui32 buffer_size;   /* max_offset must stay < buffer_size */
211
212   BOOL dirty;           /* Has window been changed since last redraw? */
213   BOOL defined;         /* Is our location well defined? */
214
215   unsigned char *text_buffer;  /* whole window for grid, current line for buffer */
216   colorstyle *color_buffer;
217
218   z_image images[12];
219
220   colorstyle current;
221   colorstyle actual;
222 };
223
224
225 #define num_z_windows 16
226
227 static struct z_window game_windows[num_z_windows];
228
229 static glui32 upper_width, upper_height;
230
231
232 static int waitforinput(zwinid window, glui32 *val,
233                         BOOL (*timer_callback)(zword), zword timer_arg);
234
235
236 void set_glk_stream_current(void)
237 {
238   z_flush_text(&game_windows[0]);
239   glk_stream_set_current(game_windows[0].str);
240 }
241
242 void draw_intext_picture(zwinid window, glui32 picture, glui32 alignment)
243 {
244   z_flush_text(window);
245   wrap_glk_image_draw(window->win, picture, alignment, 0);
246 }
247
248 void draw_picture(zwinid window, glui32 picture, glui32 x, glui32 y)
249 {
250   int i;
251   int useimage = -1;
252   glui32 width, height;
253
254   wrap_glk_image_get_info(operand[0], &width, &height);
255
256   for(i = 0; i < 12; i++) {
257     if(is_in_bounds(window->images[i].x, window->images[i].y,
258                     window->images[i].width, window->images[i].height,
259                     x, y, width, height))
260       useimage = i;
261   }
262   if(useimage == -1)
263     for(i = 0; i < 12; i++)
264       if(window->images[i].image_num == 0)
265         useimage = i;
266   if(useimage == -1)
267     return;
268
269   window->images[useimage].image_num = picture;
270   window->images[useimage].x = x;
271   window->images[useimage].y = y;
272   window->images[useimage].width = width;
273   window->images[useimage].height = height;
274 }
275
276
277 static int showstuffcount = 0;
278
279 /* Show an interpreter message */
280 void showstuff(const char *title, const char *type, const char *message, offset number)
281 {
282   static BOOL loopy = FALSE;
283   if(loopy) {
284     loopy = FALSE;
285     n_show_fatal(E_SYSTEM, "loopy message reporting", 0);
286   }
287   loopy = TRUE;
288
289   z_pause_timed_input(&game_windows[0]);
290   z_flush_text(&game_windows[0]);
291   glk_stream_set_current(game_windows[0].str);
292
293   glk_set_style(style_Alert);
294   w_glk_put_string("\n[");
295   w_glk_put_string(title);
296   w_glk_put_string(": ");
297   w_glk_put_string(type);
298   w_glk_put_string("]: ");
299   w_glk_put_string(message);
300   w_glk_put_string(" (");
301   g_print_snumber(number);
302   w_glk_put_string(") ");
303
304 #ifdef DEBUGGING
305   infix_gprint_loc(stack_get_depth(), 0);
306 #else
307   w_glk_put_string("PC=");
308   g_print_number(oldPC);
309   glk_put_char('\n');
310 #endif
311
312   if(++showstuffcount == 100) {
313     w_glk_put_string("[pausing every 100 errors]\n");
314     z_wait_for_key(&game_windows[0]);
315   }
316
317   glk_set_style(style_Normal);
318
319   loopy = FALSE;
320 }
321
322
323 void init_lower(zwinid *lower)
324 {
325   zwinid lower_win = &game_windows[0];
326   glui32 i;
327
328   if(lower)
329     *lower = lower_win;
330
331   if(lower_win->win) {
332     z_pause_timed_input(lower_win);
333     glk_window_close(lower_win->win, NULL);
334   }
335
336   set_stylehints(lower_win->current.fore,
337                  lower_win->current.back);
338
339
340   lower_win->dirty = TRUE;
341   lower_win->wintype = wintype_TextBuffer;
342   lower_win->method = winmethod_Below;
343   lower_win->curr_offset = 0;
344   lower_win->max_offset = 80 * 24;
345   lower_win->buffer_size = lower_win->max_offset;
346
347   if(!lower_win->text_buffer)
348     lower_win->text_buffer = (unsigned char *) n_malloc(lower_win->buffer_size);
349   if(!lower_win->color_buffer)
350     lower_win->color_buffer = (colorstyle *) n_malloc(lower_win->buffer_size * sizeof(colorstyle));
351
352   for(i = 0; i < lower_win->buffer_size; i++) {
353     lower_win->text_buffer[i] = ' ';
354     lower_win->color_buffer[i] = lower_win->current;
355   }
356
357   lower_win->actual = lower_win->current;
358
359   lower_win->win = glk_window_open(game_windows[1].win,
360                                     winmethod_Below | winmethod_Proportional,
361                                     50,  /* Percent doesn't matter */
362                                     wintype_TextBuffer, 0);
363
364
365   if(lower_win->win == 0) {
366     n_show_fatal(E_OUTPUT, "cannot open lower window", 0);
367     return;
368   }
369
370   lower_win->str = glk_window_get_stream(lower_win->win);
371
372   if(lower_win->transcript)
373     glk_window_set_echo_stream(lower_win->win, lower_win->transcript);
374 }
375
376
377 void init_upper(zwinid *upper)
378 {
379   zwinid upper_win = &game_windows[1];
380   glui32 i;
381
382   if(upper)
383     *upper = upper_win;
384
385   if(upper_win->win) {
386     z_pause_timed_input(upper_win);
387     glk_window_close(upper_win->win, NULL);
388   }
389
390   upper_win->dirty = TRUE;
391   upper_win->wintype = wintype_TextGrid;
392   upper_win->method = winmethod_Above | winmethod_Fixed;
393   upper_win->height = 0;
394   upper_win->width = upper_width;
395   upper_win->curr_offset = 0;
396   upper_win->max_offset = upper_height * upper_width;
397   upper_win->buffer_size = upper_win->max_offset;
398
399   if(!upper_win->text_buffer)
400     upper_win->text_buffer = (unsigned char *) n_malloc(upper_win->buffer_size);
401   if(!upper_win->color_buffer)
402     upper_win->color_buffer = (colorstyle *) n_malloc(upper_win->buffer_size * sizeof(colorstyle));
403
404   for(i = 0; i < upper_win->buffer_size; i++) {
405     upper_win->text_buffer[i] = ' ';
406     upper_win->color_buffer[i] = upper_win->current;
407   }
408
409   upper_win->actual = upper_win->current;
410
411   upper_win->win = glk_window_open(game_windows[0].win,
412                                     winmethod_Above | winmethod_Fixed,
413                                     1, /* XXX huh? upper_height, */
414                                     wintype_TextGrid, 1);
415
416   if(upper_win->win == 0) {
417     upper_win->str = 0;
418     return;
419   }
420
421   upper_win->str = glk_window_get_stream(upper_win->win);
422
423   if(upper_win->str == 0) {
424     glk_window_close(upper_win->win, NULL);
425     upper_win->win = 0;
426     return;
427   }
428 }
429
430
431 void z_init_windows(BOOL dofixed,
432                     glui32 (*draw_callback)(winid_t, glui32, glui32),
433                     BOOL (*mouse_callback)(BOOL, winid_t, glui32, glui32),
434                     glui32 maxwidth, glui32 maxheight,
435                     zwinid *upper, zwinid *lower)
436 {
437   colorstyle defaultstyle;
438   defaultstyle.fore = 1; defaultstyle.back = 1; defaultstyle.style = 0;
439
440   is_fixed = dofixed;
441
442   kill_windows();
443
444   upper_width = maxwidth; upper_height = maxheight;
445
446   game_windows[0].current = game_windows[1].current = defaultstyle;
447   game_windows[1].draw_callback  = draw_callback;
448   game_windows[1].mouse_callback = mouse_callback;
449
450   init_lower(lower);
451   init_upper(upper);
452 }
453
454
455 zwinid z_split_screen(glui32 wintype, glui32 method,
456                       glui32 (*draw_callback)(winid_t, glui32, glui32),
457                       BOOL (*mouse_callback)(BOOL, winid_t, glui32, glui32))
458 {
459   int i;
460   for(i = 0; i < num_z_windows; i++) {
461     if(!game_windows[i].win) {
462       winid_t root = glk_window_get_root();
463       game_windows[i].win = glk_window_open(root, method, 0, wintype, 0);
464       game_windows[i].str = glk_window_get_stream(game_windows[i].win);
465       game_windows[i].wintype = wintype;
466       game_windows[i].method = method;
467       game_windows[i].transcript = NULL;
468       game_windows[i].glk_input_pending = FALSE;
469       game_windows[i].draw_callback = draw_callback;
470       game_windows[i].mouse_callback = mouse_callback;
471       game_windows[i].width = 0;
472       game_windows[i].height = 0;
473       game_windows[i].curr_offset = 0;
474       game_windows[i].max_offset = 1;
475       game_windows[i].buffer_size = 2;
476       game_windows[i].text_buffer = n_malloc(2);
477       game_windows[i].color_buffer = n_malloc(2);
478       game_windows[i].dirty = TRUE;
479       return &game_windows[i];
480     }
481   }
482   return NULL;
483 }
484
485
486 void z_kill_window(zwinid win)
487 {
488   if(!win)
489     return;
490   n_free(win->text_buffer);
491   win->text_buffer = NULL;
492   n_free(win->color_buffer);
493   win->color_buffer = NULL;
494   win->transcript = NULL;
495   glk_window_close(win->win, NULL);
496   win->win = NULL;
497   win->str = NULL;
498 }
499
500
501 /* close any open windows */
502 void kill_windows(void)
503 {
504   int i;
505   
506   for(i = 0; i < num_z_windows; i++)
507     z_clear_window(&game_windows[i]);
508
509   free_windows();
510   for(i = 0; i < num_z_windows; i++) {
511     if(game_windows[i].win) {
512       game_windows[i].transcript = NULL;
513
514       glk_window_close(game_windows[i].win, NULL);
515       game_windows[i].win = NULL;
516       game_windows[i].str = NULL;
517     }
518   }
519   showstuffcount = 0;
520 }
521
522
523 /* free memory space used by windows, but don't close them */
524 void free_windows(void)
525 {
526   int i;
527
528   z_flush_all_windows();
529
530   for(i = 0; i < num_z_windows; i++) {
531     if(game_windows[i].win) {
532       n_free(game_windows[i].text_buffer);
533       game_windows[i].text_buffer = NULL;
534
535       n_free(game_windows[i].color_buffer);
536       game_windows[i].color_buffer = NULL;
537     }
538   }
539 }
540
541 zwinid z_find_win(winid_t win)
542 {
543   int i;
544   for(i = 0; i < num_z_windows; i++) {
545     if(game_windows[i].win == win)
546       return &game_windows[i];
547   }
548   return NULL;
549 }
550
551
552 static BOOL coloreq(colorstyle a, colorstyle b) /* return true if colors are equivalent */
553 {
554   return (fgcolortable[(int) a.fore] == fgcolortable[(int) b.fore]) &&
555          (bgcolortable[(int) a.back] == bgcolortable[(int) b.back]);
556 }
557
558
559 static void checkforblockquote(zwinid window, zwinid dest_win)
560 {
561   if(window->biggest_height > window->last_height &&
562      window->biggest_height > window->height) {
563     /* find borders of the blockquote */
564     unsigned leftx = window->width, rightx = 0;
565     unsigned topy = window->biggest_height;
566     unsigned bottomy = window->height;
567     unsigned x, y, i;
568
569     i = window->height * window->width;
570     for(y = window->height; y < window->biggest_height; y++)
571       for(x = 0; x < window->width; x++)
572         if(window->text_buffer[i++] != ' ') {
573           if(x < leftx)
574             leftx = x;
575           if(x > rightx)
576             rightx = x;
577           if(y < topy)
578             topy = y;
579           if(y > bottomy)
580             bottomy = y;
581         }
582     
583     z_pause_timed_input(dest_win);
584     glk_stream_set_current(game_windows[1].str);
585     glk_put_char(10);
586     glk_set_style(style_BlockQuote);
587     
588     /* draw the blockquote */
589     for(y = topy; y <= bottomy; y++) {
590       i = y * window->width + leftx;
591       for(x = leftx; x <= rightx; x++)
592         glk_put_char(window->text_buffer[i++]);
593       glk_put_char(10);
594     }
595   }
596 }
597
598
599 void z_pause_timed_input(zwinid window)
600 {
601   event_t eep;
602   if(window->glk_input_pending) {
603     window->glk_input_pending = FALSE;
604
605     switch(window->pending_input_type) {
606     case evtype_CharInput:
607       glk_cancel_char_event(window->win);
608       break;
609     case evtype_LineInput:
610       glk_cancel_line_event(window->win, &eep);
611       window->pending_input_length = eep.val1;
612     }
613   }
614 }
615
616
617 void z_flush_all_windows(void)
618 {
619   int window;
620   for(window = 0; window < num_z_windows; window++) {
621     if(game_windows[window].dirty) {
622       z_pause_timed_input(&game_windows[window]);
623       
624       switch(game_windows[window].wintype) {
625       case wintype_TextBuffer:
626         z_flush_text(&game_windows[window]);
627         break;
628       case wintype_TextGrid:
629         z_flush_fixed(&game_windows[window]);
630         break;
631       case wintype_Graphics:
632         z_flush_graphics(&game_windows[window]);
633         break;
634       }
635     }
636   }
637 }
638
639 void z_draw_all_windows(void)
640 {
641   int window;
642   for(window = 0; window < num_z_windows; window++) {
643     if(game_windows[window].wintype == wintype_TextGrid) {
644       game_windows[window].dirty = TRUE;
645       z_flush_fixed(&game_windows[window]);
646     }
647   }
648 }
649
650
651 static void z_put_styled_string(zwinid window, unsigned char *text,
652                                 colorstyle *color, glui32 length)
653 {
654   glui32 n;
655   colorstyle laststyle = color[0];
656
657   if(!length)
658     return;
659
660   glk_set_style_stream(window->str, bitmap_to_style[laststyle.style]);
661
662   for(n = 0; n < length; n++) {
663     if(color[n].style != laststyle.style)
664       glk_set_style_stream(window->str, bitmap_to_style[color[n].style]);
665     glk_put_char_stream(window->str, text[n]);
666     laststyle = color[n];
667   }
668 }
669
670 void z_flush_fixed(zwinid window)
671 {
672   glui32 winx, winy;
673   winid_t o;
674   glui32 start_line, end_line;
675
676   /* If there's no such window, give up */
677   if(!window->win || !window->str ||
678      !window->text_buffer || !window->color_buffer)
679     return;
680
681   /* glk doesn't allow writing to a window while input is pending */
682   z_pause_timed_input(window);
683
684   end_line = window->height;
685
686   /* Has the window grown and shrunk?  If so, probably because someone wants
687      to draw a box quote - don't let them shrink the window quite so fast */
688   if(window->biggest_height > window->last_height &&
689      window->biggest_height > window->height)
690     end_line = window->biggest_height;
691   
692   /* For v3 games, there's a callback function to draw the room name and
693      score; if this is present, we start drawing at a lower position */
694   start_line = 0;
695   if(window->draw_callback)
696     start_line = window->draw_callback(NULL, 0, 0);
697   end_line += start_line;
698
699   o = glk_window_get_parent(window->win);
700   glk_window_set_arrangement(o, window->method,
701                              end_line, window->win);
702   glk_window_get_size(window->win, &winx, &winy);
703
704   if(window->draw_callback) {
705     glk_stream_set_current(window->str);
706     glk_window_clear(window->win);
707     glk_window_move_cursor(window->win, 0, 0);
708     window->draw_callback(window->win, winx, winy);
709   }
710
711   if(end_line > start_line && window->dirty) {
712
713     unsigned padleft = 0, padmiddle = 0, padright = 0;
714     unsigned skipleft = 0, skipmiddle = 0, skipright = 0;
715     unsigned width;
716     unsigned firstwidth, lastwidth;
717
718     unsigned x, y, i;
719
720     /* Calculate how much space is used for margins */
721
722     unsigned left_margin = window->width, right_margin = window->width;
723
724     i = 0;
725     for(y = start_line; y < end_line; y++) {
726       
727       for(x = 0; x < window->width; x++)
728         if(window->text_buffer[i + x] != ' ') {
729           if(x < left_margin)
730             left_margin = x;
731           break;
732         }
733       
734       for(x = 0; x < window->width; x++)
735         if(window->text_buffer[i + window->width - x - 1] != ' ') {
736           if(x < right_margin)
737             right_margin = x;
738           break;
739         }
740       
741       i += window->width;
742     }
743     
744     firstwidth = window->width; lastwidth = 0;
745     
746     if(start_line + 1 == end_line) {
747       unsigned longestx = 0;
748       unsigned longestlen = 0;
749       unsigned thisx = 0;
750       unsigned thislen = 0;
751       colorstyle lastcolor;
752       width = window->width;
753       
754       for(x = skipleft; x < width + skipleft; x++) {
755         if(window->text_buffer[x] == ' '
756            && (!thislen || coloreq(window->color_buffer[x], lastcolor))) {
757           if(!thislen)
758             thisx = x;
759           thislen++;
760           lastcolor = window->color_buffer[x];
761         } else {
762           if(thislen > longestlen) {
763             longestx = thisx;
764             longestlen = thislen;
765           }
766           thislen = 0;
767         }
768       }
769       if(longestlen > 4) {
770         firstwidth = longestx - skipleft;
771         skipmiddle = longestlen - 1;
772         lastwidth = width - firstwidth - skipmiddle;
773       }
774     }
775     
776     if(skipmiddle && winx < firstwidth + 2 + lastwidth)
777       padmiddle = 2;
778     
779     if(lastwidth && winx >= firstwidth + padmiddle + lastwidth) {
780       padmiddle = winx - firstwidth - lastwidth;
781     } else {
782       if(winx >= window->width)
783         width = window->width;
784       else
785         width = winx;
786       
787       if(right_margin + left_margin) {
788         if(winx > window->width)
789           padleft = (unsigned) ((winx - window->width) * (((float) left_margin) / (right_margin + left_margin)));
790         else
791           skipleft = (unsigned) ((window->width - winx) * (((float) left_margin) / (right_margin + left_margin)));
792       }
793       else {
794         padleft = winx - window->width;
795       }
796       
797       if(skipleft > left_margin)
798         skipleft = left_margin;
799       
800       if(winx > window->width)
801         padright = winx - window->width - padleft;
802       else
803         skipright = window->width - winx - skipleft;
804       
805       if(width < firstwidth + padmiddle) {
806         firstwidth = width;
807         padmiddle = 0;
808         lastwidth = 0;
809       } else if(width < firstwidth + padmiddle + lastwidth) {
810         lastwidth = width - firstwidth - padmiddle;
811       }
812     }
813
814     
815     glk_stream_set_current(window->str);
816     glk_window_move_cursor(window->win, 0, start_line);
817
818     /* draw to the upper window */
819     i = 0;
820     for(y = start_line; y < end_line; y++) {
821
822       for(x = 0; x < padleft; x++)
823         glk_put_char(' ');
824
825       i += skipleft;
826
827       z_put_styled_string(window, window->text_buffer + i,
828                           window->color_buffer + i, firstwidth);
829       i += firstwidth;
830
831       for(x = 0; x < padmiddle; x++)
832         glk_put_char(' ');
833       i += skipmiddle;
834       
835       z_put_styled_string(window, window->text_buffer + i,
836                           window->color_buffer + i, lastwidth);
837       i += lastwidth;
838
839       for(x = 0; x < padright; x++)
840         glk_put_char(' ');
841
842       i += skipright;
843     }
844
845     /* Bureaucracy needs the cursor positioned and visible in upper window. */
846     glk_window_move_cursor(window->win,
847                            window->curr_offset % window->width,
848                            window->curr_offset / window->width);
849     window->dirty = FALSE;
850   }
851 }
852
853
854 void z_flush_text(zwinid window)
855 {
856   z_pause_timed_input(window);
857
858   if(!window->win || !window->str
859      || !window->text_buffer || !window->color_buffer
860      || window->curr_offset == 0) {
861     window->curr_offset = 0;
862     return;
863   }
864   
865   z_put_styled_string(window, window->text_buffer, window->color_buffer,
866                       window->curr_offset);
867
868   window->curr_offset = 0;
869   window->dirty = FALSE;
870 }
871
872
873 void z_flush_graphics(zwinid window)
874 {
875   int i;
876   glui32 winx, winy;
877   float xratio, yratio;
878   winid_t parent;
879
880   if(!window->win)
881     return;
882
883   glk_window_get_size(window->win, &winx, &winy);
884   xratio = ((float) winx) / window->width;
885   yratio = ((float) winy) / window->height;
886
887   parent = glk_window_get_parent(window->win);
888
889   /* We want the window to maintain its original height/width ratio */
890   switch(window->method & winmethod_DirMask) {
891   case winmethod_Left:   /* Left and right splits mean height is fixed - */
892   case winmethod_Right:  /* adjust width to the yratio */
893     glk_window_set_arrangement(parent, window->method,
894                                (glui32) (window->width * yratio), 0);
895     break;
896   case winmethod_Above:  /* Above and below splits mean width is fixed - */
897   case winmethod_Below:  /* adjust height to the xratio */
898     glk_window_set_arrangement(parent, window->method,
899                                (glui32) (window->height * xratio), 0);
900     break;
901   }
902
903   /* Check to see what it became, and if it's still off, don't worry */
904   glk_window_get_size(window->win, &winx, &winy);
905   xratio = ((float) winx) / window->width;
906   yratio = ((float) winy) / window->height;
907
908   for(i = 0; i < 12; i++) {
909     if(window->images[i].image_num) {
910       wrap_glk_image_draw_scaled(window->win, window->images[i].image_num,
911                                  (glui32) (window->images[i].x * xratio),
912                                  (glui32) (window->images[i].y * yratio),
913                                  (glui32) (window->images[i].width * xratio),
914                                  (glui32) (window->images[i].height * yratio));
915     }
916   }
917 }
918
919 void z_print_number(zwinid window, int number)
920 {
921   int i;
922   char buffer[12];
923   int length = n_to_decimal(buffer, number);
924
925   for(i = length - 1; i >= 0; i--)
926     z_put_char(window, buffer[i]);
927 }
928
929 void z_put_char(zwinid window, unsigned c)
930 {
931   colorstyle color = window->current;
932   if(is_fixed)
933     color.style |= sFIXE;
934
935   if(c == 0)     /* Section 3.8.2.1 */
936     return;
937
938   window->dirty = TRUE;
939
940   if((c < 32 && c != 13) || (c >= 127 && c <= 159)) {  /*Undefined in latin-1*/
941     switch(window->wintype) {
942     case wintype_TextBuffer:
943       z_put_char(window, '[');
944       z_print_number(window, c);
945       z_put_char(window, ']');
946       return;
947     case wintype_TextGrid:
948       c = '?';
949     }
950   }
951
952   switch(c) {
953   case 0x152:
954     z_put_char(window, 'O');
955     c = 'E';
956     break;
957   case 0x153:
958     z_put_char(window, 'o');
959     c = 'e';
960     break;
961   }
962
963
964   if(c > 255)    /* Section 3.8.5.4.3 */
965     c = '?';
966
967   if(c == 13) {  /* Section 7.1.2.2.1 */
968     switch(window->wintype) {
969     case wintype_TextBuffer:
970       window->text_buffer[window->curr_offset] = 10;
971       window->curr_offset++;
972       z_flush_text(window);
973       break;
974     case wintype_TextGrid:
975       window->curr_offset += window->width;
976       window->curr_offset -= window->curr_offset % window->width;
977     }
978   } else {
979     window->text_buffer[window->curr_offset] = c;
980     window->color_buffer[window->curr_offset] = color;
981     window->curr_offset++;
982   }
983
984   if(window->curr_offset >= window->max_offset) {
985     switch(window->wintype) {
986     case wintype_TextBuffer:
987       z_flush_text(window);
988       break;
989     case wintype_TextGrid:
990       if(!window->defined)                  /* Section 8.6.2 */
991         n_show_port(E_OUTPUT, "writing past end of window", c);
992       
993       if(window->max_offset)
994         window->curr_offset = window->max_offset - 1;
995       else
996         window->curr_offset = 0;
997
998       window->defined = FALSE;
999     }
1000   }
1001 }
1002
1003 void z_setxy(zwinid window, zword x, zword y)
1004 {
1005   window->curr_offset = (y - 1) * window->width + (x - 1);
1006   window->defined = TRUE;
1007 }
1008
1009 void z_getxy(zwinid window, zword *x, zword *y)
1010 {
1011   if(window->width) {
1012     *x = (window->curr_offset % window->width) + 1;
1013     *y = (window->curr_offset / window->width) + 1;
1014   } else {
1015     *x = window->curr_offset + 1;
1016     *y = 1;
1017   }
1018 }
1019
1020 void z_getsize(zwinid window, unsigned *width, unsigned *height)
1021 {
1022   *width = window->width;
1023   *height = window->height;
1024 }
1025
1026 void z_find_size(glui32 *wid, glui32 *hei)
1027 {
1028   glui32 oldwid, oldhei;
1029   zwinid upper = &game_windows[1];
1030   winid_t o = glk_window_get_parent(upper->win);
1031   glk_window_get_size(upper->win, &oldwid, &oldhei);
1032   glk_window_set_arrangement(o, (upper->method & ~winmethod_Fixed) |
1033                              winmethod_Proportional, 100, upper->win);
1034   glk_window_get_size(upper->win, wid, hei);
1035   glk_window_set_arrangement(o, upper->method, oldhei, upper->win);
1036
1037   upper_width = *wid; upper_height = *hei;
1038   init_upper(&upper);
1039 }
1040
1041 void z_set_height(zwinid window, unsigned height)
1042 {
1043   unsigned x;
1044   if(height * window->width > window->buffer_size) {
1045     n_show_error(E_OUTPUT, "height too large", height);
1046     return;
1047   }
1048
1049   window->height = height;
1050   if(height > window->biggest_height)
1051     window->biggest_height = height;
1052
1053   x = window->max_offset;
1054   window->max_offset = height * window->width;
1055
1056   for(; x < window->max_offset; x++) {
1057     window->text_buffer[x] = ' ';
1058     window->color_buffer[x] = window->current;
1059   }
1060
1061   window->dirty = TRUE;
1062 }
1063
1064 void z_set_color(zwinid window, unsigned fore, unsigned back)
1065 {
1066   if(fore >= sizeof(fgcolortable) / sizeof(*fgcolortable)) {
1067     n_show_error(E_OUTPUT, "illegal foreground color", fore);
1068     return;
1069   }
1070   if(back >= sizeof(bgcolortable) / sizeof(*bgcolortable)) {
1071     n_show_error(E_OUTPUT, "illegal background color", back);
1072     return;
1073   }
1074
1075   fgcolortable[0] = fgcolortable[fore];
1076   bgcolortable[0] = bgcolortable[back];
1077   
1078   window->current.fore = fore;
1079   window->current.back = back;
1080 }
1081
1082 void z_set_style(zwinid window, int style)
1083 {
1084   switch(style) {
1085   case 0: window->current.style = 0; break;
1086   case 1: window->current.style |= sREVE; break;
1087   case 2: window->current.style |= sBOLD; break;
1088   case 4: window->current.style |= sITAL; break;
1089   case 8: window->current.style |= sFIXE; break;
1090   default: n_show_error(E_OUTPUT, "undefined style", style);
1091   }
1092 }
1093
1094 void set_fixed(BOOL p)
1095 {
1096   if(!p) {
1097     is_fixed = FALSE;
1098   } else {
1099     is_fixed = TRUE;
1100   }
1101 }
1102
1103 void z_set_transcript(zwinid window, strid_t stream)
1104 {
1105   window->transcript = stream;
1106   glk_window_set_echo_stream(window->win, stream);
1107 }
1108
1109 void z_clear_window(zwinid window)
1110 {
1111   glui32 i;
1112
1113   if(window == &game_windows[0] && showstuffcount) {
1114     z_pause_timed_input(&game_windows[0]);
1115     z_flush_text(&game_windows[0]);
1116     glk_stream_set_current(game_windows[0].str);
1117     w_glk_put_string("[pausing to show unread error message]\n");
1118     z_wait_for_key(&game_windows[0]);
1119   }
1120
1121   window->dirty = TRUE;
1122   window->curr_offset = 0;
1123
1124   if(window->win && window->text_buffer && window->color_buffer) {
1125     switch(window->wintype) {
1126     case wintype_TextGrid:
1127       for(i = 0; i < window->max_offset; i++) {
1128         window->text_buffer[i] = ' ';
1129         window->color_buffer[i] = window->current;
1130       }
1131       window->curr_offset = 0;
1132       window->dirty = TRUE;
1133       break;
1134     case wintype_TextBuffer:
1135       z_pause_timed_input(window);
1136       z_flush_text(window);
1137       if(coloreq(window->actual, window->current)) {
1138         glk_window_clear(window->win);
1139       } else {
1140         init_lower(NULL); /* **FIXME** This is wrong, but deal with it later */
1141       }
1142     }
1143   }
1144 }
1145
1146 void z_erase_line(zwinid window)
1147 {
1148   if(window->wintype == wintype_TextGrid) {
1149     int i;
1150     int x = window->curr_offset % window->width;
1151     int endoffset = window->curr_offset + (window->width - x);
1152     
1153     window->dirty = TRUE;
1154     for(i = window->curr_offset; i < endoffset; i++) {
1155       window->text_buffer[i] = ' ';
1156       window->color_buffer[i] = window->current;
1157     }
1158   }
1159 }
1160
1161
1162 /* Waits for input or timeout
1163  * Returns:
1164  *   0 - output during wait; may need to redraw or somesuch
1165  *  -1 - callback routine said to stop
1166  *  10 - read input
1167  * 254 - mouse input
1168  * char and line events will be canceled by the time it exits
1169  */
1170 static int waitforinput(zwinid window, glui32 *val,
1171                         BOOL (*timer_callback)(zword), zword timer_arg)
1172 {
1173   int i;
1174   event_t moo;
1175   zwinid t;
1176   
1177   showstuffcount = 0;
1178
1179   for(i = 0; i < num_z_windows; i++)
1180     if(game_windows[i].mouse_callback && game_windows[i].win)
1181       glk_request_mouse_event(game_windows[i].win);
1182   
1183   window->glk_input_pending = TRUE;
1184
1185   while(window->glk_input_pending) {
1186     glk_select(&moo);
1187
1188     check_sound(moo);
1189
1190     switch(moo.type) {
1191     case evtype_Timer:
1192       if(timer_callback && timer_callback(timer_arg)) {
1193         if(window->pending_input_type == evtype_CharInput) {
1194           glk_cancel_char_event(window->win);
1195           *val = 0;
1196         } else {
1197           glk_cancel_line_event(window->win, &moo);
1198           *val = moo.val1;
1199         }
1200         window->glk_input_pending = FALSE;
1201         return -1;
1202       }
1203       break;
1204
1205     case evtype_CharInput:
1206       *val = moo.val1;
1207       window->glk_input_pending = FALSE;
1208       return 10;
1209
1210     case evtype_LineInput:
1211       *val = moo.val1;
1212       window->glk_input_pending = FALSE;
1213       return 10;
1214
1215     case evtype_MouseInput:
1216       t = z_find_win(moo.win);
1217       if(t && t->mouse_callback &&
1218          t->mouse_callback(window->pending_input_type == evtype_CharInput,
1219                            moo.win, moo.val1, moo.val2)) {
1220         if(window->pending_input_type == evtype_CharInput) {
1221           glk_cancel_char_event(window->win);
1222           *val = 254;
1223         } else {
1224           glk_cancel_line_event(window->win, &moo);
1225           *val = moo.val1;
1226         }
1227         window->glk_input_pending = FALSE;
1228         return 254;
1229       }
1230       glk_request_mouse_event(moo.win);
1231       break;
1232     
1233     case evtype_Arrange:
1234       z_draw_all_windows();
1235     }
1236
1237     z_flush_all_windows();
1238   }
1239
1240   if(window->pending_input_type == evtype_LineInput)
1241     *val = window->pending_input_length;
1242   else
1243     *val = 0;
1244
1245   return 0;
1246 }
1247
1248
1249 void z_wait_for_key(zwinid window)
1250 {
1251   glui32 ch;
1252   do {
1253     z_draw_all_windows();
1254     glk_request_char_event(window->win);
1255     window->pending_input_type = evtype_CharInput;
1256   } while(waitforinput(window, &ch, NULL, 0) == 0);
1257   window->pending_input_type = 0;
1258 }
1259
1260
1261 zwinid check_valid_for_input(zwinid window)
1262 {  
1263   glui32 y, i;
1264   if(!window->win) {
1265     zwinid newwin = NULL;
1266     for(i = 0; i < num_z_windows; i++) {
1267       if(game_windows[i].win) {
1268         newwin = &game_windows[i];
1269         break;
1270       }
1271     }
1272     if(!newwin)
1273       return NULL;
1274
1275     if(window->wintype == wintype_TextGrid) {
1276       i = 0;
1277       for(y = 0; y < window->height; y++) {
1278         z_put_char(newwin, 13);
1279         z_put_styled_string(newwin, window->text_buffer + i,
1280                             window->color_buffer + i, window->width);
1281         i += window->width;
1282       }
1283       z_put_char(newwin, 13);
1284     }
1285
1286     window = newwin;
1287   }
1288   return window;
1289 }
1290
1291
1292 /* returns number of characters read */
1293 int z_read(zwinid window, char *dest, unsigned maxlen, unsigned initlen,
1294            zword timer, BOOL (*timer_callback)(zword), zword timer_arg,
1295            unsigned char *terminator)
1296 {
1297   /* FIXME: support terminating characters when (if) glk gets support for
1298      them */
1299   unsigned i;
1300   unsigned ux, uy;
1301   glui32 length;
1302   BOOL done;
1303
1304   if(automap_unexplore()) {
1305     read_abort = TRUE;
1306     return 0;
1307   }
1308   
1309   read_abort = FALSE;
1310
1311   if(initlen > maxlen) {
1312     n_show_error(E_OUTPUT, "initlen > maxlen", initlen);
1313     return 0;
1314   }
1315
1316   if(window == 0)
1317     window = &game_windows[0];
1318   
1319   if(window->pending_input_type != 0) {
1320     n_show_error(E_OUTPUT, "nested input attempted", 0);
1321     return 0;
1322   }
1323
1324 #ifdef DEBUGGING
1325
1326   if(do_automap) {
1327     const char *dir = automap_explore();
1328     if(dir) {
1329       length = n_strlen(dir);
1330       if(length > maxlen)
1331         length = maxlen;
1332       n_strncpy(dest, dir, length);
1333       return length;
1334     }
1335   }
1336 #endif
1337
1338   glk_request_timer_events(timer * 100);  /* if time is zero, does nothing */
1339     
1340   if(initlen != 0 && window->wintype == wintype_TextBuffer) {
1341     BOOL good = FALSE;
1342     if(initlen <= window->curr_offset) {
1343       good = TRUE;
1344       for(i = 0; i < initlen; i++)  /* check the end of the linebuffer */
1345         if(window->text_buffer[window->curr_offset - initlen + i] != dest[i]) {
1346           good = FALSE;
1347           break;
1348         }
1349     }
1350     if(!good) {
1351       /* bleah */
1352       /* argh */
1353       /* oof */
1354     } else {
1355       window->curr_offset -= initlen; /* Remove initial text from linebuffer */
1356     }
1357   }
1358   
1359   if(window->wintype == wintype_TextGrid) {
1360     ux = window->curr_offset % window->width;
1361     uy = window->curr_offset / window->width;
1362   }
1363
1364   z_flush_all_windows();
1365   window = check_valid_for_input(window);
1366
1367   done = FALSE;
1368   length = initlen;
1369   while(!done) {
1370     int t;
1371
1372     if(window->wintype == wintype_TextGrid)
1373       glk_window_move_cursor(window->win, ux, uy);
1374
1375     if(input_stream1) {
1376       glui32 len = maxlen;
1377       *terminator = transcript_getline(dest, &len);
1378       length = len;
1379     }
1380     if(input_stream1) { /* If didn't EOF, input_stream1 will be non-zero */
1381       glk_stream_set_current(window->str);
1382       set_glk_stream_current();
1383       glk_set_style(style_Input);
1384       glk_put_buffer(dest, length);
1385       glk_put_char(10);
1386       done = TRUE;
1387     } else {
1388       glk_request_line_event(window->win, dest, maxlen, length);
1389       window->pending_input_type = evtype_LineInput;
1390     
1391       t = waitforinput(window, &length, timer_callback, timer_arg);
1392       if(t != 0) {
1393         if(t == -1)
1394           *terminator = 0;
1395         else
1396           *terminator = t;
1397         done = TRUE;
1398       }
1399     }
1400
1401     if(done)
1402       stream4line(dest, length, *terminator);
1403
1404 #ifdef DEBUGGING
1405     if(done && length >= 2 && dest[0] == '/') {
1406       if(dest[1] == '/') {  /* "//" means no command, but start with "/" */
1407         for(i = 1; i < length; i++)
1408           dest[i-1] = dest[i];
1409         length--;
1410       } else {
1411         done = FALSE;
1412         dest[length] = 0;
1413         
1414         process_debug_command(dest+1);
1415
1416         if(read_abort)
1417           done = TRUE;
1418
1419         length = 0;
1420       }
1421     }
1422 #endif
1423   }
1424   glk_request_timer_events(0);  /* stop timer */
1425
1426   window->pending_input_type = 0;
1427
1428   for(i = 0; i < num_z_windows; i++)
1429     game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height;
1430
1431   return length;
1432 }
1433
1434 zword z_read_char(zwinid window,
1435                   zword timer, BOOL (*timer_callback)(zword), zword timer_arg)
1436 {
1437   unsigned i;
1438   glui32 ch;
1439   zword validch = 0;
1440
1441   if(automap_unexplore()) {
1442     read_abort = TRUE;
1443     return 0;
1444   }
1445
1446   if(input_stream1) {
1447     unsigned num;
1448     validch = transcript_getchar(&num);
1449     if(!validch)
1450       validch = num;
1451   }
1452   if(input_stream1) {
1453     return validch;
1454   }
1455   
1456   read_abort = FALSE;
1457
1458   glk_request_timer_events(timer * 100);
1459
1460   z_flush_all_windows();
1461   window = check_valid_for_input(window);
1462
1463   do {
1464     do {
1465       z_draw_all_windows();
1466       glk_request_char_event(window->win);
1467       window->pending_input_type = evtype_CharInput;
1468     } while(waitforinput(window, &ch, timer_callback, timer_arg) == 0);
1469     
1470     if(' ' <= ch && ch <= '~')
1471       validch = ch;
1472     
1473     switch(ch) {
1474     case 8:
1475     case keycode_Delete: validch = 8; break;
1476     case 9:
1477     case keycode_Tab:    validch = 9; break;
1478     case 13:
1479     case keycode_Return: validch = 13; break;
1480 /*  case 21:
1481       if(restoreundo()) {
1482         read_abort = TRUE;
1483         return 0;
1484       }
1485       break; */
1486     case 27:
1487     case keycode_Escape: validch = 27; break;
1488     case 16:
1489     case keycode_Up:     validch = 129; break;
1490     case 14:
1491     case keycode_Down:   validch = 130; break;
1492     case 2:
1493     case keycode_Left:   validch = 131; break;
1494     case 6:
1495     case keycode_Right:  validch = 132; break;
1496     case keycode_Func1:  validch = 133; break;
1497     case keycode_Func2:  validch = 134; break;
1498     case keycode_Func3:  validch = 135; break;
1499     case keycode_Func4:  validch = 136; break;
1500     case keycode_Func5:  validch = 137; break;
1501     case keycode_Func6:  validch = 138; break;
1502     case keycode_Func7:  validch = 139; break;
1503     case keycode_Func8:  validch = 140; break;
1504     case keycode_Func9:  validch = 141; break;
1505     case keycode_Func10: validch = 142; break;
1506     case keycode_Func11: validch = 143; break;
1507     case keycode_Func12: validch = 144; break;
1508     }
1509   } while(!(validch || ch == 0));
1510
1511   glk_request_timer_events(0);     /* stop timer */
1512
1513   window->pending_input_type = 0;
1514
1515   for(i = 0; i < num_z_windows; i++)
1516     game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height;
1517
1518   return validch;
1519 }
1520
1521
1522 /*
1523 void zwin_init(int number, glui32 wintype,
1524                glui32 x_coord, glui32 y_coord, glui32 x_size, glui32 y_size)
1525 {
1526   zwinid self = game_windows + number;
1527
1528   if(x_coord == self->x1) {
1529     if(y_coord == self->y1) {
1530       
1531
1532   if(game_windows[number].win) {
1533     z_pause_timed_input(game_windows[number].win);
1534     glk_window_close(game_windows[number].win, NULL);
1535   }
1536   set_style_hints();
1537   game_windows[number].win = glk_window_open(
1538 }
1539 */