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