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