/**************************************************************************** * Copyright (c) 1998 Free Software Foundation, Inc. * * * * Permission is hereby granted, free of charge, to any person obtaining a * * copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, distribute with modifications, sublicense, and/or sell * * copies of the Software, and to permit persons to whom the Software is * * furnished to do so, subject to the following conditions: * * * * The above copyright notice and this permission notice shall be included * * in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * * * Except as contained in this notice, the name(s) of the above copyright * * holders shall not be used in advertising or otherwise to promote the * * sale, use or other dealings in this Software without prior written * * authorization. * ****************************************************************************/ /**************************************************************************** NAME ncurses.c --- ncurses library exerciser SYNOPSIS ncurses DESCRIPTION An interactive test module for the ncurses library. AUTHOR Author: Eric S. Raymond 1993 $Id: ncurses.c,v 1.108 1998/02/28 01:11:47 tom Exp $ ***************************************************************************/ #include #include #include #include #include #include #if HAVE_LOCALE_H #include #endif #if HAVE_GETTIMEOFDAY #if HAVE_SYS_TIME_H && HAVE_SYS_TIME_SELECT #include #endif #if HAVE_SYS_SELECT_H #include #endif #endif #if HAVE_PANEL_H #define USE_LIBPANEL 1 #include #else #define USE_LIBPANEL 0 #endif #if HAVE_MENU_H && HAVE_LIBMENU #define USE_LIBMENU 1 #include #else #define USE_LIBMENU 0 #endif #if HAVE_FORM_H && HAVE_LIBFORM #define USE_LIBFORM 1 #include #else #define USE_LIBFORM 0 #endif #ifdef NCURSES_VERSION #ifdef TRACE static int save_trace = TRACE_ORDINARY|TRACE_CALLS; extern int _nc_tracing; #endif #if !HAVE_NAPMS #define HAVE_NAPMS 1 #endif #else #define mmask_t chtype /* not specified in XSI */ #define attr_t chtype /* not specified in XSI */ #define ACS_S3 (acs_map['p']) /* scan line 3 */ #define ACS_S7 (acs_map['r']) /* scan line 7 */ #define ACS_LEQUAL (acs_map['y']) /* less/equal */ #define ACS_GEQUAL (acs_map['z']) /* greater/equal */ #define ACS_PI (acs_map['{']) /* Pi */ #define ACS_NEQUAL (acs_map['|']) /* not equal */ #define ACS_STERLING (acs_map['}']) /* UK pound sign */ #endif #define P(string) printw("%s\n", string) #ifndef CTRL #define CTRL(x) ((x) & 0x1f) #endif #define QUIT CTRL('Q') #define ESCAPE CTRL('[') #define BLANK ' ' /* this is the background character */ /* The behavior of mvhline, mvvline for negative/zero length is unspecified, * though we can rely on negative x/y values to stop the macro. */ static void do_h_line(int y, int x, chtype c, int to) { if ((to) > (x)) mvhline(y, x, c, (to) - (x)); } static void do_v_line(int y, int x, chtype c, int to) { if ((to) > (y)) mvvline(y, x, c, (to) - (y)); } /* Common function to allow ^T to toggle trace-mode in the middle of a test * so that trace-files can be made smaller. */ static int wGetchar(WINDOW *win) { int c; #ifdef TRACE while ((c = wgetch(win)) == CTRL('T')) { if (_nc_tracing) { save_trace = _nc_tracing; _tracef("TOGGLE-TRACING OFF"); _nc_tracing = 0; } else { _nc_tracing = save_trace; } trace(_nc_tracing); if (_nc_tracing) _tracef("TOGGLE-TRACING ON"); } #else c = wgetch(win); #endif return c; } #define Getchar() wGetchar(stdscr) static void Pause(void) { move(LINES - 1, 0); addstr("Press any key to continue... "); (void) Getchar(); } static void Cannot(const char *what) { printw("\nThis %s terminal %s\n\n", getenv("TERM"), what); Pause(); } static void ShellOut(bool message) { if (message) addstr("Shelling out..."); def_prog_mode(); endwin(); system("sh"); if (message) addstr("returned from shellout.\n"); refresh(); } /**************************************************************************** * * Character input test * ****************************************************************************/ static void getch_test(void) /* test the keypad feature */ { char buf[BUFSIZ]; int c; int incount = 0, firsttime = 0; bool blocking = TRUE; int y, x; refresh(); #ifdef NCURSES_MOUSE_VERSION mousemask(ALL_MOUSE_EVENTS, (mmask_t *)0); #endif (void) printw("Delay in 10ths of a second ( for blocking input)? "); echo(); getstr(buf); noecho(); nonl(); if (isdigit(buf[0])) { timeout(atoi(buf) * 100); blocking = FALSE; } c = '?'; raw(); for (;;) { if (firsttime++) { printw("Key pressed: %04o ", c); #ifdef NCURSES_MOUSE_VERSION if (c == KEY_MOUSE) { MEVENT event; getmouse(&event); printw("KEY_MOUSE, %s\n", _tracemouse(&event)); } else #endif /* NCURSES_MOUSE_VERSION */ if (c >= KEY_MIN) { (void) addstr(keyname(c)); addch('\n'); } else if (c > 0x80) { int c2 = (c & 0x7f); if (isprint(c2)) (void) printw("M-%c", c2); else (void) printw("M-%s", unctrl(c2)); addstr(" (high-half character)\n"); } else { if (isprint(c)) (void) printw("%c (ASCII printable character)\n", c); else (void) printw("%s (ASCII control character)\n", unctrl(c)); } getyx(stdscr, y, x); if (y >= LINES-1) move(0,0); clrtoeol(); } if (c == 'g') { addstr("getstr test: "); echo(); getstr(buf); noecho(); printw("I saw `%s'.\n", buf); } if (c == 's') { ShellOut(TRUE); } if (c == 'x' || c == 'q' || (c == ERR && blocking)) break; if (c == '?') { addstr("Type any key to see its keypad value. Also:\n"); addstr("g -- triggers a getstr test\n"); addstr("s -- shell out\n"); addstr("q -- quit\n"); addstr("? -- repeats this help message\n"); } while ((c = Getchar()) == ERR) if (!blocking) (void) printw("%05d: input timed out\n", incount++); else { (void) printw("%05d: input error\n", incount++); break; } } #ifdef NCURSES_MOUSE_VERSION mousemask(0, (mmask_t *)0); #endif timeout(-1); erase(); noraw(); nl(); endwin(); } static int show_attr(int row, int skip, chtype attr, const char *name, bool once) { mvprintw(row, 8, "%s mode:", name); mvprintw(row, 24, "|"); if (skip) printw("%*s", skip, " "); if (once) attron(attr); else attrset(attr); addstr("abcde fghij klmno pqrst uvwxy z"); if (once) attroff(attr); if (skip) printw("%*s", skip, " "); printw("|"); if (attr != A_NORMAL && !(termattrs() & attr)) printw(" (N/A)"); return row + 2; } static bool attr_getc(int *skip, int *fg, int *bg) { int ch = Getchar(); if (isdigit(ch)) { *skip = (ch - '0'); return TRUE; } else if (ch == CTRL('L')) { touchwin(stdscr); touchwin(curscr); return TRUE; } else if (has_colors()) { switch (ch) { case 'f': *fg = (*fg + 1); break; case 'F': *fg = (*fg - 1); break; case 'b': *bg = (*bg + 1); break; case 'B': *bg = (*bg - 1); break; default: return FALSE; } if (*fg >= COLORS) *fg = 0; if (*fg < 0) *fg = COLORS - 1; if (*bg >= COLORS) *bg = 0; if (*bg < 0) *bg = COLORS - 1; return TRUE; } return FALSE; } static void attr_test(void) /* test text attributes */ { int n; int skip = tigetnum("xmc"); int fg = COLOR_WHITE; int bg = COLOR_BLACK; bool *pairs = (bool *)calloc(COLOR_PAIRS, sizeof(bool)); pairs[0] = TRUE; if (skip < 0) skip = 0; n = skip; /* make it easy */ do { int row = 2; int normal = A_NORMAL | BLANK; if (has_colors()) { int pair = (fg * COLORS) + bg; if (!pairs[pair]) { init_pair(pair, fg, bg); pairs[pair] = TRUE; } normal |= COLOR_PAIR(pair); } bkgdset(normal); erase(); mvaddstr(0, 20, "Character attribute test display"); row = show_attr(row, n, A_STANDOUT, "STANDOUT", TRUE); row = show_attr(row, n, A_REVERSE, "REVERSE", TRUE); row = show_attr(row, n, A_BOLD, "BOLD", TRUE); row = show_attr(row, n, A_UNDERLINE, "UNDERLINE", TRUE); row = show_attr(row, n, A_DIM, "DIM", TRUE); row = show_attr(row, n, A_BLINK, "BLINK", TRUE); row = show_attr(row, n, A_PROTECT, "PROTECT", TRUE); row = show_attr(row, n, A_INVIS, "INVISIBLE", TRUE); row = show_attr(row, n, A_NORMAL, "NORMAL", FALSE); mvprintw(row, 8, "This terminal does %shave the magic-cookie glitch", tigetnum("xmc") > -1 ? "" : "not "); mvprintw(row+1, 8, "Enter a digit to set gaps on each side of displayed attributes"); mvprintw(row+2, 8, "^L = repaint"); if (has_colors()) printw(". f/F/b/F toggle colors (now %d/%d)", fg, bg); refresh(); } while (attr_getc(&n, &fg, &bg)); bkgdset(A_NORMAL | BLANK); erase(); endwin(); } /**************************************************************************** * * Color support tests * ****************************************************************************/ static NCURSES_CONST char *color_names[] = { "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white" }; static void show_color_name(int y, int x, int color) { if (COLORS > 8) mvprintw(y, x, "%02d ", color); else mvaddstr(y, x, color_names[color]); } static void color_test(void) /* generate a color test pattern */ { int i; int base, top, width; NCURSES_CONST char *hello; refresh(); (void) printw("There are %d color pairs\n", COLOR_PAIRS); width = (COLORS > 8) ? 4 : 8; hello = (COLORS > 8) ? "Test" : "Hello"; for (base = 0; base < 2; base++) { top = (COLORS > 8) ? 0 : base * (COLORS+3); clrtobot(); (void) mvprintw(top + 1, 0, "%dx%d matrix of foreground/background colors, bright *%s*\n", COLORS, COLORS, base ? "on" : "off"); for (i = 0; i < COLORS; i++) show_color_name(top + 2, (i+1) * width, i); for (i = 0; i < COLORS; i++) show_color_name(top + 3 + i, 0, i); for (i = 1; i < COLOR_PAIRS; i++) { init_pair(i, i % COLORS, i / COLORS); attron((attr_t)COLOR_PAIR(i)); if (base) attron((attr_t)A_BOLD); mvaddstr(top + 3 + (i / COLORS), (i % COLORS + 1) * width, hello); attrset(A_NORMAL); } if ((COLORS > 8) || base) Pause(); } erase(); endwin(); } static void change_color(int current, int field, int value, int usebase) { short red, green, blue; if (usebase) color_content(current, &red, &green, &blue); else red = green = blue = 0; switch (field) { case 0: red += value; break; case 1: green += value; break; case 2: blue += value; break; } if (init_color(current, red, green, blue) == ERR) beep(); } static void color_edit(void) /* display the color test pattern, without trying to edit colors */ { int i, this_c = 0, value = 0, current = 0, field = 0; int last_c; refresh(); for (i = 0; i < COLORS; i++) init_pair(i, COLOR_WHITE, i); mvprintw(LINES-2, 0, "Number: %d", value); do { short red, green, blue; attron(A_BOLD); mvaddstr(0, 20, "Color RGB Value Editing"); attroff(A_BOLD); for (i = 0; i < COLORS; i++) { mvprintw(2 + i, 0, "%c %-8s:", (i == current ? '>' : ' '), (i < (int) SIZEOF(color_names) ? color_names[i] : "")); attrset(COLOR_PAIR(i)); addstr(" "); attrset(A_NORMAL); /* * Note: this refresh should *not* be necessary! It works around * a bug in attribute handling that apparently causes the A_NORMAL * attribute sets to interfere with the actual emission of the * color setting somehow. This needs to be fixed. */ refresh(); color_content(i, &red, &green, &blue); addstr(" R = "); if (current == i && field == 0) attron(A_STANDOUT); printw("%04d", red); if (current == i && field == 0) attrset(A_NORMAL); addstr(", G = "); if (current == i && field == 1) attron(A_STANDOUT); printw("%04d", green); if (current == i && field == 1) attrset(A_NORMAL); addstr(", B = "); if (current == i && field == 2) attron(A_STANDOUT); printw("%04d", blue); if (current == i && field == 2) attrset(A_NORMAL); attrset(A_NORMAL); addstr(")"); } mvaddstr(COLORS + 3, 0, "Use up/down to select a color, left/right to change fields."); mvaddstr(COLORS + 4, 0, "Modify field by typing nnn=, nnn-, or nnn+. ? for help."); move(2 + current, 0); last_c = this_c; this_c = Getchar(); if (isdigit(this_c) && !isdigit(last_c)) value = 0; switch (this_c) { case KEY_UP: current = (current == 0 ? (COLORS - 1) : current - 1); break; case KEY_DOWN: current = (current == (COLORS - 1) ? 0 : current + 1); break; case KEY_RIGHT: field = (field == 2 ? 0 : field + 1); break; case KEY_LEFT: field = (field == 0 ? 2 : field - 1); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': value = value * 10 + (this_c - '0'); break; case '+': change_color(current, field, value, 1); break; case '-': change_color(current, field, -value, 1); break; case '=': change_color(current, field, value, 0); break; case '?': erase(); P(" RGB Value Editing Help"); P(""); P("You are in the RGB value editor. Use the arrow keys to select one of"); P("the fields in one of the RGB triples of the current colors; the one"); P("currently selected will be reverse-video highlighted."); P(""); P("To change a field, enter the digits of the new value; they are echoed"); P("as entered. Finish by typing `='. The change will take effect instantly."); P("To increment or decrement a value, use the same procedure, but finish"); P("with a `+' or `-'."); P(""); P("To quit, do `x' or 'q'"); Pause(); erase(); break; case 'x': case 'q': break; default: beep(); break; } mvprintw(LINES-2, 0, "Number: %d", value); clrtoeol(); } while (this_c != 'x' && this_c != 'q'); erase(); endwin(); } /**************************************************************************** * * Soft-key label test * ****************************************************************************/ static void slk_test(void) /* exercise the soft keys */ { int c, fmt = 1; char buf[9]; c = CTRL('l'); do { move(0, 0); switch(c) { case CTRL('l'): erase(); attron(A_BOLD); mvaddstr(0, 20, "Soft Key Exerciser"); attroff(A_BOLD); move(2, 0); P("Available commands are:"); P(""); P("^L -- refresh screen"); P("a -- activate or restore soft keys"); P("d -- disable soft keys"); P("c -- set centered format for labels"); P("l -- set left-justified format for labels"); P("r -- set right-justified format for labels"); P("[12345678] -- set label; labels are numbered 1 through 8"); P("e -- erase stdscr (should not erase labels)"); P("s -- test scrolling of shortened screen"); P("x, q -- return to main menu"); P(""); P("Note: if activating the soft keys causes your terminal to"); P("scroll up one line, your terminal auto-scrolls when anything"); P("is written to the last screen position. The ncurses code"); P("does not yet handle this gracefully."); refresh(); /* fall through */ case 'a': slk_restore(); break; case 'e': wclear(stdscr); break; case 's': mvprintw(20, 0, "Press Q to stop the scrolling-test: "); while ((c = Getchar()) != 'Q' && (c != ERR)) addch((chtype)c); break; case 'd': slk_clear(); break; case 'l': fmt = 0; break; case 'c': fmt = 1; break; case 'r': fmt = 2; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': (void) mvaddstr(20, 0, "Please enter the label value: "); echo(); wgetnstr(stdscr, buf, 8); noecho(); slk_set((c - '0'), buf, fmt); slk_refresh(); move(20, 0); clrtoeol(); break; case 'x': case 'q': goto done; default: beep(); } } while ((c = Getchar()) != EOF); done: erase(); endwin(); } /**************************************************************************** * * Alternate character-set stuff * ****************************************************************************/ /* ISO 6429: codes 0x80 to 0x9f may be control characters that cause the * terminal to perform functions. The remaining codes can be graphic. */ static void show_upper_chars(int first) { bool C1 = (first == 128); int code; int last = first + 31; int reply; erase(); attron(A_BOLD); mvprintw(0, 20, "Display of %s Character Codes %d to %d", C1 ? "C1" : "GR", first, last); attroff(A_BOLD); refresh(); for (code = first; code <= last; code++) { int row = 4 + ((code - first) % 16); int col = ((code - first) / 16) * COLS / 2; char tmp[80]; sprintf(tmp, "%3d (0x%x)", code, code); mvprintw(row, col, "%*s: ", COLS/4, tmp); if (C1) nodelay(stdscr, TRUE); echochar(code); if (C1) { /* (yes, this _is_ crude) */ while ((reply = Getchar()) != ERR) { addch(reply); napms(10); } nodelay(stdscr, FALSE); } } } static int show_1_acs(int n, const char *name, chtype code) { const int height = 16; int row = 4 + (n % height); int col = (n / height) * COLS / 2; mvprintw(row, col, "%*s : ", COLS/4, name); addch(code); return n + 1; } static void show_acs_chars(void) /* display the ACS character set */ { int n; #define BOTH(name) #name, name erase(); attron(A_BOLD); mvaddstr(0, 20, "Display of the ACS Character Set"); attroff(A_BOLD); refresh(); n = show_1_acs(0, BOTH(ACS_ULCORNER)); n = show_1_acs(n, BOTH(ACS_LLCORNER)); n = show_1_acs(n, BOTH(ACS_URCORNER)); n = show_1_acs(n, BOTH(ACS_LRCORNER)); n = show_1_acs(n, BOTH(ACS_RTEE)); n = show_1_acs(n, BOTH(ACS_LTEE)); n = show_1_acs(n, BOTH(ACS_BTEE)); n = show_1_acs(n, BOTH(ACS_TTEE)); n = show_1_acs(n, BOTH(ACS_HLINE)); n = show_1_acs(n, BOTH(ACS_VLINE)); n = show_1_acs(n, BOTH(ACS_PLUS)); n = show_1_acs(n, BOTH(ACS_S1)); n = show_1_acs(n, BOTH(ACS_S9)); n = show_1_acs(n, BOTH(ACS_DIAMOND)); n = show_1_acs(n, BOTH(ACS_CKBOARD)); n = show_1_acs(n, BOTH(ACS_DEGREE)); n = show_1_acs(n, BOTH(ACS_PLMINUS)); n = show_1_acs(n, BOTH(ACS_BULLET)); n = show_1_acs(n, BOTH(ACS_LARROW)); n = show_1_acs(n, BOTH(ACS_RARROW)); n = show_1_acs(n, BOTH(ACS_DARROW)); n = show_1_acs(n, BOTH(ACS_UARROW)); n = show_1_acs(n, BOTH(ACS_BOARD)); n = show_1_acs(n, BOTH(ACS_LANTERN)); n = show_1_acs(n, BOTH(ACS_BLOCK)); n = show_1_acs(n, BOTH(ACS_S3)); n = show_1_acs(n, BOTH(ACS_S7)); n = show_1_acs(n, BOTH(ACS_LEQUAL)); n = show_1_acs(n, BOTH(ACS_GEQUAL)); n = show_1_acs(n, BOTH(ACS_PI)); n = show_1_acs(n, BOTH(ACS_NEQUAL)); n = show_1_acs(n, BOTH(ACS_STERLING)); } static void acs_display(void) { int c = 'a'; do { switch (c) { case 'a': show_acs_chars(); break; case '0': case '1': case '2': case '3': show_upper_chars((c - '0') * 32 + 128); break; } mvprintw(LINES-3,0, "Note: ANSI terminals may not display C1 characters."); mvprintw(LINES-2,0, "Select: a=ACS, 0=C1, 1,2,3=GR characters, q=quit"); refresh(); } while ((c = Getchar()) != 'x' && c != 'q'); Pause(); erase(); endwin(); } /* * Graphic-rendition test (adapted from vttest) */ static void test_sgr_attributes(void) { int pass; for (pass = 0; pass < 2; pass++) { int normal = ((pass == 0 ? A_NORMAL : A_REVERSE)) | BLANK; /* Use non-default colors if possible to exercise bce a little */ if (has_colors()) { init_pair(1, COLOR_WHITE, COLOR_BLUE); normal |= COLOR_PAIR(1); } bkgdset(normal); erase(); mvprintw( 1,20, "Graphic rendition test pattern:"); mvprintw( 4, 1, "vanilla"); #define set_sgr(mask) bkgdset((normal^(mask))); set_sgr(A_BOLD); mvprintw( 4,40, "bold"); set_sgr(A_UNDERLINE); mvprintw( 6, 6, "underline"); set_sgr(A_BOLD|A_UNDERLINE); mvprintw( 6,45, "bold underline"); set_sgr(A_BLINK); mvprintw( 8, 1, "blink"); set_sgr(A_BLINK|A_BOLD); mvprintw( 8,40, "bold blink"); set_sgr(A_UNDERLINE|A_BLINK); mvprintw(10, 6, "underline blink"); set_sgr(A_BOLD|A_UNDERLINE|A_BLINK); mvprintw(10,45, "bold underline blink"); set_sgr(A_REVERSE); mvprintw(12, 1, "negative"); set_sgr(A_BOLD|A_REVERSE); mvprintw(12,40, "bold negative"); set_sgr(A_UNDERLINE|A_REVERSE); mvprintw(14, 6, "underline negative"); set_sgr(A_BOLD|A_UNDERLINE|A_REVERSE); mvprintw(14,45, "bold underline negative"); set_sgr(A_BLINK|A_REVERSE); mvprintw(16, 1, "blink negative"); set_sgr(A_BOLD|A_BLINK|A_REVERSE); mvprintw(16,40, "bold blink negative"); set_sgr(A_UNDERLINE|A_BLINK|A_REVERSE); mvprintw(18, 6, "underline blink negative"); set_sgr(A_BOLD|A_UNDERLINE|A_BLINK|A_REVERSE); mvprintw(18,45, "bold underline blink negative"); bkgdset(normal); mvprintw(LINES-2,1, "%s background. ", pass == 0 ? "Dark" : "Light"); clrtoeol(); Pause(); } bkgdset(A_NORMAL | BLANK); erase(); endwin(); } /**************************************************************************** * * Windows and scrolling tester. * ****************************************************************************/ #define BOTLINES 4 /* number of line stolen from screen bottom */ typedef struct { int y, x; } pair; #define FRAME struct frame FRAME { FRAME *next, *last; bool do_scroll; bool do_keypad; WINDOW *wind; }; /* We need to know if these flags are actually set, so don't look in FRAME. * These names are known to work with SVr4 curses as well as ncurses. */ static bool HaveKeypad(FRAME *curp) { WINDOW *win = (curp ? curp->wind : stdscr); return win->_use_keypad; } static bool HaveScroll(FRAME *curp) { WINDOW *win = (curp ? curp->wind : stdscr); return win->_scroll; } static void newwin_legend(FRAME *curp) { static const struct { const char *msg; int code; } legend[] = { { "^C = create window", 0 }, { "^N = next window", 0 }, { "^P = previous window", 0 }, { "^F = scroll forward", 0 }, { "^B = scroll backward", 0 }, { "^K = keypad(%s)", 1 }, { "^S = scrollok(%s)", 2 }, { "^W = save window to file", 0 }, { "^R = restore window", 0 }, #ifdef NCURSES_VERSION { "^X = resize", 0 }, #endif { "^Q%s = exit", 3 } }; size_t n; int y, x; bool do_keypad = HaveKeypad(curp); bool do_scroll = HaveScroll(curp); char buf[BUFSIZ]; move(LINES-4, 0); for (n = 0; n < SIZEOF(legend); n++) { switch (legend[n].code) { default: strcpy(buf, legend[n].msg); break; case 1: sprintf(buf, legend[n].msg, do_keypad ? "yes" : "no"); break; case 2: sprintf(buf, legend[n].msg, do_scroll ? "yes" : "no"); break; case 3: sprintf(buf, legend[n].msg, do_keypad ? "/ESC" : ""); break; } getyx(stdscr, y, x); addstr((COLS < (x + 3 + (int)strlen(buf))) ? "\n" : (n ? ", " : "")); addstr(buf); } clrtoeol(); } static void transient(FRAME *curp, NCURSES_CONST char *msg) { newwin_legend(curp); if (msg) { mvaddstr(LINES - 1, 0, msg); refresh(); napms(1000); } move(LINES-1, 0); printw("%s characters are echoed, window should %sscroll.", HaveKeypad(curp) ? "Non-arrow" : "All other", HaveScroll(curp) ? "" : "not " ); clrtoeol(); } static void newwin_report(FRAME *curp) /* report on the cursor's current position, then restore it */ { WINDOW *win = (curp != 0) ? curp->wind : stdscr; int y, x; if (win != stdscr) transient(curp, (char *)0); getyx(win, y, x); move(LINES - 1, COLS - 17); printw("Y = %2d X = %2d", y, x); if (win != stdscr) refresh(); else wmove(win, y, x); } static pair *selectcell(int uli, int ulj, int lri, int lrj) /* arrows keys move cursor, return location at current on non-arrow key */ { static pair res; /* result cell */ int si = lri - uli + 1; /* depth of the select area */ int sj = lrj - ulj + 1; /* width of the select area */ int i = 0, j = 0; /* offsets into the select area */ res.y = uli; res.x = ulj; for (;;) { move(uli + i, ulj + j); newwin_report((FRAME *)0); switch(Getchar()) { case KEY_UP: i += si - 1; break; case KEY_DOWN: i++; break; case KEY_LEFT: j += sj - 1; break; case KEY_RIGHT: j++; break; case QUIT: case ESCAPE: return((pair *)0); #ifdef NCURSES_MOUSE_VERSION case KEY_MOUSE: { MEVENT event; getmouse(&event); if (event.y > uli && event.x > ulj) { i = event.y - uli; j = event.x - ulj; } else { beep(); break; } } /* FALLTHRU */ #endif default: res.y = uli + i; res.x = ulj + j; return(&res); } i %= si; j %= sj; } } static void outerbox(pair ul, pair lr, bool onoff) /* draw or erase a box *outside* the given pair of corners */ { mvaddch(ul.y-1, lr.x-1, onoff ? ACS_ULCORNER : ' '); mvaddch(ul.y-1, lr.x+1, onoff ? ACS_URCORNER : ' '); mvaddch(lr.y+1, lr.x+1, onoff ? ACS_LRCORNER : ' '); mvaddch(lr.y+1, ul.x-1, onoff ? ACS_LLCORNER : ' '); move(ul.y-1, ul.x); hline(onoff ? ACS_HLINE : ' ', lr.x - ul.x + 1); move(ul.y, ul.x-1); vline(onoff ? ACS_VLINE : ' ', lr.y - ul.y + 1); move(lr.y+1, ul.x); hline(onoff ? ACS_HLINE : ' ', lr.x - ul.x + 1); move(ul.y, lr.x+1); vline(onoff ? ACS_VLINE : ' ', lr.y - ul.y + 1); } static WINDOW *getwindow(void) /* Ask user for a window definition */ { WINDOW *rwindow; pair ul, lr, *tmp; move(0, 0); clrtoeol(); addstr("Use arrows to move cursor, anything else to mark corner 1"); refresh(); if ((tmp = selectcell(2, 1, LINES-BOTLINES-2, COLS-2)) == (pair *)0) return((WINDOW *)0); memcpy(&ul, tmp, sizeof(pair)); mvaddch(ul.y-1, ul.x-1, ACS_ULCORNER); move(0, 0); clrtoeol(); addstr("Use arrows to move cursor, anything else to mark corner 2"); refresh(); if ((tmp = selectcell(ul.y, ul.x, LINES-BOTLINES-2, COLS-2)) == (pair *)0) return((WINDOW *)0); memcpy(&lr, tmp, sizeof(pair)); rwindow = subwin(stdscr, lr.y - ul.y + 1, lr.x - ul.x + 1, ul.y, ul.x); outerbox(ul, lr, TRUE); refresh(); wrefresh(rwindow); move(0, 0); clrtoeol(); return(rwindow); } static void newwin_move(FRAME *curp, int dy, int dx) { WINDOW *win = (curp != 0) ? curp->wind : stdscr; int cur_y, cur_x; int max_y, max_x; getyx(win, cur_y, cur_x); getmaxyx(win, max_y, max_x); if ((cur_x += dx) < 0) cur_x = 0; else if (cur_x >= max_x) cur_x = max_x - 1; if ((cur_y += dy) < 0) cur_y = 0; else if (cur_y >= max_y) cur_y = max_y - 1; wmove(win, cur_y, cur_x); } static FRAME *delete_framed(FRAME *fp, bool showit) { FRAME *np; fp->last->next = fp->next; fp->next->last = fp->last; if (showit) { werase(fp->wind); wrefresh(fp->wind); } delwin(fp->wind); np = (fp == fp->next) ? 0 : fp->next; free(fp); return np; } static void acs_and_scroll(void) /* Demonstrate windows */ { int c, i; FILE *fp; FRAME *current = (FRAME *)0, *neww; WINDOW *usescr = stdscr; #define DUMPFILE "screendump" #ifdef NCURSES_MOUSE_VERSION mousemask(BUTTON1_CLICKED, (mmask_t *)0); #endif c = CTRL('C'); raw(); do { transient((FRAME *)0, (char *)0); switch(c) { case CTRL('C'): neww = (FRAME *) calloc(1, sizeof(FRAME)); if ((neww->wind = getwindow()) == (WINDOW *)0) goto breakout; if (current == 0) /* First element, */ { neww->next = neww; /* so point it at itself */ neww->last = neww; } else { neww->next = current->next; neww->last = current; neww->last->next = neww; neww->next->last = neww; } current = neww; /* SVr4 curses sets the keypad on all newly-created windows to * false. Someone reported that PDCurses makes new windows inherit * this flag. Remove the following 'keypad()' call to test this */ keypad(current->wind, TRUE); current->do_keypad = HaveKeypad(current); current->do_scroll = HaveScroll(current); break; case CTRL('N'): /* go to next window */ if (current) current = current->next; break; case CTRL('P'): /* go to previous window */ if (current) current = current->last; break; case CTRL('F'): /* scroll current window forward */ if (current) wscrl(current->wind, 1); break; case CTRL('B'): /* scroll current window backwards */ if (current) wscrl(current->wind, -1); break; case CTRL('K'): /* toggle keypad mode for current */ if (current) { current->do_keypad = !current->do_keypad; keypad(current->wind, current->do_keypad); } break; case CTRL('S'): if (current) { current->do_scroll = !current->do_scroll; scrollok(current->wind, current->do_scroll); } break; case CTRL('W'): /* save and delete window */ if (current == current->next) break; if ((fp = fopen(DUMPFILE, "w")) == (FILE *)0) transient(current, "Can't open screen dump file"); else { (void) putwin(current->wind, fp); (void) fclose(fp); current = delete_framed(current, TRUE); } break; case CTRL('R'): /* restore window */ if ((fp = fopen(DUMPFILE, "r")) == (FILE *)0) transient(current, "Can't open screen dump file"); else { neww = (FRAME *) calloc(1, sizeof(FRAME)); neww->next = current->next; neww->last = current; neww->last->next = neww; neww->next->last = neww; neww->wind = getwin(fp); (void) fclose(fp); wrefresh(neww->wind); } break; #ifdef NCURSES_VERSION case CTRL('X'): /* resize window */ if (current) { pair *tmp, ul, lr; int mx, my; move(0, 0); clrtoeol(); addstr("Use arrows to move cursor, anything else to mark new corner"); refresh(); getbegyx(current->wind, ul.y, ul.x); tmp = selectcell(ul.y, ul.x, LINES-BOTLINES-2, COLS-2); if (tmp == (pair *)0) { beep(); break; } getmaxyx(current->wind, lr.y, lr.x); lr.y += (ul.y - 1); lr.x += (ul.x - 1); outerbox(ul, lr, FALSE); wnoutrefresh(stdscr); /* strictly cosmetic hack for the test */ getmaxyx(current->wind, my, mx); if (my > tmp->y - ul.y) { getyx(current->wind, lr.y, lr.x); wmove(current->wind, tmp->y - ul.y + 1, 0); wclrtobot(current->wind); wmove(current->wind, lr.y, lr.x); } if (mx > tmp->x - ul.x) for (i = 0; i < my; i++) { wmove(current->wind, i, tmp->x - ul.x + 1); wclrtoeol(current->wind); } wnoutrefresh(current->wind); memcpy(&lr, tmp, sizeof(pair)); (void) wresize(current->wind, lr.y-ul.y+0, lr.x-ul.x+0); getbegyx(current->wind, ul.y, ul.x); getmaxyx(current->wind, lr.y, lr.x); lr.y += (ul.y - 1); lr.x += (ul.x - 1); outerbox(ul, lr, TRUE); wnoutrefresh(stdscr); wnoutrefresh(current->wind); move(0, 0); clrtoeol(); doupdate(); } break; #endif /* NCURSES_VERSION */ case KEY_F(10): /* undocumented --- use this to test area clears */ selectcell(0, 0, LINES - 1, COLS - 1); clrtobot(); refresh(); break; case KEY_UP: newwin_move(current, -1, 0); break; case KEY_DOWN: newwin_move(current, 1, 0); break; case KEY_LEFT: newwin_move(current, 0, -1); break; case KEY_RIGHT: newwin_move(current, 0, 1); break; case KEY_BACKSPACE: /* FALLTHROUGH */ case KEY_DC: { int y, x; getyx(current->wind, y, x); if (--x < 0) { if (--y < 0) break; x = getmaxx(current->wind) - 1; } mvwdelch(current->wind, y, x); } break; case '\r': c = '\n'; /* FALLTHROUGH */ default: if (current) waddch(current->wind, (chtype)c); else beep(); break; } newwin_report(current); usescr = (current ? current->wind : stdscr); wrefresh(usescr); } while ((c = wGetchar(usescr)) != QUIT && !((c == ESCAPE) && (usescr->_use_keypad)) && (c != ERR)); breakout: while (current != 0) current = delete_framed(current, FALSE); scrollok(stdscr, TRUE); /* reset to driver's default */ #ifdef NCURSES_MOUSE_VERSION mousemask(0, (mmask_t *)0); #endif noraw(); erase(); endwin(); } /**************************************************************************** * * Panels tester * ****************************************************************************/ #if USE_LIBPANEL static PANEL *p1; static PANEL *p2; static PANEL *p3; static PANEL *p4; static PANEL *p5; static WINDOW *w1; static WINDOW *w2; static WINDOW *w3; static WINDOW *w4; static WINDOW *w5; static unsigned long nap_msec = 1; static NCURSES_CONST char *mod[] = { "test ", "TEST ", "(**) ", "*()* ", "<--> ", "LAST " }; /*+------------------------------------------------------------------------- wait_a_while(msec) --------------------------------------------------------------------------*/ static void wait_a_while(unsigned long msec GCC_UNUSED) { #if HAVE_NAPMS if(nap_msec == 1) wGetchar(stdscr); else napms(nap_msec); #else if(nap_msec == 1) wGetchar(stdscr); else if(msec > 1000L) sleep((int)msec/1000L); else sleep(1); #endif } /* end of wait_a_while */ /*+------------------------------------------------------------------------- saywhat(text) --------------------------------------------------------------------------*/ static void saywhat(NCURSES_CONST char *text) { wmove(stdscr,LINES - 1,0); wclrtoeol(stdscr); waddstr(stdscr, text); } /* end of saywhat */ /*+------------------------------------------------------------------------- mkpanel(rows,cols,tly,tlx) - alloc a win and panel and associate them --------------------------------------------------------------------------*/ static PANEL * mkpanel(int rows, int cols, int tly, int tlx) { WINDOW *win = newwin(rows,cols,tly,tlx); PANEL *pan; if(!win) return((PANEL *)0); if((pan = new_panel(win))) return(pan); delwin(win); return((PANEL *)0); } /* end of mkpanel */ /*+------------------------------------------------------------------------- rmpanel(pan) --------------------------------------------------------------------------*/ static void rmpanel(PANEL *pan) { WINDOW *win = panel_window(pan); del_panel(pan); delwin(win); } /* end of rmpanel */ /*+------------------------------------------------------------------------- pflush() --------------------------------------------------------------------------*/ static void pflush(void) { update_panels(); doupdate(); } /* end of pflush */ /*+------------------------------------------------------------------------- fill_panel(win) --------------------------------------------------------------------------*/ static void fill_panel(PANEL *pan) { WINDOW *win = panel_window(pan); int num = ((const char *)panel_userptr(pan))[1]; int y,x; box(win, 0, 0); wmove(win,1,1); wprintw(win,"-pan%c-", num); for(y = 2; y < getmaxy(win) - 1; y++) { for(x = 1; x < getmaxx(win) - 1; x++) { wmove(win,y,x); waddch(win,num); } } } /* end of fill_panel */ static void demo_panels(void) { int itmp; register y,x; refresh(); for(y = 0; y < LINES - 1; y++) { for(x = 0; x < COLS; x++) wprintw(stdscr,"%d",(y + x) % 10); } for(y = 0; y < 5; y++) { p1 = mkpanel(LINES/2 - 2, COLS/8 + 1, 0, 0); w1 = panel_window(p1); set_panel_userptr(p1,"p1"); p2 = mkpanel(LINES/2 + 1, COLS/7, LINES/4, COLS/10); w2 = panel_window(p2); set_panel_userptr(p2,"p2"); p3 = mkpanel(LINES/4, COLS/10, LINES/2, COLS/9); w3 = panel_window(p3); set_panel_userptr(p3,"p3"); p4 = mkpanel(LINES/2 - 2, COLS/8, LINES/2 - 2, COLS/3); w4 = panel_window(p4); set_panel_userptr(p4,"p4"); p5 = mkpanel(LINES/2 - 2, COLS/8, LINES/2, COLS/2 - 2); w5 = panel_window(p5); set_panel_userptr(p5,"p5"); fill_panel(p1); fill_panel(p2); fill_panel(p3); fill_panel(p4); fill_panel(p5); hide_panel(p4); hide_panel(p5); pflush(); saywhat("press any key to continue"); wait_a_while(nap_msec); saywhat("h3 s1 s2 s4 s5; press any key to continue"); move_panel(p1,0,0); hide_panel(p3); show_panel(p1); show_panel(p2); show_panel(p4); show_panel(p5); pflush(); wait_a_while(nap_msec); saywhat("s1; press any key to continue"); show_panel(p1); pflush(); wait_a_while(nap_msec); saywhat("s2; press any key to continue"); show_panel(p2); pflush(); wait_a_while(nap_msec); saywhat("m2; press any key to continue"); move_panel(p2, LINES/3 + 1, COLS / 8); pflush(); wait_a_while(nap_msec); saywhat("s3;"); show_panel(p3); pflush(); wait_a_while(nap_msec); saywhat("m3; press any key to continue"); move_panel(p3, LINES/4 + 1, COLS / 15); pflush(); wait_a_while(nap_msec); saywhat("b3; press any key to continue"); bottom_panel(p3); pflush(); wait_a_while(nap_msec); saywhat("s4; press any key to continue"); show_panel(p4); pflush(); wait_a_while(nap_msec); saywhat("s5; press any key to continue"); show_panel(p5); pflush(); wait_a_while(nap_msec); saywhat("t3; press any key to continue"); top_panel(p3); pflush(); wait_a_while(nap_msec); saywhat("t1; press any key to continue"); top_panel(p1); pflush(); wait_a_while(nap_msec); saywhat("t2; press any key to continue"); top_panel(p2); pflush(); wait_a_while(nap_msec); saywhat("t3; press any key to continue"); top_panel(p3); pflush(); wait_a_while(nap_msec); saywhat("t4; press any key to continue"); top_panel(p4); pflush(); wait_a_while(nap_msec); for(itmp = 0; itmp < 6; itmp++) { saywhat("m4; press any key to continue"); wmove(w4, LINES/8, 1); waddstr(w4,mod[itmp]); move_panel(p4, LINES/6, itmp*(COLS/8)); wmove(w5, LINES/6, 1); waddstr(w5,mod[itmp]); pflush(); wait_a_while(nap_msec); saywhat("m5; press any key to continue"); wmove(w4, LINES/6, 1); waddstr(w4,mod[itmp]); move_panel(p5, LINES/3 - 1,(itmp*10) + 6); wmove(w5, LINES/8, 1); waddstr(w5,mod[itmp]); pflush(); wait_a_while(nap_msec); } saywhat("m4; press any key to continue"); move_panel(p4, LINES/6, itmp*(COLS/8)); pflush(); wait_a_while(nap_msec); saywhat("t5; press any key to continue"); top_panel(p5); pflush(); wait_a_while(nap_msec); saywhat("t2; press any key to continue"); top_panel(p2); pflush(); wait_a_while(nap_msec); saywhat("t1; press any key to continue"); top_panel(p1); pflush(); wait_a_while(nap_msec); saywhat("d2; press any key to continue"); rmpanel(p2); pflush(); wait_a_while(nap_msec); saywhat("h3; press any key to continue"); hide_panel(p3); pflush(); wait_a_while(nap_msec); saywhat("d1; press any key to continue"); rmpanel(p1); pflush(); wait_a_while(nap_msec); saywhat("d4; press any key to continue"); rmpanel(p4); pflush(); wait_a_while(nap_msec); saywhat("d5; press any key to continue"); rmpanel(p5); pflush(); wait_a_while(nap_msec); if(nap_msec == 1) break; nap_msec = 100L; } erase(); endwin(); } /**************************************************************************** * * Pad tester * ****************************************************************************/ #define GRIDSIZE 3 static bool show_panner_legend = TRUE; static int panner_legend(int line) { static const char *const legend[] = { "Use arrow keys (or U,D,L,R) to pan, q to quit (?,t,s flags)", "Use ! to shell-out. Toggle legend:?, timer:t, scroll mark:s.", "Use +,- (or j,k) to grow/shrink the panner vertically.", "Use <,> (or h,l) to grow/shrink the panner horizontally." }; int n = (SIZEOF(legend) - (LINES - line)); if (line < LINES && (n >= 0)) { move(line, 0); if (show_panner_legend) printw("%s", legend[n]); clrtoeol(); return show_panner_legend; } return FALSE; } static void panner_h_cleanup(int from_y, int from_x, int to_x) { if (!panner_legend(from_y)) do_h_line(from_y, from_x, ' ', to_x); } static void panner_v_cleanup(int from_y, int from_x, int to_y) { if (!panner_legend(from_y)) do_v_line(from_y, from_x, ' ', to_y); } static void panner(WINDOW *pad, int top_x, int top_y, int porty, int portx, int (*pgetc)(WINDOW *)) { #if HAVE_GETTIMEOFDAY struct timeval before, after; bool timing = TRUE; #endif bool scrollers = TRUE; int basex = 0; int basey = 0; int pxmax, pymax, lowend, highend, c; getmaxyx(pad, pymax, pxmax); scrollok(stdscr, FALSE); /* we don't want stdscr to scroll! */ c = KEY_REFRESH; do { #ifdef NCURSES_VERSION /* * During shell-out, the user may have resized the window. Adjust * the port size of the pad to accommodate this. Ncurses automatically * resizes all of the normal windows to fit on the new screen. */ if (top_x > COLS) top_x = COLS; if (portx > COLS) portx = COLS; if (top_y > LINES) top_y = LINES; if (porty > LINES) porty = LINES; #endif switch(c) { case KEY_REFRESH: erase(); /* FALLTHRU */ case '?': if (c == '?') show_panner_legend = !show_panner_legend; panner_legend(LINES - 4); panner_legend(LINES - 3); panner_legend(LINES - 2); panner_legend(LINES - 1); break; #if HAVE_GETTIMEOFDAY case 't': timing = !timing; if (!timing) panner_legend(LINES-1); break; #endif case 's': scrollers = !scrollers; break; /* Move the top-left corner of the pad, keeping the bottom-right * corner fixed. */ case 'h': /* increase-columns: move left edge to left */ if (top_x <= 0) beep(); else { panner_v_cleanup(top_y, top_x, porty); top_x--; } break; case 'j': /* decrease-lines: move top-edge down */ if (top_y >= porty) beep(); else { panner_h_cleanup(top_y - 1, top_x - (top_x > 0), portx); top_y++; } break; case 'k': /* increase-lines: move top-edge up */ if (top_y <= 0) beep(); else { top_y--; panner_h_cleanup(top_y, top_x, portx); } break; case 'l': /* decrease-columns: move left-edge to right */ if (top_x >= portx) beep(); else { panner_v_cleanup(top_y - (top_y > 0), top_x - 1, porty); top_x++; } break; /* Move the bottom-right corner of the pad, keeping the top-left * corner fixed. */ case KEY_IC: /* increase-columns: move right-edge to right */ if (portx >= pxmax || portx >= COLS) beep(); else { panner_v_cleanup(top_y - (top_y > 0), portx - 1, porty); ++portx; } break; case KEY_IL: /* increase-lines: move bottom-edge down */ if (porty >= pymax || porty >= LINES) beep(); else { panner_h_cleanup(porty - 1, top_x - (top_x > 0), portx); ++porty; } break; case KEY_DC: /* decrease-columns: move bottom edge up */ if (portx <= top_x) beep(); else { portx--; panner_v_cleanup(top_y - (top_y > 0), portx, porty); } break; case KEY_DL: /* decrease-lines */ if (porty <= top_y) beep(); else { porty--; panner_h_cleanup(porty, top_x - (top_x > 0), portx); } break; case KEY_LEFT: /* pan leftwards */ if (basex > 0) basex--; else beep(); break; case KEY_RIGHT: /* pan rightwards */ if (basex + portx - (pymax > porty) < pxmax) basex++; else beep(); break; case KEY_UP: /* pan upwards */ if (basey > 0) basey--; else beep(); break; case KEY_DOWN: /* pan downwards */ if (basey + porty - (pxmax > portx) < pymax) basey++; else beep(); break; default: beep(); break; } mvaddch(top_y - 1, top_x - 1, ACS_ULCORNER); do_v_line(top_y, top_x - 1, ACS_VLINE, porty); do_h_line(top_y - 1, top_x, ACS_HLINE, portx); if (scrollers && (pxmax > portx - 1)) { int length = (portx - top_x - 1); float ratio = ((float) length) / ((float) pxmax); lowend = (int)(top_x + (basex * ratio)); highend = (int)(top_x + ((basex + length) * ratio)); do_h_line(porty - 1, top_x, ACS_HLINE, lowend); if (highend < portx) { attron(A_REVERSE); do_h_line(porty - 1, lowend, ' ', highend + 1); attroff(A_REVERSE); do_h_line(porty - 1, highend + 1, ACS_HLINE, portx); } } else do_h_line(porty - 1, top_x, ACS_HLINE, portx); if (scrollers && (pymax > porty - 1)) { int length = (porty - top_y - 1); float ratio = ((float) length) / ((float) pymax); lowend = (int)(top_y + (basey * ratio)); highend = (int)(top_y + ((basey + length) * ratio)); do_v_line(top_y, portx - 1, ACS_VLINE, lowend); if (highend < porty) { attron(A_REVERSE); do_v_line(lowend, portx - 1, ' ', highend + 1); attroff(A_REVERSE); do_v_line(highend + 1, portx - 1, ACS_VLINE, porty); } } else do_v_line(top_y, portx - 1, ACS_VLINE, porty); mvaddch(top_y - 1, portx - 1, ACS_URCORNER); mvaddch(porty - 1, top_x - 1, ACS_LLCORNER); mvaddch(porty - 1, portx - 1, ACS_LRCORNER); #if HAVE_GETTIMEOFDAY gettimeofday(&before, 0); #endif wnoutrefresh(stdscr); pnoutrefresh(pad, basey, basex, top_y, top_x, porty - (pxmax > portx) - 1, portx - (pymax > porty) - 1); doupdate(); #if HAVE_GETTIMEOFDAY if (timing) { double elapsed; gettimeofday(&after, 0); elapsed = (after.tv_sec + after.tv_usec / 1.0e6) - (before.tv_sec + before.tv_usec / 1.0e6); move(LINES-1, COLS-20); printw("Secs: %2.03f", elapsed); refresh(); } #endif } while ((c = pgetc(pad)) != KEY_EXIT); scrollok(stdscr, TRUE); /* reset to driver's default */ } static int padgetch(WINDOW *win) { int c; switch(c = wGetchar(win)) { case '!': ShellOut(FALSE); return KEY_REFRESH; case CTRL('r'): endwin(); refresh(); return KEY_REFRESH; case CTRL('l'): return KEY_REFRESH; case 'U': return(KEY_UP); case 'D': return(KEY_DOWN); case 'R': return(KEY_RIGHT); case 'L': return(KEY_LEFT); case '+': return(KEY_IL); case '-': return(KEY_DL); case '>': return(KEY_IC); case '<': return(KEY_DC); case ERR: /* FALLTHRU */ case 'q': return(KEY_EXIT); default: return(c); } } static void demo_pad(void) /* Demonstrate pads. */ { int i, j; unsigned gridcount = 0; WINDOW *panpad = newpad(200, 200); for (i = 0; i < 200; i++) { for (j = 0; j < 200; j++) if (i % GRIDSIZE == 0 && j % GRIDSIZE == 0) { if (i == 0 || j == 0) waddch(panpad, '+'); else waddch(panpad, (chtype)('A' + (gridcount++ % 26))); } else if (i % GRIDSIZE == 0) waddch(panpad, '-'); else if (j % GRIDSIZE == 0) waddch(panpad, '|'); else waddch(panpad, ' '); } panner_legend(LINES - 4); panner_legend(LINES - 3); panner_legend(LINES - 2); panner_legend(LINES - 1); keypad(panpad, TRUE); /* Make the pad (initially) narrow enough that a trace file won't wrap. * We'll still be able to widen it during a test, since that's required * for testing boundaries. */ panner(panpad, 2, 2, LINES - 5, COLS-15, padgetch); delwin(panpad); endwin(); erase(); } #endif /* USE_LIBPANEL */ /**************************************************************************** * * Tests from John Burnell's PDCurses tester * ****************************************************************************/ static void Continue (WINDOW *win) { noecho(); wmove(win, 10, 1); mvwaddstr(win, 10, 1, " Press any key to continue"); wrefresh(win); wGetchar(win); } static void flushinp_test(WINDOW *win) /* Input test, adapted from John Burnell's PDCurses tester */ { int w, h, bx, by, sw, sh, i; WINDOW *subWin; wclear (win); getmaxyx(win, h, w); getbegyx(win, by, bx); sw = w / 3; sh = h / 3; if((subWin = subwin(win, sh, sw, by + h - sh - 2, bx + w - sw - 2)) == 0) return; #ifdef A_COLOR if (has_colors()) { init_pair(2,COLOR_CYAN,COLOR_BLUE); wattrset(subWin, COLOR_PAIR(2) | A_BOLD); } else wattrset(subWin, A_BOLD); #else wattrset(subWin, A_BOLD); #endif box(subWin, ACS_VLINE, ACS_HLINE); mvwaddstr(subWin, 2, 1, "This is a subwindow"); wrefresh(win); /* * This used to set 'nocbreak()'. However, Alexander Lukyanov says that * it only happened to "work" on SVr4 because that implementation does not * emulate nocbreak+noecho mode, whereas ncurses does. To get the desired * test behavior, we're using 'cbreak()', which will allow a single * character to return without needing a newline. - T.Dickey 1997/10/11. */ cbreak(); mvwaddstr(win, 0, 1, "This is a test of the flushinp() call."); mvwaddstr(win, 2, 1, "Type random keys for 5 seconds."); mvwaddstr(win, 3, 1, "These should be discarded (not echoed) after the subwindow goes away."); wrefresh(win); for (i = 0; i < 5; i++) { mvwprintw (subWin, 1, 1, "Time = %d", i); wrefresh(subWin); napms(1000); flushinp(); } delwin (subWin); werase(win); flash(); wrefresh(win); napms(1000); mvwaddstr(win, 2, 1, "If you were still typing when the window timer expired,"); mvwaddstr(win, 3, 1, "or else you typed nothing at all while it was running,"); mvwaddstr(win, 4, 1, "test was invalid. You'll see garbage or nothing at all. "); mvwaddstr(win, 6, 1, "Press a key"); wmove(win, 9, 10); wrefresh(win); echo(); wGetchar(win); flushinp(); mvwaddstr(win, 12, 0, "If you see any key other than what you typed, flushinp() is broken."); Continue(win); wmove(win, 9, 10); wdelch(win); wrefresh(win); wmove(win, 12, 0); clrtoeol(); waddstr(win, "What you typed should now have been deleted; if not, wdelch() failed."); Continue(win); cbreak(); } /**************************************************************************** * * Menu test * ****************************************************************************/ #if USE_LIBMENU #define MENU_Y 8 #define MENU_X 8 static int menu_virtualize(int c) { if (c == '\n' || c == KEY_EXIT) return(MAX_COMMAND + 1); else if (c == 'n' || c == KEY_DOWN) return(REQ_NEXT_ITEM); else if (c == 'p' || c == KEY_UP) return(REQ_PREV_ITEM); else if (c == ' ') return(REQ_TOGGLE_ITEM); else return(c); } static const char *animals[] = { "Lions", "Tigers", "Bears", "(Oh my!)", "Newts", "Platypi", "Lemurs", (char *)0 }; static void menu_test(void) { MENU *m; ITEM *items[SIZEOF(animals)]; ITEM **ip = items; const char **ap; int mrows, mcols; WINDOW *menuwin; mvaddstr(0, 0, "This is the menu test:"); mvaddstr(2, 0, " Use up and down arrow to move the select bar."); mvaddstr(3, 0, " 'n' and 'p' act like arrows."); mvaddstr(4, 0, " Press return to exit."); refresh(); for (ap = animals; *ap; ap++) *ip++ = new_item(*ap, ""); *ip = (ITEM *)0; m = new_menu(items); scale_menu(m, &mrows, &mcols); menuwin = newwin(mrows + 2, mcols + 2, MENU_Y, MENU_X); set_menu_win(m, menuwin); keypad(menuwin, TRUE); box(menuwin, 0, 0); set_menu_sub(m, derwin(menuwin, mrows, mcols, 1, 1)); post_menu(m); while (menu_driver(m, menu_virtualize(wGetchar(menuwin))) != E_UNKNOWN_COMMAND) continue; (void) mvprintw(LINES - 2, 0, "You chose: %s\n", item_name(current_item(m))); (void) addstr("Press any key to continue..."); wGetchar(stdscr); unpost_menu(m); delwin(menuwin); free_menu(m); for (ip = items; *ip; ip++) free_item(*ip); } #ifdef TRACE #define T_TBL(name) { #name, name } static struct { const char *name; int mask; } t_tbl[] = { T_TBL(TRACE_DISABLE), T_TBL(TRACE_TIMES), T_TBL(TRACE_TPUTS), T_TBL(TRACE_UPDATE), T_TBL(TRACE_MOVE), T_TBL(TRACE_CHARPUT), T_TBL(TRACE_ORDINARY), T_TBL(TRACE_CALLS), T_TBL(TRACE_VIRTPUT), T_TBL(TRACE_IEVENT), T_TBL(TRACE_BITS), T_TBL(TRACE_ICALLS), T_TBL(TRACE_CCALLS), T_TBL(TRACE_MAXIMUM), { (char *)0, 0 } }; static char *tracetrace(int tlevel) { static char *buf; int n; if (buf == 0) { size_t need = 12; for (n = 0; t_tbl[n].name != 0; n++) need += strlen(t_tbl[n].name) + 2; buf = (char *)malloc(need); } sprintf(buf, "0x%02x = {", tlevel); if (tlevel == 0) { sprintf(buf + strlen(buf), "%s, ", t_tbl[0].name); } else { for (n = 1; t_tbl[n].name != 0; n++) if ((tlevel & t_tbl[n].mask) == t_tbl[n].mask) { strcat(buf, t_tbl[n].name); strcat(buf, ", "); } } if (buf[strlen(buf) - 2] == ',') buf[strlen(buf) - 2] = '\0'; return(strcat(buf,"}")); } /* fake a dynamically reconfigurable menu using the 0th entry to deselect * the others */ static int run_trace_menu(MENU *m) { ITEM **items; ITEM *i, **p; for (;;) { bool changed = FALSE; switch (menu_driver(m, menu_virtualize(wGetchar(menu_win(m))))) { case E_UNKNOWN_COMMAND: return FALSE; default: items = menu_items(m); i = current_item(m); if (i == items[0]) { if (item_value(i)) { for (p = items+1; *p != 0; p++) if (item_value(*p)) { set_item_value(*p, FALSE); changed = TRUE; } } } else { for (p = items+1; *p != 0; p++) if (item_value(*p)) { set_item_value(items[0], FALSE); changed = TRUE; break; } } if (!changed) return TRUE; } } } static void trace_set(void) /* interactively set the trace level */ { MENU *m; ITEM *items[SIZEOF(t_tbl)]; ITEM **ip = items; int mrows, mcols, newtrace; int n; WINDOW *menuwin; mvaddstr(0, 0, "Interactively set trace level:"); mvaddstr(2, 0, " Press space bar to toggle a selection."); mvaddstr(3, 0, " Use up and down arrow to move the select bar."); mvaddstr(4, 0, " Press return to set the trace level."); mvprintw(6, 0, "(Current trace level is %s)", tracetrace(_nc_tracing)); refresh(); for (n = 0; t_tbl[n].name != 0; n++) *ip++ = new_item(t_tbl[n].name, ""); *ip = (ITEM *)0; m = new_menu(items); set_menu_format(m, 0, 2); scale_menu(m, &mrows, &mcols); menu_opts_off(m, O_ONEVALUE); menuwin = newwin(mrows + 2, mcols + 2, MENU_Y, MENU_X); set_menu_win(m, menuwin); keypad(menuwin, TRUE); box(menuwin, 0, 0); set_menu_sub(m, derwin(menuwin, mrows, mcols, 1, 1)); post_menu(m); for (ip = menu_items(m); *ip; ip++) { int mask = t_tbl[item_index(*ip)].mask; if (mask == 0) set_item_value(*ip, _nc_tracing == 0); else if ((mask & _nc_tracing) == mask) set_item_value(*ip, TRUE); } while (run_trace_menu(m)) continue; newtrace = 0; for (ip = menu_items(m); *ip; ip++) if (item_value(*ip)) newtrace |= t_tbl[item_index(*ip)].mask; trace(newtrace); _tracef("trace level interactively set to %s", tracetrace(_nc_tracing)); (void) mvprintw(LINES - 2, 0, "Trace level is %s\n", tracetrace(_nc_tracing)); (void) addstr("Press any key to continue..."); wGetchar(stdscr); unpost_menu(m); delwin(menuwin); free_menu(m); for (ip = items; *ip; ip++) free_item(*ip); } #endif /* TRACE */ #endif /* USE_LIBMENU */ /**************************************************************************** * * Forms test * ****************************************************************************/ #if USE_LIBFORM static FIELD *make_label(int frow, int fcol, NCURSES_CONST char *label) { FIELD *f = new_field(1, strlen(label), frow, fcol, 0, 0); if (f) { set_field_buffer(f, 0, label); set_field_opts(f, field_opts(f) & ~O_ACTIVE); } return(f); } static FIELD *make_field(int frow, int fcol, int rows, int cols) { FIELD *f = new_field(rows, cols, frow, fcol, 0, 0); if (f) set_field_back(f, A_UNDERLINE); return(f); } static void display_form(FORM *f) { WINDOW *w; int rows, cols; scale_form(f, &rows, &cols); if ((w =newwin(rows+2, cols+4, 0, 0)) != (WINDOW *)0) { set_form_win(f, w); set_form_sub(f, derwin(w, rows, cols, 1, 2)); box(w, 0, 0); keypad(w, TRUE); } if (post_form(f) != E_OK) wrefresh(w); } static void erase_form(FORM *f) { WINDOW *w = form_win(f); WINDOW *s = form_sub(f); unpost_form(f); werase(w); wrefresh(w); delwin(s); delwin(w); } static int form_virtualize(WINDOW *w) { static int mode = REQ_INS_MODE; int c = wGetchar(w); switch(c) { case QUIT: case ESCAPE: return(MAX_FORM_COMMAND + 1); /* demo doesn't use these three, leave them in anyway as sample code */ case KEY_NPAGE: case CTRL('F'): return(REQ_NEXT_PAGE); case KEY_PPAGE: return(REQ_PREV_PAGE); case KEY_NEXT: case CTRL('N'): return(REQ_NEXT_FIELD); case KEY_PREVIOUS: case CTRL('P'): return(REQ_PREV_FIELD); case KEY_HOME: return(REQ_FIRST_FIELD); case KEY_END: case KEY_LL: return(REQ_LAST_FIELD); case CTRL('L'): return(REQ_LEFT_FIELD); case CTRL('R'): return(REQ_RIGHT_FIELD); case CTRL('U'): return(REQ_UP_FIELD); case CTRL('D'): return(REQ_DOWN_FIELD); case CTRL('W'): return(REQ_NEXT_WORD); case CTRL('B'): return(REQ_PREV_WORD); case CTRL('S'): return(REQ_BEG_FIELD); case CTRL('E'): return(REQ_END_FIELD); case KEY_LEFT: return(REQ_LEFT_CHAR); case KEY_RIGHT: return(REQ_RIGHT_CHAR); case KEY_UP: return(REQ_UP_CHAR); case KEY_DOWN: return(REQ_DOWN_CHAR); case CTRL('M'): return(REQ_NEW_LINE); case CTRL('I'): return(REQ_INS_CHAR); case CTRL('O'): return(REQ_INS_LINE); case CTRL('V'): return(REQ_DEL_CHAR); case CTRL('H'): case KEY_BACKSPACE: return(REQ_DEL_PREV); case CTRL('Y'): return(REQ_DEL_LINE); case CTRL('G'): return(REQ_DEL_WORD); case CTRL('C'): return(REQ_CLR_EOL); case CTRL('K'): return(REQ_CLR_EOF); case CTRL('X'): return(REQ_CLR_FIELD); case CTRL('A'): return(REQ_NEXT_CHOICE); case CTRL('Z'): return(REQ_PREV_CHOICE); case CTRL(']'): if (mode == REQ_INS_MODE) return(mode = REQ_OVL_MODE); else return(mode = REQ_INS_MODE); default: return(c); } } static int my_form_driver(FORM *form, int c) { if (c == (MAX_FORM_COMMAND + 1) && form_driver(form, REQ_VALIDATION) == E_OK) return(TRUE); else { beep(); return(FALSE); } } static void demo_forms(void) { WINDOW *w; FORM *form; FIELD *f[10]; int finished = 0, c; mvaddstr(10, 57, "Forms Entry Test"); move(18, 0); addstr("Defined form-traversal keys: ^Q/ESC- exit form\n"); addstr("^N -- go to next field ^P -- go to previous field\n"); addstr("Home -- go to first field End -- go to last field\n"); addstr("^L -- go to field to left ^R -- go to field to right\n"); addstr("^U -- move upward to field ^D -- move downward to field\n"); addstr("^W -- go to next word ^B -- go to previous word\n"); addstr("^S -- go to start of field ^E -- go to end of field\n"); addstr("^H -- delete previous char ^Y -- delete line\n"); addstr("^G -- delete current word ^C -- clear to end of line\n"); addstr("^K -- clear to end of field ^X -- clear field\n"); addstr("Arrow keys move within a field as you would expect."); refresh(); /* describe the form */ f[0] = make_label(0, 15, "Sample Form"); f[1] = make_label(2, 0, "Last Name"); f[2] = make_field(3, 0, 1, 18); f[3] = make_label(2, 20, "First Name"); f[4] = make_field(3, 20, 1, 12); f[5] = make_label(2, 34, "Middle Name"); f[6] = make_field(3, 34, 1, 12); f[7] = make_label(5, 0, "Comments"); f[8] = make_field(6, 0, 4, 46); f[9] = (FIELD *)0; form = new_form(f); display_form(form); w = form_win(form); raw(); while (!finished) { switch(form_driver(form, c = form_virtualize(w))) { case E_OK: break; case E_UNKNOWN_COMMAND: finished = my_form_driver(form, c); break; default: beep(); break; } } erase_form(form); free_form(form); for (c = 0; f[c] != 0; c++) free_field(f[c]); noraw(); } #endif /* USE_LIBFORM */ /**************************************************************************** * * Overlap test * ****************************************************************************/ static void fillwin(WINDOW *win, char ch) { int y, x; int y1, x1; getmaxyx(win, y1, x1); for (y = 0; y < y1; y++) { wmove(win, y, 0); for (x = 0; x < x1; x++) waddch(win, ch); } } static void crosswin(WINDOW *win, char ch) { int y, x; int y1, x1; getmaxyx(win, y1, x1); for (y = 0; y < y1; y++) { for (x = 0; x < x1; x++) if (((x > (x1 - 1) / 3) && (x <= (2 * (x1 - 1)) / 3)) || (((y > (y1 - 1) / 3) && (y <= (2 * (y1 - 1)) / 3)))) { wmove(win, y, x); waddch(win, ch); } } } static void overlap_test(void) /* test effects of overlapping windows */ { int ch; WINDOW *win1 = newwin(9, 20, 3, 3); WINDOW *win2 = newwin(9, 20, 9, 16); raw(); refresh(); move(0, 0); printw("This test shows the behavior of wnoutrefresh() with respect to\n"); printw("the shared region of two overlapping windows A and B. The cross\n"); printw("pattern in each window does not overlap the other.\n"); move(18, 0); printw("a = refresh A, then B, then doupdate. b = refresh B, then A, then doupdaute\n"); printw("c = fill window A with letter A. d = fill window B with letter B.\n"); printw("e = cross pattern in window A. f = cross pattern in window B.\n"); printw("g = clear window A. h = clear window B.\n"); printw("i = overwrite A onto B. j = overwrite B onto A.\n"); printw("^Q/ESC = terminate test."); while ((ch = Getchar()) != QUIT && ch != ESCAPE) switch (ch) { case 'a': /* refresh window A first, then B */ wnoutrefresh(win1); wnoutrefresh(win2); doupdate(); break; case 'b': /* refresh window B first, then A */ wnoutrefresh(win2); wnoutrefresh(win1); doupdate(); break; case 'c': /* fill window A so it's visible */ fillwin(win1, 'A'); break; case 'd': /* fill window B so it's visible */ fillwin(win2, 'B'); break; case 'e': /* cross test pattern in window A */ crosswin(win1, 'A'); break; case 'f': /* cross test pattern in window A */ crosswin(win2, 'B'); break; case 'g': /* clear window A */ wclear(win1); wmove(win1, 0, 0); break; case 'h': /* clear window B */ wclear(win2); wmove(win2, 0, 0); break; case 'i': /* overwrite A onto B */ overwrite(win1, win2); break; case 'j': /* overwrite B onto A */ overwrite(win2, win1); break; } delwin(win2); delwin(win1); erase(); endwin(); } /**************************************************************************** * * Main sequence * ****************************************************************************/ static bool do_single_test(const char c) /* perform a single specified test */ { switch (c) { case 'a': getch_test(); break; case 'b': attr_test(); break; case 'c': if (!has_colors()) Cannot("does not support color."); else color_test(); break; case 'd': if (!has_colors()) Cannot("does not support color."); else if (!can_change_color()) Cannot("has hardwired color values."); else color_edit(); break; case 'e': slk_test(); break; case 'f': acs_display(); break; #if USE_LIBPANEL case 'o': demo_panels(); break; #endif case 'g': acs_and_scroll(); break; case 'i': flushinp_test(stdscr); break; case 'k': test_sgr_attributes(); break; #if USE_LIBMENU case 'm': menu_test(); break; #endif #if USE_LIBPANEL case 'p': demo_pad(); break; #endif #if USE_LIBFORM case 'r': demo_forms(); break; #endif case 's': overlap_test(); break; #if USE_LIBMENU && defined(TRACE) case 't': trace_set(); break; #endif case '?': break; default: return FALSE; } return TRUE; } static void usage(void) { static const char *const tbl[] = { "Usage: ncurses [options]" ,"" ,"Options:" ," -e fmt specify format for soft-keys test (e)" ," -f rip-off footer line (can repeat)" ," -h rip-off header line (can repeat)" ," -s msec specify nominal time for panel-demo (default: 1, to hold)" #ifdef TRACE ," -t mask specify default trace-level (may toggle with ^T)" #endif }; size_t n; for (n = 0; n < sizeof(tbl)/sizeof(tbl[0]); n++) fprintf(stderr, "%s\n", tbl[n]); exit(EXIT_FAILURE); } static void set_terminal_modes(void) { noraw(); cbreak(); noecho(); scrollok(stdscr, TRUE); idlok(stdscr, TRUE); keypad(stdscr, TRUE); } #ifdef SIGUSR1 static RETSIGTYPE announce_sig(int sig) { (void) fprintf(stderr, "Handled signal %d\r\n", sig); } #endif static int rip_footer(WINDOW *win, int columns) { wbkgd(win, A_REVERSE); werase(win); wmove(win, 0, 0); wprintw(win, "footer: %d columns", columns); wnoutrefresh(win); return OK; } static int rip_header(WINDOW *win, int columns) { wbkgd(win, A_REVERSE); werase(win); wmove(win, 0, 0); wprintw(win, "header: %d columns", columns); wnoutrefresh(win); return OK; } /*+------------------------------------------------------------------------- main(argc,argv) --------------------------------------------------------------------------*/ int main(int argc, char *argv[]) { int command, c; int my_e_param = 1; #if HAVE_LOCALE_H setlocale(LC_CTYPE, ""); #endif while ((c = getopt(argc, argv, "e:fhs:t:")) != EOF) { switch (c) { case 'e': my_e_param = atoi(optarg); #ifdef NCURSES_VERSION if (my_e_param > 3) /* allow extended layouts */ usage(); #else if (my_e_param > 1) usage(); #endif break; case 'f': ripoffline(-1, rip_footer); break; case 'h': ripoffline(1, rip_header); break; #if USE_LIBPANEL case 's': nap_msec = atol(optarg); break; #endif #ifdef TRACE case 't': save_trace = atoi(optarg); break; #endif default: usage(); } } /* * If there's no menus (unlikely for ncurses!), then we'll have to set * tracing on initially, just in case the user wants to test something that * doesn't involve wGetchar. */ #ifdef TRACE /* enable debugging */ #if !USE_LIBMENU trace(save_trace); #else if (!isatty(fileno(stdin))) trace(save_trace); #endif /* USE_LIBMENU */ #endif /* TRACE */ /* tell it we're going to play with soft keys */ slk_init(my_e_param); #ifdef SIGUSR1 /* set up null signal catcher so we can see what interrupts to getch do */ signal(SIGUSR1, announce_sig); #endif /* we must initialize the curses data structure only once */ initscr(); bkgdset(BLANK); /* tests, in general, will want these modes */ start_color(); set_terminal_modes(); def_prog_mode(); /* * Return to terminal mode, so we're guaranteed of being able to * select terminal commands even if the capabilities are wrong. */ endwin(); (void) puts("Welcome to ncurses. Press ? for help."); do { (void) puts("This is the ncurses main menu"); (void) puts("a = keyboard and mouse input test"); (void) puts("b = character attribute test"); (void) puts("c = color test pattern"); (void) puts("d = edit RGB color values"); (void) puts("e = exercise soft keys"); (void) puts("f = display ACS characters"); (void) puts("g = display windows and scrolling"); (void) puts("i = test of flushinp()"); (void) puts("k = display character attributes"); #if USE_LIBMENU (void) puts("m = menu code test"); #endif #if USE_LIBPANEL (void) puts("o = exercise panels library"); (void) puts("p = exercise pad features"); (void) puts("q = quit"); #endif #if USE_LIBFORM (void) puts("r = exercise forms code"); #endif (void) puts("s = overlapping-refresh test"); #if USE_LIBMENU && defined(TRACE) (void) puts("t = set trace level"); #endif (void) puts("? = repeat this command summary"); (void) fputs("> ", stdout); (void) fflush(stdout); /* necessary under SVr4 curses */ /* * This used to be an 'fgets()' call. However (on Linux, at least) * mixing stream I/O and 'read()' (used in the library) causes the * input stream to be flushed when switching between the two. */ command = 0; for(;;) { char ch; if (read(fileno(stdin), &ch, 1) <= 0) { if (command == 0) command = 'q'; break; } else if (command == 0 && !isspace(ch)) { command = ch; } else if (ch == '\n' || ch == '\r') { if (command != 0) break; (void) fputs("> ", stdout); (void) fflush(stdout); } } if (do_single_test(command)) { /* * This may be overkill; it's intended to reset everything back * to the initial terminal modes so that tests don't get in * each other's way. */ flushinp(); set_terminal_modes(); reset_prog_mode(); clear(); refresh(); endwin(); if (command == '?') { (void) puts("This is the ncurses capability tester."); (void) puts("You may select a test from the main menu by typing the"); (void) puts("key letter of the choice (the letter to left of the =)"); (void) puts("at the > prompt. The commands `x' or `q' will exit."); } continue; } } while (command != 'q'); ExitProgram(EXIT_SUCCESS); } /* ncurses.c ends here */