]>
Commit | Line | Data |
---|---|---|
07c56447 | 1 | /* |
2aaf83c2 | 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 | * $Log$ | |
74e7b641 | 10 | * Revision 1.5 1987-08-07 18:09:46 poto |
11 | * will not enter menu if ->m_entry returns DM_QUIT; | |
12 | * the command args from a submenu command will be passed on to ->m_entry(); | |
13 | * | |
14 | * Revision 1.4 87/08/05 14:48:04 ambar | |
e9db439c | 15 | * added latest set of hackery, to fix missing |
16 | * newlines, and not being able to quit out of | |
17 | * the pager. | |
74e7b641 | 18 | * |
e9db439c | 19 | * Revision 1.3 87/08/03 05:10:34 wesommer |
20 | * This one appears to work. | |
21 | * | |
33a6b7f2 | 22 | * Revision 1.2 87/08/03 04:16:51 wesommer |
23 | * Here's another, which is probably better. | |
24 | * | |
2aaf83c2 | 25 | * Revision 1.1 87/07/31 18:02:23 ambar |
26 | * Initial revision | |
27 | * | |
28 | * | |
07c56447 | 29 | * Generic menu system module. |
30 | * | |
31 | * Basically, we define an enormous tree structure which represents the | |
32 | * menu. Some extra pieces (ml_command, ma_doc) get thrown in so we can | |
33 | * also use the structure for a command-based system. | |
34 | * | |
35 | * By making the menu descriptions so general, we can ease porting to just | |
36 | * about anything. | |
37 | */ | |
38 | ||
2aaf83c2 | 39 | #ifndef lint |
40 | static char rcsid_menu_c[] = "$Header$"; | |
e9db439c | 41 | |
2aaf83c2 | 42 | #endif lint |
43 | ||
07c56447 | 44 | #include <stdio.h> |
45 | #include <curses.h> | |
2aaf83c2 | 46 | #include <ctype.h> |
07c56447 | 47 | #include "menu.h" |
48 | ||
49 | #define MAX(A,B) ((A) > (B) ? (A) : (B)) | |
50 | #define MIN(A,B) ((A) < (B) ? (A) : (B)) | |
51 | ||
52 | #define MIN_INPUT 2 /* Minimum number of lines for input window */ | |
53 | ||
2aaf83c2 | 54 | char *strcpy(); |
55 | char *strncpy(); | |
e9db439c | 56 | int more_flg = 1; |
2aaf83c2 | 57 | |
07c56447 | 58 | /* Structure for holding current displayed menu */ |
59 | struct menu_screen { | |
60 | WINDOW *ms_screen; /* Window for this menu */ | |
61 | WINDOW *ms_title; /* Title subwindow */ | |
62 | WINDOW *ms_menu; /* Menu subwindow */ | |
63 | WINDOW *ms_input; /* Input subwindow */ | |
64 | int ms_input_y; /* Input subwindow reference coordinate */ | |
e9db439c | 65 | } *cur_ms; |
07c56447 | 66 | |
2aaf83c2 | 67 | #define NULLMS ((struct menu_screen *) 0) |
68 | ||
69 | Menu *top_menu; /* Root for command search */ | |
70 | ||
07c56447 | 71 | /* |
72 | * Start_menu takes a menu as an argument. It initializes curses u.s.w., | |
73 | * and a quit in any submenu should unwind back to here. (it might not, | |
74 | * if user functions which run their own menus don't cooperate.) | |
75 | * Start_menu should only be called once, at the start of the program. | |
76 | */ | |
77 | Start_menu(m) | |
e9db439c | 78 | Menu *m; |
07c56447 | 79 | { |
2aaf83c2 | 80 | struct menu_screen *make_ms(); |
81 | ||
e9db439c | 82 | if (initscr() == ERR) { |
07c56447 | 83 | fputs("Can't initialize curses!\n", stderr); |
2aaf83c2 | 84 | Start_no_menu(m); |
07c56447 | 85 | } |
86 | raw(); /* We parse & print everything ourselves */ | |
87 | noecho(); | |
2aaf83c2 | 88 | cur_ms = make_ms(0); /* So we always have some current menu_screen */ |
89 | top_menu = m; | |
74e7b641 | 90 | /* Run the menu */ |
91 | (void) Do_menu(m, 0, (char **) NULL); | |
07c56447 | 92 | endwin(); |
93 | } | |
94 | ||
2aaf83c2 | 95 | /* Like Start_menu, except it doesn't print menus and doesn't use curses */ |
96 | Start_no_menu(m) | |
e9db439c | 97 | Menu *m; |
2aaf83c2 | 98 | { |
99 | cur_ms = NULLMS; | |
100 | top_menu = m; | |
74e7b641 | 101 | /* Run the menu */ |
102 | (void) Do_menu(m, 0, (char **) NULL); | |
2aaf83c2 | 103 | } |
104 | ||
07c56447 | 105 | /* |
106 | * Create a new menu screen template with the specified menu length | |
107 | * and return it. | |
108 | */ | |
e9db439c | 109 | struct menu_screen * |
110 | make_ms(length) | |
111 | int length; | |
07c56447 | 112 | { |
113 | struct menu_screen *ms; | |
114 | char *malloc(); | |
115 | ||
e9db439c | 116 | if (MAX_TITLE + length + MIN_INPUT > LINES) { |
07c56447 | 117 | fputs("Menu too big!\n", stderr); |
118 | exit(2); | |
119 | } | |
120 | ||
121 | ms = (struct menu_screen *) malloc(sizeof(struct menu_screen)); | |
122 | ||
123 | ms->ms_screen = newwin(0, 0, 0, 0); | |
124 | ms->ms_title = subwin(ms->ms_screen, MAX_TITLE, 0, 0, 0); | |
125 | ms->ms_menu = subwin(ms->ms_screen, | |
126 | length, 0, MAX_TITLE, 0); | |
127 | ms->ms_input = subwin(ms->ms_screen, 0, 0, | |
128 | ms->ms_input_y = MAX_TITLE + length, | |
129 | 0); | |
130 | ||
131 | scrollok(ms->ms_input, TRUE); | |
2aaf83c2 | 132 | (void) wmove(ms->ms_input, 0, 0); |
133 | (void) wclear(ms->ms_screen); | |
07c56447 | 134 | |
e9db439c | 135 | return (ms); |
07c56447 | 136 | } |
137 | ||
138 | /* | |
139 | * This routine destroys a menu_screen. | |
140 | */ | |
141 | destroy_ms(ms) | |
e9db439c | 142 | struct menu_screen *ms; |
07c56447 | 143 | { |
144 | delwin(ms->ms_title); | |
145 | delwin(ms->ms_menu); | |
146 | delwin(ms->ms_input); | |
147 | delwin(ms->ms_screen); | |
e9db439c | 148 | free((char *) ms); |
07c56447 | 149 | } |
150 | ||
151 | /* | |
152 | * This guy actually puts up the menu | |
153 | */ | |
e9db439c | 154 | int |
74e7b641 | 155 | Do_menu(m, margc, margv) |
e9db439c | 156 | Menu *m; |
74e7b641 | 157 | int margc; |
158 | char *margv[]; | |
07c56447 | 159 | { |
160 | struct menu_screen *my_ms, *old_cur_ms; | |
161 | char argvals[MAX_ARGC][MAX_ARGLEN]; /* This is where args are stored */ | |
2aaf83c2 | 162 | char buf[MAX_ARGC * MAX_ARGLEN]; |
07c56447 | 163 | char *argv[MAX_ARGC]; |
164 | int line; | |
2aaf83c2 | 165 | int i; |
166 | struct menu_line *command, *Find_command(); | |
07c56447 | 167 | int argc; |
168 | int quitflag; | |
169 | ||
07c56447 | 170 | /* Entry function gets called with old menu_screen still current */ |
e9db439c | 171 | if (m->m_entry != NULLFUNC) |
74e7b641 | 172 | if (m->m_entry(m, margc, margv) == DM_QUIT) |
173 | return DM_NORMAL; | |
07c56447 | 174 | |
2aaf83c2 | 175 | /* The following get run only in curses mode */ |
e9db439c | 176 | if (cur_ms != NULLMS) { |
2aaf83c2 | 177 | /* Get a menu_screen */ |
178 | old_cur_ms = cur_ms; | |
179 | cur_ms = my_ms = make_ms(m->m_length + 2); | |
07c56447 | 180 | |
2aaf83c2 | 181 | /* Now print the title and the menu */ |
e9db439c | 182 | (void) wclear(my_ms->ms_menu); |
183 | (void) wmove(my_ms->ms_title, 0, MAX(0, (COLS - | |
184 | strlen(m->m_title)) >> 1)); | |
2aaf83c2 | 185 | (void) wstandout(my_ms->ms_title); |
186 | (void) waddstr(my_ms->ms_title, m->m_title); | |
187 | (void) wstandend(my_ms->ms_title); | |
188 | ||
e9db439c | 189 | for (line = 0; line < m->m_length; line++) { |
2aaf83c2 | 190 | (void) wmove(my_ms->ms_menu, line, 0); |
e9db439c | 191 | (void) wprintw(my_ms->ms_menu, "%2d. (%-12s) %s.", line + 1, |
192 | m->m_lines[line].ml_command, | |
193 | m->m_lines[line].ml_doc); | |
2aaf83c2 | 194 | } |
195 | (void) wmove(my_ms->ms_menu, line++, 0); | |
196 | (void) waddstr(my_ms->ms_menu, " r. (return ) Return to previous menu."); | |
197 | (void) wmove(my_ms->ms_menu, line, 0); | |
198 | (void) waddstr(my_ms->ms_menu, " q. (quit ) Quit."); | |
07c56447 | 199 | |
07c56447 | 200 | } |
07c56447 | 201 | |
e9db439c | 202 | for (;;) { |
07c56447 | 203 | /* This will be set by a return val from func or submenu */ |
204 | quitflag = DM_NORMAL; | |
2aaf83c2 | 205 | /* This is here because we may be coming from another menu */ |
e9db439c | 206 | if (cur_ms != NULL) |
207 | touchwin(my_ms->ms_screen); | |
07c56447 | 208 | /* Get a command */ |
2aaf83c2 | 209 | Prompt_input("Command: ", buf, sizeof(buf)); |
210 | /* Parse it into the argument list */ | |
211 | /* If there's nothing there, try again */ | |
212 | /* Initialize argv */ | |
e9db439c | 213 | for (argc = 0; argc < MAX_ARGC; argc++) |
214 | argv[argc] = argvals[argc]; | |
215 | ||
216 | if ((argc = Parse_words(buf, argv, MAX_ARGLEN)) == 0) | |
217 | continue; | |
218 | if ((line = atoi(argv[0])) > 0 && line <= m->m_length) { | |
219 | command = &m->m_lines[line - 1]; | |
220 | } | |
221 | else if (!strcmp(argv[0], "r") | |
222 | || !strcmp(argv[0], "q") | |
223 | || !strcmp(argv[0], "return") | |
224 | || !strcmp(argv[0], "quit")) { | |
07c56447 | 225 | /* here if it's either return or quit */ |
e9db439c | 226 | if (cur_ms != NULLMS) { |
2aaf83c2 | 227 | cur_ms = old_cur_ms; |
228 | destroy_ms(my_ms); | |
229 | } | |
e9db439c | 230 | if (m->m_exit != NULLFUNC) |
231 | m->m_exit(m); | |
232 | return (*argv[0] == 'r' ? DM_NORMAL : DM_QUIT); | |
233 | /* finally, try to find it using Find_command */ | |
234 | } | |
235 | else if ((command = Find_command(argvals[0])) == | |
236 | (struct menu_line *) 0) { | |
237 | Put_message("Command not recognized"); | |
238 | continue; | |
239 | } | |
2aaf83c2 | 240 | /* If we got to here, command is a valid menu_line */ |
241 | /* Send the offical command name into the argv */ | |
242 | (void) strcpy(argvals[0], command->ml_command); | |
243 | /* Show that we're working on it */ | |
244 | Put_message(command->ml_doc); | |
245 | /* Print args that we've already got */ | |
e9db439c | 246 | for (i = 1; i < argc; i++) { |
247 | if (command->ml_args[i].ma_prompt == NULL) | |
248 | break; | |
2aaf83c2 | 249 | (void) sprintf(buf, "%s%s", command->ml_args[i].ma_prompt, |
250 | argv[i]); | |
251 | Put_message(buf); | |
252 | } | |
253 | /* Get remaining arguments, if any */ | |
e9db439c | 254 | for (; argc < command->ml_argc; argc++) { |
2aaf83c2 | 255 | Prompt_input(command->ml_args[argc].ma_prompt, |
256 | argvals[argc], sizeof(argvals[argc])); | |
257 | } | |
e9db439c | 258 | if (command->ml_function != NULLFUNC) { |
2aaf83c2 | 259 | /* If it's got a function, call it */ |
260 | quitflag = command->ml_function(argc, argv); | |
e9db439c | 261 | } |
262 | else if (command->ml_submenu != NULLMENU) { | |
2aaf83c2 | 263 | /* Else see if it is a submenu */ |
74e7b641 | 264 | quitflag = Do_menu(command->ml_submenu, argc, argv); |
e9db439c | 265 | } |
266 | else { | |
2aaf83c2 | 267 | /* If it's got neither, something is wrong */ |
268 | Put_message("*INTERNAL ERROR: NO FUNCTION OR MENU FOR LINE*"); | |
07c56447 | 269 | } |
e9db439c | 270 | if (quitflag == DM_QUIT) { |
271 | if (cur_ms != NULLMS) { | |
2aaf83c2 | 272 | cur_ms = old_cur_ms; |
273 | destroy_ms(my_ms); | |
274 | } | |
e9db439c | 275 | if (m->m_exit != NULLFUNC) |
276 | m->m_exit(m); | |
277 | return (DM_QUIT); | |
07c56447 | 278 | } |
279 | } | |
280 | } | |
281 | ||
282 | /* Prompt the user for input in the input window of cur_ms */ | |
283 | Prompt_input(prompt, buf, buflen) | |
e9db439c | 284 | char *prompt; |
285 | char *buf; | |
286 | int buflen; | |
07c56447 | 287 | { |
288 | int c; | |
289 | char *p; | |
290 | int y, x, oldx; | |
291 | ||
e9db439c | 292 | if (cur_ms != NULLMS) { |
293 | more_flg = 1; | |
294 | getyx(cur_ms->ms_input, y, x); | |
295 | (void) wmove(cur_ms->ms_input, y, 0); | |
296 | ||
297 | touchwin(cur_ms->ms_screen); | |
298 | refresh_ms(cur_ms); | |
2aaf83c2 | 299 | (void) waddstr(cur_ms->ms_input, prompt); |
300 | getyx(cur_ms->ms_input, y, x); | |
e9db439c | 301 | |
2aaf83c2 | 302 | oldx = x; |
e9db439c | 303 | for (p = buf; p - buf < buflen;) { |
2aaf83c2 | 304 | (void) wmove(cur_ms->ms_input, y, x); |
305 | (void) wclrtoeol(cur_ms->ms_input); | |
306 | refresh_ms(cur_ms); | |
307 | c = getchar(); | |
308 | switch (c) { | |
309 | case 'L' & 037: | |
e9db439c | 310 | (void) wmove(cur_ms->ms_input, 0, 0); |
311 | getyx(cur_ms->ms_input, y, x); | |
312 | ||
2aaf83c2 | 313 | touchwin(cur_ms->ms_screen); |
e9db439c | 314 | (void) wclear(cur_ms->ms_input); |
315 | (void) waddstr(cur_ms->ms_input, prompt); | |
316 | getyx(cur_ms->ms_input, y, x); | |
317 | refresh_ms(cur_ms); | |
318 | ||
2aaf83c2 | 319 | break; |
e9db439c | 320 | case '\n': |
321 | case '\r': | |
2aaf83c2 | 322 | (void) waddch(cur_ms->ms_input, '\n'); |
e9db439c | 323 | (void) waddch(cur_ms->ms_input, '\r'); |
324 | ||
325 | (void) wclrtoeol(cur_ms->ms_input); | |
326 | refresh_ms(cur_ms); | |
2aaf83c2 | 327 | *p = '\0'; |
328 | Start_paging(); | |
329 | return; | |
330 | case '\b': | |
331 | case '\177': | |
332 | if (p > buf) { | |
333 | p--; | |
334 | x--; | |
335 | } | |
336 | break; | |
e9db439c | 337 | case 'U' & 037: |
338 | case '\007': | |
339 | case '\033': | |
2aaf83c2 | 340 | x = oldx; |
e9db439c | 341 | p = buf; |
2aaf83c2 | 342 | break; |
343 | default: | |
344 | (void) waddch(cur_ms->ms_input, c); | |
345 | *p++ = c; | |
346 | x++; | |
347 | break; | |
07c56447 | 348 | } |
07c56447 | 349 | } |
e9db439c | 350 | } |
351 | else { | |
2aaf83c2 | 352 | printf("%s", prompt); |
353 | (void) gets(buf); | |
354 | Start_paging(); | |
355 | return; | |
07c56447 | 356 | } |
357 | } | |
358 | ||
2aaf83c2 | 359 | int lines_left; |
360 | ||
361 | /* Start paging */ | |
362 | /* This routine will cause the most recently put message to be the | |
363 | one at the top of the screen when a ---More--- prompt is displayed */ | |
364 | Start_paging() | |
365 | { | |
e9db439c | 366 | if (cur_ms != NULLMS) { |
2aaf83c2 | 367 | lines_left = LINES - cur_ms->ms_input_y - 1; |
e9db439c | 368 | } |
369 | else { | |
2aaf83c2 | 370 | lines_left = 23; |
371 | } | |
372 | } | |
373 | ||
374 | /* Turn off paging */ | |
375 | Stop_paging() | |
376 | { | |
377 | lines_left = -1; | |
378 | } | |
379 | ||
07c56447 | 380 | /* Print a message in the input window of cur_ms */ |
381 | Put_message(msg) | |
e9db439c | 382 | char *msg; |
07c56447 | 383 | { |
e9db439c | 384 | int y, x, i; |
385 | char *msg1, chr; | |
386 | ||
387 | if (!more_flg) | |
388 | return; | |
2aaf83c2 | 389 | |
e9db439c | 390 | if (lines_left >= 0) { |
391 | if (--lines_left == 0) { | |
2aaf83c2 | 392 | /* Give the user a more prompt */ |
e9db439c | 393 | if (cur_ms != NULLMS) { |
2aaf83c2 | 394 | (void) wstandout(cur_ms->ms_input); |
395 | (void) wprintw(cur_ms->ms_input, "---More---"); | |
396 | (void) wstandend(cur_ms->ms_input); | |
397 | refresh_ms(cur_ms); | |
e9db439c | 398 | chr = getchar();/* We do care what it is */ |
399 | if (chr == 'q' || chr == 'Q') { | |
400 | more_flg = 0; | |
401 | return; | |
402 | } | |
2aaf83c2 | 403 | getyx(cur_ms->ms_input, y, x); |
404 | (void) wmove(cur_ms->ms_input, y, 0); | |
405 | (void) wclrtoeol(cur_ms->ms_input); | |
e9db439c | 406 | } |
407 | else { | |
2aaf83c2 | 408 | printf("---More (hit return)---"); |
409 | (void) getchar(); | |
410 | } | |
411 | Start_paging(); /* Reset lines_left */ | |
412 | } | |
413 | } | |
e9db439c | 414 | |
415 | if (cur_ms != NULLMS) { | |
416 | msg1 = (char *) calloc(COLS, 1); | |
417 | (void) strcpy(msg1, msg); | |
418 | for (i = strlen(msg); i < COLS - 1; i++) | |
419 | msg1[i] = ' '; | |
420 | (void) wprintw(cur_ms->ms_input, "%s\n", msg1); | |
2aaf83c2 | 421 | /* refresh_ms(cur_ms); */ |
e9db439c | 422 | } |
423 | else { | |
2aaf83c2 | 424 | puts(msg); |
425 | } | |
07c56447 | 426 | } |
427 | ||
428 | /* Refresh a menu_screen onto the real screen */ | |
429 | refresh_ms(ms) | |
e9db439c | 430 | struct menu_screen *ms; |
07c56447 | 431 | { |
432 | int y, x; | |
433 | ||
434 | getyx(ms->ms_input, y, x); | |
2aaf83c2 | 435 | (void) wmove(ms->ms_screen, y + ms->ms_input_y, x); |
436 | (void) wrefresh(ms->ms_screen); | |
437 | } | |
438 | ||
439 | /* Parse buf into a list of words, which will be placed in strings specified by | |
440 | argv. Space for these strings must have already been allocated. | |
441 | Only the first n characters of each word will be copied */ | |
442 | Parse_words(buf, argv, n) | |
e9db439c | 443 | char *buf; |
444 | char *argv[]; | |
2aaf83c2 | 445 | int n; |
e9db439c | 446 | |
2aaf83c2 | 447 | { |
448 | char *start, *end; /* For sausage machine */ | |
449 | int argc; | |
450 | ||
451 | start = buf; | |
e9db439c | 452 | for (argc = 0; argc < MAX_ARGC; argc++) { |
453 | while (isspace(*start)) | |
454 | start++; /* Kill whitespace */ | |
455 | if (*start == '\0') | |
456 | break; /* Nothing left */ | |
2aaf83c2 | 457 | /* Now find the end of the word */ |
e9db439c | 458 | for (end = start; *end != '\0' && !isspace(*end); end++); |
459 | (void) strncpy(argv[argc], start, MIN(end - start, n)); /* Copy it */ | |
460 | argv[argc][MIN(end - start, n - 1)] = '\0'; /* Terminate */ | |
2aaf83c2 | 461 | start = end; |
462 | } | |
e9db439c | 463 | return (argc); |
2aaf83c2 | 464 | } |
465 | ||
466 | /* This is the internal form of Find_command, which recursively searches | |
467 | for a menu_line with command command in the specified menu */ | |
468 | /* It will search to a maximum depth of d */ | |
e9db439c | 469 | struct menu_line * |
470 | find_command_from(c, m, d) | |
471 | char *c; | |
472 | struct menu *m; | |
473 | int d; | |
2aaf83c2 | 474 | { |
475 | int line; | |
476 | struct menu_line *maybe; | |
477 | ||
e9db439c | 478 | if (d < 0) |
479 | return ((struct menu_line *) 0); /* Too deep! */ | |
480 | for (line = 0; line < m->m_length; line++) { | |
481 | if (!strcmp(c, m->m_lines[line].ml_command)) { | |
482 | return (&m->m_lines[line]); | |
483 | } | |
484 | else if (m->m_lines[line].ml_submenu != NULLMENU | |
485 | && (maybe = | |
486 | find_command_from(c, m->m_lines[line].ml_submenu, d - 1)) | |
487 | != (struct menu_line *) 0) { | |
488 | return (maybe); | |
489 | } | |
2aaf83c2 | 490 | } |
491 | /* If we got to here, nothing works */ | |
e9db439c | 492 | return ((struct menu_line *) 0); |
07c56447 | 493 | } |
2aaf83c2 | 494 | |
495 | /* Find_command searches down the current menu tree */ | |
496 | /* And returns a pointer to a menu_line with the specified command name */ | |
497 | /* It returns (struct menu_line *) 0 if none is found */ | |
e9db439c | 498 | struct menu_line * |
499 | Find_command(command) | |
500 | char *command; | |
2aaf83c2 | 501 | { |
e9db439c | 502 | if (top_menu == NULLMENU) { |
503 | return ((struct menu_line *) 0); | |
504 | } | |
505 | else { | |
506 | return (find_command_from(command, top_menu, MAX_MENU_DEPTH)); | |
2aaf83c2 | 507 | } |
508 | } | |
509 | ||
510 | /* | |
511 | * Local Variables: | |
512 | * mode: c | |
513 | * c-indent-level: 4 | |
514 | * c-continued-statement-offset: 4 | |
515 | * c-brace-offset: -4 | |
516 | * c-argdecl-indent: 4 | |
517 | * c-label-offset: -4 | |
518 | * End: | |
519 | */ |