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