/* $NetBSD: menu_sys.def,v 1.42 2003/06/27 22:06:14 dsl Exp $ */ /* * Copyright 1997 Piermont Information Systems Inc. * All rights reserved. * * Written by Philip A. Nelson for Piermont Information Systems Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software develooped for the NetBSD Project by * Piermont Information Systems Inc. * 4. The name of Piermont Information Systems Inc. may not be used to endorse * or promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. * */ /* menu_sys.defs -- Menu system standard routines. */ #include #include #define REQ_EXECUTE 1000 #define REQ_NEXT_ITEM 1001 #define REQ_PREV_ITEM 1002 #define REQ_REDISPLAY 1003 #define REQ_SCROLLDOWN 1004 #define REQ_SCROLLUP 1005 #define REQ_HELP 1006 /* Macros */ #define MAX(x,y) ((x)>(y)?(x):(y)) #define MIN(x,y) ((x)<(y)?(x):(y)) /* Initialization state. */ static int __menu_init = 0; int __m_endwin = 0; static int max_lines = 0, max_cols = 0; static char *scrolltext = " <: page up, >: page down"; static menudesc *menus = menu_def; #ifdef DYNAMIC_MENUS static int num_menus = 0; #define DYN_INIT_NUM 32 #endif /* prototypes for in here! */ static void init_menu(menudesc *m); static char opt_ch(int op_no); static void draw_menu(menudesc *m); static void process_help(menudesc *m, int num); static void process_req(menudesc *m, void *arg, int num, int req); static int menucmd(WINDOW *w); #ifndef NULL #define NULL 0 #endif /* menu system processing routines */ #define mbeep() (void)fputc('\a', stderr) static int menucmd(WINDOW *w) { int ch; while (TRUE) { ch = wgetch(w); switch (ch) { case '\n': return REQ_EXECUTE; case '\016': /* Control-P */ case KEY_DOWN: return REQ_NEXT_ITEM; case '\020': /* Control-N */ case KEY_UP: return REQ_PREV_ITEM; case '\014': /* Control-L */ return REQ_REDISPLAY; case '<': case '\010': /* Control-H (backspace) */ case KEY_PPAGE: case KEY_LEFT: return REQ_SCROLLUP; case '\026': /* Control-V */ case '>': case ' ': case KEY_NPAGE: case KEY_RIGHT: return REQ_SCROLLDOWN; case '?': return REQ_HELP; case '\033': /* esc-v is scroll down */ ch = wgetch(w); if (ch == 'v') return REQ_SCROLLUP; else ch = 0; /* zap char so we beep */ } if (isalpha(ch)) return ch; mbeep(); wrefresh(w); } } static void init_menu(menudesc *m) { int wmax; int hadd, wadd, exithadd; int i; const char *title; hadd = ((m->mopt & MC_NOBOX) ? 0 : 2); wadd = ((m->mopt & MC_NOBOX) ? 2 : 4); if (m->title && *(title = MSG_XLAT(m->title)) != 0) { wmax = strlen(title); hadd += 2; } else { m->title = NULL; title = "untitled"; wmax = 0; } exithadd = ((m->mopt & MC_NOEXITOPT) ? 0 : 1); /* Calculate h? h == number of visible options. */ if (m->h == 0) m->h = m->numopts + exithadd; m->h = MIN(m->h, max_lines - m->y - hadd); if (m->h < m->numopts + exithadd) { if (!(m->mopt & MC_SCROLL) || m->h < 3) { endwin(); (void)fprintf(stderr, "Window too short for menu \"%s\"\n", title); exit(1); } hadd++; m->h--; } else m->mopt &= ~MC_SCROLL; #if 0 /* check for screen fit */ if (m->y + m->h + hadd > max_lines) { endwin(); (void)fprintf(stderr, "Screen too short (%d + %d + %d > %d) for menu \"%s\"\n", m->y, m->h, hadd, max_lines, title); exit(1); } #endif /* Calculate w? */ if (m->w == 0) { if (m->mopt & MC_SCROLL) wmax = MAX(wmax,strlen(scrolltext)); for (i = 0; i < m->numopts; i++) wmax = MAX(wmax, strlen(MSG_XLAT(m->opts[i].opt_name)) + 3); m->w = wmax; } /* check and adjust for screen fit */ if (m->w + wadd > max_cols) { endwin(); (void)fprintf(stderr, "Screen too narrow for menu \"%s\"\n", title); exit(1); } if (m->x == -1) m->x = (max_cols - (m->w + wadd)) / 2; /* center */ else if (m->x + m->w + wadd > max_cols) m->x = max_cols - (m->w + wadd); /* Get the windows. */ m->mw = newwin(m->h + hadd, m->w + wadd, m->y, m->x); if (m->mw == NULL) { endwin(); (void)fprintf(stderr, "Could not create window (%d + %d, %d + %d, %d, %d) for menu \"%s\"\n", m->h, hadd, m->w, wadd, m->y, m->x, title); exit(1); } keypad(m->mw, TRUE); /* enable multi-key assembling for win */ /* XXX is it even worth doing this right? */ if (has_colors()) { wbkgd(m->mw, COLOR_PAIR(1)); wattrset(m->mw, COLOR_PAIR(1)); } } static char opt_ch(int op_no) { char c; if (op_no < 25) { c = 'a' + op_no; if (c >= 'x') c++; } else c = 'A' + op_no - 25; return c; } static void draw_menu_line(menudesc *m, int i, int cury, char opt, const char *text) { int hasbox = m->mopt & MC_NOBOX ? 0 : 1; if (m->cursel == i) { mvwaddstr(m->mw, cury, hasbox, ">"); wstandout(m->mw); } else mvwaddstr(m->mw, cury, hasbox, " "); if (!(m->mopt & MC_NOSHORTCUT)) wprintw(m->mw, "%c: ", opt); waddstr(m->mw, text); if (m->cursel == i) wstandend(m->mw); } static void draw_menu(menudesc *m) { int opt; int hasbox, cury, maxy; int tadd; if (m->mopt & MC_NOBOX) { cury = 0; maxy = m->h; hasbox = 0; } else { cury = 1; maxy = m->h + 1; hasbox = 1; } /* Clear the window */ wclear(m->mw); if (m->title) { mvwaddstr(m->mw, hasbox, hasbox, " "); mvwaddstr(m->mw, hasbox, hasbox + 1, MSG_XLAT(m->title)); tadd = 2; cury += tadd; maxy += tadd; } else tadd = 0; if (m->cursel == -1) { m->cursel = m->numopts; if (m->h <= m->numopts) m->topline = m->numopts + 1 - m->h; } for (opt = m->topline; opt < m->numopts; opt++) { if (cury >= maxy) break; draw_menu_line(m, opt, cury++, opt_ch(opt), MSG_XLAT(m->opts[opt].opt_name)); } /* Add the exit option. */ if (!(m->mopt & MC_NOEXITOPT)) { if (cury < maxy) draw_menu_line(m, m->numopts, cury++, 'x', MSG_XLAT(m->exitstr)); else opt = 0; } /* Add the scroll line */ if (opt != m->numopts || m->topline != 0) mvwaddstr(m->mw, cury, hasbox, scrolltext); /* Add the box. */ if (!(m->mopt & MC_NOBOX)) box(m->mw, 0, 0); wmove(m->mw, tadd + hasbox + m->cursel - m->topline, hasbox); wrefresh(m->mw); } static void /*ARGSUSED*/ process_help(menudesc *m, int num) { const char *help = m->helpstr; int lineoff = 0; int curoff = 0; int again; int winin; /* Is there help? */ if (!help) { mbeep(); return; } help = MSG_XLAT(help); /* Display the help information. */ do { if (lineoff < curoff) { help = MSG_XLAT(m->helpstr); curoff = 0; } while (*help && curoff < lineoff) { if (*help == '\n') curoff++; help++; } wclear(stdscr); mvwaddstr(stdscr, 0, 0, "Help: exit: x, page up: u <, page down: d >"); mvwaddstr(stdscr, 2, 0, help); wmove(stdscr, 1, 0); wrefresh(stdscr); do { winin = wgetch(stdscr); if (winin < KEY_MIN) winin = tolower(winin); again = 0; switch (winin) { case '<': case 'u': case KEY_UP: case KEY_LEFT: case KEY_PPAGE: if (lineoff) lineoff -= max_lines - 2; else again = 1; break; case '>': case 'd': case KEY_DOWN: case KEY_RIGHT: case KEY_NPAGE: if (*help) lineoff += max_lines - 2; else again = 1; break; case 'q': break; case 'x': winin = 'q'; break; default: again = 1; } if (again) mbeep(); } while (again); } while (winin != 'q'); } static void process_req(menudesc *m, void *arg, int num, int req) { int ch; int hasexit = (m->mopt & MC_NOEXITOPT ? 0 : 1); switch(req) { case REQ_EXECUTE: return; case REQ_NEXT_ITEM: ch = m->cursel; for (;;) { ch++; if (ch >= m->numopts + hasexit) { mbeep(); return; } if (hasexit && ch == m->numopts) break; if (m->opts[ch].opt_flags & OPT_EXIT || m->opts[ch].opt_menu != -1 || m->opts[ch].opt_action != NULL) break; } m->cursel = ch; if (m->mopt & MC_SCROLL && m->cursel >= m->topline + m->h) m->topline = m->cursel - m->h + 1; break; case REQ_PREV_ITEM: ch = m->cursel; for (;;) { if (ch <= 0) { mbeep(); return; } ch--; if (m->opts[ch].opt_flags & OPT_EXIT || m->opts[ch].opt_menu != -1 || m->opts[ch].opt_action != NULL) break; } m->cursel = ch; if (m->cursel < m->topline) m->topline = m->cursel; break; case REQ_HELP: process_help(m, num); /* FALLTHROUGH */ case REQ_REDISPLAY: wclear(stdscr); wrefresh(stdscr); if (m->post_act) (*m->post_act)(m, arg); break; case REQ_SCROLLUP: if (m->cursel == 0) { mbeep(); return; } m->topline = MAX(0, m->topline - m->h); m->cursel = MAX(0, m->cursel - m->h); wclear(m->mw); break; case REQ_SCROLLDOWN: if (m->cursel >= m->numopts + hasexit - 1) { mbeep(); return; } m->topline = MIN(m->topline + m->h, MAX(m->numopts + hasexit - m->h, 0)); m->cursel = MIN(m->numopts + hasexit - 1, m->cursel + m->h); wclear(m->mw); break; default: ch = req; if (ch == 'x' && hasexit) { m->cursel = m->numopts; break; } if (m->mopt & MC_NOSHORTCUT) { mbeep(); return; } if (ch > 'z') ch = 255; if (ch >= 'a') { if (ch > 'x') ch--; ch = ch - 'a'; } else ch = 25 + ch - 'A'; if (ch < 0 || ch >= m->numopts) { mbeep(); return; } if (!(m->opts[ch].opt_flags & OPT_EXIT) && m->opts[ch].opt_menu == -1 && m->opts[ch].opt_action == NULL) { mbeep(); return; } m->cursel = ch; } while (m->cursel >= m->topline + m->h) m->topline = MIN(m->topline + m->h, m->numopts + hasexit - m->h); while (m->cursel < m->topline) m->topline = MAX(0, m->topline - m->h); draw_menu(m); } int menu_init(void) { if (__menu_init) return 0; #ifdef USER_MENU_INIT if (USER_MENU_INIT) return 1; #endif if (initscr() == NULL) return 1; cbreak(); noecho(); /* XXX Should be configurable but it almost isn't worth it. */ if (has_colors()) { start_color(); init_pair(1, COLOR_WHITE, COLOR_BLUE); bkgd(COLOR_PAIR(1)); attrset(COLOR_PAIR(1)); } max_lines = getmaxy(stdscr); max_cols = getmaxx(stdscr); keypad(stdscr, TRUE); #ifdef DYNAMIC_MENUS num_menus = DYN_INIT_NUM; while (num_menus < DYN_MENU_START) num_menus *= 2; menus = malloc(sizeof(menudesc) * num_menus); if (menus == NULL) return 2; (void)memset(menus, 0, sizeof(menudesc) * num_menus); (void)memcpy(menus, menu_def, sizeof(menudesc) * DYN_MENU_START); #endif __menu_init = 1; return 0; } void process_menu(int num, void *arg) { int sel = 0; int req, done; int last_num; menu_ent *opt; menudesc *m; m = &menus[num]; done = FALSE; /* Initialize? */ if (menu_init()) { __menu_initerror(); return; } if (__m_endwin) { wclear(stdscr); wrefresh(stdscr); __m_endwin = 0; } if (m->mw == NULL) init_menu(m); /* Default to select option 0 and display from 0 */ m->topline = 0; if ((m->mopt & (MC_DFLTEXIT | MC_NOEXITOPT)) == MC_DFLTEXIT) m->cursel = -1; else m->cursel = 0; while (!done) { last_num = num; if (__m_endwin) { wclear(stdscr); wrefresh(stdscr); __m_endwin = 0; } /* Process the display action */ if (m->post_act) (*m->post_act)(m, arg); draw_menu(m); while ((req = menucmd(m->mw)) != REQ_EXECUTE) process_req(m, arg, num, req); sel = m->cursel; if (!(m->mopt & MC_NOCLEAR)) { wclear(m->mw); wrefresh(m->mw); } /* Process the items */ if (sel < m->numopts) { opt = &m->opts[sel]; if (opt->opt_flags & OPT_ENDWIN) { endwin(); __m_endwin = 1; } if (opt->opt_action) done = (*opt->opt_action)(m, opt, arg); if (opt->opt_menu != -1) { if (opt->opt_flags & OPT_SUB) process_menu(opt->opt_menu, arg); else num = opt->opt_menu; } if (opt->opt_flags & OPT_EXIT) done = TRUE; } else done = TRUE; /* Reselect m just in case */ if (num != last_num) { m = &menus[num]; /* Initialize? */ if (m->mw == NULL) init_menu(m); if (m->post_act) (*m->post_act)(m,arg); } } if (m->mopt & MC_NOCLEAR) { wclear(m->mw); wrefresh(m->mw); } /* Process the exit action */ if (m->exit_act) (*m->exit_act)(m, arg); } /* Control L is end of standard routines, remaining only for dynamic. */ /* Beginning of routines for dynamic menus. */ static int double_menus(void) { menudesc *temp; int sz = sizeof(menudesc) * num_menus; temp = realloc(menus, sz * 2); if (temp == NULL) return 0; (void)memset(temp + num_menus, 0, sz); menus = temp; num_menus *= 2; return 1; } int new_menu(const char *title, menu_ent *opts, int numopts, int x, int y, int h, int w, int mopt, void (*post_act)(menudesc *, void *), void (*exit_act)(menudesc *, void *), const char * help, const char *exit) { int ix; menudesc *m; /* Find free menu entry. */ for (ix = DYN_MENU_START; ; ix++) { if (ix > num_menus && !double_menus()) return -1; if (!(menus[ix].mopt & MC_VALID)) break; } /* Set Entries */ m = menus + ix; m->title = title; m->opts = opts; m->numopts = numopts; m->x = x; m->y = y; m->h = h; m->w = w; m->mopt = mopt | MC_VALID; m->post_act = post_act; m->exit_act = exit_act; m->helpstr = help; m->exitstr = exit ? exit : "Exit"; init_menu(m); return ix; } void free_menu(int menu_no) { menudesc *m; if (menu_no < 0 || menu_no >= num_menus) return; m = menus + menu_no; if ((m->mopt & MC_VALID)) return; if (m->mw != NULL) delwin(m->mw); memset(m, 0, sizeof *m); } void set_menu_numopts(int menu, int numopts) { menus[menu].numopts = numopts; }