]> andersk Git - moira.git/blob - clients/moira/menu.c
89e81c7be80e972506c58c8728115e63e451e405
[moira.git] / clients / moira / menu.c
1 /*
2  * Copyright 1987 by the Massachusetts Institute of Technology.
3  * For copying and distribution information, see the file
4  * "mit-copyright.h".
5  *
6  * $Source$
7  * $Author$
8  * $Header$
9  *
10  * Generic menu system module.
11  *
12  * Basically, we define an enormous tree structure which represents the
13  * menu.  Some extra pieces (ml_command, ma_doc) get thrown in so we can
14  * also use the structure for a command-based system.
15  *
16  * By making the menu descriptions so general, we can ease porting to just
17  * about anything.
18  */
19
20 #ifndef lint
21 static char rcsid_menu_c[] = "$Header$";
22
23 #endif lint
24
25 #include <sys/types.h>
26 #include <stdio.h>
27 #include <signal.h>
28 #include <curses.h>
29 #include <ctype.h>
30 #include <strings.h>
31 #include <varargs.h>
32 #include <com_err.h>
33 #include "menu.h"
34
35 #define MAX(A,B)        ((A) > (B) ? (A) : (B))
36 #define MIN(A,B)        ((A) < (B) ? (A) : (B))
37 #define CTL(ch)         ((ch) & 037)
38
39 #define MIN_INPUT 2             /* Minimum number of lines for input window */
40
41 extern char *calloc();
42 int more_flg = 1;
43
44 /* Structure for holding current displayed menu */
45 struct menu_screen {
46     WINDOW *ms_screen;          /* Window for this menu */
47     WINDOW *ms_title;           /* Title subwindow */
48     WINDOW *ms_menu;            /* Menu subwindow */
49     WINDOW *ms_input;           /* Input subwindow */
50     int ms_input_y;             /* Input subwindow reference coordinate */
51 }          *cur_ms;
52
53 #define NULLMS ((struct menu_screen *) 0)
54
55 Menu *top_menu;                 /* Root for command search */
56
57 /*
58  * Hook function to cause error messages to be printed through
59  * curses instead of around it.
60  */
61
62 int
63 menu_com_err_hook(who, code, fmt, args)
64     char *who;
65     int code;
66     char *fmt;
67     va_list args;
68 {
69     char buf[BUFSIZ], *cp;
70
71     FILE _strbuf;
72
73     (void) strcpy(buf, who);
74     for (cp = buf; *cp; cp++);
75     *cp++ = ':';
76     *cp++ = ' ';
77     if (code) {
78         (void) strcpy(cp, error_message(code));
79         while (*cp)
80             cp++;
81     }
82     _strbuf._flag = _IOWRT + _IOSTRG;
83     _strbuf._ptr = cp;
84     _strbuf._cnt = BUFSIZ - (cp - buf);
85     _doprnt(fmt, args, &_strbuf);
86     (void) putc('\0', &_strbuf);
87     Put_message(buf);
88 }
89
90 /*
91  * Start_menu takes a menu as an argument.  It initializes curses u.s.w.,
92  * and a quit in any submenu should unwind back to here.  (it might not,
93  * if user functions which run their own menus don't cooperate.)
94  * Start_menu should only be called once, at the start of the program.
95  */
96 Start_menu(m)
97     Menu *m;
98 {
99     struct menu_screen *make_ms();
100     register int (*old_hook)() = set_com_err_hook(menu_com_err_hook);
101     
102     if (initscr() == ERR) {
103         fputs("Can't initialize curses!\n", stderr);
104         Start_no_menu(m);
105     } else {
106         (void) raw();           /* We parse & print everything ourselves */
107         (void) noecho();
108         cur_ms = make_ms(0);    /* So we always have some current */
109         /* menu_screen */ 
110         top_menu = m;
111         /* Run the menu */
112         (void) Do_menu(m, -1, (char **) NULL);
113     }
114     (void) set_com_err_hook(old_hook);
115     Cleanup_menu();
116 }
117
118 Cleanup_menu()
119 {
120     if (cur_ms) {
121         (void) wclear(cur_ms->ms_screen);
122         (void) wrefresh(cur_ms->ms_screen);
123     }
124     endwin();
125 }
126     
127
128 /* Like Start_menu, except it doesn't print menus and doesn't use curses */
129 Start_no_menu(m)
130     Menu *m;
131 {
132     cur_ms = NULLMS;
133     top_menu = m;
134     /* Run the menu */
135     (void) Do_menu(m, -1, (char **) NULL);
136 }
137
138 /*
139  * Create a new menu screen template with the specified menu length
140  * and return it.
141  */
142 struct menu_screen *
143 make_ms(length)
144     int length;
145 {
146     struct menu_screen *ms;
147     char *malloc();
148
149     if (MAX_TITLE + length + MIN_INPUT > LINES) {
150         fputs("Menu too big!\n", stderr);
151         exit(2);
152     }
153
154     ms = (struct menu_screen *) malloc(sizeof(struct menu_screen));
155
156     ms->ms_screen = newwin(0, 0, 0, 0);
157     ms->ms_title = subwin(ms->ms_screen, MAX_TITLE, 0, 0, 0);
158     ms->ms_menu = subwin(ms->ms_screen,
159                          length, 0, MAX_TITLE, 0);
160     ms->ms_input = subwin(ms->ms_screen, 0, 0,
161                           ms->ms_input_y = MAX_TITLE + length,
162                           0);
163
164     scrollok(ms->ms_input, TRUE);
165     (void) wmove(ms->ms_input, 0, 0);
166     (void) wclear(ms->ms_screen);
167
168     return (ms);
169 }
170
171 /*
172  * This routine destroys a menu_screen.
173  */
174 destroy_ms(ms)
175     struct menu_screen *ms;
176 {
177     delwin(ms->ms_title);
178     delwin(ms->ms_menu);
179     delwin(ms->ms_input);
180     delwin(ms->ms_screen);
181     free((char *) ms);
182 }
183
184 /*
185  * This guy actually puts up the menu
186  * Note: if margc < 0, no 'r' option will be displayed (i.e., on the
187  * top level menu)
188  */
189 int 
190 Do_menu(m, margc, margv)
191     Menu *m;
192     int margc;
193     char *margv[];
194 {
195     struct menu_screen *my_ms, *old_cur_ms;
196     char argvals[MAX_ARGC][MAX_ARGLEN]; /* This is where args are stored */
197     char buf[MAX_ARGC * MAX_ARGLEN];
198     char *argv[MAX_ARGC];
199     int line;
200     int i;
201     struct menu_line *command, *Find_command();
202     int argc;
203     int quitflag, is_topmenu = (margc < 0);
204     
205     /* Entry function gets called with old menu_screen still current */
206     if (m->m_entry != NULLFUNC)
207         if (m->m_entry(m, margc, margv) == DM_QUIT)
208             return DM_NORMAL;
209
210     /* The following get run only in curses mode */
211     if (cur_ms != NULLMS) {
212         /* Get a menu_screen */
213         old_cur_ms = cur_ms;
214         cur_ms = my_ms = make_ms(m->m_length + 1 + (is_topmenu?0:1));
215
216         /* Now print the title and the menu */
217         (void) wclear(my_ms->ms_menu);
218         (void) wmove(my_ms->ms_title, 0, MAX(0, (COLS -
219                                                  strlen(m->m_title)) >> 1));
220         (void) wstandout(my_ms->ms_title);
221         (void) waddstr(my_ms->ms_title, m->m_title);
222         (void) wstandend(my_ms->ms_title);
223
224         for (line = 0; line < m->m_length; line++) {
225             int len = strlen(m->m_lines[line].ml_command);
226             if (len > 12) len=12;
227             
228             (void) wmove(my_ms->ms_menu, line, 0);
229             
230             (void) wprintw(my_ms->ms_menu, "%2d. (%s)%*s %s.", line + 1,
231                            m->m_lines[line].ml_command,
232                            12-len, "",
233                            m->m_lines[line].ml_doc);
234         }
235         (void) wmove(my_ms->ms_menu, line++, 0);
236         if (!is_topmenu) {
237             (void) waddstr(my_ms->ms_menu,
238                            " r. (return)       Return to previous menu.");
239             (void) wmove(my_ms->ms_menu, line, 0);
240         }
241         (void) waddstr(my_ms->ms_menu, " q. (quit)         Quit.");
242     }
243
244     for (;;) {
245         /* This will be set by a return val from func or submenu */
246         quitflag = DM_NORMAL;
247         /* This is here because we may be coming from another menu */
248         if (cur_ms != NULL)
249             touchwin(my_ms->ms_screen);
250         /* Get a command */
251         if (!Prompt_input("Command: ", buf, sizeof(buf)))
252             continue;
253         /* Parse it into the argument list */
254         /* If there's nothing there, try again */
255         /* Initialize argv */
256         for (argc = 0; argc < MAX_ARGC; argc++)
257             argv[argc] = argvals[argc];
258
259         if ((argc = Parse_words(buf, argv, MAX_ARGLEN)) == 0)
260             continue;
261         if ((line = atoi(argv[0])) > 0 && line <= m->m_length) {
262             command = &m->m_lines[line - 1];
263         }
264         else if ((!is_topmenu &&
265                   (!strcmp(argv[0], "r")
266                    || !strcmp(argv[0], "return")))
267                  || !strcmp(argv[0], "q")
268                  || !strcmp(argv[0], "quit")) {
269             /* here if it's either return or quit */
270             if (cur_ms != NULLMS) {
271                 cur_ms = old_cur_ms;
272                 destroy_ms(my_ms);
273             }
274             if (m->m_exit != NULLFUNC)
275                 m->m_exit(m);
276             return (*argv[0] == 'r' ? DM_NORMAL : DM_QUIT);
277             /* finally, try to find it using Find_command */
278         }
279         else if ((command = Find_command(argvals[0])) ==
280                  (struct menu_line *) 0) {
281             Put_message("Command not recognized");
282             continue;
283         }
284         /* If we got to here, command is a valid menu_line */
285         /* Send the offical command name into the argv */
286         (void) strcpy(argvals[0], command->ml_command);
287         /* Show that we're working on it */
288         Put_message(command->ml_doc);
289         /* Print args that we've already got */
290         for (i = 1; i < argc; i++) {
291             if (command->ml_args[i].ma_prompt == NULL)
292                 break;
293             (void) sprintf(buf, "%s%s", command->ml_args[i].ma_prompt,
294                            argv[i]);
295             Put_message(buf);
296         }
297         /* Get remaining arguments, if any */
298         for (; argc < command->ml_argc; argc++) {
299             if (!Prompt_input(command->ml_args[argc].ma_prompt,
300                               argvals[argc], sizeof(argvals[argc])))
301                 goto punt_command;
302         }
303         if (command->ml_function != NULLFUNC) {
304             /* If it's got a function, call it */
305             quitflag = command->ml_function(argc, argv);
306         }
307         else if (command->ml_submenu != NULLMENU) {
308             /* Else see if it is a submenu */
309             quitflag = Do_menu(command->ml_submenu, argc, argv);
310         }
311         else {
312             /* If it's got neither, something is wrong */
313             Put_message("*INTERNAL ERROR: NO FUNCTION OR MENU FOR LINE*");
314         }
315         if (quitflag == DM_QUIT) {
316             if (cur_ms != NULLMS) {
317                 cur_ms = old_cur_ms;
318                 destroy_ms(my_ms);
319             }
320             if (m->m_exit != NULLFUNC)
321                 m->m_exit(m);
322             return (DM_QUIT);
323         }
324     punt_command:
325         ;
326     }
327 }
328
329 /* Prompt the user for input in the input window of cur_ms */
330 int Prompt_input(prompt, buf, buflen)
331     char *prompt;
332     char *buf;
333     int buflen;
334 {
335     int c;
336     char *p;
337     int y, x, oldx;
338
339     if (cur_ms != NULLMS) {
340         more_flg = 1;
341         getyx(cur_ms->ms_input, y, x);
342         (void) wmove(cur_ms->ms_input, y, 0);
343
344         touchwin(cur_ms->ms_screen);
345         refresh_ms(cur_ms);
346         (void) waddstr(cur_ms->ms_input, prompt);
347         getyx(cur_ms->ms_input, y, x);
348
349         oldx = x;
350         p = buf;
351         while(1) {
352             (void) wmove(cur_ms->ms_input, y, x);
353             (void) wclrtoeol(cur_ms->ms_input);
354             refresh_ms(cur_ms);
355             c = getchar();
356             switch (c) {
357             case CTL('C'):
358                 return 0;
359             case CTL('Z'):
360                 (void) kill(getpid(), SIGTSTP);
361                 touchwin(curscr);
362                 break;
363             case CTL('L'):
364                 (void) wclear(cur_ms->ms_input);
365                 (void) waddstr(cur_ms->ms_input, prompt);
366                 (void) touchwin(cur_ms->ms_screen);
367 #ifdef notdef
368                 (void) wrefresh(curscr);
369 #endif notdef
370                 getyx(cur_ms->ms_input, y, x);
371                 break;
372
373             case '\n':
374             case '\r':
375                 goto end_input;
376                 /* these should be obtained by doing ioctl() on tty */
377             case '\b':
378             case '\177':
379                 if (p > buf) {
380                     p--;
381                     x--;
382                 }
383                 break;
384             case CTL('U'):
385             case CTL('G'):
386             case CTL('['):
387                 x = oldx;
388                 p = buf;
389                 break;
390             default:
391                 if (isprint(c) && (p - buf < buflen)) {
392                     (void) waddch(cur_ms->ms_input, c);
393                     *p++ = c;
394                     x++;
395                 } else
396                     putchar(CTL('G'));
397                 break;
398             }
399         }
400     end_input:
401         (void) waddch(cur_ms->ms_input, '\n');
402         (void) waddch(cur_ms->ms_input, '\r');
403         
404         (void) wclrtoeol(cur_ms->ms_input);
405         refresh_ms(cur_ms);
406         *p = '\0';
407         Start_paging();
408         goto gotit;
409     } else {
410         printf("%s", prompt);
411         if (gets(buf) == NULL)
412             return 0;
413         Start_paging();
414         goto gotit;
415     }
416 gotit:
417     strcpy(buf, strtrim(buf));
418     return 1;
419 }
420
421 /* Prompt the user for input in the input window of cur_ms, but don't echo
422    and allow some control characters */
423 int Password_input(prompt, buf, buflen)
424     char *prompt;
425     char *buf;
426     int buflen;
427 {
428     int c;
429     char *p;
430     int y, x, oldx;
431
432     if (cur_ms != NULLMS) {
433         more_flg = 1;
434         getyx(cur_ms->ms_input, y, x);
435         (void) wmove(cur_ms->ms_input, y, 0);
436
437         touchwin(cur_ms->ms_screen);
438         refresh_ms(cur_ms);
439         (void) waddstr(cur_ms->ms_input, prompt);
440         getyx(cur_ms->ms_input, y, x);
441
442         oldx = x;
443         for (p = buf; p - buf < buflen;) {
444             (void) wmove(cur_ms->ms_input, y, x);
445             (void) wclrtoeol(cur_ms->ms_input);
446             refresh_ms(cur_ms);
447             c = getchar();
448             switch (c) {
449             case CTL('C'):
450                 return 0;
451             case CTL('Z'):
452                 (void) kill(getpid(), SIGTSTP);
453                 touchwin(curscr);
454                 break;
455             case CTL('L'):
456                 (void) wclear(cur_ms->ms_input);
457                 (void) waddstr(cur_ms->ms_input, prompt);
458                 refresh_ms(cur_ms);
459                 getyx(cur_ms->ms_input, y, x);
460                 break;
461             case '\n':
462             case '\r':
463                 (void) waddch(cur_ms->ms_input, '\n');
464                 (void) waddch(cur_ms->ms_input, '\r');
465
466                 (void) wclrtoeol(cur_ms->ms_input);
467                 refresh_ms(cur_ms);
468                 *p = '\0';
469                 Start_paging();
470                 return 1;
471             case '\b':
472             case '\177':
473                 if (p > buf) {
474                     p--;
475                     x--;
476                 }
477                 break;
478             case CTL('U'):
479                 x = oldx;
480                 p = buf;
481                 break;
482             default:
483                 *p++ = c;
484                 break;
485             }
486         }
487     }
488     else {
489         struct sgttyb ttybuf, nttybuf;
490         printf("%s", prompt);
491         /* turn off echoing */
492         (void) ioctl(0, TIOCGETP, (char *)&ttybuf);
493         nttybuf = ttybuf;
494         nttybuf.sg_flags &= ~ECHO;
495         (void)ioctl(0, TIOCSETP, (char *)&nttybuf);
496         if (gets(buf) == NULL) {
497             (void) ioctl(0, TIOCSETP, (char *)&ttybuf);
498             putchar('\n');
499             return 0;
500         }
501         putchar('\n');
502         (void) ioctl(0, TIOCSETP, (char *)&ttybuf);
503         Start_paging();
504         return 1;
505     }
506     return 0;
507 }
508
509 int lines_left;
510
511 /* Start paging */
512 /* This routine will cause the most recently put message to be the
513    one at the top of the screen when a ---More--- prompt is displayed */
514 Start_paging()
515 {
516     if (cur_ms != NULLMS) {
517         lines_left = LINES - cur_ms->ms_input_y - 1;
518     }
519     else {
520         lines_left = 23;
521     }
522 }
523
524 /* Turn off paging */
525 Stop_paging()
526 {
527     lines_left = -1;
528 }
529
530 /* Print a message in the input window of cur_ms.  */
531 Put_message(msg)
532 char *msg;
533 {
534     char *copy, *line, *s;
535
536     copy = (char *) malloc((u_int)COLS);
537     s = line = msg;
538     while(*s++) {
539         if (s - line >= COLS-1) {
540             (void) strncpy(copy, line, COLS-1);
541             line += COLS-1;
542         } else if (*s == '\n') {
543             *s = '\0';
544             (void) strcpy(copy, line);
545             line = ++s;
546         } else
547             continue;
548         Put_line(copy);
549     }
550     Put_line(line);
551     free(copy);
552 }
553
554 /* Will be truncated to COLS characters.  */
555 Put_line(msg)
556 char *msg;
557 {
558     int y, x, i;
559     char *msg1, chr;
560
561     if (!more_flg)
562         return;
563
564     if (lines_left >= 0) {
565         if (--lines_left == 0) {
566             /* Give the user a more prompt */
567             if (cur_ms != NULLMS) {
568                 (void) wstandout(cur_ms->ms_input);
569                 (void) wprintw(cur_ms->ms_input, "---More---");
570                 (void) wstandend(cur_ms->ms_input);
571                 refresh_ms(cur_ms);
572                 chr = getchar();/* We do care what it is */
573                 if (chr == 'q' || chr == 'Q') {
574                     more_flg = 0;
575                     return;
576                 }
577                 getyx(cur_ms->ms_input, y, x);
578                 /* x is a bitbucket; avoid lint problems */
579                 x=x;
580                 (void) wmove(cur_ms->ms_input, y, 0);
581                 (void) wclrtoeol(cur_ms->ms_input);
582             }
583             else {
584                 printf("---More (hit return)---");
585                 (void) getchar();
586             }
587             Start_paging();     /* Reset lines_left */
588         }
589     }
590
591     if (cur_ms != NULLMS) {
592         msg1 = (char *) calloc((u_int)COLS, 1);
593         (void) strncpy(msg1, msg, COLS-1);
594         for (i = strlen(msg1); i < COLS - 1; i++)
595             msg1[i] = ' ';
596         (void) wprintw(cur_ms->ms_input, "%s\n", msg1);
597 /*      refresh_ms(cur_ms); */
598     }
599     else {
600         puts(msg);
601     }
602 }
603
604 /* Refresh a menu_screen onto the real screen */
605 refresh_ms(ms)
606     struct menu_screen *ms;
607 {
608     int y, x;
609
610     getyx(ms->ms_input, y, x);
611     (void) wmove(ms->ms_screen, y + ms->ms_input_y, x);
612     (void) wrefresh(ms->ms_screen);
613 }
614
615 /* Parse buf into a list of words, which will be placed in strings specified by
616    argv.  Space for these strings must have already been allocated.
617    Only the first n characters of each word will be copied */
618 Parse_words(buf, argv, n)
619     char *buf;
620     char *argv[];
621 int n;
622
623 {
624     char *start, *end;          /* For sausage machine */
625     int argc;
626
627     start = buf;
628     for (argc = 0; argc < MAX_ARGC; argc++) {
629         while (isspace(*start))
630             start++;            /* Kill whitespace */
631         if (*start == '\0')
632             break;              /* Nothing left */
633         /* Now find the end of the word */
634         for (end = start; *end != '\0' && !isspace(*end); end++);
635         (void) strncpy(argv[argc], start, MIN(end - start, n)); /* Copy it */
636         argv[argc][MIN(end - start, n - 1)] = '\0';     /* Terminate */
637         start = end;
638     }
639     return (argc);
640 }
641
642 /* This is the internal form of Find_command, which recursively searches
643    for a menu_line with command command in the specified menu */
644 /* It will search to a maximum depth of d */
645 struct menu_line *
646 find_command_from(c, m, d)
647     char *c;
648     struct menu *m;
649     int d;
650 {
651     int line;
652     struct menu_line *maybe;
653
654     if (d < 0)
655         return ((struct menu_line *) 0);        /* Too deep! */
656     for (line = 0; line < m->m_length; line++) {
657         if (!strcmp(c, m->m_lines[line].ml_command)) {
658             return (&m->m_lines[line]);
659         }
660         else if (m->m_lines[line].ml_submenu != NULLMENU
661                  && (maybe =
662                    find_command_from(c, m->m_lines[line].ml_submenu, d - 1))
663                  != (struct menu_line *) 0) {
664             return (maybe);
665         }
666     }
667     /* If we got to here, nothing works */
668     return ((struct menu_line *) 0);
669 }
670
671 /* Find_command searches down the current menu tree */
672 /* And returns a pointer to a menu_line with the specified command name */
673 /* It returns (struct menu_line *) 0 if none is found */
674 struct menu_line *
675 Find_command(command)
676     char *command;
677 {
678     if (top_menu == NULLMENU) {
679         return ((struct menu_line *) 0);
680     }
681     else {
682         return (find_command_from(command, top_menu, MAX_MENU_DEPTH));
683     }
684 }
685
686 /*
687  * Local Variables:
688  * mode: c
689  * c-indent-level: 4
690  * c-continued-statement-offset: 4
691  * c-brace-offset: -4
692  * c-argdecl-indent: 4
693  * c-label-offset: -4
694  * End:
695  */
This page took 0.16266 seconds and 3 git commands to generate.