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