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