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