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