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