menu_sys.def revision 1.13
1/*	$NetBSD: menu_sys.def,v 1.13 1998/07/03 15:20:30 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':  /* Contnrol-P */
193		case KEYSEQ_DOWN_ARROW:
194			return REQ_NEXT_ITEM;
195		case '\020':  /* Control-N */
196		case KEYSEQ_UP_ARROW:
197			return REQ_PREV_ITEM;
198		case '\014':  /* Control-L */
199		        return REQ_REDISPLAY;
200		case '<':
201		case '\010':  /* Control-H (backspace) */
202		case KEYSEQ_PAGE_UP:
203			return REQ_SCROLLUP;
204		case '>':
205		case ' ':
206		case KEYSEQ_PAGE_DOWN:
207			return REQ_SCROLLDOWN;
208		case '?':
209			return REQ_HELP;
210		}
211		
212		if (isalpha(ch)) 
213			return (ch);
214
215		mbeep();
216		wrefresh(w);
217	}
218}
219
220static void init_menu (struct menudesc *m)
221{
222	int max;
223	char **p;
224	int add, exitadd;
225
226	add = ((m->mopt & NOBOX) ? 2 : 4);
227	exitadd = ((m->mopt & NOEXITOPT) ? 0 : 1);
228	max = strlen(m->title);
229
230	/* Calculate h? h == number of visible options. */
231	if (m->h == 0) {
232		m->h = m->numopts + exitadd;
233		if (m->h + m->y + add >= max_lines && (m->mopt & SCROLL))
234			m->h = max_lines - m->y - add ;
235	}
236
237	/* Check window heights and set scrolling */
238	if (m->h < m->numopts + exitadd) {
239		if (!(m->mopt & SCROLL) || m->h < 3) {
240			endwin();
241			(void) fprintf (stderr,
242				"Window too small for menu \"%s\"\n",
243				m->title);
244			exit(1);
245		}
246	} else
247		m->mopt &= ~SCROLL;
248
249	/* check for screen fit */
250	if (m->y + m->h + add > max_lines) {
251		endwin();
252		(void) fprintf (stderr,
253			"Screen too small for menu \"%s\"\n", m->title);
254		exit(1);
255
256	}
257
258	/* Calculate w? */
259	if (m->w == 0) {
260		p = m->opts;
261		if (m->mopt & SCROLL)
262			max = strlen(scrolltext);
263		while (*p) {
264			max = MAX(max,strlen(*p));
265			p++;
266		}
267		m->w = max;
268	}
269
270	/* Get the windows. */
271	m->mw = newwin(m->h+add, m->w+add, m->y, m->x);
272
273	if (m->mw == NULL) {
274		endwin();
275		(void) fprintf (stderr,
276			"Could not create window for window with title "
277			" \"%s\"\n", m->title);
278		exit(1);
279	} 
280}
281
282static void post_menu (struct menudesc *m)
283{
284	int i;
285	int hasbox, cury, maxy, selrow, lastopt;
286	int tadd;
287	
288	if (m->mopt & NOBOX) {
289		cury = 0;
290		maxy = m->h;
291		hasbox = 0;
292	} else {
293		cury = 1;
294		maxy = m->h+1;
295		hasbox = 1;
296	}
297
298	/* Clear the window */
299	wclear (m->mw);
300
301	tadd = strlen(m->title) ? 2 : 0;
302
303	if (tadd) {
304		mvwaddstr(m->mw, cury, cury, m->title);
305		cury += 2;
306		maxy += 2;
307	}
308
309	/* Set defaults, calculate lastopt. */
310	selrow = -1;
311	if (m->mopt & SCROLL) {
312		lastopt = MIN(m->numopts, m->topline+m->h-1);
313		maxy -= 1;
314	} else
315		lastopt = m->numopts;
316
317	for (i=m->topline; i<lastopt; i++, cury++) {
318		if (m->cursel == i) {
319			mvwaddstr (m->mw, cury, hasbox, ">");
320			wstandout(m->mw);
321			selrow = cury;
322		} else
323			mvwaddstr (m->mw, cury, hasbox, " ");
324		waddstr (m->mw, m->opts[i]);
325		if (m->cursel == i)
326			wstandend(m->mw);
327	}
328
329	/* Add the exit option. */
330	if (!(m->mopt & NOEXITOPT) && cury < maxy) {
331		if (m->cursel >= m->numopts) {
332			mvwaddstr (m->mw, cury, hasbox, ">");
333			wstandout(m->mw);
334			selrow = cury;
335		} else
336			mvwaddstr (m->mw, cury, hasbox, " ");
337		waddstr (m->mw, "x: Exit");
338		if (m->cursel >= m->numopts)
339			wstandend(m->mw);
340		cury++;
341	}
342
343	/* Add the scroll line */
344	if (m->mopt & SCROLL) {
345		mvwaddstr (m->mw, cury, hasbox, scrolltext);
346		if (selrow < 0)
347			selrow = cury;
348	}
349
350	/* Add the box. */
351	if (!(m->mopt & NOBOX))
352		box(m->mw, '*', '*');
353
354	wmove(m->mw, selrow, hasbox);
355}
356
357static void process_help (struct menudesc *m, int num)
358{
359	char *help = m->helpstr;
360	int lineoff = 0;
361	int curoff = 0;
362	int again;
363	int winin;
364
365	/* Is there help? */
366	if (!help) {
367		mbeep();
368		return;
369	}
370
371	/* Display the help information. */
372	do {
373		if (lineoff < curoff) {
374			help = m->helpstr;
375			curoff = 0;
376		}
377		while (*help && curoff < lineoff) {
378			if (*help == '\n')
379				curoff++;
380			help++;
381		}
382	
383		wclear(stdscr);
384		mvwaddstr (stdscr, 0, 0, 
385			"Help: exit: x,  page up: u <, page down: d >");
386		mvwaddstr (stdscr, 2, 0, help);
387		wmove (stdscr, 1, 0);
388	  	wrefresh(stdscr);
389
390		do {
391			winin = mgetch(stdscr);
392			if (winin < KEYSEQ_FIRST)
393				winin = tolower(winin);
394			again = 0;
395			switch (winin) {
396				case '<':
397				case 'u':
398				case KEYSEQ_UP_ARROW:
399				case KEYSEQ_LEFT_ARROW:
400				case KEYSEQ_PAGE_UP:
401					if (lineoff)
402						lineoff -= max_lines - 2;
403					else
404						again = 1;
405					break;
406				case '>':
407				case 'd':
408				case KEYSEQ_DOWN_ARROW:
409				case KEYSEQ_RIGHT_ARROW:
410				case KEYSEQ_PAGE_DOWN:
411					if (*help)
412						lineoff += max_lines - 2;
413					else
414						again = 1;
415					break;
416				case 'q':
417					break;
418				case 'x':
419					winin = 'q';
420					break;
421				default:
422					again = 1;
423			}
424			if (again)
425				mbeep();
426		} while (again);
427	} while (winin != 'q');
428
429	/* Restore current menu */    
430	wclear(stdscr);
431	wrefresh(stdscr);
432	process_item (&num, -2);
433}
434
435static void process_req (struct menudesc *m, int num, int req)
436{
437	int ch;
438	int hasexit = (m->mopt & NOEXITOPT ? 0 : 1 );
439	int refresh = 0;
440	int scroll_sel = 0;
441
442	if (req == REQ_EXECUTE)
443		return;
444
445	else if (req == REQ_NEXT_ITEM) {
446		if (m->cursel < m->numopts + hasexit - 1) {
447			m->cursel++;
448			scroll_sel = 1;
449			refresh = 1;
450			if (m->mopt & SCROLL && 
451			    m->cursel >= m->topline + m->h -1 )
452				m->topline += 1;
453		} else
454			mbeep();
455
456	} else if (req == REQ_PREV_ITEM) {
457		if (m->cursel > 0) {
458			m->cursel--;
459			scroll_sel = 1;
460			refresh = 1;
461			if (m->cursel < m->topline )
462				m->topline -= 1;
463		} else
464			mbeep();
465
466	} else if (req == REQ_REDISPLAY) {
467		wclear(stdscr);
468		wrefresh(stdscr);
469		process_item (&num, -2);
470		refresh = 1;
471
472	} else if (req == REQ_HELP) {
473		process_help (m, num);
474		refresh = 1;
475
476	} else if (req == REQ_SCROLLUP) {
477		if (!(m->mopt & SCROLL))
478			mbeep();
479		else if (m->topline == 0)
480			mbeep();
481		else {
482			m->topline = MAX(0,m->topline-m->h-1);
483			wclear (m->mw);
484			refresh = 1;
485		}
486
487	} else if (req == REQ_SCROLLDOWN) {
488		if (!(m->mopt & SCROLL))
489			mbeep();
490		else if (m->topline + m->h - 1 >= m->numopts + hasexit)
491			mbeep();
492		else {
493			m->topline = MIN(m->topline+m->h-1,
494					 m->numopts+hasexit-m->h+1);
495			wclear (m->mw);
496			refresh = 1;
497		}
498
499	} else {
500		ch = req;
501		if (ch == 'x' && hasexit) {
502			m->cursel = m->numopts;
503			scroll_sel = 1;
504			refresh = 1;
505		} else {
506			if (ch > 'z')
507				ch = 255;
508			if (ch >= 'a') {
509				if (ch > 'x') ch--;
510				ch = ch - 'a';
511			} else
512				ch = 25 + ch - 'A';
513			if (ch < 0 || ch >= m->numopts)
514				mbeep();
515			else {
516				m->cursel = ch;
517				scroll_sel = 1;
518				refresh = 1;
519			}
520		}
521	}
522
523	if (m->mopt & SCROLL && scroll_sel) {
524		while (m->cursel >= m->topline + m->h -1 )
525			m->topline = MIN(m->topline+m->h-1,
526					 m->numopts+hasexit-m->h+1);
527		while (m->cursel < m->topline)
528			m->topline = MAX(0,m->topline-m->h+1);
529	}
530
531	if (refresh) {
532		post_menu (m);
533		wrefresh (m->mw);
534	}
535}
536
537void process_menu (int num)
538{
539	int sel = 0;
540	int req, done;
541	int last_num;
542
543	struct menudesc *m = &menus[num];
544
545	done = FALSE;
546
547	/* Initialize? */
548	if (!__menu_init) {
549		if (initscr() == NULL) {
550			__menu_initerror();
551			return;
552		}
553		cbreak();
554		noecho();
555		max_lines = stdscr->maxy;
556		init_keyseq();
557		__menu_init = 1;
558	}
559	if (__m_endwin) {
560     		wclear(stdscr);
561		wrefresh(stdscr);
562		__m_endwin = 0;
563	}
564	if (m->mw == NULL)
565		init_menu (m);
566
567	/* Always preselect option 0 and display from 0! */
568	m->cursel = 0;
569	m->topline = 0;
570
571	while (!done) {
572		last_num = num;
573		if (__m_endwin) {
574			wclear(stdscr);
575			wrefresh(stdscr);
576			__m_endwin = 0;
577		}
578		/* Process the display action */
579		process_item (&num, -2);
580		post_menu (m);
581		wrefresh (m->mw);
582
583		while ((req = menucmd (m->mw)) != REQ_EXECUTE)
584			process_req (m, num, req);
585
586		sel = m->cursel;
587		wclear (m->mw);
588		wrefresh (m->mw);
589
590		/* Process the items */
591		if (sel < m->numopts)
592			done = process_item (&num, sel);
593		else
594			done = TRUE;
595
596		/* Reselect m just in case */
597		if (num != last_num) {
598			m = &menus[num];
599			/* Initialize? */
600			if (m->mw == NULL)
601				init_menu (m);
602			process_item (&num, -2);
603		}
604	}
605
606	/* Process the exit action */
607	process_item (&num, -1);
608}
609
610/* Control L is end of standard routines, remaining only for dynamic. */
611
612/* Beginning of routines for dynamic menus. */
613
614
615