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