1/* $NetBSD: menu_sys.def,v 1.59 2012/03/06 16:55:18 mbalmer 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. The name of Piermont Information Systems Inc. may not be used to endorse 18 * or promote products derived from this software without specific prior 19 * written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS'' 22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE 25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 31 * THE POSSIBILITY OF SUCH DAMAGE. 32 * 33 */ 34 35/* menu_sys.defs -- Menu system standard routines. */ 36 37#include <string.h> 38#include <ctype.h> 39 40#define REQ_EXECUTE 1000 41#define REQ_NEXT_ITEM 1001 42#define REQ_PREV_ITEM 1002 43#define REQ_REDISPLAY 1003 44#define REQ_SCROLLDOWN 1004 45#define REQ_SCROLLUP 1005 46#define REQ_HELP 1006 47 48/* Macros */ 49#define MAX(x,y) ((x)>(y)?(x):(y)) 50#define MIN(x,y) ((x)<(y)?(x):(y)) 51 52/* Initialization state. */ 53static int __menu_init = 0; 54static int max_lines = 0, max_cols = 0; 55#ifndef scrolltext 56static const char *scrolltext = " <: page up, >: page down"; 57#endif 58 59#ifdef DYNAMIC_MENUS 60static int num_menus = 0; 61#define DYN_INIT_NUM 32 62static menudesc **menu_list; 63#define MENUS(n) (*(menu_list[n])) 64#else 65#define MENUS(n) (menu_def[n]) 66#endif 67 68/* prototypes for in here! */ 69static void init_menu(menudesc *m); 70static char opt_ch(menudesc *m, int op_no); 71static void draw_menu(menudesc *m, void *arg); 72static void process_help(menudesc *m); 73static void process_req(menudesc *m, void *arg, int req); 74static int menucmd(WINDOW *w); 75 76#ifndef NULL 77#define NULL 0 78#endif 79 80/* menu system processing routines */ 81#define mbeep() (void)fputc('\a', stderr) 82 83static int 84menucmd(WINDOW *w) 85{ 86 int ch; 87 88 while (TRUE) { 89 ch = wgetch(w); 90 91 switch (ch) { 92 case '\n': 93 return REQ_EXECUTE; 94 case '\016': /* Control-P */ 95 case KEY_DOWN: 96 return REQ_NEXT_ITEM; 97 case '\020': /* Control-N */ 98 case KEY_UP: 99 return REQ_PREV_ITEM; 100 case '\014': /* Control-L */ 101 return REQ_REDISPLAY; 102 case '<': 103 case '\010': /* Control-H (backspace) */ 104 case KEY_PPAGE: 105 case KEY_LEFT: 106 return REQ_SCROLLUP; 107 case '\026': /* Control-V */ 108 case '>': 109 case ' ': 110 case KEY_NPAGE: 111 case KEY_RIGHT: 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(menudesc *m) 133{ 134 int wmax; 135 int hadd, wadd, exithadd; 136 int i; 137 int x, y, w; 138 const char *title, *tp, *ep; 139 140 x = m->x; 141 y = m->y; 142 w = m->w; 143 wmax = 0; 144 hadd = ((m->mopt & MC_NOBOX) ? 0 : 2); 145 wadd = ((m->mopt & MC_NOBOX) ? 2 : 4); 146 if (!(m->mopt & MC_NOSHORTCUT)) 147 wadd += 3; 148 149 if (m->title && *(title = MSG_XLAT(m->title)) != 0) { 150 /* Allow multiple line titles */ 151 for (tp = title; (ep = strchr(tp, '\n')); tp = ep + 1) { 152 i = ep - tp; 153 wmax = MAX(wmax, i); 154 hadd++; 155 } 156 hadd++; 157 i = strlen(tp); 158 wmax = MAX(wmax, i); 159 if (i != 0) 160 hadd++; 161 } else { 162 m->title = NULL; 163 title = "untitled"; 164 } 165 exithadd = ((m->mopt & MC_NOEXITOPT) ? 0 : 1); 166 167#ifdef MSG_DEFS_H 168 if (y < 0) { 169 /* put menu box below message text */ 170 y = -y; 171 i = msg_row(); 172 if (i > y) 173 y = i; 174 } 175#endif 176 177 /* Calculate h? h == number of visible options. */ 178 if (m->h == 0) 179 m->h = m->numopts + exithadd; 180 181 if (m->h > max_lines - y - hadd) { 182 /* Not enough space for all the options */ 183 if (m->h <= 4 || !(m->mopt & (MC_SCROLL | MC_ALWAYS_SCROLL))) { 184 /* move menu up screen */ 185 y = max_lines - hadd - m->h; 186 if (y < 0) 187 y = 0; 188 } 189 m->h = max_lines - y - hadd; 190 } 191 192 if (m->h < m->numopts + exithadd || m->mopt & MC_ALWAYS_SCROLL) { 193 /* We need to add the scroll text... 194 * The used to be a check for MC_SCROLL here, but it is 195 * fairly pointless - you just don't want the program 196 * to exit on this sort of error. 197 */ 198 if (m->h < 3) { 199 endwin(); 200 (void)fprintf(stderr, 201 "Window too short (m->h %d, m->numopts %d, exithadd %d, y %d, max_lines %d, hadd %d) for menu \"%.30s\"\n", 202 m->h, m->numopts, exithadd, y, max_lines, hadd, 203 title); 204 exit(1); 205 } 206 hadd++; 207 m->h = MIN(m->h, max_lines - y - hadd); 208 i = strlen(scrolltext); 209 wmax = MAX(wmax, i); 210 } 211 212 /* Calculate w? */ 213 if (w == 0) { 214 int l; 215 for (i = 0; i < m->numopts; i++) { 216 tp = MSG_XLAT(m->opts[i].opt_name); 217 if (tp == NULL) 218 continue; 219 l = strlen(tp); 220 wmax = MAX(wmax, l); 221 } 222 w = wmax; 223 } 224 225 /* check and adjust for screen fit */ 226 if (w + wadd > max_cols) { 227 endwin(); 228 (void)fprintf(stderr, 229 "Screen too narrow (%d + %d > %d) for menu \"%s\"\n", 230 w, wadd, max_cols, title); 231 exit(1); 232 233 } 234 235 if (x == -1) 236 x = (max_cols - (w + wadd)) / 2; /* center */ 237 else if (x + w + wadd > max_cols) 238 x = max_cols - (w + wadd); /* right align */ 239 240 if (y == 0) { 241 /* Center - rather than top */ 242 y = (max_lines - hadd - m->h) / 2; 243 } 244 245 /* Get the windows. */ 246 m->mw = newwin(m->h + hadd, w + wadd, y, x); 247 248 if (m->mw == NULL) { 249 endwin(); 250 (void)fprintf(stderr, 251 "Could not create window (%d + %d, %d + %d, %d, %d) for menu \"%s\"\n", 252 m->h, hadd, w, wadd, y, x, title); 253 exit(1); 254 } 255 keypad(m->mw, TRUE); /* enable multi-key assembling for win */ 256 257 /* XXX is it even worth doing this right? */ 258 if (has_colors()) { 259 wbkgd(m->mw, COLOR_PAIR(1)); 260 wattrset(m->mw, COLOR_PAIR(1)); 261 } 262 263 if (m->mopt & MC_SUBMENU) { 264 /* Keep a copy of what is on the screen under the window */ 265 m->sv_mw = newwin(m->h + hadd, w + wadd, y, x); 266 /* 267 * cursrc contains post-doupdate() data, not post-refresh() 268 * data so we must call doupdate to ensure we save the 269 * correct data. Avoids PR 26660. 270 */ 271 doupdate(); 272 if (m->sv_mw) 273 overwrite(curscr, m->sv_mw); 274 } 275} 276 277static char 278opt_ch(menudesc *m, int op_no) 279{ 280 char c; 281 282 if (op_no == m->numopts) 283 return 'x'; 284 285 if (op_no < 25) { 286 c = 'a' + op_no; 287 if (c >= 'x') 288 c++; 289 } else 290 c = 'A' + op_no - 25; 291 return c; 292} 293 294static void 295draw_menu_line(menudesc *m, int opt, int cury, void *arg, const char *text) 296{ 297 int hasbox = m->mopt & MC_NOBOX ? 0 : 1; 298 299 if (m->cursel == opt) { 300 mvwaddstr(m->mw, cury, hasbox, ">"); 301 wstandout(m->mw); 302 } else 303 mvwaddstr(m->mw, cury, hasbox, " "); 304 if (!(m->mopt & MC_NOSHORTCUT)) 305 wprintw(m->mw, "%c: ", opt_ch(m, opt)); 306 307 if (!text && m->draw_line) 308 m->draw_line(m, opt, arg); 309 else 310 waddstr(m->mw, MSG_XLAT(text)); 311 if (m->cursel == opt) 312 wstandend(m->mw); 313} 314 315static void 316draw_menu(menudesc *m, void *arg) 317{ 318 int opt; 319 int hasbox, cury, maxy; 320 int tadd; 321 int hasexit = (m->mopt & MC_NOEXITOPT ? 0 : 1); 322 const char *tp, *ep; 323 324 hasbox = (m->mopt & MC_NOBOX ? 0 : 1); 325 326 /* Clear the window */ 327 wclear(m->mw); 328 329 tadd = hasbox; 330 if (m->title) { 331 for (tp = MSG_XLAT(m->title); ; tp = ep + 1) { 332 ep = strchr(tp , '\n'); 333 mvwaddnstr(m->mw, tadd++, hasbox + 1, tp, 334 ep ? ep - tp : -1); 335 if (ep == NULL || *ep == 0) 336 break; 337 } 338 tadd++; 339 } 340 341 cury = tadd; 342 maxy = getmaxy(m->mw) - hasbox; 343 if (m->numopts + hasexit > m->h) 344 /* allow for scroll text */ 345 maxy--; 346 347 if (m->cursel == -1) { 348 m->cursel = m->numopts; 349 if (m->h <= m->numopts) 350 m->topline = m->numopts + 1 - m->h; 351 } 352 353 while (m->cursel >= m->topline + m->h) 354 m->topline = MIN(m->topline + m->h, 355 m->numopts + hasexit - m->h); 356 while (m->cursel < m->topline) 357 m->topline = MAX(0, m->topline - m->h); 358 359 for (opt = m->topline; opt < m->numopts; opt++) { 360 if (cury >= maxy) 361 break; 362 draw_menu_line(m, opt, cury++, arg, m->opts[opt].opt_name); 363 } 364 365 /* Add the exit option. */ 366 if (!(m->mopt & MC_NOEXITOPT)) { 367 if (cury < maxy) 368 draw_menu_line(m, m->numopts, cury++, arg, m->exitstr); 369 else 370 opt = 0; 371 } 372 373 /* Add the scroll line */ 374 if (opt != m->numopts || m->topline != 0) 375 mvwaddstr(m->mw, cury, hasbox, scrolltext); 376 377 /* Add the box. */ 378 if (!(m->mopt & MC_NOBOX)) 379 box(m->mw, 0, 0); 380 381 wmove(m->mw, tadd + m->cursel - m->topline, hasbox); 382 wrefresh(m->mw); 383} 384 385 386static void 387/*ARGSUSED*/ 388process_help(menudesc *m) 389{ 390 const char *help = m->helpstr; 391 WINDOW *sv_curscr; 392 int lineoff = 0; 393 int curoff = 0; 394 int again; 395 int winin; 396 397 /* Is there help? */ 398 if (!help) { 399 mbeep(); 400 return; 401 } 402 sv_curscr = newwin(getmaxy(curscr), getmaxx(curscr), 0, 0); 403 if (!sv_curscr) { 404 mbeep(); 405 return; 406 } 407 /* 408 * Save screen contents so we can restore before returning. 409 * cursrc contains post-doupdate() data, not post-refresh() 410 * data so we must call doupdate to ensure we save the 411 * correct data. Avoids PR 26660. 412 */ 413 doupdate(); 414 overwrite(curscr, sv_curscr); 415 touchwin(stdscr); 416 417 help = MSG_XLAT(help); 418 /* Display the help information. */ 419 do { 420 if (lineoff < curoff) { 421 help = MSG_XLAT(m->helpstr); 422 curoff = 0; 423 } 424 while (*help && curoff < lineoff) { 425 if (*help == '\n') 426 curoff++; 427 help++; 428 } 429 430 wclear(stdscr); 431 mvwaddstr(stdscr, 0, 0, 432 "Help: exit: x, page up: u <, page down: d >"); 433 mvwaddstr(stdscr, 2, 0, help); 434 wmove(stdscr, 1, 0); 435 wrefresh(stdscr); 436 437 do { 438 winin = wgetch(stdscr); 439 if (winin < KEY_MIN) 440 winin = tolower(winin); 441 again = 0; 442 switch (winin) { 443 case '<': 444 case 'u': 445 case KEY_UP: 446 case KEY_LEFT: 447 case KEY_PPAGE: 448 if (lineoff) 449 lineoff -= max_lines - 2; 450 else 451 again = 1; 452 break; 453 case '>': 454 case 'd': 455 case KEY_DOWN: 456 case KEY_RIGHT: 457 case KEY_NPAGE: 458 if (*help) 459 lineoff += max_lines - 2; 460 else 461 again = 1; 462 break; 463 case 'q': 464 break; 465 case 'x': 466 winin = 'q'; 467 break; 468 default: 469 again = 1; 470 } 471 if (again) 472 mbeep(); 473 } while (again); 474 } while (winin != 'q'); 475 476 /* Restore the original screen contents */ 477 touchwin(sv_curscr); 478 wnoutrefresh(sv_curscr); 479 delwin(sv_curscr); 480 481 /* Some code thinks that wrefresh(stdout) is a good idea... */ 482 wclear(stdscr); 483} 484 485static void 486process_req(menudesc *m, void *arg, int req) 487{ 488 int ch; 489 int hasexit = (m->mopt & MC_NOEXITOPT ? 0 : 1); 490 491 switch(req) { 492 493 case REQ_EXECUTE: 494 return; 495 496 case REQ_NEXT_ITEM: 497 ch = m->cursel; 498 for (;;) { 499 ch++; 500 if (ch >= m->numopts + hasexit) { 501 mbeep(); 502 return; 503 } 504 if (hasexit && ch == m->numopts) 505 break; 506 if (!(m->opts[ch].opt_flags & OPT_IGNORE)) 507 break; 508 } 509 m->cursel = ch; 510 if (m->cursel >= m->topline + m->h) 511 m->topline = m->cursel - m->h + 1; 512 break; 513 514 case REQ_PREV_ITEM: 515 ch = m->cursel; 516 for (;;) { 517 if (ch <= 0) { 518 mbeep(); 519 return; 520 } 521 ch--; 522 if (!(m->opts[ch].opt_flags & OPT_IGNORE)) 523 break; 524 } 525 m->cursel = ch; 526 if (m->cursel < m->topline) 527 m->topline = m->cursel; 528 break; 529 530 case REQ_HELP: 531 process_help(m); 532 break; 533 534 case REQ_REDISPLAY: 535 endwin(); 536 doupdate(); 537 break; 538 539 case REQ_SCROLLUP: 540 if (m->cursel == 0) { 541 mbeep(); 542 return; 543 } 544 m->topline = MAX(0, m->topline - m->h); 545 m->cursel = MAX(0, m->cursel - m->h); 546 wclear(m->mw); 547 break; 548 549 case REQ_SCROLLDOWN: 550 if (m->cursel >= m->numopts + hasexit - 1) { 551 mbeep(); 552 return; 553 } 554 m->topline = MIN(m->topline + m->h, 555 MAX(m->numopts + hasexit - m->h, 0)); 556 m->cursel = MIN(m->numopts + hasexit - 1, m->cursel + m->h); 557 wclear(m->mw); 558 break; 559 560 default: 561 ch = req; 562 if (ch == 'x' && hasexit) { 563 m->cursel = m->numopts; 564 break; 565 } 566 if (m->mopt & MC_NOSHORTCUT) { 567 mbeep(); 568 return; 569 } 570 if (ch > 'z') 571 ch = 255; 572 if (ch >= 'a') { 573 if (ch > 'x') 574 ch--; 575 ch = ch - 'a'; 576 } else 577 ch = 25 + ch - 'A'; 578 if (ch < 0 || ch >= m->numopts) { 579 mbeep(); 580 return; 581 } 582 if (m->opts[ch].opt_flags & OPT_IGNORE) { 583 mbeep(); 584 return; 585 } 586 m->cursel = ch; 587 } 588 589 draw_menu(m, arg); 590} 591 592int 593menu_init(void) 594{ 595 int i; 596 597 if (__menu_init) 598 return 0; 599 600#ifdef USER_MENU_INIT 601 if (USER_MENU_INIT) 602 return 1; 603#endif 604 605 if (initscr() == NULL) 606 return 1; 607 608 cbreak(); 609 noecho(); 610 611 /* XXX Should be configurable but it almost isn't worth it. */ 612 if (has_colors()) { 613 start_color(); 614 init_pair(1, COLOR_WHITE, COLOR_BLUE); 615 bkgd(COLOR_PAIR(1)); 616 attrset(COLOR_PAIR(1)); 617 } 618 619 max_lines = getmaxy(stdscr); 620 max_cols = getmaxx(stdscr); 621 keypad(stdscr, TRUE); 622#ifdef DYNAMIC_MENUS 623 num_menus = DYN_INIT_NUM; 624 while (num_menus < DYN_MENU_START) 625 num_menus *= 2; 626 menu_list = malloc(sizeof *menu_list * num_menus); 627 if (menu_list == NULL) 628 return 2; 629 (void)memset(menu_list, 0, sizeof *menu_list * num_menus); 630 for (i = 0; i < DYN_MENU_START; i++) 631 menu_list[i] = &menu_def[i]; 632#endif 633 634 __menu_init = 1; 635 return 0; 636} 637 638void 639process_menu(int num, void *arg) 640{ 641 int sel = 0; 642 int req; 643 menu_ent *opt; 644 645 menudesc *m; 646 647 /* Initialize? */ 648 if (menu_init()) { 649 __menu_initerror(); 650 return; 651 } 652 653 if (num < 0 || num >= num_menus) 654 return; 655 m = &MENUS(num); 656 if (m == NULL) 657 return; 658 659 /* Default to select option 0 and display from 0 */ 660 m->topline = 0; 661 if ((m->mopt & (MC_DFLTEXIT | MC_NOEXITOPT)) == MC_DFLTEXIT) 662 m->cursel = -1; 663 else 664 m->cursel = 0; 665 666 for (;;) { 667 if (isendwin()) 668 /* I'm not sure this is needed with netbsd's curses */ 669 doupdate(); 670 /* Process the display action */ 671 if (m->post_act) 672 (*m->post_act)(m, arg); 673 if (m->mw == NULL) 674 init_menu(m); 675 draw_menu(m, arg); 676 677 while ((req = menucmd(m->mw)) != REQ_EXECUTE) 678 process_req(m, arg, req); 679 680 sel = m->cursel; 681 if (!(m->mopt & MC_NOCLEAR)) { 682 wclear(m->mw); 683 if (m->sv_mw) 684 overwrite(m->sv_mw, m->mw); 685 wnoutrefresh(m->mw); 686 } 687 688 /* Process the items */ 689 if (sel >= m->numopts) 690 /* exit option */ 691 break; 692 693 opt = &m->opts[sel]; 694 if (opt->opt_flags & OPT_IGNORE) 695 continue; 696 if (opt->opt_flags & OPT_ENDWIN) 697 endwin(); 698 if (opt->opt_action && (*opt->opt_action)(m, arg)) 699 break; 700 701 if (opt->opt_menu != -1) { 702 if (!(opt->opt_flags & OPT_SUB)) { 703 num = opt->opt_menu; 704 wclear(m->mw); 705 if (m->sv_mw) { 706 overwrite(m->sv_mw, m->mw); 707 delwin(m->sv_mw); 708 m->sv_mw = NULL; 709 } 710 wnoutrefresh(m->mw); 711 delwin(m->mw); 712 m->mw = NULL; 713 m = &MENUS(num); 714 continue; 715 } 716 process_menu(opt->opt_menu, arg); 717 } 718 if (opt->opt_flags & OPT_EXIT) 719 break; 720 } 721 722 if (m->mopt & MC_NOCLEAR) { 723 wclear(m->mw); 724 if (m->sv_mw) 725 overwrite(m->sv_mw, m->mw); 726 wnoutrefresh(m->mw); 727 } 728 729 /* Process the exit action */ 730 if (m->exit_act) 731 (*m->exit_act)(m, arg); 732 delwin(m->mw); 733 m->mw = NULL; 734 if (m->sv_mw) { 735 delwin(m->sv_mw); 736 m->sv_mw = NULL; 737 } 738} 739 740 741void 742set_menu_numopts(int menu, int numopts) 743{ 744 745 MENUS(menu).numopts = numopts; 746} 747 748/* Control L is end of standard routines, remaining only for dynamic. */ 749 750/* Beginning of routines for dynamic menus. */ 751 752static int 753double_menus(void) 754{ 755 menudesc **temp; 756 int sz = sizeof *menu_list * num_menus; 757 758 temp = realloc(menu_list, sz * 2); 759 if (temp == NULL) 760 return 0; 761 (void)memset(temp + num_menus, 0, sz); 762 menu_list = temp; 763 num_menus *= 2; 764 765 return 1; 766} 767 768int 769new_menu(const char *title, menu_ent *opts, int numopts, 770 int x, int y, int h, int w, int mopt, 771 void (*post_act)(menudesc *, void *), 772 void (*draw_line)(menudesc *, int, void *), 773 void (*exit_act)(menudesc *, void *), 774 const char *help, const char *exit_str) 775{ 776 int ix; 777 menudesc *m; 778 779 /* Find free menu entry. */ 780 for (ix = DYN_MENU_START; ; ix++) { 781 if (ix >= num_menus && !double_menus()) 782 return -1; 783 m = menu_list[ix]; 784 if (m == NULL) { 785 m = calloc(sizeof *m, 1); 786 if (m == NULL) 787 return -1; 788 menu_list[ix] = m; 789 break; 790 } 791 if (!(m->mopt & MC_VALID)) 792 break; 793 } 794 795 /* Set Entries */ 796 m->title = title; 797 m->opts = opts; 798 m->numopts = numopts; 799 m->x = x; 800 m->y = y; 801 m->h = h; 802 m->w = w; 803 m->mopt = mopt | MC_VALID; 804 m->post_act = post_act; 805 m->draw_line = draw_line; 806 m->exit_act = exit_act; 807 m->helpstr = help; 808 m->exitstr = exit_str ? exit_str : "Exit"; 809 810 return ix; 811} 812 813void 814free_menu(int menu_no) 815{ 816 menudesc *m; 817 818 if (menu_no < 0 || menu_no >= num_menus) 819 return; 820 821 m = menu_list[menu_no]; 822 if (!(m->mopt & MC_VALID)) 823 return; 824 if (m->mw != NULL) 825 delwin(m->mw); 826 memset(m, 0, sizeof *m); 827} 828