/* $NetBSD: menu_sys.def,v 1.25 2001/01/07 06:09:20 phil 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 /* Multiple key support */ #define KEYSEQ_FIRST 256 #define KEYSEQ_DOWN_ARROW 256 #define KEYSEQ_UP_ARROW 257 #define KEYSEQ_LEFT_ARROW 258 #define KEYSEQ_RIGHT_ARROW 259 #define KEYSEQ_PAGE_DOWN 260 #define KEYSEQ_PAGE_UP 261 struct keyseq { char *termcap_name; char *chars; int numchars; int keyseq_val; struct keyseq *next; }; /* keypad and other definitions */ struct keyseq _mc_key_seq[] = { /* Cludge for xterm ... */ { NULL, "\033[B", 0, KEYSEQ_DOWN_ARROW, NULL }, { NULL, "\033[D", 0, KEYSEQ_LEFT_ARROW, NULL }, { NULL, "\033[C", 0, KEYSEQ_RIGHT_ARROW, NULL }, { NULL, "\033[A", 0, KEYSEQ_UP_ARROW, NULL }, /* Termcap defined */ { "kd", NULL, 0, KEYSEQ_DOWN_ARROW, NULL }, { "kl", NULL, 0, KEYSEQ_LEFT_ARROW, NULL }, { "kr", NULL, 0, KEYSEQ_RIGHT_ARROW, NULL }, { "ku", NULL, 0, KEYSEQ_UP_ARROW, NULL }, { "kf", NULL, 0, KEYSEQ_PAGE_DOWN, NULL }, /* scroll forward */ { "kN", NULL, 0, KEYSEQ_PAGE_DOWN, NULL }, /* next page */ { "kP", NULL, 0, KEYSEQ_PAGE_UP, NULL }, /* scroll backward */ { "kR", NULL, 0, KEYSEQ_PAGE_UP, NULL }, /* prev page */ /* other definitions */ { NULL, "\033v", 0, KEYSEQ_PAGE_UP, NULL }, /* ESC-v */ { NULL, "\026", 0, KEYSEQ_PAGE_DOWN, NULL }, /* CTL-v */ }; int _mc_num_key_seq = sizeof(_mc_key_seq) / sizeof(struct keyseq); struct keyseq *pad_list = NULL; static char str_area [512]; static char *str_ptr = str_area; /* 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 ins_keyseq (struct keyseq **seq, struct keyseq *ins); static void init_keyseq (void); 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 void mbeep (void); static int menucmd (WINDOW *w); #ifndef NULL #define NULL (void *)0 #endif /* menu system processing routines */ static void mbeep (void) { fprintf (stderr,"\a"); } static void ins_keyseq (struct keyseq **seq, struct keyseq *ins) { if (*seq == NULL) { ins->next = NULL; *seq = ins; } else if (ins->numchars <= (*seq)->numchars) { ins->next = *seq; *seq = ins; } else ins_keyseq (&(*seq)->next, ins); } static void init_keyseq (void) { /* * XXX XXX XXX THIS SHOULD BE NUKED FROM ORBIT! DO THIS * XXX XXX XXX WITH NORMAL CURSES FACILITIES! */ extern struct tinfo *_cursesi_genbuf; int i; for (i=0; i<_mc_num_key_seq; i++) { if (_mc_key_seq[i].termcap_name) _mc_key_seq[i].chars = t_getstr (_cursesi_genbuf, _mc_key_seq[i].termcap_name, &str_ptr, NULL); if (_mc_key_seq[i].chars != NULL && (_mc_key_seq[i].numchars = strlen(_mc_key_seq[i].chars)) > 0) ins_keyseq (&pad_list,&_mc_key_seq[i]); } } static int mgetch(WINDOW *w) { static char buf[20]; static int num = 0; struct keyseq *list = pad_list; int i, ret; /* key pad processing */ while (list) { for (i=0; i< list->numchars; i++) { if (i >= num) buf[num++] = wgetch(w); if (buf[i] != list->chars[i]) break; } if (i == list->numchars) { num = 0; return list->keyseq_val; } list = list->next; } ret = buf[0]; for (i = 0; i < strlen(buf); i++) buf[i] = buf[i+1]; num--; return ret; } static int menucmd (WINDOW *w) { int ch; while (TRUE) { ch = mgetch(w); switch (ch) { case '\n': return REQ_EXECUTE; case '\016': /* Contnrol-P */ case KEYSEQ_DOWN_ARROW: return REQ_NEXT_ITEM; case '\020': /* Control-N */ case KEYSEQ_UP_ARROW: return REQ_PREV_ITEM; case '\014': /* Control-L */ return REQ_REDISPLAY; case '<': case '\010': /* Control-H (backspace) */ case KEYSEQ_PAGE_UP: return REQ_SCROLLUP; case '>': case ' ': case KEYSEQ_PAGE_DOWN: return REQ_SCROLLDOWN; case '?': return REQ_HELP; } 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); if (m->mw == NULL) { endwin(); (void) fprintf (stderr, "Could not create window for menu \"%s\"\n", m->title); exit(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, '*', '*'); 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 = mgetch(stdscr); if (winin < KEYSEQ_FIRST) winin = tolower(winin); again = 0; switch (winin) { case '<': case 'u': case KEYSEQ_UP_ARROW: case KEYSEQ_LEFT_ARROW: case KEYSEQ_PAGE_UP: if (lineoff) lineoff -= max_lines - 2; else again = 1; break; case '>': case 'd': case KEYSEQ_DOWN_ARROW: case KEYSEQ_RIGHT_ARROW: case KEYSEQ_PAGE_DOWN: 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(); max_lines = getmaxy(stdscr); max_cols = getmaxx(stdscr); init_keyseq(); #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); } }