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