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