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