1/* $NetBSD: menu_sys.def,v 1.41 2003/06/16 20:57:43 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; 66#define DYN_INIT_NUM 32 67#endif 68 69/* prototypes for in here! */ 70static void init_menu(menudesc *m); 71static char opt_ch(int op_no); 72static void draw_menu(menudesc *m); 73static void process_help(menudesc *m, int num); 74static void process_req(menudesc *m, void *arg, int num, int req); 75static int menucmd(WINDOW *w); 76 77#ifndef NULL 78#define NULL 0 79#endif 80 81/* menu system processing routines */ 82#define mbeep() (void)fputc('\a', stderr) 83 84static int 85menucmd(WINDOW *w) 86{ 87 int ch; 88 89 while (TRUE) { 90 ch = wgetch(w); 91 92 switch (ch) { 93 case '\n': 94 return REQ_EXECUTE; 95 case '\016': /* Control-P */ 96 case KEY_DOWN: 97 return REQ_NEXT_ITEM; 98 case '\020': /* Control-N */ 99 case KEY_UP: 100 return REQ_PREV_ITEM; 101 case '\014': /* Control-L */ 102 return REQ_REDISPLAY; 103 case '<': 104 case '\010': /* Control-H (backspace) */ 105 case KEY_PPAGE: 106 case KEY_LEFT: 107 return REQ_SCROLLUP; 108 case '\026': /* Control-V */ 109 case '>': 110 case ' ': 111 case KEY_NPAGE: 112 case KEY_RIGHT: 113 return REQ_SCROLLDOWN; 114 case '?': 115 return REQ_HELP; 116 case '\033': /* esc-v is scroll down */ 117 ch = wgetch(w); 118 if (ch == 'v') 119 return REQ_SCROLLUP; 120 else 121 ch = 0; /* zap char so we beep */ 122 } 123 124 if (isalpha(ch)) 125 return ch; 126 127 mbeep(); 128 wrefresh(w); 129 } 130} 131 132static void 133init_menu(menudesc *m) 134{ 135 int wmax; 136 int hadd, wadd, exithadd; 137 int i; 138 const char *title; 139 140 hadd = ((m->mopt & MC_NOBOX) ? 0 : 2); 141 wadd = ((m->mopt & MC_NOBOX) ? 2 : 4); 142 143 if (m->title && *(title = MSG_XLAT(m->title)) != 0) { 144 wmax = strlen(title); 145 hadd += 2; 146 } else { 147 m->title = NULL; 148 title = "untitled"; 149 wmax = 0; 150 } 151 exithadd = ((m->mopt & MC_NOEXITOPT) ? 0 : 1); 152 153 154 /* Calculate h? h == number of visible options. */ 155 if (m->h == 0) 156 m->h = m->numopts + exithadd; 157 m->h = MIN(m->h, max_lines - m->y - hadd); 158 159 if (m->h < m->numopts + exithadd) { 160 if (!(m->mopt & MC_SCROLL) || m->h < 3) { 161 endwin(); 162 (void)fprintf(stderr, 163 "Window too short for menu \"%s\"\n", 164 title); 165 exit(1); 166 } 167 hadd++; 168 m->h--; 169 } else 170 m->mopt &= ~MC_SCROLL; 171#if 0 172 /* check for screen fit */ 173 if (m->y + m->h + hadd > max_lines) { 174 endwin(); 175 (void)fprintf(stderr, 176 "Screen too short (%d + %d + %d > %d) for menu \"%s\"\n", 177 m->y, m->h, hadd, max_lines, title); 178 exit(1); 179 180 } 181#endif 182 183 /* Calculate w? */ 184 if (m->w == 0) { 185 if (m->mopt & MC_SCROLL) 186 wmax = MAX(wmax,strlen(scrolltext)); 187 for (i = 0; i < m->numopts; i++) 188 wmax = MAX(wmax, strlen(MSG_XLAT(m->opts[i].opt_name)) + 3); 189 m->w = wmax; 190 } 191 192 /* check and adjust for screen fit */ 193 if (m->w + wadd > max_cols) { 194 endwin(); 195 (void)fprintf(stderr, 196 "Screen too narrow for menu \"%s\"\n", title); 197 exit(1); 198 199 } 200 if (m->x == -1) 201 m->x = (max_cols - (m->w + wadd)) / 2; /* center */ 202 else if (m->x + m->w + wadd > max_cols) 203 m->x = max_cols - (m->w + wadd); 204 205 /* Get the windows. */ 206 m->mw = newwin(m->h + hadd, m->w + wadd, m->y, m->x); 207 208 if (m->mw == NULL) { 209 endwin(); 210 (void)fprintf(stderr, 211 "Could not create window for menu \"%s\"\n", title); 212 exit(1); 213 } 214 keypad(m->mw, TRUE); /* enable multi-key assembling for win */ 215 216 /* XXX is it even worth doing this right? */ 217 if (has_colors()) { 218 wbkgd(m->mw, COLOR_PAIR(1)); 219 wattrset(m->mw, COLOR_PAIR(1)); 220 } 221} 222 223static char 224opt_ch(int op_no) 225{ 226 char c; 227 if (op_no < 25) { 228 c = 'a' + op_no; 229 if (c >= 'x') 230 c++; 231 } else 232 c = 'A' + op_no - 25; 233 return c; 234} 235 236static void 237draw_menu_line(menudesc *m, int i, int cury, char opt, const char *text) 238{ 239 int hasbox = m->mopt & MC_NOBOX ? 0 : 1; 240 241 if (m->cursel == i) { 242 mvwaddstr(m->mw, cury, hasbox, ">"); 243 wstandout(m->mw); 244 } else 245 mvwaddstr(m->mw, cury, hasbox, " "); 246 if (!(m->mopt & MC_NOSHORTCUT)) 247 wprintw(m->mw, "%c: ", opt); 248 waddstr(m->mw, text); 249 if (m->cursel == i) 250 wstandend(m->mw); 251} 252 253static void 254draw_menu(menudesc *m) 255{ 256 int opt; 257 int hasbox, cury, maxy; 258 int tadd; 259 260 if (m->mopt & MC_NOBOX) { 261 cury = 0; 262 maxy = m->h; 263 hasbox = 0; 264 } else { 265 cury = 1; 266 maxy = m->h + 1; 267 hasbox = 1; 268 } 269 270 /* Clear the window */ 271 wclear(m->mw); 272 273 if (m->title) { 274 mvwaddstr(m->mw, hasbox, hasbox, " "); 275 mvwaddstr(m->mw, hasbox, hasbox + 1, MSG_XLAT(m->title)); 276 tadd = 2; 277 cury += tadd; 278 maxy += tadd; 279 } else 280 tadd = 0; 281 282 if (m->cursel == -1) { 283 m->cursel = m->numopts; 284 if (m->h <= m->numopts) 285 m->topline = m->numopts + 1 - m->h; 286 } 287 288 for (opt = m->topline; opt < m->numopts; opt++) { 289 if (cury >= maxy) 290 break; 291 draw_menu_line(m, opt, cury++, opt_ch(opt), 292 MSG_XLAT(m->opts[opt].opt_name)); 293 } 294 295 /* Add the exit option. */ 296 if (!(m->mopt & MC_NOEXITOPT)) { 297 if (cury < maxy) 298 draw_menu_line(m, m->numopts, cury++, 'x', 299 MSG_XLAT(m->exitstr)); 300 else 301 opt = 0; 302 } 303 304 /* Add the scroll line */ 305 if (opt != m->numopts || m->topline != 0) 306 mvwaddstr(m->mw, cury, hasbox, scrolltext); 307 308 /* Add the box. */ 309 if (!(m->mopt & MC_NOBOX)) 310 box(m->mw, 0, 0); 311 312 wmove(m->mw, tadd + hasbox + m->cursel - m->topline, hasbox); 313 wrefresh(m->mw); 314} 315 316static void 317/*ARGSUSED*/ 318process_help(menudesc *m, int num) 319{ 320 const char *help = m->helpstr; 321 int lineoff = 0; 322 int curoff = 0; 323 int again; 324 int winin; 325 326 /* Is there help? */ 327 if (!help) { 328 mbeep(); 329 return; 330 } 331 help = MSG_XLAT(help); 332 333 /* Display the help information. */ 334 do { 335 if (lineoff < curoff) { 336 help = MSG_XLAT(m->helpstr); 337 curoff = 0; 338 } 339 while (*help && curoff < lineoff) { 340 if (*help == '\n') 341 curoff++; 342 help++; 343 } 344 345 wclear(stdscr); 346 mvwaddstr(stdscr, 0, 0, 347 "Help: exit: x, page up: u <, page down: d >"); 348 mvwaddstr(stdscr, 2, 0, help); 349 wmove(stdscr, 1, 0); 350 wrefresh(stdscr); 351 352 do { 353 winin = wgetch(stdscr); 354 if (winin < KEY_MIN) 355 winin = tolower(winin); 356 again = 0; 357 switch (winin) { 358 case '<': 359 case 'u': 360 case KEY_UP: 361 case KEY_LEFT: 362 case KEY_PPAGE: 363 if (lineoff) 364 lineoff -= max_lines - 2; 365 else 366 again = 1; 367 break; 368 case '>': 369 case 'd': 370 case KEY_DOWN: 371 case KEY_RIGHT: 372 case KEY_NPAGE: 373 if (*help) 374 lineoff += max_lines - 2; 375 else 376 again = 1; 377 break; 378 case 'q': 379 break; 380 case 'x': 381 winin = 'q'; 382 break; 383 default: 384 again = 1; 385 } 386 if (again) 387 mbeep(); 388 } while (again); 389 } while (winin != 'q'); 390} 391 392static void 393process_req(menudesc *m, void *arg, int num, int req) 394{ 395 int ch; 396 int hasexit = (m->mopt & MC_NOEXITOPT ? 0 : 1); 397 398 switch(req) { 399 400 case REQ_EXECUTE: 401 return; 402 403 case REQ_NEXT_ITEM: 404 ch = m->cursel; 405 for (;;) { 406 ch++; 407 if (ch >= m->numopts + hasexit) { 408 mbeep(); 409 return; 410 } 411 if (hasexit && ch == m->numopts) 412 break; 413 if (m->opts[ch].opt_flags & OPT_EXIT 414 || m->opts[ch].opt_menu != -1 415 || m->opts[ch].opt_action != NULL) 416 break; 417 } 418 m->cursel = ch; 419 if (m->mopt & MC_SCROLL && m->cursel >= m->topline + m->h) 420 m->topline = m->cursel - m->h + 1; 421 break; 422 423 case REQ_PREV_ITEM: 424 ch = m->cursel; 425 for (;;) { 426 if (ch <= 0) { 427 mbeep(); 428 return; 429 } 430 ch--; 431 if (m->opts[ch].opt_flags & OPT_EXIT 432 || m->opts[ch].opt_menu != -1 433 || m->opts[ch].opt_action != NULL) 434 break; 435 } 436 m->cursel = ch; 437 if (m->cursel < m->topline) 438 m->topline = m->cursel; 439 break; 440 441 case REQ_HELP: 442 process_help(m, num); 443 /* FALLTHROUGH */ 444 445 case REQ_REDISPLAY: 446 wclear(stdscr); 447 wrefresh(stdscr); 448 if (m->post_act) 449 (*m->post_act)(m, arg); 450 break; 451 452 case REQ_SCROLLUP: 453 if (m->cursel == 0) { 454 mbeep(); 455 return; 456 } 457 m->topline = MAX(0, m->topline - m->h); 458 m->cursel = MAX(0, m->cursel - m->h); 459 wclear(m->mw); 460 break; 461 462 case REQ_SCROLLDOWN: 463 if (m->cursel >= m->numopts + hasexit - 1) { 464 mbeep(); 465 return; 466 } 467 m->topline = MIN(m->topline + m->h, 468 MAX(m->numopts + hasexit - m->h, 0)); 469 m->cursel = MIN(m->numopts + hasexit - 1, m->cursel + m->h); 470 wclear(m->mw); 471 break; 472 473 default: 474 ch = req; 475 if (ch == 'x' && hasexit) { 476 m->cursel = m->numopts; 477 break; 478 } 479 if (m->mopt & MC_NOSHORTCUT) { 480 mbeep(); 481 return; 482 } 483 if (ch > 'z') 484 ch = 255; 485 if (ch >= 'a') { 486 if (ch > 'x') 487 ch--; 488 ch = ch - 'a'; 489 } else 490 ch = 25 + ch - 'A'; 491 if (ch < 0 || ch >= m->numopts) { 492 mbeep(); 493 return; 494 } 495 if (!(m->opts[ch].opt_flags & OPT_EXIT) 496 && m->opts[ch].opt_menu == -1 497 && m->opts[ch].opt_action == NULL) { 498 mbeep(); 499 return; 500 } 501 m->cursel = ch; 502 } 503 504 while (m->cursel >= m->topline + m->h) 505 m->topline = MIN(m->topline + m->h, 506 m->numopts + hasexit - m->h); 507 while (m->cursel < m->topline) 508 m->topline = MAX(0, m->topline - m->h); 509 510 draw_menu(m); 511} 512 513int 514menu_init(void) 515{ 516 517 if (__menu_init) 518 return 0; 519 520#ifdef USER_MENU_INIT 521 if (USER_MENU_INIT) 522 return 1; 523#endif 524 525 if (initscr() == NULL) 526 return 1; 527 528 cbreak(); 529 noecho(); 530 531 /* XXX Should be configurable but it almost isn't worth it. */ 532 if (has_colors()) { 533 start_color(); 534 init_pair(1, COLOR_WHITE, COLOR_BLUE); 535 bkgd(COLOR_PAIR(1)); 536 attrset(COLOR_PAIR(1)); 537 } 538 539 max_lines = getmaxy(stdscr); 540 max_cols = getmaxx(stdscr); 541 keypad(stdscr, TRUE); 542#ifdef DYNAMIC_MENUS 543 num_menus = DYN_INIT_NUM; 544 while (num_menus < DYN_MENU_START) 545 num_menus *= 2; 546 menus = malloc(sizeof(menudesc) * num_menus); 547 if (menus == NULL) 548 return 2; 549 (void)memset(menus, 0, sizeof(menudesc) * num_menus); 550 (void)memcpy(menus, menu_def, sizeof(menudesc) * DYN_MENU_START); 551#endif 552 553 __menu_init = 1; 554 return 0; 555} 556 557void 558process_menu(int num, void *arg) 559{ 560 int sel = 0; 561 int req, done; 562 int last_num; 563 menu_ent *opt; 564 565 menudesc *m; 566 567 m = &menus[num]; 568 569 done = FALSE; 570 571 /* Initialize? */ 572 if (menu_init()) { 573 __menu_initerror(); 574 return; 575 } 576 577 if (__m_endwin) { 578 wclear(stdscr); 579 wrefresh(stdscr); 580 __m_endwin = 0; 581 } 582 if (m->mw == NULL) 583 init_menu(m); 584 585 /* Default to select option 0 and display from 0 */ 586 m->topline = 0; 587 if ((m->mopt & (MC_DFLTEXIT | MC_NOEXITOPT)) == MC_DFLTEXIT) 588 m->cursel = -1; 589 else 590 m->cursel = 0; 591 592 while (!done) { 593 last_num = num; 594 if (__m_endwin) { 595 wclear(stdscr); 596 wrefresh(stdscr); 597 __m_endwin = 0; 598 } 599 /* Process the display action */ 600 if (m->post_act) 601 (*m->post_act)(m, arg); 602 draw_menu(m); 603 604 while ((req = menucmd(m->mw)) != REQ_EXECUTE) 605 process_req(m, arg, num, req); 606 607 sel = m->cursel; 608 if (!(m->mopt & MC_NOCLEAR)) { 609 wclear(m->mw); 610 wrefresh(m->mw); 611 } 612 613 /* Process the items */ 614 if (sel < m->numopts) { 615 opt = &m->opts[sel]; 616 if (opt->opt_flags & OPT_ENDWIN) { 617 endwin(); 618 __m_endwin = 1; 619 } 620 if (opt->opt_action) 621 done = (*opt->opt_action)(m, opt, arg); 622 if (opt->opt_menu != -1) { 623 if (opt->opt_flags & OPT_SUB) 624 process_menu(opt->opt_menu, arg); 625 else 626 num = opt->opt_menu; 627 } 628 629 if (opt->opt_flags & OPT_EXIT) 630 done = TRUE; 631 632 } else 633 done = TRUE; 634 635 /* Reselect m just in case */ 636 if (num != last_num) { 637 m = &menus[num]; 638 639 /* Initialize? */ 640 if (m->mw == NULL) 641 init_menu(m); 642 if (m->post_act) 643 (*m->post_act)(m,arg); 644 } 645 } 646 647 if (m->mopt & MC_NOCLEAR) { 648 wclear(m->mw); 649 wrefresh(m->mw); 650 } 651 652 /* Process the exit action */ 653 if (m->exit_act) 654 (*m->exit_act)(m, arg); 655} 656 657/* Control L is end of standard routines, remaining only for dynamic. */ 658 659/* Beginning of routines for dynamic menus. */ 660 661static int 662double_menus(void) 663{ 664 menudesc *temp; 665 int sz = sizeof(menudesc) * num_menus; 666 667 temp = realloc(menus, sz * 2); 668 if (temp == NULL) 669 return 0; 670 (void)memset(temp + num_menus, 0, sz); 671 menus = temp; 672 num_menus *= 2; 673 674 return 1; 675} 676 677int 678new_menu(const char *title, menu_ent *opts, int numopts, 679 int x, int y, int h, int w, int mopt, 680 void (*post_act)(menudesc *, void *), 681 void (*exit_act)(menudesc *, void *), 682 const char * help, const char *exit) 683{ 684 int ix; 685 menudesc *m; 686 687 /* Find free menu entry. */ 688 for (ix = DYN_MENU_START; ; ix++) { 689 if (ix > num_menus && !double_menus()) 690 return -1; 691 if (!(menus[ix].mopt & MC_VALID)) 692 break; 693 } 694 695 /* Set Entries */ 696 m = menus + ix; 697 m->title = title; 698 m->opts = opts; 699 m->numopts = numopts; 700 m->x = x; 701 m->y = y; 702 m->h = h; 703 m->w = w; 704 m->mopt = mopt | MC_VALID; 705 m->post_act = post_act; 706 m->exit_act = exit_act; 707 m->helpstr = help; 708 m->exitstr = exit ? exit : "Exit"; 709 710 init_menu(m); 711 712 return ix; 713} 714 715void 716free_menu(int menu_no) 717{ 718 menudesc *m; 719 720 if (menu_no < 0 || menu_no >= num_menus) 721 return; 722 723 m = menus + menu_no; 724 if ((m->mopt & MC_VALID)) 725 return; 726 if (m->mw != NULL) 727 delwin(m->mw); 728 memset(m, 0, sizeof *m); 729} 730 731void 732set_menu_numopts(int menu, int numopts) 733{ 734 735 menus[menu].numopts = numopts; 736} 737