/* $NetBSD: menu_sys.def,v 1.29 2002/04/04 14:11:23 blymn 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; static int num_avail = 0; #define DYN_INIT_NUM 32 #endif /* prototypes for in here! */ static void init_menu (struct menudesc *m); static char opt_ch (int op_no); static void post_menu (struct menudesc *m); static void process_help (struct menudesc *m, int num); static void process_req (struct menudesc *m, int num, int req); static int menucmd (WINDOW *w); #ifndef NULL #define NULL (void *)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': /* Contnrol-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: return REQ_SCROLLUP; case '\026': case '>': case ' ': case KEY_NPAGE: 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 (struct menudesc *m) { int wmax; int hadd, wadd, exithadd; int i; hadd = ((m->mopt & MC_NOBOX) ? 0 : 2); wadd = ((m->mopt & MC_NOBOX) ? 2 : 4); hadd += strlen(m->title) != 0 ? 2 : 0; exithadd = ((m->mopt & MC_NOEXITOPT) ? 0 : 1); wmax = strlen(m->title); /* Calculate h? h == number of visible options. */ if (m->h == 0) { m->h = m->numopts + exithadd; if (m->h + m->y + hadd >= max_lines && (m->mopt & MC_SCROLL)) m->h = max_lines - m->y - hadd ; } /* Check window heights and set scrolling */ 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", m->title); exit(1); } } else m->mopt &= ~MC_SCROLL; /* check for screen fit */ if (m->y + m->h + hadd > max_lines) { endwin(); (void) fprintf (stderr, "Screen too short for menu \"%s\"\n", m->title); exit(1); } /* 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(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", m->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); keypad(m->mw, TRUE); /* enable multi-key assembling for win */ if (m->mw == NULL) { endwin(); (void) fprintf (stderr, "Could not create window for menu \"%s\"\n", m->title); exit(1); } /* 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 (char) c; } static void post_menu (struct menudesc *m) { int i; int hasbox, cury, maxy, selrow, lastopt; int tadd; char optstr[5]; 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); tadd = strlen(m->title) ? 2 : 0; if (tadd) { mvwaddstr(m->mw, cury, cury, " "); mvwaddstr(m->mw, cury, cury + 1, m->title); cury += 2; maxy += 2; } /* Set defaults, calculate lastopt. */ selrow = -1; if (m->mopt & MC_SCROLL) { lastopt = MIN(m->numopts, m->topline+m->h-1); maxy -= 1; } else lastopt = m->numopts; for (i=m->topline; icursel == i) { mvwaddstr (m->mw, cury, hasbox, ">"); wstandout(m->mw); selrow = cury; } else mvwaddstr (m->mw, cury, hasbox, " "); if (!(m->mopt & MC_NOSHORTCUT)) { (void) sprintf (optstr, "%c: ", opt_ch(i)); waddstr (m->mw, optstr); } waddstr (m->mw, m->opts[i].opt_name); if (m->cursel == i) wstandend(m->mw); } /* Add the exit option. */ if (!(m->mopt & MC_NOEXITOPT) && cury < maxy) { if (m->cursel >= m->numopts) { mvwaddstr (m->mw, cury, hasbox, ">"); wstandout(m->mw); selrow = cury; } else mvwaddstr (m->mw, cury, hasbox, " "); if (!(m->mopt & MC_NOSHORTCUT)) waddstr (m->mw, "x: "); waddstr (m->mw, m->exitstr); if (m->cursel >= m->numopts) wstandend(m->mw); cury++; } /* Add the scroll line */ if (m->mopt & MC_SCROLL) { mvwaddstr (m->mw, cury, hasbox, scrolltext); if (selrow < 0) selrow = cury; } /* Add the box. */ if (!(m->mopt & MC_NOBOX)) box(m->mw, 0, 0); wmove(m->mw, selrow, hasbox); } static void process_help (struct menudesc *m, int num) { char *help = m->helpstr; int lineoff = 0; int curoff = 0; int again; int winin; /* Is there help? */ if (!help) { mbeep(); return; } /* Display the help information. */ do { if (lineoff < curoff) { help = 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'); /* Restore current menu */ wclear(stdscr); wrefresh(stdscr); if (m->post_act) (*m->post_act)(); } static void process_req (struct menudesc *m, int num, int req) { int ch; int hasexit = (m->mopt & MC_NOEXITOPT ? 0 : 1 ); int refresh = 0; int scroll_sel = 0; if (req == REQ_EXECUTE) return; else if (req == REQ_NEXT_ITEM) { if (m->cursel < m->numopts + hasexit - 1) { m->cursel++; scroll_sel = 1; refresh = 1; if (m->mopt & MC_SCROLL && m->cursel >= m->topline + m->h -1 ) m->topline += 1; } else mbeep(); } else if (req == REQ_PREV_ITEM) { if (m->cursel > 0) { m->cursel--; scroll_sel = 1; refresh = 1; if (m->cursel < m->topline ) m->topline -= 1; } else mbeep(); } else if (req == REQ_REDISPLAY) { wclear(stdscr); wrefresh(stdscr); if (m->post_act) (*m->post_act)(); refresh = 1; } else if (req == REQ_HELP) { process_help (m, num); refresh = 1; } else if (req == REQ_SCROLLUP) { if (!(m->mopt & MC_SCROLL)) mbeep(); else if (m->topline == 0) mbeep(); else { m->topline = MAX(0,m->topline-m->h+1); m->cursel = MAX(0, m->cursel-m->h+1); wclear (m->mw); refresh = 1; } } else if (req == REQ_SCROLLDOWN) { if (!(m->mopt & MC_SCROLL)) mbeep(); else if (m->topline + m->h - 1 >= m->numopts + hasexit) mbeep(); else { m->topline = MIN(m->topline+m->h-1, m->numopts+hasexit-m->h+1); m->cursel = MIN(m->numopts-1, m->cursel+m->h-1); wclear (m->mw); refresh = 1; } } else { ch = req; if (ch == 'x' && hasexit) { m->cursel = m->numopts; scroll_sel = 1; refresh = 1; } else if (!(m->mopt & MC_NOSHORTCUT)) { 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(); else { m->cursel = ch; scroll_sel = 1; refresh = 1; } } else mbeep(); } if (m->mopt & MC_SCROLL && scroll_sel) { while (m->cursel >= m->topline + m->h -1 ) m->topline = MIN(m->topline+m->h-1, m->numopts+hasexit-m->h+1); while (m->cursel < m->topline) m->topline = MAX(0,m->topline-m->h+1); } if (refresh) { post_menu (m); wrefresh (m->mw); } } int menu_init (void) { if (__menu_init) return 0; 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 = (menudesc *) malloc(sizeof(menudesc)*num_menus); if (menus == NULL) return 2; (void) memset ((void *)menus, 0, sizeof(menudesc)*num_menus); (void) memcpy ((void *)menus, (void *)menu_def, sizeof(menudesc)*DYN_MENU_START); num_avail = num_menus - DYN_MENU_START; #endif __menu_init = 1; return (0); } void process_menu (int num) { int sel = 0; int req, done; int last_num; struct 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); /* Always preselect option 0 and display from 0! */ m->cursel = 0; m->topline = 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)(); post_menu (m); wrefresh (m->mw); while ((req = menucmd (m->mw)) != REQ_EXECUTE) process_req (m, num, req); sel = m->cursel; wclear (m->mw); wrefresh (m->mw); /* Process the items */ if (sel < m->numopts) { if (m->opts[sel].opt_flags & OPT_ENDWIN) { endwin(); __m_endwin = 1; } if (m->opts[sel].opt_action) done = (*m->opts[sel].opt_action)(m); if (m->opts[sel].opt_menu != -1) { if (m->opts[sel].opt_flags & OPT_SUB) process_menu (m->opts[sel].opt_menu); else num = m->opts[sel].opt_menu; } if (m->opts[sel].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)(); } } /* Process the exit action */ if (m->exit_act) (*m->exit_act)(); } /* Control L is end of standard routines, remaining only for dynamic. */ /* Beginning of routines for dynamic menus. */ /* local prototypes */ static int double_menus (void); static int double_menus (void) { menudesc *temp; temp = (menudesc *) malloc(sizeof(menudesc)*num_menus*2); if (temp == NULL) return 0; (void) memset ((void *)temp, 0, sizeof(menudesc)*num_menus*2); (void) memcpy ((void *)temp, (void *)menus, sizeof(menudesc)*num_menus); free (menus); menus = temp; num_avail = num_menus; num_menus *= 2; return 1; } int new_menu (char * title, menu_ent * opts, int numopts, int x, int y, int h, int w, int mopt, void (*post_act)(void), void (*exit_act)(void), char * help) { int ix; /* Check for free menu entry. */ if (num_avail == 0) if (!double_menus ()) return -1; /* Find free menu entry. */ for (ix = DYN_MENU_START; ix < num_menus && menus[ix].mopt & MC_VALID; ix++) /* do nothing */; /* if ix == num_menus ... panic */ /* Set Entries */ menus[ix].title = title ? title : ""; menus[ix].opts = opts; menus[ix].numopts = numopts; menus[ix].x = x; menus[ix].y = y; menus[ix].h = h; menus[ix].w = w; menus[ix].mopt = mopt | MC_VALID; menus[ix].post_act = post_act; menus[ix].exit_act = exit_act; menus[ix].helpstr = help; menus[ix].exitstr = "Exit"; init_menu (&menus[ix]); return ix; } void free_menu (int menu_no) { if (menu_no < num_menus) { menus[menu_no].mopt &= ~MC_VALID; if (menus[menu_no].mw != NULL) delwin (menus[menu_no].mw); } }