/* $NetBSD: menu_sys.def,v 1.37 2003/06/03 11:51:56 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; 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 draw_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 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(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)) { hadd++; 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 c; } static void draw_menu_line(struct 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(struct menudesc *m) { int i; 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); tadd = strlen(m->title) ? 2 : 0; if (tadd) { mvwaddstr(m->mw, hasbox, hasbox, " "); mvwaddstr(m->mw, hasbox, hasbox + 1, m->title); cury += tadd; maxy += tadd; } for (i = m->topline; i < m->numopts; i++) { if (cury >= maxy) break; draw_menu_line(m, i, cury++, opt_ch(i), m->opts[i].opt_name); } /* Add the exit option. */ if (!(m->mopt & MC_NOEXITOPT) && cury < maxy) draw_menu_line(m, m->numopts, cury++, 'x', m->exitstr); /* Add the scroll line */ if (m->mopt & MC_SCROLL) 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(struct 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; } /* 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); switch(req) { case REQ_EXECUTE: return; case REQ_NEXT_ITEM: if (m->cursel >= m->numopts + hasexit - 1) { mbeep(); return; } m->cursel++; if (m->mopt & MC_SCROLL && m->cursel >= m->topline + m->h) m->topline += 1; break; case REQ_PREV_ITEM: if (m->cursel <= 0) { mbeep(); return; } m->cursel--; if (m->cursel < m->topline) m->topline -= 1; break; case REQ_REDISPLAY: wclear(stdscr); wrefresh(stdscr); if (m->post_act) (*m->post_act)(); break; case REQ_HELP: process_help(m, num); 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, m->numopts + hasexit - m->h); 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; } 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); num_avail = num_menus - 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; 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)(); draw_menu(m); 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) { 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)(); } } /* 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. */ static int double_menus(void) { menudesc *temp; temp = malloc(sizeof(menudesc) * num_menus * 2); if (temp == NULL) return 0; (void)memset(temp, 0, sizeof(menudesc) * num_menus * 2); (void)memcpy(temp, 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); } }