1 /* Nitfol - z-machine interpreter using Glk for output.
2 Copyright (C) 1999 Evin Robertson
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.
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.
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.
18 The author can be reached at nitfol@deja.com
25 typedef struct z_window *zwinid;
29 BOOL is_fixed; /* If we are forcing output to be fixed-width */
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.
36 Go ahead and customize these (making background colors lighter than
37 foreground colors might be interesting)
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 */
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 */
73 static void killglkwithcolor(glui32 styl, int fore, int back)
75 if(fgcolortable[fore] == -1)
76 glk_stylehint_clear(wintype_AllTypes, styl,
79 glk_stylehint_set(wintype_AllTypes, styl,
80 stylehint_TextColor, fgcolortable[fore]);
82 if(bgcolortable[back] == -1)
83 glk_stylehint_clear(wintype_AllTypes, styl,
86 glk_stylehint_set(wintype_AllTypes, styl,
87 stylehint_BackColor, bgcolortable[back]);
91 static void set_stylehints(char fore, char back)
94 for(n = 0; n < style_NUMSTYLES; n++)
95 killglkwithcolor(n, fore, back);
97 /* Subheader will be used for bold */
98 glk_stylehint_set(wintype_TextBuffer, style_Subheader,
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);
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);
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);
131 static glui32 bitmap_to_style[16] = {
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 */
152 static glui32 bitmap_to_style[16] = {
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*/
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*/
182 glui32 width, height;
194 BOOL glk_input_pending;
195 glui32 pending_input_type;
196 glui32 pending_input_length;
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);
202 glui32 width, height;
203 glui32 x1, y1, x2, y2;
205 glui32 last_height; /* What the height was last time we got input */
206 glui32 biggest_height;/* The biggest it's been since */
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 */
212 BOOL dirty; /* Has window been changed since last redraw? */
213 BOOL defined; /* Is our location well defined? */
215 unsigned char *text_buffer; /* whole window for grid, current line for buffer */
216 colorstyle *color_buffer;
225 #define num_z_windows 16
227 static struct z_window game_windows[num_z_windows];
229 static glui32 upper_width, upper_height;
232 static int waitforinput(zwinid window, glui32 *val,
233 BOOL (*timer_callback)(zword), zword timer_arg);
236 void set_glk_stream_current(void)
238 z_flush_text(&game_windows[0]);
239 glk_stream_set_current(game_windows[0].str);
242 void draw_intext_picture(zwinid window, glui32 picture, glui32 alignment)
244 z_flush_text(window);
245 wrap_glk_image_draw(window->win, picture, alignment, 0);
248 void draw_picture(zwinid window, glui32 picture, glui32 x, glui32 y)
252 glui32 width, height;
254 wrap_glk_image_get_info(operand[0], &width, &height);
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))
263 for(i = 0; i < 12; i++)
264 if(window->images[i].image_num == 0)
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;
277 static int showstuffcount = 0;
279 /* Show an interpreter message */
280 void showstuff(const char *title, const char *type, const char *message, offset number)
282 static BOOL loopy = FALSE;
285 n_show_fatal(E_SYSTEM, "loopy message reporting", 0);
289 z_pause_timed_input(&game_windows[0]);
290 z_flush_text(&game_windows[0]);
291 glk_stream_set_current(game_windows[0].str);
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(") ");
305 infix_gprint_loc(stack_get_depth(), 0);
307 w_glk_put_string("PC=");
308 g_print_number(oldPC);
312 if(++showstuffcount == 100) {
313 w_glk_put_string("[pausing every 100 errors]\n");
314 z_wait_for_key(&game_windows[0]);
317 glk_set_style(style_Normal);
323 void init_lower(zwinid *lower)
325 zwinid lower_win = &game_windows[0];
332 z_pause_timed_input(lower_win);
333 glk_window_close(lower_win->win, NULL);
336 set_stylehints(lower_win->current.fore,
337 lower_win->current.back);
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;
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));
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;
357 lower_win->actual = lower_win->current;
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);
365 if(lower_win->win == 0) {
366 n_show_fatal(E_OUTPUT, "cannot open lower window", 0);
370 lower_win->str = glk_window_get_stream(lower_win->win);
372 if(lower_win->transcript)
373 glk_window_set_echo_stream(lower_win->win, lower_win->transcript);
377 void init_upper(zwinid *upper)
379 zwinid upper_win = &game_windows[1];
386 z_pause_timed_input(upper_win);
387 glk_window_close(upper_win->win, NULL);
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;
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));
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;
409 upper_win->actual = upper_win->current;
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);
416 if(upper_win->win == 0) {
421 upper_win->str = glk_window_get_stream(upper_win->win);
423 if(upper_win->str == 0) {
424 glk_window_close(upper_win->win, NULL);
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)
437 colorstyle defaultstyle;
438 defaultstyle.fore = 1; defaultstyle.back = 1; defaultstyle.style = 0;
444 upper_width = maxwidth; upper_height = maxheight;
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;
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))
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];
486 void z_kill_window(zwinid win)
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);
501 /* close any open windows */
502 void kill_windows(void)
506 for(i = 0; i < num_z_windows; i++)
507 z_clear_window(&game_windows[i]);
510 for(i = 0; i < num_z_windows; i++) {
511 if(game_windows[i].win) {
512 game_windows[i].transcript = NULL;
514 glk_window_close(game_windows[i].win, NULL);
515 game_windows[i].win = NULL;
516 game_windows[i].str = NULL;
523 /* free memory space used by windows, but don't close them */
524 void free_windows(void)
528 z_flush_all_windows();
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;
535 n_free(game_windows[i].color_buffer);
536 game_windows[i].color_buffer = NULL;
541 zwinid z_find_win(winid_t win)
544 for(i = 0; i < num_z_windows; i++) {
545 if(game_windows[i].win == win)
546 return &game_windows[i];
552 static BOOL coloreq(colorstyle a, colorstyle b) /* return true if colors are equivalent */
554 return (fgcolortable[(int) a.fore] == fgcolortable[(int) b.fore]) &&
555 (bgcolortable[(int) a.back] == bgcolortable[(int) b.back]);
559 static void checkforblockquote(zwinid window, zwinid dest_win)
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;
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++] != ' ') {
583 z_pause_timed_input(dest_win);
584 glk_stream_set_current(game_windows[1].str);
586 glk_set_style(style_BlockQuote);
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++]);
599 void z_pause_timed_input(zwinid window)
602 if(window->glk_input_pending) {
603 window->glk_input_pending = FALSE;
605 switch(window->pending_input_type) {
606 case evtype_CharInput:
607 glk_cancel_char_event(window->win);
609 case evtype_LineInput:
610 glk_cancel_line_event(window->win, &eep);
611 window->pending_input_length = eep.val1;
617 void z_flush_all_windows(void)
620 for(window = 0; window < num_z_windows; window++) {
621 if(game_windows[window].dirty) {
622 z_pause_timed_input(&game_windows[window]);
624 switch(game_windows[window].wintype) {
625 case wintype_TextBuffer:
626 z_flush_text(&game_windows[window]);
628 case wintype_TextGrid:
629 z_flush_fixed(&game_windows[window]);
631 case wintype_Graphics:
632 z_flush_graphics(&game_windows[window]);
639 void z_draw_all_windows(void)
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]);
651 static void z_put_styled_string(zwinid window, unsigned char *text,
652 colorstyle *color, glui32 length)
655 colorstyle laststyle = color[0];
660 glk_set_style_stream(window->str, bitmap_to_style[laststyle.style]);
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];
670 void z_flush_fixed(zwinid window)
674 glui32 start_line, end_line;
676 /* If there's no such window, give up */
677 if(!window->win || !window->str ||
678 !window->text_buffer || !window->color_buffer)
681 /* glk doesn't allow writing to a window while input is pending */
682 z_pause_timed_input(window);
684 end_line = window->height;
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;
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 */
695 if(window->draw_callback)
696 start_line = window->draw_callback(NULL, 0, 0);
697 end_line += start_line;
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);
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);
711 if(end_line > start_line && window->dirty) {
713 unsigned padleft = 0, padmiddle = 0, padright = 0;
714 unsigned skipleft = 0, skipmiddle = 0, skipright = 0;
716 unsigned firstwidth, lastwidth;
720 /* Calculate how much space is used for margins */
722 unsigned left_margin = window->width, right_margin = window->width;
725 for(y = start_line; y < end_line; y++) {
727 for(x = 0; x < window->width; x++)
728 if(window->text_buffer[i + x] != ' ') {
734 for(x = 0; x < window->width; x++)
735 if(window->text_buffer[i + window->width - x - 1] != ' ') {
744 firstwidth = window->width; lastwidth = 0;
746 if(start_line + 1 == end_line) {
747 unsigned longestx = 0;
748 unsigned longestlen = 0;
750 unsigned thislen = 0;
751 colorstyle lastcolor;
752 width = window->width;
754 for(x = skipleft; x < width + skipleft; x++) {
755 if(window->text_buffer[x] == ' '
756 && (!thislen || coloreq(window->color_buffer[x], lastcolor))) {
760 lastcolor = window->color_buffer[x];
762 if(thislen > longestlen) {
764 longestlen = thislen;
770 firstwidth = longestx - skipleft;
771 skipmiddle = longestlen - 1;
772 lastwidth = width - firstwidth - skipmiddle;
776 if(skipmiddle && winx < firstwidth + 2 + lastwidth)
779 if(lastwidth && winx >= firstwidth + padmiddle + lastwidth) {
780 padmiddle = winx - firstwidth - lastwidth;
782 if(winx >= window->width)
783 width = window->width;
787 if(right_margin + left_margin) {
788 if(winx > window->width)
789 padleft = (unsigned) ((winx - window->width) * (((float) left_margin) / (right_margin + left_margin)));
791 skipleft = (unsigned) ((window->width - winx) * (((float) left_margin) / (right_margin + left_margin)));
794 padleft = winx - window->width;
797 if(skipleft > left_margin)
798 skipleft = left_margin;
800 if(winx > window->width)
801 padright = winx - window->width - padleft;
803 skipright = window->width - winx - skipleft;
805 if(width < firstwidth + padmiddle) {
809 } else if(width < firstwidth + padmiddle + lastwidth) {
810 lastwidth = width - firstwidth - padmiddle;
815 glk_stream_set_current(window->str);
816 glk_window_move_cursor(window->win, 0, start_line);
818 /* draw to the upper window */
820 for(y = start_line; y < end_line; y++) {
822 for(x = 0; x < padleft; x++)
827 z_put_styled_string(window, window->text_buffer + i,
828 window->color_buffer + i, firstwidth);
831 for(x = 0; x < padmiddle; x++)
835 z_put_styled_string(window, window->text_buffer + i,
836 window->color_buffer + i, lastwidth);
839 for(x = 0; x < padright; x++)
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;
854 void z_flush_text(zwinid window)
856 z_pause_timed_input(window);
858 if(!window->win || !window->str
859 || !window->text_buffer || !window->color_buffer
860 || window->curr_offset == 0) {
861 window->curr_offset = 0;
865 z_put_styled_string(window, window->text_buffer, window->color_buffer,
866 window->curr_offset);
868 window->curr_offset = 0;
869 window->dirty = FALSE;
873 void z_flush_graphics(zwinid window)
877 float xratio, yratio;
883 glk_window_get_size(window->win, &winx, &winy);
884 xratio = ((float) winx) / window->width;
885 yratio = ((float) winy) / window->height;
887 parent = glk_window_get_parent(window->win);
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);
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);
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;
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));
919 void z_print_number(zwinid window, int number)
923 int length = n_to_decimal(buffer, number);
925 for(i = length - 1; i >= 0; i--)
926 z_put_char(window, buffer[i]);
929 void z_put_char(zwinid window, unsigned c)
931 colorstyle color = window->current;
933 color.style |= sFIXE;
935 if(c == 0) /* Section 3.8.2.1 */
938 window->dirty = TRUE;
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, ']');
947 case wintype_TextGrid:
954 z_put_char(window, 'O');
958 z_put_char(window, 'o');
964 if(c > 255) /* Section 3.8.5.4.3 */
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);
974 case wintype_TextGrid:
975 window->curr_offset += window->width;
976 window->curr_offset -= window->curr_offset % window->width;
979 window->text_buffer[window->curr_offset] = c;
980 window->color_buffer[window->curr_offset] = color;
981 window->curr_offset++;
984 if(window->curr_offset >= window->max_offset) {
985 switch(window->wintype) {
986 case wintype_TextBuffer:
987 z_flush_text(window);
989 case wintype_TextGrid:
990 if(!window->defined) /* Section 8.6.2 */
991 n_show_port(E_OUTPUT, "writing past end of window", c);
993 if(window->max_offset)
994 window->curr_offset = window->max_offset - 1;
996 window->curr_offset = 0;
998 window->defined = FALSE;
1003 void z_setxy(zwinid window, zword x, zword y)
1005 window->curr_offset = (y - 1) * window->width + (x - 1);
1006 window->defined = TRUE;
1009 void z_getxy(zwinid window, zword *x, zword *y)
1012 *x = (window->curr_offset % window->width) + 1;
1013 *y = (window->curr_offset / window->width) + 1;
1015 *x = window->curr_offset + 1;
1020 void z_getsize(zwinid window, unsigned *width, unsigned *height)
1022 *width = window->width;
1023 *height = window->height;
1026 void z_find_size(glui32 *wid, glui32 *hei)
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);
1037 upper_width = *wid; upper_height = *hei;
1041 void z_set_height(zwinid window, unsigned height)
1044 if(height * window->width > window->buffer_size) {
1045 n_show_error(E_OUTPUT, "height too large", height);
1049 window->height = height;
1050 if(height > window->biggest_height)
1051 window->biggest_height = height;
1053 x = window->max_offset;
1054 window->max_offset = height * window->width;
1056 for(; x < window->max_offset; x++) {
1057 window->text_buffer[x] = ' ';
1058 window->color_buffer[x] = window->current;
1061 window->dirty = TRUE;
1064 void z_set_color(zwinid window, unsigned fore, unsigned back)
1066 if(fore >= sizeof(fgcolortable) / sizeof(*fgcolortable)) {
1067 n_show_error(E_OUTPUT, "illegal foreground color", fore);
1070 if(back >= sizeof(bgcolortable) / sizeof(*bgcolortable)) {
1071 n_show_error(E_OUTPUT, "illegal background color", back);
1075 fgcolortable[0] = fgcolortable[fore];
1076 bgcolortable[0] = bgcolortable[back];
1078 window->current.fore = fore;
1079 window->current.back = back;
1082 void z_set_style(zwinid window, int 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);
1094 void set_fixed(BOOL p)
1103 void z_set_transcript(zwinid window, strid_t stream)
1105 window->transcript = stream;
1106 glk_window_set_echo_stream(window->win, stream);
1109 void z_clear_window(zwinid window)
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]);
1121 window->dirty = TRUE;
1122 window->curr_offset = 0;
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;
1131 window->curr_offset = 0;
1132 window->dirty = TRUE;
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);
1140 init_lower(NULL); /* **FIXME** This is wrong, but deal with it later */
1146 void z_erase_line(zwinid window)
1148 if(window->wintype == wintype_TextGrid) {
1150 int x = window->curr_offset % window->width;
1151 int endoffset = window->curr_offset + (window->width - x);
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;
1162 /* Waits for input or timeout
1164 * 0 - output during wait; may need to redraw or somesuch
1165 * -1 - callback routine said to stop
1168 * char and line events will be canceled by the time it exits
1170 static int waitforinput(zwinid window, glui32 *val,
1171 BOOL (*timer_callback)(zword), zword timer_arg)
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);
1183 window->glk_input_pending = TRUE;
1185 while(window->glk_input_pending) {
1192 if(timer_callback && timer_callback(timer_arg)) {
1193 if(window->pending_input_type == evtype_CharInput) {
1194 glk_cancel_char_event(window->win);
1197 glk_cancel_line_event(window->win, &moo);
1200 window->glk_input_pending = FALSE;
1205 case evtype_CharInput:
1207 window->glk_input_pending = FALSE;
1210 case evtype_LineInput:
1212 window->glk_input_pending = FALSE;
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);
1224 glk_cancel_line_event(window->win, &moo);
1227 window->glk_input_pending = FALSE;
1230 glk_request_mouse_event(moo.win);
1233 case evtype_Arrange:
1234 z_draw_all_windows();
1237 z_flush_all_windows();
1240 if(window->pending_input_type == evtype_LineInput)
1241 *val = window->pending_input_length;
1249 void z_wait_for_key(zwinid window)
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;
1261 zwinid check_valid_for_input(zwinid window)
1265 zwinid newwin = NULL;
1266 for(i = 0; i < num_z_windows; i++) {
1267 if(game_windows[i].win) {
1268 newwin = &game_windows[i];
1275 if(window->wintype == wintype_TextGrid) {
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);
1283 z_put_char(newwin, 13);
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)
1297 /* FIXME: support terminating characters when (if) glk gets support for
1304 if(automap_unexplore()) {
1311 if(initlen > maxlen) {
1312 n_show_error(E_OUTPUT, "initlen > maxlen", initlen);
1317 window = &game_windows[0];
1319 if(window->pending_input_type != 0) {
1320 n_show_error(E_OUTPUT, "nested input attempted", 0);
1327 const char *dir = automap_explore();
1329 length = n_strlen(dir);
1332 n_strncpy(dest, dir, length);
1338 glk_request_timer_events(timer * 100); /* if time is zero, does nothing */
1340 if(initlen != 0 && window->wintype == wintype_TextBuffer) {
1342 if(initlen <= window->curr_offset) {
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]) {
1355 window->curr_offset -= initlen; /* Remove initial text from linebuffer */
1359 if(window->wintype == wintype_TextGrid) {
1360 ux = window->curr_offset % window->width;
1361 uy = window->curr_offset / window->width;
1364 z_flush_all_windows();
1365 window = check_valid_for_input(window);
1372 if(window->wintype == wintype_TextGrid)
1373 glk_window_move_cursor(window->win, ux, uy);
1376 glui32 len = maxlen;
1377 *terminator = transcript_getline(dest, &len);
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);
1388 glk_request_line_event(window->win, dest, maxlen, length);
1389 window->pending_input_type = evtype_LineInput;
1391 t = waitforinput(window, &length, timer_callback, timer_arg);
1402 stream4line(dest, length, *terminator);
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];
1414 process_debug_command(dest+1);
1424 glk_request_timer_events(0); /* stop timer */
1426 window->pending_input_type = 0;
1428 for(i = 0; i < num_z_windows; i++)
1429 game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height;
1434 zword z_read_char(zwinid window,
1435 zword timer, BOOL (*timer_callback)(zword), zword timer_arg)
1441 if(automap_unexplore()) {
1448 validch = transcript_getchar(&num);
1458 glk_request_timer_events(timer * 100);
1460 z_flush_all_windows();
1461 window = check_valid_for_input(window);
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);
1470 if(' ' <= ch && ch <= '~')
1475 case keycode_Delete: validch = 8; break;
1477 case keycode_Tab: validch = 9; break;
1479 case keycode_Return: validch = 13; break;
1487 case keycode_Escape: validch = 27; break;
1489 case keycode_Up: validch = 129; break;
1491 case keycode_Down: validch = 130; break;
1493 case keycode_Left: validch = 131; break;
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;
1509 } while(!(validch || ch == 0));
1511 glk_request_timer_events(0); /* stop timer */
1513 window->pending_input_type = 0;
1515 for(i = 0; i < num_z_windows; i++)
1516 game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height;
1523 void zwin_init(int number, glui32 wintype,
1524 glui32 x_coord, glui32 y_coord, glui32 x_size, glui32 y_size)
1526 zwinid self = game_windows + number;
1528 if(x_coord == self->x1) {
1529 if(y_coord == self->y1) {
1532 if(game_windows[number].win) {
1533 z_pause_timed_input(game_windows[number].win);
1534 glk_window_close(game_windows[number].win, NULL);
1537 game_windows[number].win = glk_window_open(