1 #include <libchimara/glk.h>
3 /* multiwin.c: Sample program for Glk API, version 0.5.
4 Designed by Andrew Plotkin <erkyrath@eblong.com>
5 http://www.eblong.com/zarf/glk/index.html
6 This program is in the public domain.
9 /* This example demonstrates multiple windows and timed input in the
12 /* This is the cleanest possible form of a Glk program. It includes only
13 "glk.h", and doesn't call any functions outside Glk at all. We even
14 define our own string functions, rather than relying on the
15 standard libraries. */
17 /* We also define our own TRUE and FALSE and NULL. */
28 /* The story and status windows. */
29 static winid_t mainwin1 = NULL;
30 static winid_t mainwin2 = NULL;
31 static winid_t statuswin = NULL;
33 /* Key windows don't get stored in a global variable; we'll find them
34 by iterating over the list and looking for this rock value. */
35 #define KEYWINROCK (97)
37 /* For the two main windows, we keep a flag saying whether that window
38 has a line input request pending. (Because if it does, we need to
39 cancel the line input before printing to that window.) */
40 static int inputpending1, inputpending2;
41 /* When we cancel line input, we should remember how many characters
42 had been typed. This lets us restart the input with those characters
44 static int already1, already2;
46 /* There's a three-second timer which can be on or off. */
47 static int timer_on = FALSE;
49 /* Forward declarations */
52 static void draw_statuswin(void);
53 static void draw_keywins(void);
54 static void perform_key(winid_t win, glui32 key);
55 static void perform_timer(void);
57 static int str_eq(char *s1, char *s2);
58 static int str_len(char *s1);
59 static char *str_cpy(char *s1, char *s2);
60 static char *str_cat(char *s1, char *s2);
61 static void num_to_str(char *buf, int num);
63 static void verb_help(winid_t win);
64 static void verb_jump(winid_t win);
65 static void verb_yada(winid_t win);
66 static void verb_both(winid_t win);
67 static void verb_clear(winid_t win);
68 static void verb_page(winid_t win);
69 static void verb_pageboth(winid_t win);
70 static void verb_timer(winid_t win);
71 static void verb_untimer(winid_t win);
72 static void verb_chars(winid_t win);
73 static void verb_quit(winid_t win);
75 /* The glk_main() function is called by the Glk system; it's the main entry
76 point for your program. */
79 char commandbuf1[256]; /* For mainwin1 */
80 char commandbuf2[256]; /* For mainwin2 */
82 /* Open the main windows. */
83 mainwin1 = glk_window_open(0, 0, 0, wintype_TextBuffer, 1);
85 /* It's possible that the main window failed to open. There's
86 nothing we can do without it, so exit. */
90 /* Open a second window: a text grid, above the main window, five
91 lines high. It is possible that this will fail also, but we accept
93 statuswin = glk_window_open(mainwin1,
94 winmethod_Above | winmethod_Fixed,
95 5, wintype_TextGrid, 0);
97 /* And a third window, a second story window below the main one. */
98 mainwin2 = glk_window_open(mainwin1,
99 winmethod_Below | winmethod_Proportional,
100 50, wintype_TextBuffer, 0);
102 /* We're going to be switching from one window to another all the
103 time. So we'll be setting the output stream on a case-by-case
104 basis. Every function that prints must set the output stream
105 first. (Contrast model.c, where the output stream is always the
106 main window, and every function that changes that must set it
109 glk_set_window(mainwin1);
110 glk_put_string("Multiwin\nAn Interactive Sample Glk Program\n");
111 glk_put_string("By Andrew Plotkin.\nRelease 3.\n");
112 glk_put_string("Type \"help\" for a list of commands.\n");
114 glk_set_window(mainwin2);
115 glk_put_string("Note that the upper left-hand window accepts character");
116 glk_put_string(" input. Hit 'h' to split the window horizontally, 'v' to");
117 glk_put_string(" split the window vertically, 'c' to close a window,");
118 glk_put_string(" and any other key (including special keys) to display");
119 glk_put_string(" key codes. All new windows accept these same keys as");
120 glk_put_string(" well.\n\n");
121 glk_put_string("This bottom window accepts normal line input.\n");
124 /* For fun, let's open a fourth window now, splitting the status
127 keywin = glk_window_open(statuswin,
128 winmethod_Left | winmethod_Proportional,
129 66, wintype_TextGrid, KEYWINROCK);
131 glk_request_char_event(keywin);
135 /* Draw the key window now, since we don't draw it every input (as
136 we do the status window. */
139 inputpending1 = FALSE;
140 inputpending2 = FALSE;
151 /* We're not redrawing the key windows every command. */
153 /* Either main window, or both, could already have line input
154 pending. If so, leave that window alone. If there is no
155 input pending on a window, set a line input request, but
156 keep around any characters that were in the buffer already. */
158 if (mainwin1 && !inputpending1) {
159 glk_set_window(mainwin1);
160 glk_put_string("\n>");
161 /* We request up to 255 characters. The buffer can hold 256,
162 but we are going to stick a null character at the end, so
163 we have to leave room for that. Note that the Glk library
164 does *not* put on that null character. */
165 glk_request_line_event(mainwin1, commandbuf1, 255, already1);
166 inputpending1 = TRUE;
169 if (mainwin2 && !inputpending2) {
170 glk_set_window(mainwin2);
171 glk_put_string("\n>");
173 glk_request_line_event(mainwin2, commandbuf2, 255, already2);
174 inputpending2 = TRUE;
185 case evtype_LineInput:
186 /* If the event comes from one main window or the other,
187 we mark that window as no longer having line input
188 pending. We also set commandbuf to point to the
189 appropriate buffer. Then we leave the event loop. */
190 if (mainwin1 && ev.win == mainwin1) {
192 inputpending1 = FALSE;
196 else if (mainwin2 && ev.win == mainwin2) {
198 inputpending2 = FALSE;
204 case evtype_CharInput:
205 /* It's a key event, from one of the keywins. We
206 call a subroutine rather than exiting the
207 event loop (although I could have done it
209 perform_key(ev.win, ev.val1);
213 /* It's a timer event. This does exit from the event
214 loop, since we're going to interrupt input in
215 mainwin1 and then re-print the prompt. */
222 /* Windows have changed size, so we have to redraw the
223 status window and key window. But we stay in the
232 /* It was a timer event. */
237 /* It was a line input event. cmd now points at a line of input
238 from one of the main windows. */
240 /* The line we have received in commandbuf is not null-terminated.
241 We handle that first. */
242 len = ev.val1; /* Will be between 0 and 255, inclusive. */
245 /* Then squash to lower-case. */
246 for (cx = cmd; *cx; cx++) {
247 *cx = glk_char_to_lower(*cx);
250 /* Then trim whitespace before and after. */
252 for (cx = cmd; *cx == ' '; cx++, len--) { };
256 for (cx = cmd+len-1; cx >= cmd && *cx == ' '; cx--) { };
259 /* cmd now points to a nice null-terminated string. We'll do the
260 simplest possible parsing. */
261 if (str_eq(cmd, "")) {
262 glk_set_window(whichwin);
263 glk_put_string("Excuse me?\n");
265 else if (str_eq(cmd, "help")) {
268 else if (str_eq(cmd, "yada")) {
271 else if (str_eq(cmd, "both")) {
274 else if (str_eq(cmd, "clear")) {
275 verb_clear(whichwin);
277 else if (str_eq(cmd, "page")) {
280 else if (str_eq(cmd, "pageboth")) {
281 verb_pageboth(whichwin);
283 else if (str_eq(cmd, "timer")) {
284 verb_timer(whichwin);
286 else if (str_eq(cmd, "untimer")) {
287 verb_untimer(whichwin);
289 else if (str_eq(cmd, "chars")) {
290 verb_chars(whichwin);
292 else if (str_eq(cmd, "jump")) {
295 else if (str_eq(cmd, "quit")) {
299 glk_set_window(whichwin);
300 glk_put_string("I don't understand the command \"");
302 glk_put_string("\".\n");
305 if (whichwin == mainwin1)
307 else if (whichwin == mainwin2)
312 static void draw_statuswin(void)
314 glui32 width, height;
317 /* It is possible that the window was not successfully
318 created. If that's the case, don't try to draw it. */
322 glk_set_window(statuswin);
323 glk_window_clear(statuswin);
325 glk_window_get_size(statuswin, &width, &height);
327 /* Draw a decorative compass rose in the center. */
335 glk_window_move_cursor(statuswin, width, height+0);
336 glk_put_string("\\|/");
337 glk_window_move_cursor(statuswin, width, height+1);
338 glk_put_string("-*-");
339 glk_window_move_cursor(statuswin, width, height+2);
340 glk_put_string("/|\\");
344 /* This draws some corner decorations in *every* key window -- the
345 one created at startup, and any later ones. It finds them all
346 with glk_window_iterate. */
347 static void draw_keywins(void)
351 glui32 width, height;
353 for (win = glk_window_iterate(NULL, &rock);
355 win = glk_window_iterate(win, &rock)) {
356 if (rock == KEYWINROCK) {
358 glk_window_clear(win);
359 glk_window_get_size(win, &width, &height);
360 glk_window_move_cursor(win, 0, 0);
362 glk_window_move_cursor(win, width-1, 0);
364 glk_window_move_cursor(win, 0, height-1);
366 glk_window_move_cursor(win, width-1, height-1);
372 /* React to character input in a key window. */
373 static void perform_key(winid_t win, glui32 key)
375 glui32 width, height, len;
377 char buf[128], keyname[64];
379 if (key == 'h' || key == 'v') {
382 /* Open a new keywindow. */
384 loc = winmethod_Right | winmethod_Proportional;
386 loc = winmethod_Below | winmethod_Proportional;
387 newwin = glk_window_open(win,
388 loc, 50, wintype_TextGrid, KEYWINROCK);
389 /* Since the new window has rock value KEYWINROCK, the
390 draw_keywins() routine will redraw it. */
392 /* Request character input. In this program, only keywins
393 get char input, so the CharInput events always call
394 perform_key() -- and so the new window will respond
395 to keys just as this one does. */
396 glk_request_char_event(newwin);
397 /* We now have to redraw the keywins, because any or all of
398 them could have changed size when we opened newwin.
399 glk_window_open() does not generate Arrange events; we
400 have to do the redrawing manually. */
403 /* Re-request character input for this window, so that future
404 keys are accepted. */
405 glk_request_char_event(win);
408 else if (key == 'c') {
409 /* Close this keywindow. */
410 glk_window_close(win, NULL);
411 /* Again, any key windows could have changed size. Also the
412 status window could have (if this was the last key window). */
418 /* Print a string naming the key that was just hit. */
422 str_cpy(keyname, "space");
425 str_cpy(keyname, "left");
428 str_cpy(keyname, "right");
431 str_cpy(keyname, "up");
434 str_cpy(keyname, "down");
437 str_cpy(keyname, "return");
440 str_cpy(keyname, "delete");
443 str_cpy(keyname, "escape");
446 str_cpy(keyname, "tab");
449 str_cpy(keyname, "page up");
451 case keycode_PageDown:
452 str_cpy(keyname, "page down");
455 str_cpy(keyname, "home");
458 str_cpy(keyname, "end");
461 if (key <= keycode_Func1 && key >= keycode_Func12) {
462 str_cpy(keyname, "function key");
465 str_cpy(keyname, "ctrl-");
466 keyname[5] = '@' + key;
469 else if (key <= 255) {
474 str_cpy(keyname, "unknown key");
479 str_cpy(buf, "Key: ");
480 str_cat(buf, keyname);
484 /* Print the string centered in this window. */
486 glk_window_get_size(win, &width, &height);
487 glk_window_move_cursor(win, 0, height/2);
488 for (ix=0; ix<width; ix++)
499 glk_window_move_cursor(win, width, height/2);
502 /* Re-request character input for this window, so that future
503 keys are accepted. */
504 glk_request_char_event(win);
507 /* React to a timer event. This just prints "Tick" in mainwin1, but it
508 first has to cancel line input if any is pending. */
509 static void perform_timer()
517 glk_cancel_line_event(mainwin1, &ev);
518 if (ev.type == evtype_LineInput)
520 inputpending1 = FALSE;
523 glk_set_window(mainwin1);
524 glk_put_string("Tick.\n");
527 /* This is a utility function. Given a main window, it finds the
528 "other" main window (if both actually exist) and cancels line
529 input in that other window (if input is pending.) It does not
530 set the output stream to point there, however. If there is only
531 one main window, this returns 0. */
532 static winid_t print_to_otherwin(winid_t win)
534 winid_t otherwin = NULL;
537 if (win == mainwin1) {
540 glk_cancel_line_event(mainwin2, &ev);
541 if (ev.type == evtype_LineInput)
543 inputpending2 = FALSE;
546 else if (win == mainwin2) {
549 glk_cancel_line_event(mainwin1, &ev);
550 if (ev.type == evtype_LineInput)
552 inputpending1 = FALSE;
559 static void verb_help(winid_t win)
563 glk_put_string("This model only understands the following commands:\n");
564 glk_put_string("HELP: Display this list.\n");
565 glk_put_string("JUMP: Print a short message.\n");
566 glk_put_string("YADA: Print a long paragraph.\n");
567 glk_put_string("BOTH: Print a short message in both main windows.\n");
568 glk_put_string("CLEAR: Clear one window.\n");
569 glk_put_string("PAGE: Print thirty lines, demonstrating paging.\n");
570 glk_put_string("PAGEBOTH: Print thirty lines in each window.\n");
571 glk_put_string("TIMER: Turn on a timer, which ticks in the upper ");
572 glk_put_string("main window every three seconds.\n");
573 glk_put_string("UNTIMER: Turns off the timer.\n");
574 glk_put_string("CHARS: Prints the entire Latin-1 character set.\n");
575 glk_put_string("QUIT: Quit and exit.\n");
578 static void verb_jump(winid_t win)
582 glk_put_string("You jump on the fruit, spotlessly.\n");
585 /* Print some text in both windows. This uses print_to_otherwin() to
586 find the other window and prepare it for printing. */
587 static void verb_both(winid_t win)
592 glk_put_string("Something happens in this window.\n");
594 otherwin = print_to_otherwin(win);
597 glk_set_window(otherwin);
598 glk_put_string("Something happens in the other window.\n");
602 /* Clear a window. */
603 static void verb_clear(winid_t win)
605 glk_window_clear(win);
608 /* Print thirty lines. */
609 static void verb_page(winid_t win)
615 for (ix=0; ix<30; ix++) {
622 /* Print thirty lines in both windows. This gets fancy by printing
623 to each window alternately, without setting the output stream,
624 by using glk_put_string_stream() instead of glk_put_string().
625 There's no particular difference; this is just a demonstration. */
626 static void verb_pageboth(winid_t win)
630 strid_t str, otherstr;
633 str = glk_window_get_stream(win);
634 otherwin = print_to_otherwin(win);
636 otherstr = glk_window_get_stream(otherwin);
640 for (ix=0; ix<30; ix++) {
643 glk_put_string_stream(str, buf);
645 glk_put_string_stream(otherstr, buf);
649 /* Turn on the timer. The timer prints a tick in mainwin1 every three
651 static void verb_timer(winid_t win)
656 glk_put_string("The timer is already running.\n");
660 if (glk_gestalt(gestalt_Timer, 0) == 0) {
661 glk_put_string("Your Glk library does not support timer events.\n");
665 glk_put_string("A timer starts running in the upper window.\n");
666 glk_request_timer_events(3000); /* Every three seconds. */
670 /* Turn off the timer. */
671 static void verb_untimer(winid_t win)
676 glk_put_string("The timer is not currently running.\n");
680 glk_put_string("The timer stops running.\n");
681 glk_request_timer_events(0);
685 /* Print every character, or rather try to. */
686 static void verb_chars(winid_t win)
693 for (ix=0; ix<256; ix++) {
696 glk_put_string(": ");
702 static void verb_yada(winid_t win)
704 /* This is a goofy (and overly ornate) way to print a long paragraph.
705 It just shows off line wrapping in the Glk implementation. */
706 #define NUMWORDS (13)
707 static char *wordcaplist[NUMWORDS] = {
708 "Ga", "Bo", "Wa", "Mu", "Bi", "Fo", "Za", "Mo", "Ra", "Po",
711 static char *wordlist[NUMWORDS] = {
712 "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble",
713 "gazoon", "ting", "floo", "zonk", "loof", "lob",
715 static int wcount1 = 0;
716 static int wcount2 = 0;
717 static int wstep = 1;
724 for (ix=0; ix<85; ix++) {
730 glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]);
734 glk_put_string(wordlist[jx]);
735 jx = (jx + wstep) % NUMWORDS;
737 if (wcount1 >= NUMWORDS) {
741 if (wcount2 >= NUMWORDS-2) {
747 if ((ix % 17) == 16) {
756 static void verb_quit(winid_t win)
760 glk_put_string("Thanks for playing.\n");
762 /* glk_exit() actually stops the process; it does not return. */
765 /* simple string length test */
766 static int str_len(char *s1)
769 for (len = 0; *s1; s1++)
774 /* simple string comparison test */
775 static int str_eq(char *s1, char *s2)
777 for (; *s1 && *s2; s1++, s2++) {
788 /* simple string copy */
789 static char *str_cpy(char *s1, char *s2)
793 for (; *s2; s1++, s2++)
800 /* simple string concatenate */
801 static char *str_cat(char *s1, char *s2)
807 for (; *s2; s1++, s2++)
814 /* simple number printer */
815 static void num_to_str(char *buf, int num)
833 buf[size] = '0' + (num % 10);
837 for (ix=0; ix<size/2; ix++) {
839 buf[ix] = buf[size-ix-1];
840 buf[size-ix-1] = tmpc;