Update interpreters to latest Garglk codebase
[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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18     The author can be reached at nitfol@deja.com
19 */
20 #include "nitfol.h"
21 #include "nio.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 #if 0
701   glk_window_get_size(window->win, &winx, &winy);
702   if (!(window->method & winmethod_Above || window->method & winmethod_Below)
703       || winy != end_line)
704     glk_window_set_arrangement(o, window->method,
705                                end_line, window->win);
706 #else
707   glk_window_set_arrangement(o, window->method, end_line, window->win);
708 #endif
709   glk_window_get_size(window->win, &winx, &winy);
710
711   if(window->draw_callback) {
712     glk_stream_set_current(window->str);
713     glk_window_clear(window->win);
714     glk_window_move_cursor(window->win, 0, 0);
715     window->draw_callback(window->win, winx, winy);
716   }
717
718   if(end_line > start_line && window->dirty) {
719
720     unsigned padleft = 0, padmiddle = 0, padright = 0;
721     unsigned skipleft = 0, skipmiddle = 0, skipright = 0;
722     unsigned width;
723     unsigned firstwidth, lastwidth;
724
725     unsigned x, y, i;
726
727     /* Calculate how much space is used for margins */
728
729     unsigned left_margin = window->width, right_margin = window->width;
730
731     i = 0;
732     for(y = start_line; y < end_line; y++) {
733       
734       for(x = 0; x < window->width; x++)
735         if(window->text_buffer[i + x] != ' ') {
736           if(x < left_margin)
737             left_margin = x;
738           break;
739         }
740       
741       for(x = 0; x < window->width; x++)
742         if(window->text_buffer[i + window->width - x - 1] != ' ') {
743           if(x < right_margin)
744             right_margin = x;
745           break;
746         }
747       
748       i += window->width;
749     }
750     
751     firstwidth = window->width; lastwidth = 0;
752     
753     if(start_line + 1 == end_line) {
754       unsigned longestx = 0;
755       unsigned longestlen = 0;
756       unsigned thisx = 0;
757       unsigned thislen = 0;
758       colorstyle lastcolor;
759       width = window->width;
760       
761       for(x = skipleft; x < width + skipleft; x++) {
762         if(window->text_buffer[x] == ' '
763            && (!thislen || coloreq(window->color_buffer[x], lastcolor))) {
764           if(!thislen)
765             thisx = x;
766           thislen++;
767           lastcolor = window->color_buffer[x];
768         } else {
769           if(thislen > longestlen) {
770             longestx = thisx;
771             longestlen = thislen;
772           }
773           thislen = 0;
774         }
775       }
776       if(longestlen > 4) {
777         firstwidth = longestx - skipleft;
778         skipmiddle = longestlen - 1;
779         lastwidth = width - firstwidth - skipmiddle;
780       }
781     }
782     
783     if(skipmiddle && winx < firstwidth + 2 + lastwidth)
784       padmiddle = 2;
785     
786     if(lastwidth && winx >= firstwidth + padmiddle + lastwidth) {
787       padmiddle = winx - firstwidth - lastwidth;
788     } else {
789       if(winx >= window->width)
790         width = window->width;
791       else
792         width = winx;
793       
794       if(right_margin + left_margin) {
795         if(winx > window->width)
796           padleft = (unsigned) ((winx - window->width) * (((float) left_margin) / (right_margin + left_margin)));
797         else
798           skipleft = (unsigned) ((window->width - winx) * (((float) left_margin) / (right_margin + left_margin)));
799       }
800       else {
801         padleft = winx - window->width;
802       }
803       
804       if(skipleft > left_margin)
805         skipleft = left_margin;
806       
807       if(winx > window->width)
808         padright = winx - window->width - padleft;
809       else
810         skipright = window->width - winx - skipleft;
811       
812       if(width < firstwidth + padmiddle) {
813         firstwidth = width;
814         padmiddle = 0;
815         lastwidth = 0;
816       } else if(width < firstwidth + padmiddle + lastwidth) {
817         lastwidth = width - firstwidth - padmiddle;
818       }
819     }
820
821     
822     glk_stream_set_current(window->str);
823     glk_window_move_cursor(window->win, 0, start_line);
824
825     /* draw to the upper window */
826     i = 0;
827     for(y = start_line; y < end_line; y++) {
828
829       for(x = 0; x < padleft; x++)
830         glk_put_char(' ');
831
832       i += skipleft;
833
834       z_put_styled_string(window, window->text_buffer + i,
835                           window->color_buffer + i, firstwidth);
836       i += firstwidth;
837
838       for(x = 0; x < padmiddle; x++)
839         glk_put_char(' ');
840       i += skipmiddle;
841       
842       z_put_styled_string(window, window->text_buffer + i,
843                           window->color_buffer + i, lastwidth);
844       i += lastwidth;
845
846       for(x = 0; x < padright; x++)
847         glk_put_char(' ');
848
849       i += skipright;
850     }
851
852     /* Bureaucracy needs the cursor positioned and visible in upper window. */
853     glk_window_move_cursor(window->win,
854                            window->curr_offset % window->width,
855                            window->curr_offset / window->width);
856     window->dirty = FALSE;
857   }
858 }
859
860
861 void z_flush_text(zwinid window)
862 {
863   z_pause_timed_input(window);
864
865   if(!window->win || !window->str
866      || !window->text_buffer || !window->color_buffer
867      || window->curr_offset == 0) {
868     window->curr_offset = 0;
869     return;
870   }
871   
872   z_put_styled_string(window, window->text_buffer, window->color_buffer,
873                       window->curr_offset);
874
875   window->curr_offset = 0;
876   window->dirty = FALSE;
877 }
878
879
880 void z_flush_graphics(zwinid window)
881 {
882   int i;
883   glui32 winx, winy;
884   float xratio, yratio;
885   winid_t parent;
886
887   if(!window->win)
888     return;
889
890   glk_window_get_size(window->win, &winx, &winy);
891   xratio = ((float) winx) / window->width;
892   yratio = ((float) winy) / window->height;
893
894   parent = glk_window_get_parent(window->win);
895
896   /* We want the window to maintain its original height/width ratio */
897   switch(window->method & winmethod_DirMask) {
898   case winmethod_Left:   /* Left and right splits mean height is fixed - */
899   case winmethod_Right:  /* adjust width to the yratio */
900     glk_window_set_arrangement(parent, window->method,
901                                (glui32) (window->width * yratio), 0);
902     break;
903   case winmethod_Above:  /* Above and below splits mean width is fixed - */
904   case winmethod_Below:  /* adjust height to the xratio */
905     glk_window_set_arrangement(parent, window->method,
906                                (glui32) (window->height * xratio), 0);
907     break;
908   }
909
910   /* Check to see what it became, and if it's still off, don't worry */
911   glk_window_get_size(window->win, &winx, &winy);
912   xratio = ((float) winx) / window->width;
913   yratio = ((float) winy) / window->height;
914
915   for(i = 0; i < 12; i++) {
916     if(window->images[i].image_num) {
917       wrap_glk_image_draw_scaled(window->win, window->images[i].image_num,
918                                  (glui32) (window->images[i].x * xratio),
919                                  (glui32) (window->images[i].y * yratio),
920                                  (glui32) (window->images[i].width * xratio),
921                                  (glui32) (window->images[i].height * yratio));
922     }
923   }
924 }
925
926 void z_print_number(zwinid window, int number)
927 {
928   int i;
929   char buffer[12];
930   int length = n_to_decimal(buffer, number);
931
932   for(i = length - 1; i >= 0; i--)
933     z_put_char(window, buffer[i]);
934 }
935
936 void z_put_char(zwinid window, unsigned c)
937 {
938   colorstyle color = window->current;
939   if(is_fixed)
940     color.style |= sFIXE;
941
942   if(c == 0)     /* Section 3.8.2.1 */
943     return;
944
945   window->dirty = TRUE;
946
947   if((c < 32 && c != 13) || (c >= 127 && c <= 159)) {  /*Undefined in latin-1*/
948     switch(window->wintype) {
949     case wintype_TextBuffer:
950       z_put_char(window, '[');
951       z_print_number(window, c);
952       z_put_char(window, ']');
953       return;
954     case wintype_TextGrid:
955       c = '?';
956     }
957   }
958
959   switch(c) {
960   case 0x152:
961     z_put_char(window, 'O');
962     c = 'E';
963     break;
964   case 0x153:
965     z_put_char(window, 'o');
966     c = 'e';
967     break;
968   }
969
970
971   if(c > 255)    /* Section 3.8.5.4.3 */
972     c = '?';
973
974   if(c == 13) {  /* Section 7.1.2.2.1 */
975     switch(window->wintype) {
976     case wintype_TextBuffer:
977       window->text_buffer[window->curr_offset] = 10;
978       window->curr_offset++;
979       z_flush_text(window);
980       break;
981     case wintype_TextGrid:
982       window->curr_offset += window->width;
983       window->curr_offset -= window->curr_offset % window->width;
984     }
985   } else {
986     window->text_buffer[window->curr_offset] = c;
987     window->color_buffer[window->curr_offset] = color;
988     window->curr_offset++;
989   }
990
991   if(window->curr_offset >= window->max_offset) {
992     switch(window->wintype) {
993     case wintype_TextBuffer:
994       z_flush_text(window);
995       break;
996     case wintype_TextGrid:
997       if(!window->defined)                  /* Section 8.6.2 */
998         n_show_port(E_OUTPUT, "writing past end of window", c);
999       
1000       if(window->max_offset)
1001         window->curr_offset = window->max_offset - 1;
1002       else
1003         window->curr_offset = 0;
1004
1005       window->defined = FALSE;
1006     }
1007   }
1008 }
1009
1010 void z_setxy(zwinid window, zword x, zword y)
1011 {
1012   window->curr_offset = (y - 1) * window->width + (x - 1);
1013   window->defined = TRUE;
1014 }
1015
1016 void z_getxy(zwinid window, zword *x, zword *y)
1017 {
1018   if(window->width) {
1019     *x = (window->curr_offset % window->width) + 1;
1020     *y = (window->curr_offset / window->width) + 1;
1021   } else {
1022     *x = window->curr_offset + 1;
1023     *y = 1;
1024   }
1025 }
1026
1027 void z_getsize(zwinid window, unsigned *width, unsigned *height)
1028 {
1029   *width = window->width;
1030   *height = window->height;
1031 }
1032
1033 void z_find_size(glui32 *wid, glui32 *hei)
1034 {
1035   glui32 oldwid, oldhei;
1036   zwinid upper = &game_windows[1];
1037   winid_t o = glk_window_get_parent(upper->win);
1038   glk_window_get_size(upper->win, &oldwid, &oldhei);
1039   glk_window_set_arrangement(o, (upper->method & ~winmethod_Fixed) |
1040                              winmethod_Proportional, 100, upper->win);
1041   glk_window_get_size(upper->win, wid, hei);
1042   glk_window_set_arrangement(o, upper->method, oldhei, upper->win);
1043
1044   upper_width = *wid; upper_height = *hei;
1045   init_upper(&upper);
1046 }
1047
1048 void z_set_height(zwinid window, unsigned height)
1049 {
1050   unsigned x;
1051   if(height * window->width > window->buffer_size) {
1052     n_show_error(E_OUTPUT, "height too large", height);
1053     return;
1054   }
1055
1056   window->height = height;
1057   if(height > window->biggest_height)
1058     window->biggest_height = height;
1059
1060   x = window->max_offset;
1061   window->max_offset = height * window->width;
1062
1063   for(; x < window->max_offset; x++) {
1064     window->text_buffer[x] = ' ';
1065     window->color_buffer[x] = window->current;
1066   }
1067
1068   window->dirty = TRUE;
1069 }
1070
1071 void z_set_color(zwinid window, unsigned fore, unsigned back)
1072 {
1073   if(fore >= sizeof(fgcolortable) / sizeof(*fgcolortable)) {
1074     n_show_error(E_OUTPUT, "illegal foreground color", fore);
1075     return;
1076   }
1077   if(back >= sizeof(bgcolortable) / sizeof(*bgcolortable)) {
1078     n_show_error(E_OUTPUT, "illegal background color", back);
1079     return;
1080   }
1081
1082   fgcolortable[0] = fgcolortable[fore];
1083   bgcolortable[0] = bgcolortable[back];
1084   
1085   window->current.fore = fore;
1086   window->current.back = back;
1087 }
1088
1089 void z_set_style(zwinid window, int style)
1090 {
1091   switch(style) {
1092   case 0: window->current.style = 0; break;
1093   case 1: window->current.style |= sREVE; break;
1094   case 2: window->current.style |= sBOLD; break;
1095   case 4: window->current.style |= sITAL; break;
1096   case 8: window->current.style |= sFIXE; break;
1097   default: n_show_error(E_OUTPUT, "undefined style", style);
1098   }
1099 }
1100
1101 void set_fixed(BOOL p)
1102 {
1103   if(!p) {
1104     is_fixed = FALSE;
1105   } else {
1106     is_fixed = TRUE;
1107   }
1108 }
1109
1110 void z_set_transcript(zwinid window, strid_t stream)
1111 {
1112   window->transcript = stream;
1113   glk_window_set_echo_stream(window->win, stream);
1114 }
1115
1116 void z_clear_window(zwinid window)
1117 {
1118   glui32 i;
1119
1120   if(window == &game_windows[0] && showstuffcount) {
1121     z_pause_timed_input(&game_windows[0]);
1122     z_flush_text(&game_windows[0]);
1123     glk_stream_set_current(game_windows[0].str);
1124     w_glk_put_string("[pausing to show unread error message]\n");
1125     z_wait_for_key(&game_windows[0]);
1126   }
1127
1128   window->dirty = TRUE;
1129   window->curr_offset = 0;
1130
1131   if(window->win && window->text_buffer && window->color_buffer) {
1132     switch(window->wintype) {
1133     case wintype_TextGrid:
1134       for(i = 0; i < window->max_offset; i++) {
1135         window->text_buffer[i] = ' ';
1136         window->color_buffer[i] = window->current;
1137       }
1138       window->curr_offset = 0;
1139       window->dirty = TRUE;
1140       break;
1141     case wintype_TextBuffer:
1142       z_pause_timed_input(window);
1143       z_flush_text(window);
1144       if(coloreq(window->actual, window->current)) {
1145         glk_window_clear(window->win);
1146       } else {
1147         init_lower(NULL); /* **FIXME** This is wrong, but deal with it later */
1148       }
1149     }
1150   }
1151 }
1152
1153 void z_erase_line(zwinid window)
1154 {
1155   if(window->wintype == wintype_TextGrid) {
1156     int i;
1157     int x = window->curr_offset % window->width;
1158     int endoffset = window->curr_offset + (window->width - x);
1159     
1160     window->dirty = TRUE;
1161     for(i = window->curr_offset; i < endoffset; i++) {
1162       window->text_buffer[i] = ' ';
1163       window->color_buffer[i] = window->current;
1164     }
1165   }
1166 }
1167
1168
1169 /* Waits for input or timeout
1170  * Returns:
1171  *   0 - output during wait; may need to redraw or somesuch
1172  *  -1 - callback routine said to stop
1173  *  10 - read input
1174  * 254 - mouse input
1175  * char and line events will be canceled by the time it exits
1176  */
1177 static int waitforinput(zwinid window, glui32 *val,
1178                         BOOL (*timer_callback)(zword), zword timer_arg)
1179 {
1180   int i;
1181   event_t moo;
1182   zwinid t;
1183   
1184   showstuffcount = 0;
1185
1186   for(i = 0; i < num_z_windows; i++)
1187     if(game_windows[i].mouse_callback && game_windows[i].win)
1188       glk_request_mouse_event(game_windows[i].win);
1189   
1190   window->glk_input_pending = TRUE;
1191
1192   while(window->glk_input_pending) {
1193     glk_select(&moo);
1194
1195     check_sound(moo);
1196
1197     switch(moo.type) {
1198     case evtype_Timer:
1199       if(timer_callback && timer_callback(timer_arg)) {
1200         if(window->pending_input_type == evtype_CharInput) {
1201           glk_cancel_char_event(window->win);
1202           *val = 0;
1203         } else {
1204           glk_cancel_line_event(window->win, &moo);
1205           *val = moo.val1;
1206         }
1207         window->glk_input_pending = FALSE;
1208         return -1;
1209       }
1210       break;
1211
1212     case evtype_CharInput:
1213       *val = moo.val1;
1214       window->glk_input_pending = FALSE;
1215       return 10;
1216
1217     case evtype_LineInput:
1218       *val = moo.val1;
1219       window->glk_input_pending = FALSE;
1220       return 10;
1221
1222     case evtype_MouseInput:
1223       t = z_find_win(moo.win);
1224       if(t && t->mouse_callback &&
1225          t->mouse_callback(window->pending_input_type == evtype_CharInput,
1226                            moo.win, moo.val1, moo.val2)) {
1227         if(window->pending_input_type == evtype_CharInput) {
1228           glk_cancel_char_event(window->win);
1229           *val = 254;
1230         } else {
1231           glk_cancel_line_event(window->win, &moo);
1232           *val = moo.val1;
1233         }
1234         window->glk_input_pending = FALSE;
1235         return 254;
1236       }
1237       glk_request_mouse_event(moo.win);
1238       break;
1239     
1240     case evtype_Arrange:
1241       z_draw_all_windows();
1242     }
1243
1244     z_flush_all_windows();
1245   }
1246
1247   if(window->pending_input_type == evtype_LineInput)
1248     *val = window->pending_input_length;
1249   else
1250     *val = 0;
1251
1252   return 0;
1253 }
1254
1255
1256 void z_wait_for_key(zwinid window)
1257 {
1258   glui32 ch;
1259   do {
1260     z_draw_all_windows();
1261     glk_request_char_event(window->win);
1262     window->pending_input_type = evtype_CharInput;
1263   } while(waitforinput(window, &ch, NULL, 0) == 0);
1264   window->pending_input_type = 0;
1265 }
1266
1267
1268 zwinid check_valid_for_input(zwinid window)
1269 {  
1270   glui32 y, i;
1271   if(!window->win) {
1272     zwinid newwin = NULL;
1273     for(i = 0; i < num_z_windows; i++) {
1274       if(game_windows[i].win) {
1275         newwin = &game_windows[i];
1276         break;
1277       }
1278     }
1279     if(!newwin)
1280       return NULL;
1281
1282     if(window->wintype == wintype_TextGrid) {
1283       i = 0;
1284       for(y = 0; y < window->height; y++) {
1285         z_put_char(newwin, 13);
1286         z_put_styled_string(newwin, window->text_buffer + i,
1287                             window->color_buffer + i, window->width);
1288         i += window->width;
1289       }
1290       z_put_char(newwin, 13);
1291     }
1292
1293     window = newwin;
1294   }
1295   return window;
1296 }
1297
1298
1299 /* returns number of characters read */
1300 int z_read(zwinid window, char *dest, unsigned maxlen, unsigned initlen,
1301            zword timer, BOOL (*timer_callback)(zword), zword timer_arg,
1302            unsigned char *terminator)
1303 {
1304   /* FIXME: support terminating characters when (if) glk gets support for
1305      them */
1306   unsigned i;
1307   unsigned ux, uy;
1308   glui32 length;
1309   BOOL done;
1310
1311   if(automap_unexplore()) {
1312     read_abort = TRUE;
1313     return 0;
1314   }
1315   
1316   read_abort = FALSE;
1317
1318   if(initlen > maxlen) {
1319     n_show_error(E_OUTPUT, "initlen > maxlen", initlen);
1320     return 0;
1321   }
1322
1323   if(window == 0)
1324     window = &game_windows[0];
1325   
1326   if(window->pending_input_type != 0) {
1327     n_show_error(E_OUTPUT, "nested input attempted", 0);
1328     return 0;
1329   }
1330
1331 #ifdef DEBUGGING
1332
1333   if(do_automap) {
1334     const char *dir = automap_explore();
1335     if(dir) {
1336       length = n_strlen(dir);
1337       if(length > maxlen)
1338         length = maxlen;
1339       n_strncpy(dest, dir, length);
1340       return length;
1341     }
1342   }
1343 #endif
1344
1345   glk_request_timer_events(timer * 100);  /* if time is zero, does nothing */
1346     
1347   if(initlen != 0 && window->wintype == wintype_TextBuffer) {
1348     BOOL good = FALSE;
1349     if(initlen <= window->curr_offset) {
1350       good = TRUE;
1351       for(i = 0; i < initlen; i++)  /* check the end of the linebuffer */
1352         if(window->text_buffer[window->curr_offset - initlen + i] != dest[i]) {
1353           good = FALSE;
1354           break;
1355         }
1356     }
1357     if(!good) {
1358       /* bleah */
1359       /* argh */
1360       /* oof */
1361     } else {
1362       window->curr_offset -= initlen; /* Remove initial text from linebuffer */
1363     }
1364   }
1365   
1366   if(window->wintype == wintype_TextGrid) {
1367     ux = window->curr_offset % window->width;
1368     uy = window->curr_offset / window->width;
1369   }
1370
1371   z_flush_all_windows();
1372   window = check_valid_for_input(window);
1373
1374   done = FALSE;
1375   length = initlen;
1376   while(!done) {
1377     int t;
1378
1379     if(window->wintype == wintype_TextGrid)
1380       glk_window_move_cursor(window->win, ux, uy);
1381
1382     if(input_stream1) {
1383       glui32 len = maxlen;
1384       *terminator = transcript_getline(dest, &len);
1385       length = len;
1386     }
1387     if(input_stream1) { /* If didn't EOF, input_stream1 will be non-zero */
1388       glk_stream_set_current(window->str);
1389       set_glk_stream_current();
1390       glk_set_style(style_Input);
1391       glk_put_buffer(dest, length);
1392       glk_put_char(10);
1393       done = TRUE;
1394     } else {
1395       glk_request_line_event(window->win, dest, maxlen, length);
1396       window->pending_input_type = evtype_LineInput;
1397     
1398       t = waitforinput(window, &length, timer_callback, timer_arg);
1399       if(t != 0) {
1400         if(t == -1)
1401           *terminator = 0;
1402         else
1403           *terminator = t;
1404         done = TRUE;
1405       }
1406     }
1407
1408     if(done)
1409       stream4line(dest, length, *terminator);
1410
1411 #ifdef DEBUGGING
1412     if(done && length >= 2 && dest[0] == '/') {
1413       if(dest[1] == '/') {  /* "//" means no command, but start with "/" */
1414         for(i = 1; i < length; i++)
1415           dest[i-1] = dest[i];
1416         length--;
1417       } else {
1418         done = FALSE;
1419         dest[length] = 0;
1420         
1421         process_debug_command(dest+1);
1422
1423         if(read_abort)
1424           done = TRUE;
1425
1426         length = 0;
1427       }
1428     }
1429 #endif
1430   }
1431   glk_request_timer_events(0);  /* stop timer */
1432
1433   window->pending_input_type = 0;
1434
1435   for(i = 0; i < num_z_windows; i++)
1436     game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height;
1437
1438   return length;
1439 }
1440
1441 zword z_read_char(zwinid window,
1442                   zword timer, BOOL (*timer_callback)(zword), zword timer_arg)
1443 {
1444   unsigned i;
1445   glui32 ch;
1446   zword validch = 0;
1447
1448   if(automap_unexplore()) {
1449     read_abort = TRUE;
1450     return 0;
1451   }
1452
1453   if(input_stream1) {
1454     unsigned num;
1455     validch = transcript_getchar(&num);
1456     if(!validch)
1457       validch = num;
1458   }
1459   if(input_stream1) {
1460     return validch;
1461   }
1462   
1463   read_abort = FALSE;
1464
1465   glk_request_timer_events(timer * 100);
1466
1467   z_flush_all_windows();
1468   window = check_valid_for_input(window);
1469
1470   do {
1471     do {
1472       z_draw_all_windows();
1473       glk_request_char_event(window->win);
1474       window->pending_input_type = evtype_CharInput;
1475     } while(waitforinput(window, &ch, timer_callback, timer_arg) == 0);
1476     
1477     if(' ' <= ch && ch <= '~')
1478       validch = ch;
1479     
1480     switch(ch) {
1481     case 8:
1482     case keycode_Delete: validch = 8; break;
1483     case 9:
1484     case keycode_Tab:    validch = 9; break;
1485     case 13:
1486     case keycode_Return: validch = 13; break;
1487 /*  case 21:
1488       if(restoreundo()) {
1489         read_abort = TRUE;
1490         return 0;
1491       }
1492       break; */
1493     case 27:
1494     case keycode_Escape: validch = 27; break;
1495     case 16:
1496     case keycode_Up:     validch = 129; break;
1497     case 14:
1498     case keycode_Down:   validch = 130; break;
1499     case 2:
1500     case keycode_Left:   validch = 131; break;
1501     case 6:
1502     case keycode_Right:  validch = 132; break;
1503     case keycode_Func1:  validch = 133; break;
1504     case keycode_Func2:  validch = 134; break;
1505     case keycode_Func3:  validch = 135; break;
1506     case keycode_Func4:  validch = 136; break;
1507     case keycode_Func5:  validch = 137; break;
1508     case keycode_Func6:  validch = 138; break;
1509     case keycode_Func7:  validch = 139; break;
1510     case keycode_Func8:  validch = 140; break;
1511     case keycode_Func9:  validch = 141; break;
1512     case keycode_Func10: validch = 142; break;
1513     case keycode_Func11: validch = 143; break;
1514     case keycode_Func12: validch = 144; break;
1515     }
1516   } while(!(validch || ch == 0));
1517
1518   glk_request_timer_events(0);     /* stop timer */
1519
1520   window->pending_input_type = 0;
1521
1522   for(i = 0; i < num_z_windows; i++)
1523     game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height;
1524
1525   return validch;
1526 }
1527
1528
1529 /*
1530 void zwin_init(int number, glui32 wintype,
1531                glui32 x_coord, glui32 y_coord, glui32 x_size, glui32 y_size)
1532 {
1533   zwinid self = game_windows + number;
1534
1535   if(x_coord == self->x1) {
1536     if(y_coord == self->y1) {
1537       
1538
1539   if(game_windows[number].win) {
1540     z_pause_timed_input(game_windows[number].win);
1541     glk_window_close(game_windows[number].win, NULL);
1542   }
1543   set_style_hints();
1544   game_windows[number].win = glk_window_open(
1545 }
1546 */