menu_sys.def revision 1.36
1/* $NetBSD: menu_sys.def,v 1.36 2003/06/03 08:49:53 dsl Exp $ */ 2 3/* 4 * Copyright 1997 Piermont Information Systems Inc. 5 * All rights reserved. 6 * 7 * Written by Philip A. Nelson for Piermont Information Systems Inc. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. All advertising materials mentioning features or use of this software 18 * must display the following acknowledgement: 19 * This product includes software develooped for the NetBSD Project by 20 * Piermont Information Systems Inc. 21 * 4. The name of Piermont Information Systems Inc. may not be used to endorse 22 * or promote products derived from this software without specific prior 23 * written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS'' 26 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE 29 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 35 * THE POSSIBILITY OF SUCH DAMAGE. 36 * 37 */ 38 39/* menu_sys.defs -- Menu system standard routines. */ 40 41#include <string.h> 42#include <ctype.h> 43 44#define REQ_EXECUTE 1000 45#define REQ_NEXT_ITEM 1001 46#define REQ_PREV_ITEM 1002 47#define REQ_REDISPLAY 1003 48#define REQ_SCROLLDOWN 1004 49#define REQ_SCROLLUP 1005 50#define REQ_HELP 1006 51 52/* Macros */ 53#define MAX(x,y) ((x)>(y)?(x):(y)) 54#define MIN(x,y) ((x)<(y)?(x):(y)) 55 56/* Initialization state. */ 57static int __menu_init = 0; 58int __m_endwin = 0; 59static int max_lines = 0, max_cols = 0; 60static char *scrolltext = " <: page up, >: page down"; 61 62static menudesc *menus = menu_def; 63 64#ifdef DYNAMIC_MENUS 65static int num_menus = 0; 66static int num_avail = 0; 67#define DYN_INIT_NUM 32 68#endif 69 70/* prototypes for in here! */ 71static void init_menu(struct menudesc *m); 72static char opt_ch(int op_no); 73static void draw_menu(struct menudesc *m); 74static void process_help(struct menudesc *m, int num); 75static void process_req(struct menudesc *m, int num, int req); 76static int menucmd(WINDOW *w); 77 78#ifndef NULL 79#define NULL 0 80#endif 81 82/* menu system processing routines */ 83#define mbeep() (void)fputc('\a', stderr) 84 85static int 86menucmd(WINDOW *w) 87{ 88 int ch; 89 90 while (TRUE) { 91 ch = wgetch(w); 92 93 switch (ch) { 94 case '\n': 95 return REQ_EXECUTE; 96 case '\016': /* Control-P */ 97 case KEY_DOWN: 98 return REQ_NEXT_ITEM; 99 case '\020': /* Control-N */ 100 case KEY_UP: 101 return REQ_PREV_ITEM; 102 case '\014': /* Control-L */ 103 return REQ_REDISPLAY; 104 case '<': 105 case '\010': /* Control-H (backspace) */ 106 case KEY_PPAGE: 107 case KEY_LEFT: 108 return REQ_SCROLLUP; 109 case '\026': /* Control-V */ 110 case '>': 111 case ' ': 112 case KEY_NPAGE: 113 case KEY_RIGHT: 114 return REQ_SCROLLDOWN; 115 case '?': 116 return REQ_HELP; 117 case '\033': /* esc-v is scroll down */ 118 ch = wgetch(w); 119 if (ch == 'v') 120 return REQ_SCROLLUP; 121 else 122 ch = 0; /* zap char so we beep */ 123 } 124 125 if (isalpha(ch)) 126 return ch; 127 128 mbeep(); 129 wrefresh(w); 130 } 131} 132 133static void 134init_menu(struct menudesc *m) 135{ 136 int wmax; 137 int hadd, wadd, exithadd; 138 int i; 139 140 hadd = ((m->mopt & MC_NOBOX) ? 0 : 2); 141 wadd = ((m->mopt & MC_NOBOX) ? 2 : 4); 142 143 hadd += strlen(m->title) != 0 ? 2 : 0; 144 exithadd = ((m->mopt & MC_NOEXITOPT) ? 0 : 1); 145 146 wmax = strlen(m->title); 147 148 /* Calculate h? h == number of visible options. */ 149 if (m->h == 0) { 150 m->h = m->numopts + exithadd; 151 if (m->h + m->y + hadd >= max_lines && (m->mopt & MC_SCROLL)) { 152 hadd++; 153 m->h = max_lines - m->y - hadd; 154 } 155 } 156 157 /* Check window heights and set scrolling */ 158 if (m->h < m->numopts + exithadd) { 159 if (!(m->mopt & MC_SCROLL) || m->h < 3) { 160 endwin(); 161 (void)fprintf(stderr, 162 "Window too short for menu \"%s\"\n", 163 m->title); 164 exit(1); 165 } 166 } else 167 m->mopt &= ~MC_SCROLL; 168 169 /* check for screen fit */ 170 if (m->y + m->h + hadd > max_lines) { 171 endwin(); 172 (void)fprintf(stderr, 173 "Screen too short for menu \"%s\"\n", m->title); 174 exit(1); 175 176 } 177 178 /* Calculate w? */ 179 if (m->w == 0) { 180 if (m->mopt & MC_SCROLL) 181 wmax = MAX(wmax,strlen(scrolltext)); 182 for (i = 0; i < m->numopts; i++) 183 wmax = MAX(wmax, strlen(m->opts[i].opt_name) + 3); 184 m->w = wmax; 185 } 186 187 /* check and adjust for screen fit */ 188 if (m->w + wadd > max_cols) { 189 endwin(); 190 (void)fprintf(stderr, 191 "Screen too narrow for menu \"%s\"\n", m->title); 192 exit(1); 193 194 } 195 if (m->x == -1) 196 m->x = (max_cols - (m->w + wadd)) / 2; /* center */ 197 else if (m->x + m->w + wadd > max_cols) 198 m->x = max_cols - (m->w + wadd); 199 200 /* Get the windows. */ 201 m->mw = newwin(m->h + hadd, m->w + wadd, m->y, m->x); 202 keypad(m->mw, TRUE); /* enable multi-key assembling for win */ 203 204 if (m->mw == NULL) { 205 endwin(); 206 (void)fprintf(stderr, 207 "Could not create window for menu \"%s\"\n", m->title); 208 exit(1); 209 } 210 211 /* XXX is it even worth doing this right? */ 212 if (has_colors()) { 213 wbkgd(m->mw, COLOR_PAIR(1)); 214 wattrset(m->mw, COLOR_PAIR(1)); 215 } 216} 217 218static char 219opt_ch(int op_no) 220{ 221 char c; 222 if (op_no < 25) { 223 c = 'a' + op_no; 224 if (c >= 'x') 225 c++; 226 } else 227 c = 'A' + op_no - 25; 228 return c; 229} 230 231static void 232draw_menu_line(struct menudesc *m, int i, int cury, char opt, const char *text) 233{ 234 int hasbox = m->mopt & MC_NOBOX ? 0 : 1; 235 236 if (m->cursel == i) { 237 mvwaddstr(m->mw, cury, hasbox, ">"); 238 wstandout(m->mw); 239 } else 240 mvwaddstr(m->mw, cury, hasbox, " "); 241 if (!(m->mopt & MC_NOSHORTCUT)) 242 wprintw(m->mw, "%c: ", opt); 243 waddstr(m->mw, text); 244 if (m->cursel == i) 245 wstandend(m->mw); 246} 247 248static void 249draw_menu(struct menudesc *m) 250{ 251 int i; 252 int hasbox, cury, maxy; 253 int tadd; 254 255 if (m->mopt & MC_NOBOX) { 256 cury = 0; 257 maxy = m->h; 258 hasbox = 0; 259 } else { 260 cury = 1; 261 maxy = m->h + 1; 262 hasbox = 1; 263 } 264 265 /* Clear the window */ 266 wclear(m->mw); 267 268 tadd = strlen(m->title) ? 2 : 0; 269 270 if (tadd) { 271 mvwaddstr(m->mw, hasbox, hasbox, " "); 272 mvwaddstr(m->mw, hasbox, hasbox + 1, m->title); 273 cury += tadd; 274 maxy += tadd; 275 } 276 277 for (i = m->topline; i < m->numopts; i++) { 278 if (cury >= maxy) 279 break; 280 draw_menu_line(m, i, cury++, opt_ch(i), m->opts[i].opt_name); 281 } 282 283 /* Add the exit option. */ 284 if (!(m->mopt & MC_NOEXITOPT) && cury < maxy) 285 draw_menu_line(m, m->numopts, cury++, 'x', m->exitstr); 286 287 /* Add the scroll line */ 288 if (m->mopt & MC_SCROLL) 289 mvwaddstr(m->mw, cury, hasbox, scrolltext); 290 291 /* Add the box. */ 292 if (!(m->mopt & MC_NOBOX)) 293 box(m->mw, 0, 0); 294 295 wmove(m->mw, tadd + hasbox + m->cursel - m->topline, hasbox); 296 wrefresh(m->mw); 297} 298 299static void 300/*ARGSUSED*/ 301process_help(struct menudesc *m, int num) 302{ 303 const char *help = m->helpstr; 304 int lineoff = 0; 305 int curoff = 0; 306 int again; 307 int winin; 308 309 /* Is there help? */ 310 if (!help) { 311 mbeep(); 312 return; 313 } 314 315 /* Display the help information. */ 316 do { 317 if (lineoff < curoff) { 318 help = m->helpstr; 319 curoff = 0; 320 } 321 while (*help && curoff < lineoff) { 322 if (*help == '\n') 323 curoff++; 324 help++; 325 } 326 327 wclear(stdscr); 328 mvwaddstr(stdscr, 0, 0, 329 "Help: exit: x, page up: u <, page down: d >"); 330 mvwaddstr(stdscr, 2, 0, help); 331 wmove(stdscr, 1, 0); 332 wrefresh(stdscr); 333 334 do { 335 winin = wgetch(stdscr); 336 if (winin < KEY_MIN) 337 winin = tolower(winin); 338 again = 0; 339 switch (winin) { 340 case '<': 341 case 'u': 342 case KEY_UP: 343 case KEY_LEFT: 344 case KEY_PPAGE: 345 if (lineoff) 346 lineoff -= max_lines - 2; 347 else 348 again = 1; 349 break; 350 case '>': 351 case 'd': 352 case KEY_DOWN: 353 case KEY_RIGHT: 354 case KEY_NPAGE: 355 if (*help) 356 lineoff += max_lines - 2; 357 else 358 again = 1; 359 break; 360 case 'q': 361 break; 362 case 'x': 363 winin = 'q'; 364 break; 365 default: 366 again = 1; 367 } 368 if (again) 369 mbeep(); 370 } while (again); 371 } while (winin != 'q'); 372 373 /* Restore current menu */ 374 wclear(stdscr); 375 wrefresh(stdscr); 376 if (m->post_act) 377 (*m->post_act)(); 378} 379 380static void 381process_req(struct menudesc *m, int num, int req) 382{ 383 int ch; 384 int hasexit = (m->mopt & MC_NOEXITOPT ? 0 : 1); 385 386 switch(req) { 387 388 case REQ_EXECUTE: 389 return; 390 391 case REQ_NEXT_ITEM: 392 if (m->cursel >= m->numopts + hasexit - 1) { 393 mbeep(); 394 return; 395 } 396 m->cursel++; 397 if (m->mopt & MC_SCROLL && m->cursel >= m->topline + m->h) 398 m->topline += 1; 399 break; 400 401 case REQ_PREV_ITEM: 402 if (m->cursel <= 0) { 403 mbeep(); 404 return; 405 } 406 m->cursel--; 407 if (m->cursel < m->topline) 408 m->topline -= 1; 409 break; 410 411 case REQ_REDISPLAY: 412 wclear(stdscr); 413 wrefresh(stdscr); 414 if (m->post_act) 415 (*m->post_act)(); 416 break; 417 418 case REQ_HELP: 419 process_help(m, num); 420 break; 421 422 case REQ_SCROLLUP: 423 if (m->cursel == 0) { 424 mbeep(); 425 return; 426 } 427 m->topline = MAX(0, m->topline - m->h); 428 m->cursel = MAX(0, m->cursel - m->h); 429 wclear(m->mw); 430 break; 431 432 case REQ_SCROLLDOWN: 433 if (m->cursel >= m->numopts + hasexit - 1) { 434 mbeep(); 435 return; 436 } 437 m->topline = MIN(m->topline + m->h, 438 m->numopts + hasexit - m->h); 439 m->cursel = MIN(m->numopts + hasexit - 1, m->cursel + m->h); 440 wclear(m->mw); 441 break; 442 443 default: 444 ch = req; 445 if (ch == 'x' && hasexit) { 446 m->cursel = m->numopts; 447 break; 448 } 449 if (m->mopt & MC_NOSHORTCUT) { 450 mbeep(); 451 return; 452 } 453 if (ch > 'z') 454 ch = 255; 455 if (ch >= 'a') { 456 if (ch > 'x') 457 ch--; 458 ch = ch - 'a'; 459 } else 460 ch = 25 + ch - 'A'; 461 if (ch < 0 || ch >= m->numopts) { 462 mbeep(); 463 return; 464 } 465 m->cursel = ch; 466 } 467 468 while (m->cursel >= m->topline + m->h) 469 m->topline = MIN(m->topline + m->h, 470 m->numopts + hasexit - m->h); 471 while (m->cursel < m->topline) 472 m->topline = MAX(0, m->topline - m->h); 473 474 draw_menu(m); 475} 476 477int 478menu_init(void) 479{ 480 481 if (__menu_init) 482 return 0; 483 484#ifdef USER_MENU_INIT 485 if (USER_MENU_INIT) 486 return 1; 487#endif 488 489 if (initscr() == NULL) 490 return 1; 491 492 cbreak(); 493 noecho(); 494 495 /* XXX Should be configurable but it almost isn't worth it. */ 496 if (has_colors()) { 497 start_color(); 498 init_pair(1, COLOR_WHITE, COLOR_BLUE); 499 bkgd(COLOR_PAIR(1)); 500 attrset(COLOR_PAIR(1)); 501 } 502 503 max_lines = getmaxy(stdscr); 504 max_cols = getmaxx(stdscr); 505 keypad(stdscr, TRUE); 506#ifdef DYNAMIC_MENUS 507 num_menus = DYN_INIT_NUM; 508 while (num_menus < DYN_MENU_START) 509 num_menus *= 2; 510 menus = malloc(sizeof(menudesc) * num_menus); 511 if (menus == NULL) 512 return 2; 513 (void)memset(menus, 0, sizeof(menudesc) * num_menus); 514 (void)memcpy(menus, menu_def, sizeof(menudesc) * DYN_MENU_START); 515 num_avail = num_menus - DYN_MENU_START; 516#endif 517 518 __menu_init = 1; 519 return 0; 520} 521 522void 523process_menu(int num) 524{ 525 int sel = 0; 526 int req, done; 527 int last_num; 528 529 struct menudesc *m; 530 531 m = &menus[num]; 532 533 done = FALSE; 534 535 /* Initialize? */ 536 if (menu_init()) { 537 __menu_initerror(); 538 return; 539 } 540 541 if (__m_endwin) { 542 wclear(stdscr); 543 wrefresh(stdscr); 544 __m_endwin = 0; 545 } 546 if (m->mw == NULL) 547 init_menu(m); 548 549 /* Always preselect option 0 and display from 0! */ 550 m->cursel = 0; 551 m->topline = 0; 552 553 while (!done) { 554 last_num = num; 555 if (__m_endwin) { 556 wclear(stdscr); 557 wrefresh(stdscr); 558 __m_endwin = 0; 559 } 560 /* Process the display action */ 561 if (m->post_act) 562 (*m->post_act)(); 563 draw_menu(m); 564 565 while ((req = menucmd(m->mw)) != REQ_EXECUTE) 566 process_req(m, num, req); 567 568 sel = m->cursel; 569 wclear(m->mw); 570 wrefresh(m->mw); 571 572 /* Process the items */ 573 if (sel < m->numopts) { 574 if (m->opts[sel].opt_flags & OPT_ENDWIN) { 575 endwin(); 576 __m_endwin = 1; 577 } 578 if (m->opts[sel].opt_action) 579 done = (*m->opts[sel].opt_action)(m); 580 if (m->opts[sel].opt_menu != -1) { 581 if (m->opts[sel].opt_flags & OPT_SUB) 582 process_menu(m->opts[sel].opt_menu); 583 else 584 num = m->opts[sel].opt_menu; 585 } 586 587 if (m->opts[sel].opt_flags & OPT_EXIT) 588 done = TRUE; 589 590 } else 591 done = TRUE; 592 593 /* Reselect m just in case */ 594 if (num != last_num) { 595 m = &menus[num]; 596 597 /* Initialize? */ 598 if (m->mw == NULL) 599 init_menu(m); 600 if (m->post_act) 601 (*m->post_act)(); 602 } 603 } 604 605 /* Process the exit action */ 606 if (m->exit_act) 607 (*m->exit_act)(); 608} 609 610/* Control L is end of standard routines, remaining only for dynamic. */ 611 612/* Beginning of routines for dynamic menus. */ 613 614static int 615double_menus(void) 616{ 617 menudesc *temp; 618 619 temp = malloc(sizeof(menudesc) * num_menus * 2); 620 if (temp == NULL) 621 return 0; 622 (void) memset(temp, 0, sizeof(menudesc) * num_menus * 2); 623 (void) memcpy(temp, menus, sizeof(menudesc) * num_menus); 624 free(menus); 625 menus = temp; 626 num_avail = num_menus; 627 num_menus *= 2; 628 629 return 1; 630} 631 632int 633new_menu(char * title, menu_ent * opts, int numopts, 634 int x, int y, int h, int w, int mopt, 635 void (*post_act)(void), void (*exit_act)(void), char * help) 636{ 637 int ix; 638 639 /* Check for free menu entry. */ 640 if (num_avail == 0) 641 if (!double_menus()) 642 return -1; 643 644 /* Find free menu entry. */ 645 for (ix = DYN_MENU_START; ix < num_menus && menus[ix].mopt & MC_VALID; 646 ix++) /* do nothing */; 647 648 /* if ix == num_menus ... panic */ 649 650 /* Set Entries */ 651 menus[ix].title = title ? title : ""; 652 menus[ix].opts = opts; 653 menus[ix].numopts = numopts; 654 menus[ix].x = x; 655 menus[ix].y = y; 656 menus[ix].h = h; 657 menus[ix].w = w; 658 menus[ix].mopt = mopt | MC_VALID; 659 menus[ix].post_act = post_act; 660 menus[ix].exit_act = exit_act; 661 menus[ix].helpstr = help; 662 menus[ix].exitstr = "Exit"; 663 664 init_menu(&menus[ix]); 665 666 return ix; 667} 668 669void 670free_menu(int menu_no) 671{ 672 if (menu_no < num_menus) { 673 menus[menu_no].mopt &= ~MC_VALID; 674 if (menus[menu_no].mw != NULL) 675 delwin(menus[menu_no].mw); 676 } 677} 678