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