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