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