menu_sys.def revision 1.10
1/*	$NetBSD: menu_sys.def,v 1.10 1998/06/30 06:57:57 phil 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/* Multiple key support */
53#define KEYSEQ_FIRST       256
54#define KEYSEQ_DOWN_ARROW  256
55#define KEYSEQ_UP_ARROW    257
56#define KEYSEQ_LEFT_ARROW  258
57#define KEYSEQ_RIGHT_ARROW 259
58#define KEYSEQ_PAGE_DOWN   260
59#define KEYSEQ_PAGE_UP     261
60
61struct keyseq {
62	char *termcap_name;
63	char *chars;
64	int  numchars;
65	int  keyseq_val;
66	struct keyseq *next;
67};
68
69/* keypad and other definitions */
70struct keyseq _mc_key_seq[] = {
71	/* Cludge for xterm ... */
72	{  NULL, "\033[B", 0, KEYSEQ_DOWN_ARROW, NULL },
73	{  NULL, "\033[D", 0, KEYSEQ_LEFT_ARROW, NULL },
74	{  NULL, "\033[C", 0, KEYSEQ_RIGHT_ARROW, NULL },
75	{  NULL, "\033[A", 0, KEYSEQ_UP_ARROW, NULL },
76	/* Termcap defined */
77	{ "kd", NULL, 0, KEYSEQ_DOWN_ARROW, NULL },
78	{ "kl", NULL, 0, KEYSEQ_LEFT_ARROW, NULL },
79	{ "kr", NULL, 0, KEYSEQ_RIGHT_ARROW, NULL },
80	{ "ku", NULL, 0, KEYSEQ_UP_ARROW, NULL },
81	{ "kf", NULL, 0, KEYSEQ_PAGE_DOWN, NULL },  /* scroll forward */
82	{ "kN", NULL, 0, KEYSEQ_PAGE_DOWN, NULL },  /* next page */
83	{ "kP", NULL, 0, KEYSEQ_PAGE_UP, NULL },    /* scroll backward */
84	{ "kR", NULL, 0, KEYSEQ_PAGE_UP, NULL },    /* prev page */
85	/* other definitions */
86	{ NULL, "\033v", 0, KEYSEQ_PAGE_UP, NULL },   /* ESC-v */
87	{ NULL, "\026", 0, KEYSEQ_PAGE_DOWN, NULL },  /* CTL-v */
88};
89
90int _mc_num_key_seq = sizeof(_mc_key_seq) / sizeof(struct keyseq);
91struct keyseq *pad_list = NULL;
92static char str_area [512];
93static char *str_ptr = str_area;
94
95/* Macros */
96#define MAX(x,y) ((x)>(y)?(x):(y))
97#define MIN(x,y) ((x)<(y)?(x):(y))
98
99/* Initialization state. */
100static int __menu_init = 0;
101int __m_endwin = 0;
102static int max_lines = 0;
103static char *scrolltext = " <: page up, >: page down";
104
105/* prototypes for in here! */
106static void ins_keyseq (struct keyseq **seq, struct keyseq *ins);
107static void init_keyseq (void);
108static void init_menu (struct menudesc *m);
109static void post_menu (struct menudesc *m);
110static void process_help (struct menudesc *m, int num);
111static void process_req (struct menudesc *m, int num, int req);
112static void mbeep (void);
113static int menucmd (WINDOW *w);
114
115#ifndef NULL
116#define NULL (void *)0
117#endif
118
119/* menu system processing routines */
120
121static void mbeep (void)
122{
123	fprintf (stderr,"\a");
124}
125
126static void ins_keyseq (struct keyseq **seq, struct keyseq *ins)
127{
128	if (*seq == NULL) {
129		ins->next = NULL;
130		*seq = ins;
131	} else if (ins->numchars <= (*seq)->numchars) {
132		ins->next = *seq;
133		*seq = ins;
134	} else
135		ins_keyseq (&(*seq)->next, ins);
136}
137
138static void init_keyseq (void)
139{
140	int i;
141	for (i=0; i<_mc_num_key_seq; i++) {
142		if (_mc_key_seq[i].termcap_name)
143			_mc_key_seq[i].chars =
144				tgetstr (_mc_key_seq[i].termcap_name,
145					 &str_ptr);
146		if (_mc_key_seq[i].chars != NULL &&
147		    (_mc_key_seq[i].numchars = strlen(_mc_key_seq[i].chars))
148		    > 0)
149			ins_keyseq (&pad_list,&_mc_key_seq[i]);
150	}
151}
152
153static int mgetch(WINDOW *w)
154{
155	static char buf[20];
156	static int  num = 0;
157	struct keyseq *list = pad_list;
158	int i, ret;
159
160	/* key pad processing */
161	while (list) {
162		for (i=0; i< list->numchars; i++) {
163			if (i >= num)
164				buf[num++] = wgetch(w);
165			if (buf[i] != list->chars[i])
166				break;
167		}
168		if (i == list->numchars) {
169			num = 0;
170			return list->keyseq_val;
171		}
172		list = list->next;
173	}
174
175	ret = buf[0];
176	for (i = 0; i < strlen(buf); i++)
177		buf[i] = buf[i+1];
178	num--;
179	return ret;
180}
181
182static int menucmd (WINDOW *w)
183{
184	int ch;
185
186	while (TRUE) {
187		ch = mgetch(w);
188		
189		switch (ch) {
190		case '\n':
191			return REQ_EXECUTE;
192		case '\016':
193		case KEYSEQ_DOWN_ARROW:
194			return REQ_NEXT_ITEM;
195		case '\020':
196		case KEYSEQ_UP_ARROW:
197			return REQ_PREV_ITEM;
198		case '\014':
199		        return REQ_REDISPLAY;
200		case '<':
201		case KEYSEQ_PAGE_UP:
202			return REQ_SCROLLUP;
203		case '>':
204		case KEYSEQ_PAGE_DOWN:
205			return REQ_SCROLLDOWN;
206		case '?':
207			return REQ_HELP;
208		}
209		
210		if (isalpha(ch)) 
211			return (ch);
212
213		mbeep();
214		wrefresh(w);
215	}
216}
217
218static void init_menu (struct menudesc *m)
219{
220	int max;
221	char **p;
222	int add, exitadd;
223
224	add = ((m->mopt & NOBOX) ? 2 : 4);
225	exitadd = ((m->mopt & NOEXITOPT) ? 0 : 1);
226	max = strlen(m->title);
227
228	/* Calculate h? h == number of visible options. */
229	if (m->h == 0) {
230		m->h = m->numopts + exitadd;
231		if (m->h + m->y + add >= max_lines && (m->mopt & SCROLL))
232			m->h = max_lines - m->y - add ;
233	}
234
235	/* Check window heights and set scrolling */
236	if (m->h < m->numopts + exitadd) {
237		if (!(m->mopt & SCROLL) || m->h < 3) {
238			endwin();
239			(void) fprintf (stderr,
240				"Window too small for menu \"%s\"\n",
241				m->title);
242			exit(1);
243		}
244	} else
245		m->mopt &= ~SCROLL;
246
247	/* check for screen fit */
248	if (m->y + m->h + add > max_lines) {
249		endwin();
250		(void) fprintf (stderr,
251			"Screen too small for menu \"%s\"\n", m->title);
252		exit(1);
253
254	}
255
256	/* Calculate w? */
257	if (m->w == 0) {
258		p = m->opts;
259		if (m->mopt & SCROLL)
260			max = strlen(scrolltext);
261		while (*p) {
262			max = MAX(max,strlen(*p));
263			p++;
264		}
265		m->w = max;
266	}
267
268	/* Get the windows. */
269	m->mw = newwin(m->h+add, m->w+add, m->y, m->x);
270
271	if (m->mw == NULL) {
272		endwin();
273		(void) fprintf (stderr,
274			"Could not create window for window with title "
275			" \"%s\"\n", m->title);
276		exit(1);
277	} 
278}
279
280static void post_menu (struct menudesc *m)
281{
282	int i;
283	int hasbox, cury, maxy, selrow, lastopt;
284	int tadd;
285	
286	if (m->mopt & NOBOX) {
287		cury = 0;
288		maxy = m->h;
289		hasbox = 0;
290	} else {
291		cury = 1;
292		maxy = m->h+1;
293		hasbox = 1;
294	}
295
296	/* Clear the window */
297	wclear (m->mw);
298
299	tadd = strlen(m->title) ? 2 : 0;
300
301	if (tadd) {
302		mvwaddstr(m->mw, cury, cury, m->title);
303		cury += 2;
304		maxy += 2;
305	}
306
307	/* Set defaults, calculate lastopt. */
308	selrow = -1;
309	if (m->mopt & SCROLL) {
310		lastopt = MIN(m->numopts, m->topline+m->h-1);
311		maxy -= 1;
312	} else
313		lastopt = m->numopts;
314
315	for (i=m->topline; i<lastopt; i++, cury++) {
316		if (m->cursel == i) {
317			mvwaddstr (m->mw, cury, hasbox, ">");
318			wstandout(m->mw);
319			selrow = cury;
320		} else
321			mvwaddstr (m->mw, cury, hasbox, " ");
322		waddstr (m->mw, m->opts[i]);
323		if (m->cursel == i)
324			wstandend(m->mw);
325	}
326
327	/* Add the exit option. */
328	if (!(m->mopt & NOEXITOPT) && cury < maxy) {
329		if (m->cursel >= m->numopts) {
330			mvwaddstr (m->mw, cury, hasbox, ">");
331			wstandout(m->mw);
332			selrow = cury;
333		} else
334			mvwaddstr (m->mw, cury, hasbox, " ");
335		waddstr (m->mw, "x: Exit");
336		if (m->cursel >= m->numopts)
337			wstandend(m->mw);
338		cury++;
339	}
340
341	/* Add the scroll line */
342	if (m->mopt & SCROLL) {
343		mvwaddstr (m->mw, cury, hasbox, scrolltext);
344		if (selrow < 0)
345			selrow = cury;
346	}
347
348	/* Add the box. */
349	if (!(m->mopt & NOBOX))
350		box(m->mw, '*', '*');
351
352	wmove(m->mw, selrow, hasbox);
353}
354
355static void process_help (struct menudesc *m, int num)
356{
357	char *help = m->helpstr;
358	int lineoff = 0;
359	int curoff = 0;
360	int again;
361	int winin;
362
363	/* Is there help? */
364	if (!help) {
365		mbeep();
366		return;
367	}
368
369	/* Display the help information. */
370	do {
371		if (lineoff < curoff) {
372			help = m->helpstr;
373			curoff = 0;
374		}
375		while (*help && curoff < lineoff) {
376			if (*help == '\n')
377				curoff++;
378			help++;
379		}
380	
381		wclear(stdscr);
382		mvwaddstr (stdscr, 0, 0, 
383			"Help: exit: x,  page up: u <, page down: d >");
384		mvwaddstr (stdscr, 2, 0, help);
385		wmove (stdscr, 1, 0);
386	  	wrefresh(stdscr);
387
388		do {
389			winin = mgetch(stdscr);
390			if (winin < KEYSEQ_FIRST)
391				winin = tolower(winin);
392			again = 0;
393			switch (winin) {
394				case '<':
395				case 'u':
396				case KEYSEQ_UP_ARROW:
397				case KEYSEQ_LEFT_ARROW:
398				case KEYSEQ_PAGE_UP:
399					if (lineoff)
400						lineoff -= max_lines - 2;
401					else
402						again = 1;
403					break;
404				case '>':
405				case 'd':
406				case KEYSEQ_DOWN_ARROW:
407				case KEYSEQ_RIGHT_ARROW:
408				case KEYSEQ_PAGE_DOWN:
409					if (*help)
410						lineoff += max_lines - 2;
411					else
412						again = 1;
413					break;
414				case 'q':
415					break;
416				case 'x':
417					winin = 'q';
418					break;
419				default:
420					again = 1;
421			}
422			if (again)
423				mbeep();
424		} while (again);
425	} while (winin != 'q');
426
427	/* Restore current menu */    
428	wclear(stdscr);
429	wrefresh(stdscr);
430	process_item (&num, -2);
431}
432
433static void process_req (struct menudesc *m, int num, int req)
434{
435	int ch;
436	int hasexit = (m->mopt & NOEXITOPT ? 0 : 1 );
437	int refresh = 0;
438	int scroll_sel = 0;
439
440	if (req == REQ_EXECUTE)
441		return;
442
443	else if (req == REQ_NEXT_ITEM) {
444		if (m->cursel < m->numopts + hasexit - 1) {
445			m->cursel++;
446			scroll_sel = 1;
447			refresh = 1;
448		} else
449			mbeep();
450
451	} else if (req == REQ_PREV_ITEM) {
452		if (m->cursel > 0) {
453			m->cursel--;
454			scroll_sel = 1;
455			refresh = 1;
456		} else
457			mbeep();
458
459	} else if (req == REQ_REDISPLAY) {
460		wclear(stdscr);
461		wrefresh(stdscr);
462		process_item (&num, -2);
463		refresh = 1;
464
465	} else if (req == REQ_HELP) {
466		process_help (m, num);
467		refresh = 1;
468
469	} else if (req == REQ_SCROLLUP) {
470		if (!(m->mopt & SCROLL))
471			mbeep();
472		else if (m->topline == 0)
473			mbeep();
474		else {
475			m->topline -= m->h-1;
476			wclear (m->mw);
477			refresh = 1;
478		}
479
480	} else if (req == REQ_SCROLLDOWN) {
481		if (!(m->mopt & SCROLL))
482			mbeep();
483		else if (m->topline + m->h - 1 > m->numopts + hasexit)
484			mbeep();
485		else {
486			m->topline += m->h-1;
487			wclear (m->mw);
488			refresh = 1;
489		}
490
491	} else {
492		ch = req;
493		if (ch == 'x' && hasexit) {
494			m->cursel = m->numopts;
495			scroll_sel = 1;
496			refresh = 1;
497		} else {
498			if (ch > 'z')
499				ch = 255;
500			if (ch >= 'a') {
501				if (ch > 'x') ch--;
502				ch = ch - 'a';
503			} else
504				ch = 25 + ch - 'A';
505			if (ch < 0 || ch >= m->numopts)
506				mbeep();
507			else {
508				m->cursel = ch;
509				scroll_sel = 1;
510				refresh = 1;
511			}
512		}
513	}
514
515	if (m->mopt & SCROLL && scroll_sel) {
516		while (m->cursel >= m->topline + m->h -1 )
517			m->topline += m->h -1;
518		while (m->cursel < m->topline)
519			m->topline -= m->h -1;
520	}
521
522	if (refresh) {
523		post_menu (m);
524		wrefresh (m->mw);
525	}
526}
527
528void process_menu (int num)
529{
530	int sel = 0;
531	int req, done;
532	int last_num;
533
534	struct menudesc *m = &menus[num];
535
536	done = FALSE;
537
538	/* Initialize? */
539	if (!__menu_init) {
540		if (initscr() == NULL) {
541			__menu_initerror();
542			return;
543		}
544		cbreak();
545		noecho();
546		max_lines = stdscr->maxy;
547		init_keyseq();
548		__menu_init = 1;
549	}
550	if (__m_endwin) {
551     		wclear(stdscr);
552		wrefresh(stdscr);
553		__m_endwin = 0;
554	}
555	if (m->mw == NULL)
556		init_menu (m);
557
558	/* Always preselect option 0 and display from 0! */
559	m->cursel = 0;
560	m->topline = 0;
561
562	while (!done) {
563		last_num = num;
564		if (__m_endwin) {
565			wclear(stdscr);
566			wrefresh(stdscr);
567			__m_endwin = 0;
568		}
569		/* Process the display action */
570		process_item (&num, -2);
571		post_menu (m);
572		wrefresh (m->mw);
573
574		while ((req = menucmd (m->mw)) != REQ_EXECUTE)
575			process_req (m, num, req);
576
577		sel = m->cursel;
578		wclear (m->mw);
579		wrefresh (m->mw);
580
581		/* Process the items */
582		if (sel < m->numopts)
583			done = process_item (&num, sel);
584		else
585			done = TRUE;
586
587		/* Reselect m just in case */
588		if (num != last_num) {
589			m = &menus[num];
590			/* Initialize? */
591			if (m->mw == NULL)
592				init_menu (m);
593			process_item (&num, -2);
594		}
595	}
596
597	/* Process the exit action */
598	process_item (&num, -1);
599}
600
601