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