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