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