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