Patch from Pavel Roskin to fixup toplevel help text
[oweals/busybox.git] / scripts / config / mconf.c
1 /*
2  * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>
3  * Released under the terms of the GNU GPL v2.0.
4  *
5  * Introduced single menu mode (show all sub-menus in one large tree).
6  * 2002-11-06 Petr Baudis <pasky@ucw.cz>
7  *
8  * Directly use liblxdialog library routines.
9  * 2002-11-14 Petr Baudis <pasky@ucw.cz>
10  */
11
12 #include <sys/ioctl.h>
13 #include <sys/wait.h>
14 #include <sys/termios.h>
15 #include <ctype.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <limits.h>
19 #include <signal.h>
20 #include <stdarg.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <termios.h>
24 #include <unistd.h>
25
26 #include "dialog.h"
27
28 #define LKC_DIRECT_LINK
29 #include "lkc.h"
30
31 static const char menu_instructions[] =
32         "Arrow keys navigate the menu.  "
33         "<Enter> selects submenus --->.  "
34         "Highlighted letters are hotkeys.  "
35         "Pressing <Y> selectes a feature, while <N> will exclude a feature.  "
36         "Press <Esc><Esc> to exit, <?> for Help.  "
37         "Legend: [*] feature is selected  [ ] feature is excluded",
38 radiolist_instructions[] =
39         "Use the arrow keys to navigate this window or "
40         "press the hotkey of the item you wish to select "
41         "followed by the <SPACE BAR>. "
42         "Press <?> for additional information about this option.",
43 inputbox_instructions_int[] =
44         "Please enter a decimal value. "
45         "Fractions will not be accepted.  "
46         "Use the <TAB> key to move from the input field to the buttons below it.",
47 inputbox_instructions_hex[] =
48         "Please enter a hexadecimal value. "
49         "Use the <TAB> key to move from the input field to the buttons below it.",
50 inputbox_instructions_string[] =
51         "Please enter a string value. "
52         "Use the <TAB> key to move from the input field to the buttons below it.",
53 setmod_text[] =
54         "This feature depends on another which has been configured as a module.\n"
55         "As a result, this feature will be built as a module.",
56 nohelp_text[] =
57         "There is no help available for this option.\n",
58 load_config_text[] =
59         "Enter the name of the configuration file you wish to load.  "
60         "Accept the name shown to restore the configuration you "
61         "last retrieved.  Leave blank to abort.",
62 load_config_help[] =
63         "\n"
64         "For various reasons, one may wish to keep several different BusyBox\n"
65         "configurations available on a single machine.\n"
66         "\n"
67         "If you have saved a previous configuration in a file other than the\n"
68         "BusyBox default, entering the name of the file here will allow you\n"
69         "to modify that configuration.\n"
70         "\n"
71         "If you are uncertain, then you have probably never used alternate\n"
72         "configuration files.  You should therefor leave this blank to abort.\n",
73 save_config_text[] =
74         "Enter a filename to which this configuration should be saved "
75         "as an alternate.  Leave blank to abort.",
76 save_config_help[] =
77         "\n"
78         "For various reasons, one may wish to keep different BusyBox\n"
79         "configurations available on a single machine.\n"
80         "\n"
81         "Entering a file name here will allow you to later retrieve, modify\n"
82         "and use the current configuration as an alternate to whatever\n"
83         "configuration options you have selected at that time.\n"
84         "\n"
85         "If you are uncertain what all this means then you should probably\n"
86         "leave this blank.\n",
87 top_menu_help[] =
88         "\n"
89         "Use the Up/Down arrow keys (cursor keys) to highlight the item\n"
90         "you wish to change or submenu wish to select and press <Enter>.\n"
91         "Submenus are designated by \"--->\".\n"
92         "\n"
93         "Shortcut: Press the option's highlighted letter (hotkey).\n"
94         "\n"
95         "You may also use the <PAGE UP> and <PAGE DOWN> keys to scroll\n"
96         "unseen options into view.\n"
97 ;
98
99 static char filename[PATH_MAX+1] = ".config";
100 static int indent = 0;
101 static struct termios ios_org;
102 static int rows, cols;
103 static struct menu *current_menu;
104 static int child_count;
105 static int single_menu_mode;
106
107 static struct dialog_list_item *items[16384]; /* FIXME: This ought to be dynamic. */
108 static int item_no;
109
110 static void conf(struct menu *menu);
111 static void conf_choice(struct menu *menu);
112 static void conf_string(struct menu *menu);
113 static void conf_load(void);
114 static void conf_save(void);
115 static void show_textbox(const char *title, const char *text, int r, int c);
116 static void show_helptext(const char *title, const char *text);
117 static void show_help(struct menu *menu);
118 static void show_readme(void);
119
120 static void init_wsize(void)
121 {
122         struct winsize ws;
123
124         if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
125                 rows = 24;
126                 cols = 80;
127         } else {
128                 rows = ws.ws_row;
129                 cols = ws.ws_col;
130         }
131
132         if (rows < 19 || cols < 80) {
133                 fprintf(stderr, "Your display is too small to run Menuconfig!\n");
134                 fprintf(stderr, "It must be at least 19 lines by 80 columns.\n");
135                 exit(1);
136         }
137
138         rows -= 4;
139         cols -= 5;
140 }
141
142 static void cinit(void)
143 {
144         item_no = 0;
145 }
146
147 static void cmake(void)
148 {
149         items[item_no] = malloc(sizeof(struct dialog_list_item));
150         memset(items[item_no], 0, sizeof(struct dialog_list_item));
151         items[item_no]->tag = malloc(32); items[item_no]->tag[0] = 0;
152         items[item_no]->name = malloc(512); items[item_no]->name[0] = 0;
153         items[item_no]->namelen = 0;
154         item_no++;
155 }
156   
157 static int cprint_name(const char *fmt, ...)
158 {
159         va_list ap;
160         int res;
161
162         if (!item_no)
163                 cmake();
164         va_start(ap, fmt);
165         res = vsnprintf(items[item_no - 1]->name + items[item_no - 1]->namelen,
166                         512 - items[item_no - 1]->namelen, fmt, ap);
167         if (res > 0)
168                 items[item_no - 1]->namelen += res;
169         va_end(ap);
170
171         return res;
172 }
173   
174 static int cprint_tag(const char *fmt, ...)
175 {
176         va_list ap;
177         int res;
178
179         if (!item_no)
180                 cmake();
181         va_start(ap, fmt);
182         res = vsnprintf(items[item_no - 1]->tag, 32, fmt, ap);
183         va_end(ap);
184
185         return res;
186 }
187   
188 static void cdone(void)
189 {
190         int i;
191
192         for (i = 0; i < item_no; i++) {
193                 free(items[i]->tag);
194                 free(items[i]->name);
195                 free(items[i]);
196         }
197
198         item_no = 0;
199 }
200
201 static void build_conf(struct menu *menu)
202 {
203         struct symbol *sym;
204         struct property *prop;
205         struct menu *child;
206         int type, tmp, doint = 2;
207         tristate val;
208         char ch;
209
210         if (!menu_is_visible(menu))
211                 return;
212
213         sym = menu->sym;
214         prop = menu->prompt;
215         if (!sym) {
216                 if (prop && menu != current_menu) {
217                         const char *prompt = menu_get_prompt(menu);
218                         switch (prop->type) {
219                         case P_MENU:
220                                 child_count++;
221                                 cmake();
222                                 cprint_tag("m%p", menu);
223
224                                 if (single_menu_mode) {
225                                         cprint_name("%s%*c%s",
226                                                 menu->data ? "-->" : "++>",
227                                                 indent + 1, ' ', prompt);
228                                 } else {
229                                         if (menu->parent != &rootmenu)
230                                                 cprint_name("   %*c", indent + 1, ' ');
231                                         cprint_name("%s  --->", prompt);
232                                 }
233
234                                 if (single_menu_mode && menu->data)
235                                         goto conf_childs;
236                                 return;
237                         default:
238                                 if (prompt) {
239                                         child_count++;
240                                         cmake();
241                                         cprint_tag(":%p", menu);
242                                         cprint_name("---%*c%s", indent + 1, ' ', prompt);
243                                 }
244                         }
245                 } else
246                         doint = 0;
247                 goto conf_childs;
248         }
249
250         cmake();
251         type = sym_get_type(sym);
252         if (sym_is_choice(sym)) {
253                 struct symbol *def_sym = sym_get_choice_value(sym);
254                 struct menu *def_menu = NULL;
255
256                 child_count++;
257                 for (child = menu->list; child; child = child->next) {
258                         if (menu_is_visible(child) && child->sym == def_sym)
259                                 def_menu = child;
260                 }
261
262                 val = sym_get_tristate_value(sym);
263                 if (sym_is_changable(sym)) {
264                         cprint_tag("t%p", menu);
265                         switch (type) {
266                         case S_BOOLEAN:
267                                 cprint_name("[%c]", val == no ? ' ' : '*');
268                                 break;
269                         case S_TRISTATE:
270                                 switch (val) {
271                                 case yes: ch = '*'; break;
272                                 case mod: ch = 'M'; break;
273                                 default:  ch = ' '; break;
274                                 }
275                                 cprint_name("<%c>", ch);
276                                 break;
277                         }
278                 } else {
279                         cprint_tag("%c%p", def_menu ? 't' : ':', menu);
280                         cprint_name("   ");
281                 }
282
283                 cprint_name("%*c%s", indent + 1, ' ', menu_get_prompt(menu));
284                 if (val == yes) {
285                         if (def_menu) {
286                                 cprint_name(" (%s)", menu_get_prompt(def_menu));
287                                 cprint_name("  --->");
288                                 if (def_menu->list) {
289                                         indent += 2;
290                                         build_conf(def_menu);
291                                         indent -= 2;
292                                 }
293                         }
294                         return;
295                 }
296         } else {
297                 child_count++;
298                 val = sym_get_tristate_value(sym);
299                 if (sym_is_choice_value(sym) && val == yes) {
300                         cprint_tag(":%p", menu);
301                         cprint_name("   ");
302                 } else {
303                         switch (type) {
304                         case S_BOOLEAN:
305                                 cprint_tag("t%p", menu);
306                                 cprint_name("[%c]", val == no ? ' ' : '*');
307                                 break;
308                         case S_TRISTATE:
309                                 cprint_tag("t%p", menu);
310                                 switch (val) {
311                                 case yes: ch = '*'; break;
312                                 case mod: ch = 'M'; break;
313                                 default:  ch = ' '; break;
314                                 }
315                                 cprint_name("<%c>", ch);
316                                 break;
317                         default:
318                                 cprint_tag("s%p", menu);
319                                 tmp = cprint_name("(%s)", sym_get_string_value(sym));
320                                 tmp = indent - tmp + 4;
321                                 if (tmp < 0)
322                                         tmp = 0;
323                                 cprint_name("%*c%s%s", tmp, ' ', menu_get_prompt(menu),
324                                         sym_has_value(sym) ? "" : " (NEW)");
325                                 goto conf_childs;
326                         }
327                 }
328                 cprint_name("%*c%s%s", indent + 1, ' ', menu_get_prompt(menu),
329                         sym_has_value(sym) ? "" : " (NEW)");
330         }
331
332 conf_childs:
333         indent += doint;
334         for (child = menu->list; child; child = child->next)
335                 build_conf(child);
336         indent -= doint;
337 }
338
339 static void conf(struct menu *menu)
340 {
341         struct dialog_list_item *active_item = NULL;
342         struct menu *submenu;
343         const char *prompt = menu_get_prompt(menu);
344         struct symbol *sym;
345         char active_entry[40];
346         int stat, type;
347
348         unlink("lxdialog.scrltmp");
349         active_entry[0] = 0;
350         while (1) {
351                 indent = 0;
352                 child_count = 0;
353                 current_menu = menu;
354                 cdone(); cinit();
355                 build_conf(menu);
356                 if (!child_count)
357                         break;
358                 if (menu == &rootmenu) {
359                         cmake(); cprint_tag(":"); cprint_name("--- ");
360                         cmake(); cprint_tag("L"); cprint_name("Load an Alternate Configuration File");
361                         cmake(); cprint_tag("S"); cprint_name("Save Configuration to an Alternate File");
362                 }
363                 dialog_clear();
364                 stat = dialog_menu(prompt ? prompt : "Main Menu",
365                                 menu_instructions, rows, cols, rows - 10,
366                                 active_entry, item_no, items);
367                 if (stat < 0)
368                         return;
369
370                 if (stat == 1 || stat == 255)
371                         break;
372
373                 active_item = first_sel_item(item_no, items);
374                 if (!active_item)
375                         continue;
376                 active_item->selected = 0;
377                 strncpy(active_entry, active_item->tag, sizeof(active_entry));
378                 active_entry[sizeof(active_entry)-1] = 0;
379                 type = active_entry[0];
380                 if (!type)
381                         continue;
382
383                 sym = NULL;
384                 submenu = NULL;
385                 if (sscanf(active_entry + 1, "%p", &submenu) == 1)
386                         sym = submenu->sym;
387
388                 switch (stat) {
389                 case 0:
390                         switch (type) {
391                         case 'm':
392                                 if (single_menu_mode)
393                                         submenu->data = (submenu->data)? NULL : (void *)1;
394                                 else
395                                         conf(submenu);
396                                 break;
397                         case 't':
398                                 if (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes)
399                                         conf_choice(submenu);
400                                 break;
401                         case 's':
402                                 conf_string(submenu);
403                                 break;
404                         case 'L':
405                                 conf_load();
406                                 break;
407                         case 'S':
408                                 conf_save();
409                                 break;
410                         }
411                         break;
412                 case 2:
413                         if (sym)
414                                 show_help(submenu);
415                         else
416                                 show_readme();
417                         break;
418                 case 3:
419                         if (type == 't') {
420                                 if (sym_set_tristate_value(sym, yes))
421                                         break;
422                                 if (sym_set_tristate_value(sym, mod))
423                                         show_textbox(NULL, setmod_text, 6, 74);
424                         }
425                         break;
426                 case 4:
427                         if (type == 't')
428                                 sym_set_tristate_value(sym, no);
429                         break;
430                 case 5:
431                         if (type == 't')
432                                 sym_set_tristate_value(sym, mod);
433                         break;
434                 case 6:
435                         if (type == 't')
436                                 sym_toggle_tristate_value(sym);
437                         else if (type == 'm')
438                                 conf(submenu);
439                         break;
440                 }
441         }
442 }
443
444 static void show_textbox(const char *title, const char *text, int r, int c)
445 {
446         int fd;
447
448         fd = creat(".help.tmp", 0777);
449         write(fd, text, strlen(text));
450         close(fd);
451         while (dialog_textbox(title, ".help.tmp", r, c) < 0)
452                 ;
453         unlink(".help.tmp");
454 }
455
456 static void show_helptext(const char *title, const char *text)
457 {
458         show_textbox(title, text, rows, cols);
459 }
460
461 static void show_help(struct menu *menu)
462 {
463         const char *help;
464         char *helptext;
465         struct symbol *sym = menu->sym;
466
467         help = sym->help;
468         if (!help)
469                 help = nohelp_text;
470         if (sym->name) {
471                 helptext = malloc(strlen(sym->name) + strlen(help) + 16);
472                 sprintf(helptext, "%s:\n\n%s", sym->name, help);
473                 show_helptext(menu_get_prompt(menu), helptext);
474                 free(helptext);
475         } else
476                 show_helptext(menu_get_prompt(menu), help);
477 }
478
479 static void show_readme(void)
480 {
481         show_helptext("Help", top_menu_help);
482 }
483
484 static void conf_choice(struct menu *menu)
485 {
486         const char *prompt = menu_get_prompt(menu);
487         struct menu *child;
488         struct symbol *active;
489
490         while (1) {
491                 current_menu = menu;
492                 active = sym_get_choice_value(menu->sym);
493                 cdone(); cinit();
494                 for (child = menu->list; child; child = child->next) {
495                         if (!menu_is_visible(child))
496                                 continue;
497                         cmake();
498                         cprint_tag("%p", child);
499                         cprint_name("%s", menu_get_prompt(child));
500                         items[item_no - 1]->selected = (child->sym == active);
501                 }
502
503                 switch (dialog_checklist(prompt ? prompt : "Main Menu",
504                                         radiolist_instructions, 15, 70, 6,
505                                         item_no, items, FLAG_RADIO)) {
506                 case 0:
507                         if (sscanf(first_sel_item(item_no, items)->tag, "%p", &menu) != 1)
508                                 break;
509                         sym_set_tristate_value(menu->sym, yes);
510                         return;
511                 case 1:
512                         show_help(menu);
513                         break;
514                 case 255:
515                         return;
516                 }
517         }
518 }
519
520 static void conf_string(struct menu *menu)
521 {
522         const char *prompt = menu_get_prompt(menu);
523
524         while (1) {
525                 char *heading;
526
527                 switch (sym_get_type(menu->sym)) {
528                 case S_INT:
529                         heading = (char *) inputbox_instructions_int;
530                         break;
531                 case S_HEX:
532                         heading = (char *) inputbox_instructions_hex;
533                         break;
534                 case S_STRING:
535                         heading = (char *) inputbox_instructions_string;
536                         break;
537                 default:
538                         heading = "Internal mconf error!";
539                         /* panic? */;
540                 }
541
542                 switch (dialog_inputbox(prompt ? prompt : "Main Menu",
543                                         heading, 10, 75,
544                                         sym_get_string_value(menu->sym))) {
545                 case 0:
546                         if (sym_set_string_value(menu->sym, dialog_input_result))
547                                 return;
548                         show_textbox(NULL, "You have made an invalid entry.", 5, 43);
549                         break;
550                 case 1:
551                         show_help(menu);
552                         break;
553                 case 255:
554                         return;
555                 }
556         }
557 }
558
559 static void conf_load(void)
560 {
561         while (1) {
562                 switch (dialog_inputbox(NULL, load_config_text, 11, 55,
563                                         filename)) {
564                 case 0:
565                         if (!dialog_input_result[0])
566                                 return;
567                         if (!conf_read(dialog_input_result))
568                                 return;
569                         show_textbox(NULL, "File does not exist!", 5, 38);
570                         break;
571                 case 1:
572                         show_helptext("Load Alternate Configuration", load_config_help);
573                         break;
574                 case 255:
575                         return;
576                 }
577         }
578 }
579
580 static void conf_save(void)
581 {
582         while (1) {
583                 switch (dialog_inputbox(NULL, save_config_text, 11, 55,
584                                         filename)) {
585                 case 0:
586                         if (!dialog_input_result[0])
587                                 return;
588                         if (!conf_write(dialog_input_result))
589                                 return;
590                         show_textbox(NULL, "Can't create file!  Probably a nonexistent directory.", 5, 60);
591                         break;
592                 case 1:
593                         show_helptext("Save Alternate Configuration", save_config_help);
594                         break;
595                 case 255:
596                         return;
597                 }
598         }
599 }
600
601 static void conf_cleanup(void)
602 {
603         tcsetattr(1, TCSAFLUSH, &ios_org);
604         unlink(".help.tmp");
605         unlink("lxdialog.scrltmp");
606 }
607
608 static void winch_handler(int sig)
609 {
610         struct winsize ws;
611
612         if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
613                 rows = 24;
614                 cols = 80;
615         } else {
616                 rows = ws.ws_row;
617                 cols = ws.ws_col;
618         }
619
620         if (rows < 19 || cols < 80) {
621                 end_dialog();
622                 fprintf(stderr, "Your display is too small to run Menuconfig!\n");
623                 fprintf(stderr, "It must be at least 19 lines by 80 columns.\n");
624                 exit(1);
625         }
626
627         rows -= 4;
628         cols -= 5;
629
630 }
631
632 int main(int ac, char **av)
633 {
634         int stat;
635         char *mode;
636         struct symbol *sym;
637
638         conf_parse(av[1]);
639         conf_read(NULL);
640
641         backtitle = malloc(128);
642         sym = sym_lookup("VERSION", 0);
643         sym_calc_value(sym);
644         snprintf(backtitle, 128, "BusyBox v%s Configuration",
645                 sym_get_string_value(sym));
646
647         mode = getenv("MENUCONFIG_MODE");
648         if (mode) {
649                 if (!strcasecmp(mode, "single_menu"))
650                         single_menu_mode = 1;
651         }
652   
653         tcgetattr(1, &ios_org);
654         atexit(conf_cleanup);
655         init_wsize();
656         init_dialog();
657         signal(SIGWINCH, winch_handler); 
658         conf(&rootmenu);
659         end_dialog();
660
661         /* Restart dialog to act more like when lxdialog was still separate */
662         init_dialog();
663         do {
664                 stat = dialog_yesno(NULL, 
665                                 "Do you wish to save your new BusyBox configuration?", 5, 60);
666         } while (stat < 0);
667         end_dialog();
668
669         if (stat == 0) {
670                 conf_write(NULL);
671                 printf("\n\n"
672                         "*** End of BusyBox configuration.\n"
673                         "*** Check the top-level Makefile for additional configuration options.\n\n");
674         } else
675                 printf("\n\nYour BusyBox configuration changes were NOT saved.\n\n");
676
677         return 0;
678 }