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