[RFC v4 1/1] i-search functionality for mconf

From: Dirk Gouders
Date: Fri Jun 08 2018 - 14:53:32 EST


This patch prototypes isearch functionality 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 alphanumeric
characters or space entered are used to form a string that is
searched for.

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

* ENTER can be used to enter a submenu.

* Horizontal arrow keys put focus on the buttons with the known
behavior.

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

* Vertical arrow keys work anywhere.

* When the focus is on the buttons, all keys (e.g. hotkeys) work the
same as before this change.

* '\' can be used to search for other occurences of an already entered
string.

* To use y|n|m 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

---
scripts/kconfig/lxdialog/dialog.h | 3 +
scripts/kconfig/lxdialog/menubox.c | 207 +++++++++++++++++++++++++++++++++----
2 files changed, 189 insertions(+), 21 deletions(-)

diff --git a/scripts/kconfig/lxdialog/dialog.h b/scripts/kconfig/lxdialog/dialog.h
index 0b00be5abaa6..02b036435919 100644
--- a/scripts/kconfig/lxdialog/dialog.h
+++ b/scripts/kconfig/lxdialog/dialog.h
@@ -50,6 +50,8 @@
#define TR(params) _tracef params

#define KEY_ESC 27
+#define KEY_CTRL_S 19
+#define KEY_ENTR 10
#define TAB 9
#define MAX_LEN 2048
#define BUF_SIZE (10*1024)
@@ -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..e49280115b52 100644
--- a/scripts/kconfig/lxdialog/menubox.c
+++ b/scripts/kconfig/lxdialog/menubox.c
@@ -56,10 +56,17 @@
* fscanf would read in 'scroll', and eventually that value would get used.
*/

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

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

+static int focus_on_buttons;
+
/*
* Print menu item
*/
@@ -85,7 +92,10 @@ 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
@@ -105,6 +115,32 @@ do { \
do_print_item(menu, item_str(), choice, selected, !item_is_tag(':')); \
} while (0)

+
+/*
+* Print the isearch indicator.
+*/
+static void print_isearch(WINDOW * win, int y, int x, int height, bool isearch)
+{
+ unsigned char i = 0;
+
+ 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 = ISEARCH_INDICATOR_LEN - strlen(isearch_str);
+ }
+
+ wattrset(win, dlg.menubox_border.atr);
+
+ for ( ; i < ISEARCH_INDICATOR_LEN; i++ )
+ waddch(win, ACS_HLINE);
+}
+
/*
* Print the scroll indicators.
*/
@@ -157,6 +193,9 @@ static void print_buttons(WINDOW * win, int height, int width, int selected)
int x = width / 2 - 28;
int y = height - 2;

+ if (!focus_on_buttons)
+ selected = -1;
+
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);
@@ -178,6 +217,32 @@ 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
*/
@@ -275,6 +340,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);
@@ -285,22 +351,27 @@ 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;
- }
- if (i == max_choice)
- for (i = 0; i < max_choice; i++) {
+ if (focus_on_buttons) {
+ /*
+ * Find item matching hot 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;
}
+ 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;
+ }
+ }
}

if (item_count() != 0 &&
@@ -370,16 +441,117 @@ int dialog_menu(const char *title, const char *prompt,
continue; /* wait for another key press */
}

+ if (!focus_on_buttons) {
+ i = -1;
+
+ 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" */
+ }
+
+ if ( key == KEY_BACKSPACE && isearch_str[0] ) {
+ isearch_str[i = (strlen(isearch_str) - 1)] = '\0';
+ i = -1;
+ }
+
+ if (key < 256 && (isalnum(key) || key == ' ')) {
+ if (strlen(isearch_str) < ISEARCH_LEN - 1) {
+ isearch_str[i = strlen(isearch_str)] = key;
+ isearch_str[i+1] = '\0';
+ }
+
+ /* Remove highligt of current item */
+ print_item(scroll + choice, choice, FALSE);
+ i = do_isearch(isearch_str, choice, scroll);
+ }
+
+ if (key == '\\') {
+ /* Remove highligt of current item */
+ print_item(scroll + choice, choice, FALSE);
+ i = do_isearch(isearch_str, choice + 1, scroll);
+ }
+
+ /*
+ * 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);
+ 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);
+ }
+ choice = i;
+ } else {
+ i = choice;
+ goto a;
+ }
+
+ 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);
+ i = max_choice;
+ continue;
+ }
+ a:
switch (key) {
- case KEY_LEFT:
case TAB:
+ focus_on_buttons = 1 - focus_on_buttons;
+ 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);
+ break;
+ case KEY_LEFT:
case KEY_RIGHT:
+ focus_on_buttons = 1;
button = ((key == KEY_LEFT ? --button : ++button) < 0)
? 4 : (button > 4 ? 0 : button);

+ 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);
break;
+ case KEY_ESC:
+ key = on_key_esc(menu);
+ break;
+ case KEY_RESIZE:
+ on_key_resize();
+ delwin(menu);
+ delwin(dialog);
+ goto do_resize;
+ }
+
+ /*
+ * Focus is on buttons, handle keys appropriately
+ */
+ switch (key) {
case ' ':
case 's':
case 'y':
@@ -390,6 +562,7 @@ int dialog_menu(const char *title, const char *prompt,
case '?':
case 'z':
case '\n':
+ isearch_str[0] = '\0';
/* save scroll info */
*s_scroll = scroll;
delwin(menu);
@@ -421,14 +594,6 @@ int dialog_menu(const char *title, const char *prompt,
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;
}
}
delwin(menu);
--
2.16.4