+++ /dev/null
-#include "glk.h"
-
-/* multiwin.c: Sample program for Glk API, version 0.5.
- Designed by Andrew Plotkin <erkyrath@eblong.com>
- http://www.eblong.com/zarf/glk/index.html
- This program is in the public domain.
-*/
-
-/* This example demonstrates multiple windows and timed input in the
- Glk API. */
-
-/* This is the cleanest possible form of a Glk program. It includes only
- "glk.h", and doesn't call any functions outside Glk at all. We even
- define our own string functions, rather than relying on the
- standard libraries. */
-
-/* We also define our own TRUE and FALSE and NULL. */
-#ifndef TRUE
-#define TRUE 1
-#endif
-#ifndef FALSE
-#define FALSE 0
-#endif
-#ifndef NULL
-#define NULL 0
-#endif
-
-/* The story and status windows. */
-static winid_t mainwin1 = NULL;
-static winid_t mainwin2 = NULL;
-static winid_t statuswin = NULL;
-
-/* Key windows don't get stored in a global variable; we'll find them
- by iterating over the list and looking for this rock value. */
-#define KEYWINROCK (97)
-
-/* For the two main windows, we keep a flag saying whether that window
- has a line input request pending. (Because if it does, we need to
- cancel the line input before printing to that window.) */
-static int inputpending1, inputpending2;
-/* When we cancel line input, we should remember how many characters
- had been typed. This lets us restart the input with those characters
- already in place. */
-static int already1, already2;
-
-/* There's a three-second timer which can be on or off. */
-static int timer_on = FALSE;
-
-/* Forward declarations */
-void glk_main(void);
-
-static void draw_statuswin(void);
-static void draw_keywins(void);
-static void perform_key(winid_t win, glui32 key);
-static void perform_timer(void);
-
-static int str_eq(char *s1, char *s2);
-static int str_len(char *s1);
-static char *str_cpy(char *s1, char *s2);
-static char *str_cat(char *s1, char *s2);
-static void num_to_str(char *buf, int num);
-
-static void verb_help(winid_t win);
-static void verb_jump(winid_t win);
-static void verb_yada(winid_t win);
-static void verb_both(winid_t win);
-static void verb_clear(winid_t win);
-static void verb_page(winid_t win);
-static void verb_pageboth(winid_t win);
-static void verb_timer(winid_t win);
-static void verb_untimer(winid_t win);
-static void verb_chars(winid_t win);
-static void verb_quit(winid_t win);
-
-/* The glk_main() function is called by the Glk system; it's the main entry
- point for your program. */
-void glk_main(void)
-{
- char commandbuf1[256]; /* For mainwin1 */
- char commandbuf2[256]; /* For mainwin2 */
-
- /* Open the main windows. */
- mainwin1 = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
- if (!mainwin1) {
- /* It's possible that the main window failed to open. There's
- nothing we can do without it, so exit. */
- return;
- }
-
- /* Open a second window: a text grid, above the main window, five
- lines high. It is possible that this will fail also, but we accept
- that. */
- statuswin = glk_window_open(mainwin1,
- winmethod_Above | winmethod_Fixed,
- 5, wintype_TextGrid, 0);
-
- /* And a third window, a second story window below the main one. */
- mainwin2 = glk_window_open(mainwin1,
- winmethod_Below | winmethod_Proportional,
- 50, wintype_TextBuffer, 0);
-
- /* We're going to be switching from one window to another all the
- time. So we'll be setting the output stream on a case-by-case
- basis. Every function that prints must set the output stream
- first. (Contrast model.c, where the output stream is always the
- main window, and every function that changes that must set it
- back afterwards.) */
-
- glk_set_window(mainwin1);
- glk_put_string("Multiwin\nAn Interactive Sample Glk Program\n");
- glk_put_string("By Andrew Plotkin.\nRelease 3.\n");
- glk_put_string("Type \"help\" for a list of commands.\n");
-
- glk_set_window(mainwin2);
- glk_put_string("Note that the upper left-hand window accepts character");
- glk_put_string(" input. Hit 'h' to split the window horizontally, 'v' to");
- glk_put_string(" split the window vertically, 'c' to close a window,");
- glk_put_string(" and any other key (including special keys) to display");
- glk_put_string(" key codes. All new windows accept these same keys as");
- glk_put_string(" well.\n\n");
- glk_put_string("This bottom window accepts normal line input.\n");
-
- if (statuswin) {
- /* For fun, let's open a fourth window now, splitting the status
- window. */
- winid_t keywin;
- keywin = glk_window_open(statuswin,
- winmethod_Left | winmethod_Proportional,
- 66, wintype_TextGrid, KEYWINROCK);
- if (keywin) {
- glk_request_char_event(keywin);
- }
- }
-
- /* Draw the key window now, since we don't draw it every input (as
- we do the status window. */
- draw_keywins();
-
- inputpending1 = FALSE;
- inputpending2 = FALSE;
- already1 = 0;
- already2 = 0;
-
- while (1) {
- char *cx, *cmd;
- int doneloop, len;
- winid_t whichwin;
- event_t ev;
-
- draw_statuswin();
- /* We're not redrawing the key windows every command. */
-
- /* Either main window, or both, could already have line input
- pending. If so, leave that window alone. If there is no
- input pending on a window, set a line input request, but
- keep around any characters that were in the buffer already. */
-
- if (mainwin1 && !inputpending1) {
- glk_set_window(mainwin1);
- glk_put_string("\n>");
- /* We request up to 255 characters. The buffer can hold 256,
- but we are going to stick a null character at the end, so
- we have to leave room for that. Note that the Glk library
- does *not* put on that null character. */
- glk_request_line_event(mainwin1, commandbuf1, 255, already1);
- inputpending1 = TRUE;
- }
-
- if (mainwin2 && !inputpending2) {
- glk_set_window(mainwin2);
- glk_put_string("\n>");
- /* See above. */
- glk_request_line_event(mainwin2, commandbuf2, 255, already2);
- inputpending2 = TRUE;
- }
-
- doneloop = FALSE;
- while (!doneloop) {
-
- /* Grab an event. */
- glk_select(&ev);
-
- switch (ev.type) {
-
- case evtype_LineInput:
- /* If the event comes from one main window or the other,
- we mark that window as no longer having line input
- pending. We also set commandbuf to point to the
- appropriate buffer. Then we leave the event loop. */
- if (mainwin1 && ev.win == mainwin1) {
- whichwin = mainwin1;
- inputpending1 = FALSE;
- cmd = commandbuf1;
- doneloop = TRUE;
- }
- else if (mainwin2 && ev.win == mainwin2) {
- whichwin = mainwin2;
- inputpending2 = FALSE;
- cmd = commandbuf2;
- doneloop = TRUE;
- }
- break;
-
- case evtype_CharInput:
- /* It's a key event, from one of the keywins. We
- call a subroutine rather than exiting the
- event loop (although I could have done it
- that way too.) */
- perform_key(ev.win, ev.val1);
- break;
-
- case evtype_Timer:
- /* It's a timer event. This does exit from the event
- loop, since we're going to interrupt input in
- mainwin1 and then re-print the prompt. */
- whichwin = NULL;
- cmd = NULL;
- doneloop = TRUE;
- break;
-
- case evtype_Arrange:
- /* Windows have changed size, so we have to redraw the
- status window and key window. But we stay in the
- event loop. */
- draw_statuswin();
- draw_keywins();
- break;
- }
- }
-
- if (cmd == NULL) {
- /* It was a timer event. */
- perform_timer();
- continue;
- }
-
- /* It was a line input event. cmd now points at a line of input
- from one of the main windows. */
-
- /* The line we have received in commandbuf is not null-terminated.
- We handle that first. */
- len = ev.val1; /* Will be between 0 and 255, inclusive. */
- cmd[len] = '\0';
-
- /* Then squash to lower-case. */
- for (cx = cmd; *cx; cx++) {
- *cx = glk_char_to_lower(*cx);
- }
-
- /* Then trim whitespace before and after. */
-
- for (cx = cmd; *cx == ' '; cx++, len--) { };
-
- cmd = cx;
-
- for (cx = cmd+len-1; cx >= cmd && *cx == ' '; cx--) { };
- *(cx+1) = '\0';
-
- /* cmd now points to a nice null-terminated string. We'll do the
- simplest possible parsing. */
- if (str_eq(cmd, "")) {
- glk_set_window(whichwin);
- glk_put_string("Excuse me?\n");
- }
- else if (str_eq(cmd, "help")) {
- verb_help(whichwin);
- }
- else if (str_eq(cmd, "yada")) {
- verb_yada(whichwin);
- }
- else if (str_eq(cmd, "both")) {
- verb_both(whichwin);
- }
- else if (str_eq(cmd, "clear")) {
- verb_clear(whichwin);
- }
- else if (str_eq(cmd, "page")) {
- verb_page(whichwin);
- }
- else if (str_eq(cmd, "pageboth")) {
- verb_pageboth(whichwin);
- }
- else if (str_eq(cmd, "timer")) {
- verb_timer(whichwin);
- }
- else if (str_eq(cmd, "untimer")) {
- verb_untimer(whichwin);
- }
- else if (str_eq(cmd, "chars")) {
- verb_chars(whichwin);
- }
- else if (str_eq(cmd, "jump")) {
- verb_jump(whichwin);
- }
- else if (str_eq(cmd, "quit")) {
- verb_quit(whichwin);
- }
- else {
- glk_set_window(whichwin);
- glk_put_string("I don't understand the command \"");
- glk_put_string(cmd);
- glk_put_string("\".\n");
- }
-
- if (whichwin == mainwin1)
- already1 = 0;
- else if (whichwin == mainwin2)
- already2 = 0;
- }
-}
-
-static void draw_statuswin(void)
-{
- glui32 width, height;
-
- if (!statuswin) {
- /* It is possible that the window was not successfully
- created. If that's the case, don't try to draw it. */
- return;
- }
-
- glk_set_window(statuswin);
- glk_window_clear(statuswin);
-
- glk_window_get_size(statuswin, &width, &height);
-
- /* Draw a decorative compass rose in the center. */
- width = (width/2);
- if (width > 0)
- width--;
- height = (height/2);
- if (height > 0)
- height--;
-
- glk_window_move_cursor(statuswin, width, height+0);
- glk_put_string("\\|/");
- glk_window_move_cursor(statuswin, width, height+1);
- glk_put_string("-*-");
- glk_window_move_cursor(statuswin, width, height+2);
- glk_put_string("/|\\");
-
-}
-
-/* This draws some corner decorations in *every* key window -- the
- one created at startup, and any later ones. It finds them all
- with glk_window_iterate. */
-static void draw_keywins(void)
-{
- winid_t win;
- glui32 rock;
- glui32 width, height;
-
- for (win = glk_window_iterate(NULL, &rock);
- win;
- win = glk_window_iterate(win, &rock)) {
- if (rock == KEYWINROCK) {
- glk_set_window(win);
- glk_window_clear(win);
- glk_window_get_size(win, &width, &height);
- glk_window_move_cursor(win, 0, 0);
- glk_put_char('O');
- glk_window_move_cursor(win, width-1, 0);
- glk_put_char('O');
- glk_window_move_cursor(win, 0, height-1);
- glk_put_char('O');
- glk_window_move_cursor(win, width-1, height-1);
- glk_put_char('O');
- }
- }
-}
-
-/* React to character input in a key window. */
-static void perform_key(winid_t win, glui32 key)
-{
- glui32 width, height, len;
- int ix;
- char buf[128], keyname[64];
-
- if (key == 'h' || key == 'v') {
- winid_t newwin;
- glui32 loc;
- /* Open a new keywindow. */
- if (key == 'h')
- loc = winmethod_Right | winmethod_Proportional;
- else
- loc = winmethod_Below | winmethod_Proportional;
- newwin = glk_window_open(win,
- loc, 50, wintype_TextGrid, KEYWINROCK);
- /* Since the new window has rock value KEYWINROCK, the
- draw_keywins() routine will redraw it. */
- if (newwin) {
- /* Request character input. In this program, only keywins
- get char input, so the CharInput events always call
- perform_key() -- and so the new window will respond
- to keys just as this one does. */
- glk_request_char_event(newwin);
- /* We now have to redraw the keywins, because any or all of
- them could have changed size when we opened newwin.
- glk_window_open() does not generate Arrange events; we
- have to do the redrawing manually. */
- draw_keywins();
- }
- /* Re-request character input for this window, so that future
- keys are accepted. */
- glk_request_char_event(win);
- return;
- }
- else if (key == 'c') {
- /* Close this keywindow. */
- glk_window_close(win, NULL);
- /* Again, any key windows could have changed size. Also the
- status window could have (if this was the last key window). */
- draw_keywins();
- draw_statuswin();
- return;
- }
-
- /* Print a string naming the key that was just hit. */
-
- switch (key) {
- case ' ':
- str_cpy(keyname, "space");
- break;
- case keycode_Left:
- str_cpy(keyname, "left");
- break;
- case keycode_Right:
- str_cpy(keyname, "right");
- break;
- case keycode_Up:
- str_cpy(keyname, "up");
- break;
- case keycode_Down:
- str_cpy(keyname, "down");
- break;
- case keycode_Return:
- str_cpy(keyname, "return");
- break;
- case keycode_Delete:
- str_cpy(keyname, "delete");
- break;
- case keycode_Escape:
- str_cpy(keyname, "escape");
- break;
- case keycode_Tab:
- str_cpy(keyname, "tab");
- break;
- case keycode_PageUp:
- str_cpy(keyname, "page up");
- break;
- case keycode_PageDown:
- str_cpy(keyname, "page down");
- break;
- case keycode_Home:
- str_cpy(keyname, "home");
- break;
- case keycode_End:
- str_cpy(keyname, "end");
- break;
- default:
- if (key >= keycode_Func1 && key < keycode_Func12) {
- str_cpy(keyname, "function key");
- }
- else if (key < 32) {
- str_cpy(keyname, "ctrl-");
- keyname[5] = '@' + key;
- keyname[6] = '\0';
- }
- else if (key <= 255) {
- keyname[0] = key;
- keyname[1] = '\0';
- }
- else {
- str_cpy(keyname, "unknown key");
- }
- break;
- }
-
- str_cpy(buf, "Key: ");
- str_cat(buf, keyname);
-
- len = str_len(buf);
-
- /* Print the string centered in this window. */
- glk_set_window(win);
- glk_window_get_size(win, &width, &height);
- glk_window_move_cursor(win, 0, height/2);
- for (ix=0; ix<width; ix++)
- glk_put_char(' ');
-
- width = width/2;
- len = len/2;
-
- if (width > len)
- width = width-len;
- else
- width = 0;
-
- glk_window_move_cursor(win, width, height/2);
- glk_put_string(buf);
-
- /* Re-request character input for this window, so that future
- keys are accepted. */
- glk_request_char_event(win);
-}
-
-/* React to a timer event. This just prints "Tick" in mainwin1, but it
- first has to cancel line input if any is pending. */
-static void perform_timer()
-{
- event_t ev;
-
- if (!mainwin1)
- return;
-
- if (inputpending1) {
- glk_cancel_line_event(mainwin1, &ev);
- if (ev.type == evtype_LineInput)
- already1 = ev.val1;
- inputpending1 = FALSE;
- }
-
- glk_set_window(mainwin1);
- glk_put_string("Tick.\n");
-}
-
-/* This is a utility function. Given a main window, it finds the
- "other" main window (if both actually exist) and cancels line
- input in that other window (if input is pending.) It does not
- set the output stream to point there, however. If there is only
- one main window, this returns 0. */
-static winid_t print_to_otherwin(winid_t win)
-{
- winid_t otherwin = NULL;
- event_t ev;
-
- if (win == mainwin1) {
- if (mainwin2) {
- otherwin = mainwin2;
- glk_cancel_line_event(mainwin2, &ev);
- if (ev.type == evtype_LineInput)
- already2 = ev.val1;
- inputpending2 = FALSE;
- }
- }
- else if (win == mainwin2) {
- if (mainwin1) {
- otherwin = mainwin1;
- glk_cancel_line_event(mainwin1, &ev);
- if (ev.type == evtype_LineInput)
- already1 = ev.val1;
- inputpending1 = FALSE;
- }
- }
-
- return otherwin;
-}
-
-static void verb_help(winid_t win)
-{
- glk_set_window(win);
-
- glk_put_string("This model only understands the following commands:\n");
- glk_put_string("HELP: Display this list.\n");
- glk_put_string("JUMP: Print a short message.\n");
- glk_put_string("YADA: Print a long paragraph.\n");
- glk_put_string("BOTH: Print a short message in both main windows.\n");
- glk_put_string("CLEAR: Clear one window.\n");
- glk_put_string("PAGE: Print thirty lines, demonstrating paging.\n");
- glk_put_string("PAGEBOTH: Print thirty lines in each window.\n");
- glk_put_string("TIMER: Turn on a timer, which ticks in the upper ");
- glk_put_string("main window every three seconds.\n");
- glk_put_string("UNTIMER: Turns off the timer.\n");
- glk_put_string("CHARS: Prints the entire Latin-1 character set.\n");
- glk_put_string("QUIT: Quit and exit.\n");
-}
-
-static void verb_jump(winid_t win)
-{
- glk_set_window(win);
-
- glk_put_string("You jump on the fruit, spotlessly.\n");
-}
-
-/* Print some text in both windows. This uses print_to_otherwin() to
- find the other window and prepare it for printing. */
-static void verb_both(winid_t win)
-{
- winid_t otherwin;
-
- glk_set_window(win);
- glk_put_string("Something happens in this window.\n");
-
- otherwin = print_to_otherwin(win);
-
- if (otherwin) {
- glk_set_window(otherwin);
- glk_put_string("Something happens in the other window.\n");
- }
-}
-
-/* Clear a window. */
-static void verb_clear(winid_t win)
-{
- glk_window_clear(win);
-}
-
-/* Print thirty lines. */
-static void verb_page(winid_t win)
-{
- int ix;
- char buf[32];
-
- glk_set_window(win);
- for (ix=0; ix<30; ix++) {
- num_to_str(buf, ix);
- glk_put_string(buf);
- glk_put_char('\n');
- }
-}
-
-/* Print thirty lines in both windows. This gets fancy by printing
- to each window alternately, without setting the output stream,
- by using glk_put_string_stream() instead of glk_put_string().
- There's no particular difference; this is just a demonstration. */
-static void verb_pageboth(winid_t win)
-{
- int ix;
- winid_t otherwin;
- strid_t str, otherstr;
- char buf[32];
-
- str = glk_window_get_stream(win);
- otherwin = print_to_otherwin(win);
- if (otherwin)
- otherstr = glk_window_get_stream(otherwin);
- else
- otherstr = NULL;
-
- for (ix=0; ix<30; ix++) {
- num_to_str(buf, ix);
- str_cat(buf, "\n");
- glk_put_string_stream(str, buf);
- if (otherstr)
- glk_put_string_stream(otherstr, buf);
- }
-}
-
-/* Turn on the timer. The timer prints a tick in mainwin1 every three
- seconds. */
-static void verb_timer(winid_t win)
-{
- glk_set_window(win);
-
- if (timer_on) {
- glk_put_string("The timer is already running.\n");
- return;
- }
-
- if (glk_gestalt(gestalt_Timer, 0) == 0) {
- glk_put_string("Your Glk library does not support timer events.\n");
- return;
- }
-
- glk_put_string("A timer starts running in the upper window.\n");
- glk_request_timer_events(3000); /* Every three seconds. */
- timer_on = TRUE;
-}
-
-/* Turn off the timer. */
-static void verb_untimer(winid_t win)
-{
- glk_set_window(win);
-
- if (!timer_on) {
- glk_put_string("The timer is not currently running.\n");
- return;
- }
-
- glk_put_string("The timer stops running.\n");
- glk_request_timer_events(0);
- timer_on = FALSE;
-}
-
-/* Print every character, or rather try to. */
-static void verb_chars(winid_t win)
-{
- int ix;
- char buf[16];
-
- glk_set_window(win);
-
- for (ix=0; ix<256; ix++) {
- num_to_str(buf, ix);
- glk_put_string(buf);
- glk_put_string(": ");
- glk_put_char(ix);
- glk_put_char('\n');
- }
-}
-
-static void verb_yada(winid_t win)
-{
- /* This is a goofy (and overly ornate) way to print a long paragraph.
- It just shows off line wrapping in the Glk implementation. */
- #define NUMWORDS (13)
- static char *wordcaplist[NUMWORDS] = {
- "Ga", "Bo", "Wa", "Mu", "Bi", "Fo", "Za", "Mo", "Ra", "Po",
- "Ha", "Ni", "Na"
- };
- static char *wordlist[NUMWORDS] = {
- "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble",
- "gazoon", "ting", "floo", "zonk", "loof", "lob",
- };
- static int wcount1 = 0;
- static int wcount2 = 0;
- static int wstep = 1;
- static int jx = 0;
- int ix;
- int first = TRUE;
-
- glk_set_window(win);
-
- for (ix=0; ix<85; ix++) {
- if (ix > 0) {
- glk_put_string(" ");
- }
-
- if (first) {
- glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]);
- first = FALSE;
- }
-
- glk_put_string(wordlist[jx]);
- jx = (jx + wstep) % NUMWORDS;
- wcount1++;
- if (wcount1 >= NUMWORDS) {
- wcount1 = 0;
- wstep++;
- wcount2++;
- if (wcount2 >= NUMWORDS-2) {
- wcount2 = 0;
- wstep = 1;
- }
- }
-
- if ((ix % 17) == 16) {
- glk_put_string(".");
- first = TRUE;
- }
- }
-
- glk_put_char('\n');
-}
-
-static void verb_quit(winid_t win)
-{
- glk_set_window(win);
-
- glk_put_string("Thanks for playing.\n");
- glk_exit();
- /* glk_exit() actually stops the process; it does not return. */
-}
-
-/* simple string length test */
-static int str_len(char *s1)
-{
- int len;
- for (len = 0; *s1; s1++)
- len++;
- return len;
-}
-
-/* simple string comparison test */
-static int str_eq(char *s1, char *s2)
-{
- for (; *s1 && *s2; s1++, s2++) {
- if (*s1 != *s2)
- return FALSE;
- }
-
- if (*s1 || *s2)
- return FALSE;
- else
- return TRUE;
-}
-
-/* simple string copy */
-static char *str_cpy(char *s1, char *s2)
-{
- char *orig = s1;
-
- for (; *s2; s1++, s2++)
- *s1 = *s2;
- *s1 = '\0';
-
- return orig;
-}
-
-/* simple string concatenate */
-static char *str_cat(char *s1, char *s2)
-{
- char *orig = s1;
-
- while (*s1)
- s1++;
- for (; *s2; s1++, s2++)
- *s1 = *s2;
- *s1 = '\0';
-
- return orig;
-}
-
-/* simple number printer */
-static void num_to_str(char *buf, int num)
-{
- int ix;
- int size = 0;
- char tmpc;
-
- if (num == 0) {
- str_cpy(buf, "0");
- return;
- }
-
- if (num < 0) {
- buf[0] = '-';
- buf++;
- num = -num;
- }
-
- while (num) {
- buf[size] = '0' + (num % 10);
- size++;
- num /= 10;
- }
- for (ix=0; ix<size/2; ix++) {
- tmpc = buf[ix];
- buf[ix] = buf[size-ix-1];
- buf[size-ix-1] = tmpc;
- }
- buf[size] = '\0';
-}