[RFC v5 1/1] i-search navigation for mconf

From: Dirk Gouders
Date: Mon Jun 11 2018 - 08:59:36 EST


This patch implements an i-search navigation concept for mconf based
on an idea of Sam Ravnborg:

* mconf now distinguishes if the focus is on the menu items or the
buttons below it.

* At startup focus is on the menu items and printable characters
(except the keys below) entered are used to form a string that is
searched for.

* Hotkeys were sacrificed for the i-search navigation concept.
However, focus on the buttons offers most of mconf's known behavior,
except the TAB key and a change in the highlighting of menu items.

Keys with special meaning:

* BACKSPACE is used to remove the last character of the search string

* DEL clears the search string

* ENTER can be used to enter a submenu; it also clears the search string

* Horizontal arrow keys still cycle between the buttons but also
switch focus to the buttons.

* The TAB key is now exclusively used to toggle the focus.

* Vertical arrow keys work everywhere.

* When the focus is on the buttons, all keys work the
same as before this change -- except TAB and hotkeys.

* <\> (backslash) can be used to search for other occurences of an
already entered string.

* To use y|n|m|<space> on an item, focus has to be on the buttons.

An example to navigate into the USB support menu under Device Drivers:

1) de # Navigates to the item "Device Drivers"
2) ENTER # Enter the submenu
3) USB # Navigate to the item "USB support"
4) ENTER # Enter the submenu

Suggested-by: Sam Ravnborg <sam@xxxxxxxxxxxx>
Signed-off-by: Dirk Gouders <dirk@xxxxxxxxxxx>
---
scripts/kconfig/lxdialog/dialog.h | 3 +
scripts/kconfig/lxdialog/menubox.c | 489 +++++++++++++++++++++++++++----------
scripts/kconfig/lxdialog/util.c | 106 +++++++-
scripts/kconfig/mconf.c | 68 ++++--
4 files changed, 506 insertions(+), 160 deletions(-)

diff --git a/scripts/kconfig/lxdialog/dialog.h b/scripts/kconfig/lxdialog/dialog.h
index 0b00be5abaa6..28a3b42fdf71 100644
--- a/scripts/kconfig/lxdialog/dialog.h
+++ b/scripts/kconfig/lxdialog/dialog.h
@@ -215,6 +215,8 @@ void end_dialog(int x, int y);
void attr_clear(WINDOW * win, int height, int width, chtype attr);
void dialog_clear(void);
void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x);
+void print_autowrap_fill(WINDOW * win, const char *prompt, int width,
+ int height, int y, int x);
void print_button(WINDOW * win, const char *label, int y, int x, int selected);
void print_title(WINDOW *dialog, const char *title, int width);
void draw_box(WINDOW * win, int y, int x, int height, int width, chtype box,
@@ -238,6 +240,7 @@ int dialog_checklist(const char *title, const char *prompt, int height,
int width, int list_height);
int dialog_inputbox(const char *title, const char *prompt, int height,
int width, const char *init);
+int do_isearch(char *str, int choice, int scroll);

/*
* This is the base for fictitious keys, which activate
diff --git a/scripts/kconfig/lxdialog/menubox.c b/scripts/kconfig/lxdialog/menubox.c
index d70cab36137e..7b22ec813d3c 100644
--- a/scripts/kconfig/lxdialog/menubox.c
+++ b/scripts/kconfig/lxdialog/menubox.c
@@ -56,22 +56,38 @@
* fscanf would read in 'scroll', and eventually that value would get used.
*/

+#include <string.h>
#include "dialog.h"

+#define ISEARCH_LEN 32
+static char isearch_str[ISEARCH_LEN] = "";
+
static int menu_width, item_x;

+static int focus_on_buttons;
+
+static const char isearch_instructions[] =
+ "I-search: Arrow keys navigate the menu. "
+ "<Enter> selects submenus and/or clears i-search string. "
+ "Type any character to search for menu items, "
+ "press <\\> to find further matches, <Esc><Esc> to exit. "
+ "Legend: [*] built-in [ ] excluded <M> module < > module capable";
/*
* Print menu item
*/
static void do_print_item(WINDOW * win, const char *item, int line_y,
- int selected, int hotkey)
+ int selected)
{
- int j;
+ int i;
+ int isearch_match_pos;
+ char *isearch_match;
char *menu_item = malloc(menu_width + 1);

strncpy(menu_item, item, menu_width - item_x);
menu_item[menu_width - item_x] = '\0';
- j = first_alpha(menu_item, "YyNnMmHh");
+
+ isearch_match = strcasestr(menu_item, isearch_str);
+ isearch_match_pos = isearch_match - menu_item;

/* Clear 'residue' of last item */
wattrset(win, dlg.menubox.atr);
@@ -85,14 +101,24 @@ static void do_print_item(WINDOW * win, const char *item, int line_y,
#else
wclrtoeol(win);
#endif
- wattrset(win, selected ? dlg.item_selected.atr : dlg.item.atr);
+ if (focus_on_buttons)
+ wattrset(win, selected ? A_UNDERLINE : dlg.item.atr);
+ else
+ wattrset(win, selected ? dlg.item_selected.atr : dlg.item.atr);
mvwaddstr(win, line_y, item_x, menu_item);
- if (hotkey) {
- wattrset(win, selected ? dlg.tag_key_selected.atr
- : dlg.tag_key.atr);
- mvwaddch(win, line_y, item_x + j, menu_item[j]);
- }
+
if (selected) {
+ /*
+ * Highlight i-search matching part of selected menu item
+ */
+ if (isearch_match) {
+ for (i = 0; i < strlen(isearch_str); i++) {
+ wattrset(win, dlg.tag_key_selected.atr);
+ mvwaddch(win, line_y, item_x + isearch_match_pos + i,
+ menu_item[isearch_match_pos + i]);
+ }
+ }
+
wmove(win, line_y, item_x + 1);
}
free(menu_item);
@@ -102,9 +128,37 @@ static void do_print_item(WINDOW * win, const char *item, int line_y,
#define print_item(index, choice, selected) \
do { \
item_set(index); \
- do_print_item(menu, item_str(), choice, selected, !item_is_tag(':')); \
+ do_print_item(menu, item_str(), choice, selected); \
} while (0)

+
+/*
+* Print the isearch indicator.
+*/
+static void print_isearch(WINDOW * win, int y, int x, int height, bool isearch)
+{
+ unsigned char i = 0;
+ int text_size = ISEARCH_LEN - 1;
+ wmove(win, y, x);
+
+ y = y + height + 1;
+ wmove(win, y, x);
+
+ if (isearch) {
+ wattrset(win, dlg.button_key_inactive.atr);
+ waddstr(win, "isearch: ");
+ waddstr(win, isearch_str);
+ i = strlen(isearch_str);
+ } else {
+ text_size += 9; /* also overwrite "isearch: " */
+ }
+
+ wattrset(win, dlg.menubox_border.atr);
+
+ for ( ; i < text_size; i++ )
+ waddch(win, ACS_HLINE);
+}
+
/*
* Print the scroll indicators.
*/
@@ -156,12 +210,22 @@ static void print_buttons(WINDOW * win, int height, int width, int selected)
{
int x = width / 2 - 28;
int y = height - 2;
+ int highlight;
+
+ /*
+ * Don't highlight the selected button if the buttons don't have
+ * the focus.
+ */
+ if (!focus_on_buttons)
+ highlight = -1;
+ else
+ highlight = selected;

- print_button(win, "Select", y, x, selected == 0);
- print_button(win, " Exit ", y, x + 12, selected == 1);
- print_button(win, " Help ", y, x + 24, selected == 2);
- print_button(win, " Save ", y, x + 36, selected == 3);
- print_button(win, " Load ", y, x + 48, selected == 4);
+ print_button(win, "Select", y, x, highlight == 0);
+ print_button(win, " Exit ", y, x + 12, highlight == 1);
+ print_button(win, " Help ", y, x + 24, highlight == 2);
+ print_button(win, " Save ", y, x + 36, highlight == 3);
+ print_button(win, " Load ", y, x + 48, highlight == 4);

wmove(win, y, x + 1 + 12 * selected);
wrefresh(win);
@@ -178,13 +242,40 @@ static void do_scroll(WINDOW *win, int *scroll, int n)
wrefresh(win);
}

+/*
+ * Incremental search for text in dialog menu entries.
+ * The search operates as a ring search, continuing at the top after
+ * the last entry has been visited.
+ *
+ * Returned is -1 if no match was found, else the absolute index of
+ * the matching item.
+ */
+int do_isearch(char *str, int choice, int scroll)
+{
+ int found = 0;
+ int i;
+
+ for (i = 0; i < item_count(); i++) {
+ item_set((choice + scroll + i)%item_count());
+ if (strcasestr(item_str(), str)) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (found)
+ return (choice + scroll + i)%item_count();
+ return -1;
+}
+
/*
* Display a menu for choosing among a number of options
*/
int dialog_menu(const char *title, const char *prompt,
const void *selected, int *s_scroll)
{
- int i, j, x, y, box_x, box_y;
+ int i, x, y, box_x, box_y;
+ int key_match; /* remember match in switch statement */
int height, width, menu_height;
int key = 0, button = 0, scroll = 0, choice = 0;
int first_item = 0, max_choice;
@@ -224,7 +315,9 @@ int dialog_menu(const char *title, const char *prompt,
print_title(dialog, title, width);

wattrset(dialog, dlg.dialog.atr);
- print_autowrap(dialog, prompt, width - 2, 1, 3);
+ print_autowrap_fill(dialog,
+ focus_on_buttons ? prompt : isearch_instructions,
+ width - 2, 4, 1, 3);

menu_width = width - 6;
box_y = height - menu_height - 5;
@@ -275,6 +368,7 @@ int dialog_menu(const char *title, const char *prompt,
print_arrows(dialog, item_count(), scroll,
box_y, box_x + item_x + 1, menu_height);

+ print_isearch(dialog, box_y, box_x + item_x + 5, menu_height, !focus_on_buttons);
print_buttons(dialog, height, width, 0);
wmove(menu, choice, item_x + 1);
wrefresh(menu);
@@ -284,44 +378,129 @@ int dialog_menu(const char *title, const char *prompt,

if (key < 256 && isalpha(key))
key = tolower(key);
-
- if (strchr("ynmh", key))
- i = max_choice;
- else {
- for (i = choice + 1; i < max_choice; i++) {
- item_set(scroll + i);
- j = first_alpha(item_str(), "YyNnMmHh");
- if (key == tolower(item_str()[j]))
- break;
+ /*
+ * These keys are handled for the focus on both,
+ * menu and buttons.
+ */
+ key_match = 0;
+ switch (key) {
+ case KEY_DC: /* delete key clears i-search string */
+ key_match = 1;
+ isearch_str[0] = '\0';
+ break;
+ case TAB:
+ key_match = 1;
+ focus_on_buttons = 1 - focus_on_buttons;
+ wattrset(dialog, dlg.dialog.atr);
+ print_autowrap_fill(dialog,
+ focus_on_buttons ? prompt : isearch_instructions,
+ width - 2, 4, 1, 3);
+ break;
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ key_match = 1;
+ if (!focus_on_buttons) {
+ focus_on_buttons = 1;
+ wattrset(dialog, dlg.dialog.atr);
+ print_autowrap_fill(dialog, prompt, width - 2, 4, 1, 3);
+ wnoutrefresh(dialog);
}
- if (i == max_choice)
- for (i = 0; i < max_choice; i++) {
- item_set(scroll + i);
- j = first_alpha(item_str(), "YyNnMmHh");
- if (key == tolower(item_str()[j]))
- break;
- }
+ button = ((key == KEY_LEFT ? --button : ++button) < 0)
+ ? 4 : (button > 4 ? 0 : button);
+ break;
+ case KEY_ESC:
+ key = on_key_esc(menu);
+ continue;
+ case KEY_RESIZE:
+ on_key_resize();
+ delwin(menu);
+ delwin(dialog);
+ goto do_resize;
+ }
+ if (key_match) {
+ print_isearch(dialog, box_y, box_x + item_x + 5, menu_height, !focus_on_buttons);
+ print_item(scroll + choice, choice, TRUE);
+ print_buttons(dialog, height, width, button);
+ wrefresh(menu);
+ continue; /* wait for another key press */
}

- if (item_count() != 0 &&
- (i < max_choice ||
- key == KEY_UP || key == KEY_DOWN ||
- key == '-' || key == '+' ||
- key == KEY_PPAGE || key == KEY_NPAGE)) {
- /* Remove highligt of current item */
+ key_match = 0;
+ switch (key) {
+ case KEY_UP:
+ key_match = 1;
+ /* Remove highlight of current item */
+ print_item(scroll + choice, choice, FALSE);
+ if (choice < 2 && scroll) {
+ /* Scroll menu down */
+ do_scroll(menu, &scroll, -1);
+ print_item(scroll, 0, FALSE);
+ } else
+ choice = MAX(choice - 1, 0);
+ break;
+ case KEY_DOWN:
+ key_match = 1;
+ /* Remove highlight of current item */
print_item(scroll + choice, choice, FALSE);

- if (key == KEY_UP || key == '-') {
- if (choice < 2 && scroll) {
- /* Scroll menu down */
- do_scroll(menu, &scroll, -1);
+ if ((choice > max_choice - 3) &&
+ (scroll + max_choice < item_count())) {
+ /* Scroll menu up */
+ do_scroll(menu, &scroll, 1);
+ print_item(scroll+max_choice - 1,
+ max_choice - 1, FALSE);
+ } else
+ choice = MIN(choice + 1, max_choice - 1);
+ break;
+ case KEY_PPAGE:
+ key_match = 1;
+ /* Remove highlight of current item */
+ print_item(scroll + choice, choice, FALSE);

+ scrollok(menu, TRUE);
+ for (i = 0; (i < max_choice); i++) {
+ if (scroll > 0) {
+ do_scroll(menu, &scroll, -1);
print_item(scroll, 0, FALSE);
- } else
- choice = MAX(choice - 1, 0);
+ } else {
+ if (choice > 0)
+ choice--;
+ }
+ }
+ break;
+ case KEY_NPAGE:
+ key_match = 1;
+ /* Remove highlight of current item */
+ print_item(scroll + choice, choice, FALSE);
+ for (i = 0; (i < max_choice); i++) {
+ if (scroll + max_choice < item_count()) {
+ do_scroll(menu, &scroll, 1);
+ print_item(scroll+max_choice-1,
+ max_choice - 1, FALSE);
+ } else {
+ if (choice + 1 < max_choice)
+ choice++;
+ }
+ }
+ }
+
+ if (key_match) {
+ print_item(scroll + choice, choice, TRUE);
+ print_arrows(dialog, item_count(), scroll,
+ box_y, box_x + item_x + 1, menu_height);
+ wnoutrefresh(dialog);
+ wrefresh(menu);
+ continue; /* wait for another key press */
+ }

- } else if (key == KEY_DOWN || key == '+') {
- print_item(scroll+choice, choice, FALSE);
+ if (focus_on_buttons) {
+ /*
+ * Focus is on buttons, handle appropriate keys.
+ */
+ switch (key) {
+ case '+':
+ /* Remove highlight of current item */
+ print_item(scroll + choice, choice, FALSE);

if ((choice > max_choice - 3) &&
(scroll + max_choice < item_count())) {
@@ -332,106 +511,150 @@ int dialog_menu(const char *title, const char *prompt,
max_choice - 1, FALSE);
} else
choice = MIN(choice + 1, max_choice - 1);
-
- } else if (key == KEY_PPAGE) {
- scrollok(menu, TRUE);
- for (i = 0; (i < max_choice); i++) {
- if (scroll > 0) {
- do_scroll(menu, &scroll, -1);
- print_item(scroll, 0, FALSE);
- } else {
- if (choice > 0)
- choice--;
- }
+ print_item(scroll + choice, choice, TRUE);
+ print_arrows(dialog, item_count(), scroll,
+ box_y, box_x + item_x + 1, menu_height);
+ wnoutrefresh(dialog);
+ wrefresh(menu);
+ continue; /* wait for another key press */
+ case '-':
+ /* Remove highlight of current item */
+ print_item(scroll + choice, choice, FALSE);
+ if (choice < 2 && scroll) {
+ /* Scroll menu down */
+ do_scroll(menu, &scroll, -1);
+ print_item(scroll, 0, FALSE);
+ } else
+ choice = MAX(choice - 1, 0);
+ print_item(scroll + choice, choice, TRUE);
+ print_arrows(dialog, item_count(), scroll,
+ box_y, box_x + item_x + 1, menu_height);
+ wnoutrefresh(dialog);
+ wrefresh(menu);
+ continue; /* wait for another key press */
+ case '\n':
+ isearch_str[0] = '\0';
+ /* fallthrough */
+ case ' ':
+ case 's':
+ case 'y':
+ case 'n':
+ case 'm':
+ case '/':
+ case 'h':
+ case '?':
+ case 'z':
+ /* save scroll info */
+ *s_scroll = scroll;
+ delwin(menu);
+ delwin(dialog);
+ item_set(scroll + choice);
+ item_set_selected(1);
+ switch (key) {
+ case 'h':
+ case '?':
+ return 2;
+ case 's':
+ case 'y':
+ return 5;
+ case 'n':
+ return 6;
+ case 'm':
+ return 7;
+ case ' ':
+ return 8;
+ case '/':
+ return 9;
+ case 'z':
+ return 10;
+ case '\n':
+ return button;
}
-
- } else if (key == KEY_NPAGE) {
- for (i = 0; (i < max_choice); i++) {
- if (scroll + max_choice < item_count()) {
+ return 0;
+ case 'e':
+ case 'x':
+ key = KEY_ESC;
+ break;
+ }
+ continue;
+ } else { /* !focus_on_buttons */
+ if (key == '\n') {
+ /* save scroll info */
+ *s_scroll = scroll;
+ delwin(menu);
+ delwin(dialog);
+ item_set(scroll + choice);
+ item_set_selected(1);
+ isearch_str[0] = '\0';
+ return 0; /* 0 means first button "Select" */
+ } else if ( key == KEY_BACKSPACE ) {
+ if ( isearch_str[0] )
+ isearch_str[i = (strlen(isearch_str) - 1)] = '\0';
+ /* Remove highlight of current item */
+ print_item(scroll + choice, choice, FALSE);
+ i = do_isearch(isearch_str, choice + 1, scroll);
+ } else if (key == '\\') {
+ /*
+ * Check \ before printable chars,
+ * because it is reserved to search
+ * further matches.
+ */
+ /* Remove highlight of current item */
+ print_item(scroll + choice, choice, FALSE);
+ i = do_isearch(isearch_str, choice + 1, scroll);
+ } else if (key < 256 && (isprint(key) || key == ' ')) {
+ if (strlen(isearch_str) < ISEARCH_LEN - 1) {
+ isearch_str[i = strlen(isearch_str)] = key;
+ isearch_str[i+1] = '\0';
+ /* Remove highlight of current item */
+ print_item(scroll + choice, choice, FALSE);
+ i = do_isearch(isearch_str, choice, scroll);
+ } else
+ continue;
+ } else
+ continue;
+
+ /*
+ * Handle matches
+ */
+ if (i >= 0) {
+ i -= scroll;
+
+ if (i >= max_choice)
+ /*
+ * Handle matches below the currently visible menu entries.
+ */
+ while (i >= max_choice) {
do_scroll(menu, &scroll, 1);
- print_item(scroll+max_choice-1,
- max_choice - 1, FALSE);
- } else {
- if (choice + 1 < max_choice)
- choice++;
+ i--;
+ print_item(max_choice + scroll - 1, max_choice - 1, false);
+ }
+ else if (i < 0)
+ /*
+ * Handle matches higher in the menu (ring search).
+ */
+ while (i < 0) {
+ do_scroll(menu, &scroll, -1);
+ i++;
+ print_item(scroll, 0, false);
}
- }
- } else
choice = i;
+ } else {
+ i = choice;
+ }

print_item(scroll + choice, choice, TRUE);
-
+ print_isearch(dialog, box_y, box_x + item_x + 5, menu_height, true);
print_arrows(dialog, item_count(), scroll,
box_y, box_x + item_x + 1, menu_height);

wnoutrefresh(dialog);
wrefresh(menu);
-
- continue; /* wait for another key press */
- }
-
- switch (key) {
- case KEY_LEFT:
- case TAB:
- case KEY_RIGHT:
- button = ((key == KEY_LEFT ? --button : ++button) < 0)
- ? 4 : (button > 4 ? 0 : button);
-
- print_buttons(dialog, height, width, button);
- wrefresh(menu);
- break;
- case ' ':
- case 's':
- case 'y':
- case 'n':
- case 'm':
- case '/':
- case 'h':
- case '?':
- case 'z':
- case '\n':
- /* save scroll info */
- *s_scroll = scroll;
- delwin(menu);
- delwin(dialog);
- item_set(scroll + choice);
- item_set_selected(1);
- switch (key) {
- case 'h':
- case '?':
- return 2;
- case 's':
- case 'y':
- return 5;
- case 'n':
- return 6;
- case 'm':
- return 7;
- case ' ':
- return 8;
- case '/':
- return 9;
- case 'z':
- return 10;
- case '\n':
- return button;
- }
- return 0;
- case 'e':
- case 'x':
- key = KEY_ESC;
- break;
- case KEY_ESC:
- key = on_key_esc(menu);
- break;
- case KEY_RESIZE:
- on_key_resize();
- delwin(menu);
- delwin(dialog);
- goto do_resize;
+ continue;
}
}
delwin(menu);
delwin(dialog);
+ isearch_str[0] = '\0';
return key; /* ESC pressed */
}
diff --git a/scripts/kconfig/lxdialog/util.c b/scripts/kconfig/lxdialog/util.c
index f7abdeb92af0..fde4865ff83a 100644
--- a/scripts/kconfig/lxdialog/util.c
+++ b/scripts/kconfig/lxdialog/util.c
@@ -373,13 +373,79 @@ void print_title(WINDOW *dialog, const char *title, int width)
}
}

+void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x)
+{
+ int newl, cur_x, cur_y;
+ int prompt_len, room, wlen;
+ char tempstr[MAX_LEN + 1], *word, *sp, *sp2, *newline_separator = 0;
+
+ strcpy(tempstr, prompt);
+
+ prompt_len = strlen(tempstr);
+
+ if (prompt_len <= width - x * 2) { /* If prompt is short */
+ wmove(win, y, (width - prompt_len) / 2);
+ waddstr(win, tempstr);
+ } else {
+ cur_x = x;
+ cur_y = y;
+ newl = 1;
+ word = tempstr;
+ while (word && *word) {
+ sp = strpbrk(word, "\n ");
+ if (sp && *sp == '\n')
+ newline_separator = sp;
+
+ if (sp)
+ *sp++ = 0;
+
+ /* Wrap to next line if either the word does not fit,
+ or it is the first word of a new sentence, and it is
+ short, and the next word does not fit. */
+ room = width - cur_x;
+ wlen = strlen(word);
+ if (wlen > room ||
+ (newl && wlen < 4 && sp
+ && wlen + 1 + strlen(sp) > room
+ && (!(sp2 = strpbrk(sp, "\n "))
+ || wlen + 1 + (sp2 - sp) > room))) {
+ cur_y++;
+ cur_x = x;
+ }
+ wmove(win, cur_y, cur_x);
+ waddstr(win, word);
+ getyx(win, cur_y, cur_x);
+
+ /* Move to the next line if the word separator was a newline */
+ if (newline_separator) {
+ cur_y++;
+ cur_x = x;
+ newline_separator = 0;
+ } else
+ cur_x++;
+
+ if (sp && *sp == ' ') {
+ cur_x++; /* double space */
+ while (*++sp == ' ') ;
+ newl = 1;
+ } else
+ newl = 0;
+ word = sp;
+ }
+ }
+}
+
/*
* Print a string of text in a window, automatically wrap around to the
* next line if the string is too long to fit on one line. Newline
* characters '\n' are propperly processed. We start on a new line
* if there is no room for at least 4 nonblanks following a double-space.
+ *
+ * This function fills all of and at most the area width x height so
+ * that it can be used to overwrite previosly displayed text.
*/
-void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x)
+void print_autowrap_fill(WINDOW * win, const char *prompt, int width,
+ int height, int y, int x)
{
int newl, cur_x, cur_y;
int prompt_len, room, wlen;
@@ -415,7 +481,13 @@ void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x)
&& wlen + 1 + strlen(sp) > room
&& (!(sp2 = strpbrk(sp, "\n "))
|| wlen + 1 + (sp2 - sp) > room))) {
+ while (cur_x < width) {
+ waddch(win, ' ');
+ cur_x++;
+ }
cur_y++;
+ if (cur_y - y >= height)
+ break;
cur_x = x;
}
wmove(win, cur_y, cur_x);
@@ -424,13 +496,24 @@ void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x)

/* Move to the next line if the word separator was a newline */
if (newline_separator) {
+ while (cur_x < width) {
+ waddch(win, ' ');
+ cur_x++;
+ }
cur_y++;
+ if (cur_y - y >= height)
+ break;
cur_x = x;
newline_separator = 0;
- } else
+ } else {
+ if (cur_x < width)
+ waddch(win, ' ');
cur_x++;
+ }

if (sp && *sp == ' ') {
+ if (cur_x < width)
+ waddch(win, ' ');
cur_x++; /* double space */
while (*++sp == ' ') ;
newl = 1;
@@ -439,6 +522,25 @@ void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x)
word = sp;
}
}
+
+ /*
+ * Fill remaining space to overwrite possibly existing text.
+ */
+ wmove(win, cur_y, cur_x);
+ while (cur_x < width) {
+ waddch(win, ' ');
+ cur_x++;
+ }
+ wmove(win, cur_y + 1, x);
+ getyx(win, cur_y, cur_x);
+ while (cur_y - y < height) {
+ while (cur_x < width) {
+ waddch(win, ' ');
+ cur_x++;
+ }
+ wmove(win, cur_y + 1, x);
+ getyx(win, cur_y, cur_x);
+ }
}

/*
diff --git a/scripts/kconfig/mconf.c b/scripts/kconfig/mconf.c
index 5294ed159b98..b25d4738136b 100644
--- a/scripts/kconfig/mconf.c
+++ b/scripts/kconfig/mconf.c
@@ -36,43 +36,62 @@ static const char mconf_readme[] =
"while *, M or whitespace inside braces means to build in, build as\n"
"a module or to exclude the feature respectively.\n"
"\n"
-"To change any of these features, highlight it with the cursor\n"
-"keys and press <Y> to build it in, <M> to make it a module or\n"
+"Operation modes\n"
+"---------------\n"
+"Menuconfig operates in two modes, depending on the focus that can be\n"
+"either on the menu or the buttons below it.\n"
+"\n"
+"To change any of the above features, it has to be navigated to (see\n"
+"below) so that it is highlited, focus then has to be on the buttons\n"
+"before you press <Y> to build it in, <M> to make it a module or\n"
"<N> to remove it. You may also press the <Space Bar> to cycle\n"
"through the available options (i.e. Y->N->M->Y).\n"
"\n"
-"Some additional keyboard hints:\n"
-"\n"
-"Menus\n"
+"Navigation\n"
"----------\n"
-"o Use the Up/Down arrow keys (cursor keys) to highlight the item you\n"
-" wish to change or the submenu you wish to select and press <Enter>.\n"
-" Submenus are designated by \"--->\", empty ones by \"----\".\n"
+"The following keys work independent of the current focus:\n"
"\n"
-" Shortcut: Press the option's highlighted letter (hotkey).\n"
-" Pressing a hotkey more than once will sequence\n"
-" through all visible items which use that hotkey.\n"
+"o vertical arrow keys are used to navigate to menu items\n"
"\n"
-" You may also use the <PAGE UP> and <PAGE DOWN> keys to scroll\n"
-" unseen options into view.\n"
+"o horizontal arrow keys cycle between the buttons\n"
+" If used with focus on the menu, the focus also changes to the buttons\n"
"\n"
-"o To exit a menu use the cursor keys to highlight the <Exit> button\n"
-" and press <ENTER>.\n"
+"o <PAGE UP> and <PAGE DOWN> scroll invisible items into view\n"
"\n"
-" Shortcut: Press <ESC><ESC> or <E> or <X> if there is no hotkey\n"
-" using those letters. You may press a single <ESC>, but\n"
-" there is a delayed response which you may find annoying.\n"
+"o <ENTER> visits a submenu\n"
+" Submenus are designated by \"--->\", empty ones by \"----\".\n"
"\n"
-" Also, the <TAB> and cursor keys will cycle between <Select>,\n"
-" <Exit>, <Help>, <Save>, and <Load>.\n"
+"o <ESC><ESC> leaves a submenu or (in the main menu) exits menuconfig\n"
"\n"
-"o To get help with an item, use the cursor keys to highlight <Help>\n"
-" and press <ENTER>.\n"
+"o <TAB> is reserved to toggle the focus between menu and buttons\n"
"\n"
-" Shortcut: Press <H> or <?>.\n"
+"When menuconfig starts, the focus is on the menu and i-search mode\n"
+"is active. You can enter subsequent characters to build a string the\n"
+"menu items are searched for. Keys with a special meaning are:\n"
+"\n"
+"o <BACKSPACE> removes the last character of the current search string\n"
+"\n"
+"o <DEL> clears the complete search string\n"
+"\n"
+"o <ENTER> also clears the whole string, but also visits a submenu\n"
+" if the selected menu item is one\n"
"\n"
-"o To toggle the display of hidden options, press <Z>.\n"
+"o <\\> (backslash) can be used to find further matches of a string\n"
"\n"
+"When the focus is on the buttons the following keys can be used:\n"
+"\n"
+"o <x> can be used for exit identical to <ESC><ESC>\n"
+"\n"
+"o <y>, <n>, <m> or <SPACE> change the selected item\n"
+"\n"
+"o <+> and <-> keys navigate menu items identical to vertical arrow\n"
+" keys\n"
+"\n"
+"o <h> or <?> display help messages\n"
+"\n"
+"o <z> toggles the display of hidden options\n"
+"\n"
+"Some additional keyboard hints:\n"
"\n"
"Radiolists (Choice lists)\n"
"-----------\n"
@@ -174,7 +193,6 @@ static const char mconf_readme[] =
menu_instructions[] =
"Arrow keys navigate the menu. "
"<Enter> selects submenus ---> (or empty submenus ----). "
- "Highlighted letters are hotkeys. "
"Pressing <Y> includes, <N> excludes, <M> modularizes features. "
"Press <Esc><Esc> to exit, <?> for Help, </> for Search. "
"Legend: [*] built-in [ ] excluded <M> module < > module capable",
--
2.16.4