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);
701 glk_window_get_size(window->win, &winx, &winy);
702 if (!(window->method & winmethod_Above || window->method & winmethod_Below)
704 glk_window_set_arrangement(o, window->method,
705 end_line, window->win);
707 glk_window_set_arrangement(o, window->method, end_line, window->win);
709 glk_window_get_size(window->win, &winx, &winy);
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);
718 if(end_line > start_line && window->dirty) {
720 unsigned padleft = 0, padmiddle = 0, padright = 0;
721 unsigned skipleft = 0, skipmiddle = 0, skipright = 0;
723 unsigned firstwidth, lastwidth;
727 /* Calculate how much space is used for margins */
729 unsigned left_margin = window->width, right_margin = window->width;
732 for(y = start_line; y < end_line; y++) {
734 for(x = 0; x < window->width; x++)
735 if(window->text_buffer[i + x] != ' ') {
741 for(x = 0; x < window->width; x++)
742 if(window->text_buffer[i + window->width - x - 1] != ' ') {
751 firstwidth = window->width; lastwidth = 0;
753 if(start_line + 1 == end_line) {
754 unsigned longestx = 0;
755 unsigned longestlen = 0;
757 unsigned thislen = 0;
758 colorstyle lastcolor;
759 width = window->width;
761 for(x = skipleft; x < width + skipleft; x++) {
762 if(window->text_buffer[x] == ' '
763 && (!thislen || coloreq(window->color_buffer[x], lastcolor))) {
767 lastcolor = window->color_buffer[x];
769 if(thislen > longestlen) {
771 longestlen = thislen;
777 firstwidth = longestx - skipleft;
778 skipmiddle = longestlen - 1;
779 lastwidth = width - firstwidth - skipmiddle;
783 if(skipmiddle && winx < firstwidth + 2 + lastwidth)
786 if(lastwidth && winx >= firstwidth + padmiddle + lastwidth) {
787 padmiddle = winx - firstwidth - lastwidth;
789 if(winx >= window->width)
790 width = window->width;
794 if(right_margin + left_margin) {
795 if(winx > window->width)
796 padleft = (unsigned) ((winx - window->width) * (((float) left_margin) / (right_margin + left_margin)));
798 skipleft = (unsigned) ((window->width - winx) * (((float) left_margin) / (right_margin + left_margin)));
801 padleft = winx - window->width;
804 if(skipleft > left_margin)
805 skipleft = left_margin;
807 if(winx > window->width)
808 padright = winx - window->width - padleft;
810 skipright = window->width - winx - skipleft;
812 if(width < firstwidth + padmiddle) {
816 } else if(width < firstwidth + padmiddle + lastwidth) {
817 lastwidth = width - firstwidth - padmiddle;
822 glk_stream_set_current(window->str);
823 glk_window_move_cursor(window->win, 0, start_line);
825 /* draw to the upper window */
827 for(y = start_line; y < end_line; y++) {
829 for(x = 0; x < padleft; x++)
834 z_put_styled_string(window, window->text_buffer + i,
835 window->color_buffer + i, firstwidth);
838 for(x = 0; x < padmiddle; x++)
842 z_put_styled_string(window, window->text_buffer + i,
843 window->color_buffer + i, lastwidth);
846 for(x = 0; x < padright; x++)
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;
861 void z_flush_text(zwinid window)
863 z_pause_timed_input(window);
865 if(!window->win || !window->str
866 || !window->text_buffer || !window->color_buffer
867 || window->curr_offset == 0) {
868 window->curr_offset = 0;
872 z_put_styled_string(window, window->text_buffer, window->color_buffer,
873 window->curr_offset);
875 window->curr_offset = 0;
876 window->dirty = FALSE;
880 void z_flush_graphics(zwinid window)
884 float xratio, yratio;
890 glk_window_get_size(window->win, &winx, &winy);
891 xratio = ((float) winx) / window->width;
892 yratio = ((float) winy) / window->height;
894 parent = glk_window_get_parent(window->win);
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);
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);
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;
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));
926 void z_print_number(zwinid window, int number)
930 int length = n_to_decimal(buffer, number);
932 for(i = length - 1; i >= 0; i--)
933 z_put_char(window, buffer[i]);
936 void z_put_char(zwinid window, unsigned c)
938 colorstyle color = window->current;
940 color.style |= sFIXE;
942 if(c == 0) /* Section 3.8.2.1 */
945 window->dirty = TRUE;
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, ']');
954 case wintype_TextGrid:
961 z_put_char(window, 'O');
965 z_put_char(window, 'o');
971 if(c > 255) /* Section 3.8.5.4.3 */
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);
981 case wintype_TextGrid:
982 window->curr_offset += window->width;
983 window->curr_offset -= window->curr_offset % window->width;
986 window->text_buffer[window->curr_offset] = c;
987 window->color_buffer[window->curr_offset] = color;
988 window->curr_offset++;
991 if(window->curr_offset >= window->max_offset) {
992 switch(window->wintype) {
993 case wintype_TextBuffer:
994 z_flush_text(window);
996 case wintype_TextGrid:
997 if(!window->defined) /* Section 8.6.2 */
998 n_show_port(E_OUTPUT, "writing past end of window", c);
1000 if(window->max_offset)
1001 window->curr_offset = window->max_offset - 1;
1003 window->curr_offset = 0;
1005 window->defined = FALSE;
1010 void z_setxy(zwinid window, zword x, zword y)
1012 window->curr_offset = (y - 1) * window->width + (x - 1);
1013 window->defined = TRUE;
1016 void z_getxy(zwinid window, zword *x, zword *y)
1019 *x = (window->curr_offset % window->width) + 1;
1020 *y = (window->curr_offset / window->width) + 1;
1022 *x = window->curr_offset + 1;
1027 void z_getsize(zwinid window, unsigned *width, unsigned *height)
1029 *width = window->width;
1030 *height = window->height;
1033 void z_find_size(glui32 *wid, glui32 *hei)
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);
1044 upper_width = *wid; upper_height = *hei;
1048 void z_set_height(zwinid window, unsigned height)
1051 if(height * window->width > window->buffer_size) {
1052 n_show_error(E_OUTPUT, "height too large", height);
1056 window->height = height;
1057 if(height > window->biggest_height)
1058 window->biggest_height = height;
1060 x = window->max_offset;
1061 window->max_offset = height * window->width;
1063 for(; x < window->max_offset; x++) {
1064 window->text_buffer[x] = ' ';
1065 window->color_buffer[x] = window->current;
1068 window->dirty = TRUE;
1071 void z_set_color(zwinid window, unsigned fore, unsigned back)
1073 if(fore >= sizeof(fgcolortable) / sizeof(*fgcolortable)) {
1074 n_show_error(E_OUTPUT, "illegal foreground color", fore);
1077 if(back >= sizeof(bgcolortable) / sizeof(*bgcolortable)) {
1078 n_show_error(E_OUTPUT, "illegal background color", back);
1082 fgcolortable[0] = fgcolortable[fore];
1083 bgcolortable[0] = bgcolortable[back];
1085 window->current.fore = fore;
1086 window->current.back = back;
1089 void z_set_style(zwinid window, int 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);
1101 void set_fixed(BOOL p)
1110 void z_set_transcript(zwinid window, strid_t stream)
1112 window->transcript = stream;
1113 glk_window_set_echo_stream(window->win, stream);
1116 void z_clear_window(zwinid window)
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]);
1128 window->dirty = TRUE;
1129 window->curr_offset = 0;
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;
1138 window->curr_offset = 0;
1139 window->dirty = TRUE;
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);
1147 init_lower(NULL); /* **FIXME** This is wrong, but deal with it later */
1153 void z_erase_line(zwinid window)
1155 if(window->wintype == wintype_TextGrid) {
1157 int x = window->curr_offset % window->width;
1158 int endoffset = window->curr_offset + (window->width - x);
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;
1169 /* Waits for input or timeout
1171 * 0 - output during wait; may need to redraw or somesuch
1172 * -1 - callback routine said to stop
1175 * char and line events will be canceled by the time it exits
1177 static int waitforinput(zwinid window, glui32 *val,
1178 BOOL (*timer_callback)(zword), zword timer_arg)
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);
1190 window->glk_input_pending = TRUE;
1192 while(window->glk_input_pending) {
1199 if(timer_callback && timer_callback(timer_arg)) {
1200 if(window->pending_input_type == evtype_CharInput) {
1201 glk_cancel_char_event(window->win);
1204 glk_cancel_line_event(window->win, &moo);
1207 window->glk_input_pending = FALSE;
1212 case evtype_CharInput:
1214 window->glk_input_pending = FALSE;
1217 case evtype_LineInput:
1219 window->glk_input_pending = FALSE;
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);
1231 glk_cancel_line_event(window->win, &moo);
1234 window->glk_input_pending = FALSE;
1237 glk_request_mouse_event(moo.win);
1240 case evtype_Arrange:
1241 z_draw_all_windows();
1244 z_flush_all_windows();
1247 if(window->pending_input_type == evtype_LineInput)
1248 *val = window->pending_input_length;
1256 void z_wait_for_key(zwinid window)
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;
1268 zwinid check_valid_for_input(zwinid window)
1272 zwinid newwin = NULL;
1273 for(i = 0; i < num_z_windows; i++) {
1274 if(game_windows[i].win) {
1275 newwin = &game_windows[i];
1282 if(window->wintype == wintype_TextGrid) {
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);
1290 z_put_char(newwin, 13);
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)
1304 /* FIXME: support terminating characters when (if) glk gets support for
1311 if(automap_unexplore()) {
1318 if(initlen > maxlen) {
1319 n_show_error(E_OUTPUT, "initlen > maxlen", initlen);
1324 window = &game_windows[0];
1326 if(window->pending_input_type != 0) {
1327 n_show_error(E_OUTPUT, "nested input attempted", 0);
1334 const char *dir = automap_explore();
1336 length = n_strlen(dir);
1339 n_strncpy(dest, dir, length);
1345 glk_request_timer_events(timer * 100); /* if time is zero, does nothing */
1347 if(initlen != 0 && window->wintype == wintype_TextBuffer) {
1349 if(initlen <= window->curr_offset) {
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]) {
1362 window->curr_offset -= initlen; /* Remove initial text from linebuffer */
1366 if(window->wintype == wintype_TextGrid) {
1367 ux = window->curr_offset % window->width;
1368 uy = window->curr_offset / window->width;
1371 z_flush_all_windows();
1372 window = check_valid_for_input(window);
1379 if(window->wintype == wintype_TextGrid)
1380 glk_window_move_cursor(window->win, ux, uy);
1383 glui32 len = maxlen;
1384 *terminator = transcript_getline(dest, &len);
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);
1395 glk_request_line_event(window->win, dest, maxlen, length);
1396 window->pending_input_type = evtype_LineInput;
1398 t = waitforinput(window, &length, timer_callback, timer_arg);
1409 stream4line(dest, length, *terminator);
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];
1421 process_debug_command(dest+1);
1431 glk_request_timer_events(0); /* stop timer */
1433 window->pending_input_type = 0;
1435 for(i = 0; i < num_z_windows; i++)
1436 game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height;
1441 zword z_read_char(zwinid window,
1442 zword timer, BOOL (*timer_callback)(zword), zword timer_arg)
1448 if(automap_unexplore()) {
1455 validch = transcript_getchar(&num);
1465 glk_request_timer_events(timer * 100);
1467 z_flush_all_windows();
1468 window = check_valid_for_input(window);
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);
1477 if(' ' <= ch && ch <= '~')
1482 case keycode_Delete: validch = 8; break;
1484 case keycode_Tab: validch = 9; break;
1486 case keycode_Return: validch = 13; break;
1494 case keycode_Escape: validch = 27; break;
1496 case keycode_Up: validch = 129; break;
1498 case keycode_Down: validch = 130; break;
1500 case keycode_Left: validch = 131; break;
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;
1516 } while(!(validch || ch == 0));
1518 glk_request_timer_events(0); /* stop timer */
1520 window->pending_input_type = 0;
1522 for(i = 0; i < num_z_windows; i++)
1523 game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height;
1530 void zwin_init(int number, glui32 wintype,
1531 glui32 x_coord, glui32 y_coord, glui32 x_size, glui32 y_size)
1533 zwinid self = game_windows + number;
1535 if(x_coord == self->x1) {
1536 if(y_coord == self->y1) {
1539 if(game_windows[number].win) {
1540 z_pause_timed_input(game_windows[number].win);
1541 glk_window_close(game_windows[number].win, NULL);
1544 game_windows[number].win = glk_window_open(