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