11680Ssundar/*	$NetBSD: menu_sys.def,v 1.72 2019/06/23 22:47:22 christos Exp $	*/
21680Ssundar
31680Ssundar/*
41680Ssundar * Copyright 1997 Piermont Information Systems Inc.
51680Ssundar * All rights reserved.
61680Ssundar *
71680Ssundar * Written by Philip A. Nelson for Piermont Information Systems Inc.
81680Ssundar *
91680Ssundar * Redistribution and use in source and binary forms, with or without
101680Ssundar * modification, are permitted provided that the following conditions
111680Ssundar * are met:
121680Ssundar * 1. Redistributions of source code must retain the above copyright
131680Ssundar *    notice, this list of conditions and the following disclaimer.
141680Ssundar * 2. Redistributions in binary form must reproduce the above copyright
151680Ssundar *    notice, this list of conditions and the following disclaimer in the
161680Ssundar *    documentation and/or other materials provided with the distribution.
171680Ssundar * 3. The name of Piermont Information Systems Inc. may not be used to endorse
181680Ssundar *    or promote products derived from this software without specific prior
191680Ssundar *    written permission.
201680Ssundar *
211680Ssundar * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS''
221680Ssundar * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
231680Ssundar * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
241680Ssundar * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE 
251680Ssundar * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
261680Ssundar * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
271680Ssundar * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
281680Ssundar * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
291680Ssundar * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
301680Ssundar * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
311680Ssundar * THE POSSIBILITY OF SUCH DAMAGE.
321680Ssundar *
331680Ssundar */
341680Ssundar
351680Ssundar/* 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 :
145	    ((m->mopt & MC_CONTINUOUS) ? 1 : 2));
146	wadd = ((m->mopt & MC_NOBOX) ? 2 : 4);
147	if (!(m->mopt & MC_NOSHORTCUT))
148		wadd += 3;
149
150	title = m->title;
151#ifdef MENU_EXPANDS
152	if (m->exp_title)
153		title = m->exp_title;
154#endif
155	if (title)
156		title = MSG_XLAT(title);
157	if (title && title[0] != 0) {
158		/* Allow multiple line titles */
159		for (tp = title; (ep = strchr(tp, '\n')); tp = ep + 1) {
160			i = ep - tp;
161			wmax = MAX(wmax, i);
162			hadd++;
163		}
164		hadd++;
165		i = strlen(tp);
166		wmax = MAX(wmax, i);
167		if (i != 0)
168			hadd++;
169	} else {
170		m->title = NULL;
171		title = "untitled";
172	}
173	exithadd = ((m->mopt & MC_NOEXITOPT) ? 0 : 1);
174
175#ifdef MSG_DEFS_H
176	if (y < 0) {
177		/* put menu box below message text */
178		y = -y;
179		i = msg_row();
180		if (i > y)
181		    y = i;
182	}
183#endif
184
185	/* Calculate h? h == number of visible options. */
186	if (m->h == 0)
187		m->h = m->numopts + exithadd;
188
189	if (m->h > max_lines - y - hadd) {
190		/* Not enough space for all the options */
191		if (m->h <= 4 || !(m->mopt & (MC_SCROLL | MC_ALWAYS_SCROLL))) {
192			/* move menu up screen */
193			y = max_lines - hadd - m->h;
194			if (y < 0)
195				y = 0;
196		}
197		m->h = max_lines - y - hadd;
198	}
199
200	if (m->h < m->numopts + exithadd || m->mopt & MC_ALWAYS_SCROLL) {
201		/* We need to add the scroll text... 
202		 * The used to be a check for MC_SCROLL here, but it is
203		 * fairly pointless - you just don't want the program
204		 * to exit on this sort of error.
205		 */
206		if (m->h < 3) {
207			endwin();
208			(void)fprintf(stderr,
209				"Window too short (m->h %d, m->numopts %d, exithadd %d, y %d, max_lines %d, hadd %d) for menu \"%.30s\"\n",
210				m->h, m->numopts, exithadd, y, max_lines, hadd,
211				title);
212			exit(1);
213		}
214		hadd++;
215		m->h = MIN(m->h, max_lines - y - hadd);
216		i = strlen(scrolltext);
217		wmax = MAX(wmax, i);
218	}
219
220	/* Calculate w? */
221	if (w == 0) {
222		int l;
223		for (i = 0; i < m->numopts; i++) {
224#ifdef MENU_EXPANDS
225			tp = m->opts[i].opt_exp_name ?
226			    m->opts[i].opt_exp_name :
227			    MSG_XLAT(m->opts[i].opt_name);
228#else
229			tp = MSG_XLAT(m->opts[i].opt_name);
230#endif
231			if (tp == NULL)
232				continue;
233			l = strlen(tp);
234			wmax = MAX(wmax, l);
235		}
236		w = wmax;
237	}
238
239	/* check and adjust for screen fit */
240	if (w + wadd > max_cols) {
241		endwin();
242		(void)fprintf(stderr,
243			"Screen too narrow (%d + %d > %d) for menu \"%s\"\n",
244				w, wadd, max_cols, title);
245		exit(1);
246
247	}
248
249	if (x == -1)
250		x = (max_cols - (w + wadd)) / 2;	/* center */
251	else if (x + w + wadd > max_cols)
252		x = max_cols - (w + wadd);	/* right align */
253
254	if (y == 0) {
255		/* Center - rather than top */
256		y = (max_lines - hadd - m->h) / 2;
257	}
258
259	/* Get the windows. */
260	m->mw = newwin(m->h + hadd, w + wadd, y, x);
261
262	if (m->mw == NULL) {
263		endwin();
264		(void)fprintf(stderr,
265			"Could not create window (%d + %d, %d + %d, %d, %d) for menu \"%s\"\n",
266			m->h, hadd, w, wadd, y, x, title);
267		exit(1);
268	}
269	keypad(m->mw, TRUE); /* enable multi-key assembling for win */
270
271	/* XXX is it even worth doing this right? */
272	if (has_colors()) {
273		wbkgd(m->mw, COLOR_PAIR(1));
274		wattrset(m->mw, COLOR_PAIR(1));
275	}
276
277	if (m->mopt & MC_SUBMENU) {
278		/* Keep a copy of what is on the screen under the window */
279		m->sv_mw = newwin(m->h + hadd, w + wadd, y, x);
280		/*
281		 * cursrc contains post-doupdate() data, not post-refresh()
282		 * data so we must call doupdate to ensure we save the
283		 * correct data.  Avoids PR 26660.
284		 */
285		doupdate();
286		if (m->sv_mw)
287			overwrite(curscr, m->sv_mw);
288	}
289}
290
291static char
292opt_ch(menudesc *m, int op_no)
293{
294	char c;
295
296	if (op_no == m->numopts)
297		return 'x';
298
299	if (op_no < 25) {
300		c = 'a' + op_no;
301		if (c >= 'x')
302			c++;
303	} else 
304		c = 'A' + op_no - 25;
305	return c;
306}
307
308static void
309draw_menu_line(menudesc *m, int opt, int cury, void *arg, const char *text)
310{
311	int hasbox = m->mopt & MC_NOBOX ? 0 : 1;
312	int noshort = (opt < m->numopts)
313	    && (m->opts[opt].opt_flags & OPT_NOSHORT);
314
315	if (m->cursel == opt) {
316		mvwaddstr(m->mw, cury, hasbox, ">");
317		wstandout(m->mw);
318	} else
319		mvwaddstr(m->mw, cury, hasbox, " ");
320	if (!(m->mopt & MC_NOSHORTCUT) && !noshort)
321		wprintw(m->mw, "%c: ", opt_ch(m, opt));
322	else if (!(m->mopt & MC_NOSHORTCUT))
323		wprintw(m->mw, "   ");
324
325	if (!text && m->draw_line)
326		m->draw_line(m, opt, arg);
327	else
328		waddstr(m->mw, MSG_XLAT(text));
329	if (m->cursel == opt)
330		wstandend(m->mw);
331}
332
333static void
334draw_menu(menudesc *m, void *arg)
335{
336	int opt;
337	int hasbox, cury, maxy;
338	int tadd;
339	int hasexit = (m->mopt & MC_NOEXITOPT ? 0 : 1);
340	const char *tp, *ep;
341	const char *title;
342	
343	hasbox = (m->mopt & MC_NOBOX ? 0 : 1);
344
345	/* Clear the window */
346	wclear(m->mw);
347
348	tadd = hasbox;
349	title = m->title;
350#ifdef MENU_EXPANDS
351	if (m->exp_title)
352		title = m->exp_title;
353#endif
354	if (title) {
355		for (tp = MSG_XLAT(title); ; tp = ep + 1) {
356			ep = strchr(tp , '\n');
357			mvwaddnstr(m->mw, tadd++, hasbox + 1, tp,
358			    ep ? ep - tp : -1);
359			if (ep == NULL || *ep == 0)
360				break;
361		}
362		if ((m->mopt & MC_CONTINUOUS) == 0)
363			tadd++;
364	}
365
366	cury = tadd;
367	maxy = getmaxy(m->mw) - hasbox;
368	if (m->numopts + hasexit > m->h)
369		/* allow for scroll text */
370		maxy--;
371
372	if (m->cursel == -1) {
373		m->cursel = m->numopts;
374		if (m->h <= m->numopts)
375			m->topline = m->numopts + 1 - m->h;
376	}
377
378	while (m->cursel >= m->topline + m->h)
379		m->topline = MIN(m->topline + m->h,
380				 m->numopts + hasexit - m->h);
381	while (m->cursel < m->topline)
382		m->topline = MAX(0, m->topline - m->h);
383
384	for (opt = m->topline; opt < m->numopts; opt++) {
385		if (cury >= maxy)
386			break;
387		draw_menu_line(m, opt, cury++, arg,
388#ifdef MENU_EXPANDS
389		    m->opts[opt].opt_exp_name ?
390		    	m->opts[opt].opt_exp_name : m->opts[opt].opt_name
391#else
392		    m->opts[opt].opt_name
393#endif
394		    );
395	}
396
397	/* Add the exit option. */
398	if (!(m->mopt & MC_NOEXITOPT)) {
399		if (cury < maxy)
400			draw_menu_line(m, m->numopts, cury++, arg, m->exitstr);
401		else
402			opt = 0;
403	}
404
405	/* Add the scroll line */
406	if (opt != m->numopts || m->topline != 0)
407		mvwaddstr(m->mw, cury, hasbox, scrolltext);
408
409	/* Add the box. */
410	if (!(m->mopt & MC_NOBOX))
411		box(m->mw, 0, 0);
412
413	wmove(m->mw, tadd + m->cursel - m->topline, hasbox);
414	wrefresh(m->mw);
415}
416
417
418static void
419/*ARGSUSED*/
420process_help(menudesc *m)
421{
422	const char *help = m->helpstr;
423	WINDOW *sv_curscr;
424	int lineoff = 0;
425	int curoff = 0;
426	int again;
427	int winin;
428
429	/* Is there help? */
430	if (!help) {
431		mbeep();
432		return;
433	}
434	sv_curscr = newwin(getmaxy(curscr), getmaxx(curscr), 0, 0);
435	if (!sv_curscr) {
436		mbeep();
437		return;
438	}
439	/*
440	 * Save screen contents so we can restore before returning.
441	 * cursrc contains post-doupdate() data, not post-refresh()
442	 * data so we must call doupdate to ensure we save the
443	 * correct data.  Avoids PR 26660.
444	 */
445	doupdate();
446	overwrite(curscr, sv_curscr);
447	touchwin(stdscr);
448
449	help = MSG_XLAT(help);
450	/* Display the help information. */
451	do {
452		if (lineoff < curoff) {
453			help = MSG_XLAT(m->helpstr);
454			curoff = 0;
455		}
456		while (*help && curoff < lineoff) {
457			if (*help == '\n')
458				curoff++;
459			help++;
460		}
461	
462		wclear(stdscr);
463		mvwaddstr(stdscr, 0, 0, 
464			"Help: exit: x,  page up: u <, page down: d >");
465		mvwaddstr(stdscr, 2, 0, help);
466		wmove(stdscr, 1, 0);
467		wrefresh(stdscr);
468
469		do {
470			winin = wgetch(stdscr);
471			if (winin < KEY_MIN)
472				winin = tolower(winin);
473			again = 0;
474			switch (winin) {
475				case '<':
476				case 'u':
477				case KEY_UP:
478				case KEY_LEFT:
479				case KEY_PPAGE:
480					if (lineoff)
481						lineoff -= max_lines - 2;
482					else
483						again = 1;
484					break;
485				case '>':
486				case 'd':
487				case KEY_DOWN:
488				case KEY_RIGHT:
489				case KEY_NPAGE:
490					if (*help)
491						lineoff += max_lines - 2;
492					else
493						again = 1;
494					break;
495				case 'q':
496					break;
497				case 'x':
498					winin = 'q';
499					break;
500				default:
501					again = 1;
502			}
503			if (again)
504				mbeep();
505		} while (again);
506	} while (winin != 'q');
507
508	/* Restore the original screen contents */
509	touchwin(sv_curscr);
510	wnoutrefresh(sv_curscr);
511	delwin(sv_curscr);
512
513	/* Some code thinks that wrefresh(stdout) is a good idea... */
514	wclear(stdscr);
515}
516
517#ifdef MENU_EXPANDS
518static void
519free_exp_menu_items(menudesc *m)
520{
521	int i;
522
523	if (m->exp_title != NULL) {
524		free(__UNCONST(m->exp_title));
525		m->exp_title = NULL;
526	}
527	for (i = 0; i < m->numopts; i++) {
528		if (m->opts[i].opt_exp_name != NULL) {
529			free(__UNCONST(m->opts[i].opt_exp_name));
530			m->opts[i].opt_exp_name = NULL;
531		}
532	}
533}
534#endif
535
536static void
537process_req(menudesc *m, void *arg, int req)
538{
539	int ch;
540	int hasexit = (m->mopt & MC_NOEXITOPT ? 0 : 1);
541
542	switch(req) {
543
544	case REQ_EXECUTE:
545		return;
546
547	case REQ_NEXT_ITEM:
548		ch = m->cursel;
549		for (;;) {
550			ch++;
551			if (ch >= m->numopts + hasexit) {
552				mbeep();
553				return;
554			}
555			if (hasexit && ch == m->numopts)
556				break;
557			if (!(m->opts[ch].opt_flags & OPT_IGNORE))
558				break;
559		}
560		m->cursel = ch;
561		if (m->cursel >= m->topline + m->h)
562			m->topline = m->cursel - m->h + 1;
563		break;
564
565	case REQ_PREV_ITEM:
566		ch = m->cursel;
567		for (;;) {
568			if (ch <= 0) {
569				mbeep();
570				return;
571			}
572			ch--;
573			if (!(m->opts[ch].opt_flags & OPT_IGNORE))
574				break;
575		}
576		m->cursel = ch;
577		if (m->cursel < m->topline)
578			m->topline = m->cursel;
579		break;
580
581	case REQ_HELP:
582		process_help(m);
583		break;
584
585	case REQ_REDISPLAY:
586		endwin();
587		doupdate();
588		break;
589
590	case REQ_SCROLLUP:
591		if (m->cursel == 0) {
592			mbeep();
593			return;
594		}
595		m->topline = MAX(0, m->topline - m->h);
596		m->cursel = MAX(0, m->cursel - m->h);
597		wclear(m->mw);
598		break;
599
600	case REQ_SCROLLDOWN:
601		if (m->cursel >= m->numopts + hasexit - 1) {
602			mbeep();
603			return;
604		}
605		m->topline = MIN(m->topline + m->h,
606				 MAX(m->numopts + hasexit - m->h, 0));
607		m->cursel = MIN(m->numopts + hasexit - 1, m->cursel + m->h);
608		wclear(m->mw);
609		break;
610
611	default:
612		ch = req;
613		if (ch == 'x' && hasexit) {
614			m->cursel = m->numopts;
615			break;
616		}
617		if (m->mopt & MC_NOSHORTCUT) {
618			mbeep();
619			return;
620		}
621		if (ch > 'z')
622			ch = 255;
623		if (ch >= 'a') {
624			if (ch > 'x')
625				ch--;
626			ch = ch - 'a';
627		} else
628			ch = 25 + ch - 'A';
629		if (ch < 0 || ch >= m->numopts) {
630			mbeep();
631			return;
632		}
633		if (m->opts[ch].opt_flags & OPT_IGNORE) {
634			mbeep();
635			return;
636		}
637		m->cursel = ch;
638	}
639
640	draw_menu(m, arg);
641}
642
643int
644menu_init(void)
645{
646	int i;
647
648	if (__menu_init)
649		return 0;
650
651#ifdef	USER_MENU_INIT
652	if (USER_MENU_INIT)
653		return 1;
654#endif
655
656	if (initscr() == NULL)
657		return 1;
658
659	cbreak();
660	noecho();
661
662	/* XXX Should be configurable but it almost isn't worth it. */
663	if (has_colors()) {
664		start_color();
665		init_pair(1, COLOR_WHITE, COLOR_BLUE);
666		bkgd(COLOR_PAIR(1));
667		attrset(COLOR_PAIR(1));
668	}
669
670	max_lines = getmaxy(stdscr);
671	max_cols = getmaxx(stdscr);
672	keypad(stdscr, TRUE);
673#ifdef DYNAMIC_MENUS
674	num_menus = DYN_INIT_NUM;
675	while (num_menus < DYN_MENU_START)
676		num_menus *= 2;
677	menu_list = calloc(num_menus, sizeof *menu_list);
678	if (menu_list == NULL)
679		return 2;
680	for (i = 0; i < DYN_MENU_START; i++)
681		menu_list[i] = &menu_def[i];
682#endif
683
684	__menu_init = 1;
685	return 0;
686}
687
688void
689process_menu(int num, void *arg)
690{
691	int sel = 0;
692	int req, rv;
693	menu_ent *opt;
694
695	menudesc *m;
696
697	/* Initialize? */
698	if (menu_init()) {
699		__menu_initerror();
700		return;
701	}
702
703	if (num < 0 || num >= num_menus)
704		return;
705	m = &MENUS(num);
706	if (m == NULL)
707		return;
708
709	/* Default to select option 0 and display from 0, skip any
710	 * disabled options at the top of the menu. */
711	m->topline = 0;
712	if ((m->mopt & (MC_DFLTEXIT | MC_NOEXITOPT)) == MC_DFLTEXIT) {
713		m->cursel = -1;
714	} else if (m->opts != NULL) {
715		for (m->cursel = 0; m->cursel < m->numopts; m->cursel++)
716			if ((m->opts[m->cursel].opt_flags & OPT_IGNORE) == 0)
717				break;
718	}
719
720	for (;;) {
721		if (isendwin())
722			/* I'm not sure this is needed with netbsd's curses */
723			doupdate();
724#ifdef MENU_EXPANDS
725		/* Expand the menu items, if needed */
726		if (m->expand_act && m->mw == NULL)
727			(*m->expand_act)(m, arg);
728#endif
729
730		/* Process the display action */
731		if (m->post_act)
732			(*m->post_act)(m, arg);
733		if (m->mw == NULL)
734			init_menu(m);
735		draw_menu(m, arg);
736
737		while ((req = menucmd(m->mw)) != REQ_EXECUTE)
738			process_req(m, arg, req);
739
740		sel = m->cursel;
741		if (!(m->mopt & MC_NOCLEAR)) {
742			wclear(m->mw);
743			if (m->sv_mw)
744				overwrite(m->sv_mw, m->mw);
745			wnoutrefresh(m->mw);
746		}
747
748		/* Process the items */
749		if (sel >= m->numopts)
750			/* exit option */
751			break;
752
753		opt = &m->opts[sel];
754		if (opt->opt_flags & OPT_IGNORE)
755			continue;
756		if (opt->opt_flags & OPT_ENDWIN)
757			endwin();
758		if (opt->opt_action) {
759			rv = (*opt->opt_action)(m, arg);
760			if (rv == -1)
761				continue;
762			else if (rv != 0)
763				break;
764		}
765
766		if (opt->opt_menu != OPT_NOMENU) {
767			if (!(opt->opt_flags & OPT_SUB)) {
768				num = opt->opt_menu;
769				wclear(m->mw);
770				if (m->sv_mw) {
771					overwrite(m->sv_mw, m->mw);
772					delwin(m->sv_mw);
773					m->sv_mw = NULL;
774				}
775				wnoutrefresh(m->mw);
776				delwin(m->mw);
777				m->mw = NULL;
778				m = &MENUS(num);
779				continue;
780			}
781			process_menu(opt->opt_menu, arg);
782		}
783		if (opt->opt_flags & OPT_EXIT)
784			break;
785	}
786
787	if (m->mopt & MC_NOCLEAR) {
788		wclear(m->mw);
789		if (m->sv_mw)
790			overwrite(m->sv_mw, m->mw);
791		wnoutrefresh(m->mw);
792	}
793
794#ifdef MENU_EXPANDS
795	/* Free expanded menu items, if any */
796	free_exp_menu_items(m);
797#endif
798
799	/* Process the exit action */
800	if (m->exit_act)
801		(*m->exit_act)(m, arg);
802	delwin(m->mw);
803	m->mw = NULL;
804	if (m->sv_mw) {
805		delwin(m->sv_mw);
806		m->sv_mw = NULL;
807	}
808}
809
810
811void
812set_menu_numopts(int menu, int numopts)
813{
814
815	MENUS(menu).numopts = numopts;
816}
817
818menudesc *
819get_menudesc(int menu)
820{
821	if (menu < 0 || menu >= num_menus)
822		return NULL;
823
824	return &MENUS(menu);
825}
826
827/* Control L is end of standard routines, remaining only for dynamic. */
828
829/* Beginning of routines for dynamic menus. */
830
831static int 
832double_menus(void)
833{
834	menudesc **temp;
835	int sz = sizeof *menu_list * num_menus;
836
837	temp  = realloc(menu_list, sz * 2);
838	if (temp == NULL)
839		return 0;
840	(void)memset(temp + num_menus, 0, sz);
841	menu_list = temp;
842	num_menus *= 2;
843
844	return 1;
845}
846
847int
848new_menu(const char *title, menu_ent *opts, int numopts, 
849	int x, int y, int h, int w, int mopt,
850	void (*post_act)(menudesc *, void *),
851	void (*draw_line)(menudesc *, int, void *),
852	void (*exit_act)(menudesc *, void *),
853	const char *help, const char *exit_str)
854{
855	int ix;
856	menudesc *m;
857
858	/* Find free menu entry. */
859	for (ix = DYN_MENU_START; ; ix++) {
860		if (ix >= num_menus && !double_menus())
861			return OPT_NOMENU;
862		m = menu_list[ix];
863		if (m == NULL) {
864			m = calloc(1, sizeof *m);
865			if (m == NULL)
866				return OPT_NOMENU;
867			menu_list[ix] = m;
868			break;
869		}
870		if (!(m->mopt & MC_VALID))
871			break;
872	}
873
874	/* Set Entries */
875	m->title = title;
876#ifdef MENU_EXPANDS
877	m->exp_title = NULL;
878#endif
879	m->opts = opts;
880	m->numopts = numopts;
881	m->x = x;
882	m->y = y;
883	m->h = h;
884	m->w = w;
885	m->mopt = mopt | MC_VALID;
886#ifdef MENU_EXPANDS
887	m->expand_act = NULL;
888#endif
889	m->post_act = post_act;
890	m->draw_line = draw_line;
891	m->exit_act = exit_act;
892	m->helpstr  = help;
893	m->exitstr  = exit_str ? exit_str : "Exit";
894
895	return ix;
896}
897
898void
899free_menu(int menu_no)
900{
901	menudesc *m;
902
903	if (menu_no < 0 || menu_no >= num_menus)
904		return;
905
906	m = menu_list[menu_no];
907	if (!(m->mopt & MC_VALID))
908		return;
909	if (m->mw != NULL)
910		delwin(m->mw);
911	memset(m, 0, sizeof *m);
912}
913