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