ui/ncurses: Call widget process_key handlers first
[petitboot] / ui / ncurses / nc-widgets.c
index 3daced1a68b56c9e2cb1628384f5b06c70968194..7dc2df3f1d1fc2138c001b7511bac6bfc0e8041a 100644 (file)
@@ -51,6 +51,8 @@
 #include <types/types.h>
 #include <log/log.h>
 #include <util/util.h>
+#include <i18n/i18n.h>
+#include <fold/fold.h>
 
 #include "nc-cui.h"
 #include "nc-widgets.h"
@@ -137,6 +139,7 @@ struct nc_widget_select {
                char            *str;
                int             val;
                FIELD           *field;
+               int             lines;
        } *options;
        int                     top, left, size;
        int                     n_options, selected_option;
@@ -159,21 +162,6 @@ static bool key_is_select(int key)
        return key == ' ' || key == '\r' || key == '\n' || key == KEY_ENTER;
 }
 
-static bool key_is_minus(int key)
-{
-       return key == 055;
-}
-
-static bool key_is_left(int key)
-{
-       return key == KEY_LEFT;
-}
-
-static bool key_is_right(int key)
-{
-       return key == KEY_RIGHT;
-}
-
 static bool process_key_nop(struct nc_widget *widget __attribute__((unused)),
                FORM *form __attribute((unused)),
                int key __attribute__((unused)))
@@ -349,6 +337,14 @@ static bool textbox_process_key(
        case KEY_DC:
                form_driver(form, REQ_DEL_CHAR);
                break;
+       case '\t':
+       case KEY_BTAB:
+       case KEY_UP:
+       case KEY_DOWN:
+       case KEY_PPAGE:
+       case KEY_NPAGE:
+               /* Don't catch navigational keys */
+               return false;
        default:
                form_driver(form, key);
                break;
@@ -478,6 +474,7 @@ static void subset_delete_active(struct nc_widget_subset *subset, int idx)
        struct nc_widget *widget;
        size_t rem;
        int i, val;
+       uint32_t opts;
 
        /* Shift field focus to nearest active option or next visible field */
        if (subset->n_active > 1) {
@@ -489,8 +486,11 @@ static void subset_delete_active(struct nc_widget_subset *subset, int idx)
        } else {
                for (i = 0; i < set->n_fields; i++)
                        if (field_visible(set->fields[i])) {
-                               set->cur_field = set->fields[i];
-                               break;
+                               opts = field_opts(set->fields[i]);
+                               if ((opts & O_ACTIVE) == O_ACTIVE) {
+                                       set->cur_field = set->fields[i];
+                                       break;
+                               }
                        }
        }
 
@@ -519,7 +519,7 @@ static bool subset_process_key(struct nc_widget *w, FORM *form, int key)
        int i, val, opt_idx = -1;
        FIELD *field;
 
-       if (!key_is_minus(key) && !key_is_left(key) && !key_is_right(key))
+       if (key != '-' && key != '+' && key != KEY_DC && key != KEY_BACKSPACE)
                return false;
 
        field = current_field(form);
@@ -535,10 +535,10 @@ static bool subset_process_key(struct nc_widget *w, FORM *form, int key)
        if (opt_idx < 0)
                return false;
 
-       if (key_is_minus(key))
+       if (key == KEY_DC || key == KEY_BACKSPACE)
                subset_delete_active(subset, opt_idx);
 
-       if (key_is_left(key)){
+       if (key == '-') {
                if (opt_idx == 0)
                        return true;
 
@@ -547,7 +547,7 @@ static bool subset_process_key(struct nc_widget *w, FORM *form, int key)
                subset->active[opt_idx - 1] = val;
        }
 
-       if (key_is_right(key)){
+       if (key == '+') {
                if (opt_idx >= subset->n_active - 1)
                        return true;
 
@@ -838,21 +838,25 @@ static void select_set_visible(struct nc_widget *widget, bool visible)
 static void select_move(struct nc_widget *widget, int y, int x)
 {
        struct nc_widget_select *select = to_select(widget);
-       int i;
+       int i, cur = 0;
 
-       for (i = 0; i < select->n_options; i++)
-               field_move(select->options[i].field, y + i, x);
+       for (i = 0; i < select->n_options; i++) {
+               field_move(select->options[i].field, y + cur, x);
+               cur += select->options[i].lines;
+       }
 }
 
 static void select_field_focus(struct nc_widget *widget, FIELD *field)
 {
        struct nc_widget_select *select = to_select(widget);
-       int i;
+       int i, cur = 0;
 
        for (i = 0; i < select->n_options; i++) {
-               if (field != select->options[i].field)
+               if (field != select->options[i].field) {
+                       cur += select->options[i].lines;
                        continue;
-               widget->focus_y = i;
+               }
+               widget->focus_y = cur;
                return;
        }
 }
@@ -894,10 +898,47 @@ struct nc_widget_select *widget_new_select(struct nc_widgetset *set,
        return select;
 }
 
+static int widget_select_fold_cb(void *arg, const char *buf, int len)
+{
+       struct nc_widget_select *select = arg;
+       char *line, *newstr, *padbuf = NULL;
+       int i, pad;
+
+       if (!len)
+               return 0;
+
+       line = talloc_strndup(select->options, buf, len);
+
+       i = select->n_options - 1;
+       pad = max(0, select->widget.width - strncols(line));
+
+       if (pad) {
+               padbuf = talloc_array(select->options, char, pad + 1);
+               memset(padbuf, ' ', pad);
+               padbuf[pad] = '\0';
+       }
+
+       if (select->options[i].str)
+               newstr = talloc_asprintf_append(select->options[i].str,
+                                                        "%s%s", line,
+                                                        pad ? padbuf : "");
+       else
+               newstr = talloc_asprintf(select->options, "%s%s", line,
+                                                        pad ? padbuf : "");
+
+       select->options[i].str = newstr;
+       select->options[i].lines++;
+
+       talloc_free(padbuf);
+       talloc_free(line);
+       return 0;
+}
+
 void widget_select_add_option(struct nc_widget_select *select, int value,
                const char *text, bool selected)
 {
        const char *str;
+       char *full_text;
        FIELD *f;
        int i;
 
@@ -916,23 +957,29 @@ void widget_select_add_option(struct nc_widget_select *select, int value,
                str = select_unselected_str;
 
        i = select->n_options++;
-       select->widget.height = select->n_options;
-
        select->options = talloc_realloc(select, select->options,
                                struct select_option, i + 2);
        select->options[i].val = value;
-       select->options[i].str = talloc_asprintf(select->options,
-                                       "%s %s", str, text);
+       select->options[i].lines = 0;
+       select->options[i].str = NULL;
 
-       select->options[i].field = f = new_field(1, select->size,
-                                               select->top + i,
-                                               select->left, 0, 0);
+       full_text = talloc_asprintf(select->options, "%s %s", str, text);
+       fold_text(full_text, select->widget.width,
+                 widget_select_fold_cb, select);
 
-       field_opts_off(f, O_WRAP | O_EDIT);
+       select->options[i].field = f = new_field(select->options[i].lines,
+                                       select->size,
+                                       select->top + select->widget.height,
+                                       select->left, 0, 0);
+
+       select->widget.height += select->options[i].lines;
+
+       field_opts_off(f, O_EDIT);
        set_field_userptr(f, &select->widget);
        set_field_buffer(f, 0, select->options[i].str);
 
        widgetset_add_field(select->set, f);
+       talloc_free(full_text);
 }
 
 int widget_select_get_value(struct nc_widget_select *select)
@@ -944,7 +991,7 @@ int widget_select_get_value(struct nc_widget_select *select)
 
 int widget_select_height(struct nc_widget_select *select)
 {
-       return select->n_options;
+       return select->widget.height;
 }
 
 void widget_select_on_change(struct nc_widget_select *select,
@@ -1002,16 +1049,18 @@ struct nc_widget_button *widget_new_button(struct nc_widgetset *set,
                void (*click)(void *), void *arg)
 {
        struct nc_widget_button *button;
+       int idx, len, pad1, pad2, bufsz;
        char *text;
        FIELD *f;
-       int idx, len;
+
+       int field_size = size + 2;
 
        button = talloc_zero(set, struct nc_widget_button);
        button->widget.height = 1;
-       button->widget.width = size;
+       button->widget.width = field_size;
        button->widget.x = x;
        button->widget.y = y;
-       button->widget.field = f = new_field(1, size + 2, y, x, 0, 0);
+       button->widget.field = f = new_field(1, field_size, y, x, 0, 0);
        button->widget.process_key = button_process_key;
        button->widget.focussed_attr = A_REVERSE;
        button->widget.unfocussed_attr = A_NORMAL;
@@ -1021,17 +1070,28 @@ struct nc_widget_button *widget_new_button(struct nc_widgetset *set,
        field_opts_off(f, O_EDIT);
        set_field_userptr(f, &button->widget);
 
-       /* center str in a size-char buffer, but don't overrun */
-       len = strlen(str);
-       len = min(len, size);
-       idx = (size - len) / 2;
+       /* Center str in the field. This depends on the number of columns used
+        * by the string, not the number of chars in str */
+       len = strncols(str);
+       if (len <= size) {
+               idx = (field_size - len) / 2;
+       } else {
+               idx = 1;
+               pb_log("Warning: '%s' %d columns wide "
+                      "but button is %d columns wide\n",
+                      str, len, size);
+       }
 
-       text = talloc_array(button, char, size + 3);
-       memset(text, ' ', size + 2);
-       memcpy(text + idx + 1, str, len);
+       pad1 = max(idx - 1, 0);
+       pad2 = max(size - len - pad1, 0);
+       bufsz = 1 + pad1 + strlen(str) + pad2 + 2;
+
+       text = talloc_array(button, char, bufsz);
+       memset(text, ' ', bufsz);
+       memcpy(text + idx, str, strlen(str));
        text[0] = '[';
-       text[size + 1] = ']';
-       text[size + 2] = '\0';
+       text[bufsz - 2] = ']';
+       text[bufsz - 1] = '\0';
 
        set_field_buffer(f, 0, text);
 
@@ -1058,6 +1118,12 @@ bool widgetset_process_key(struct nc_widgetset *set, int key)
        field = current_field(set->form);
        assert(field);
 
+       widget = field_userptr(field);
+
+       if (widget->process_key)
+               if (widget->process_key(widget, set->form, key))
+                       return true;
+
        tab = false;
 
        /* handle field change events */
@@ -1066,12 +1132,14 @@ bool widgetset_process_key(struct nc_widgetset *set, int key)
                tab = true;
                /* fall through */
        case KEY_UP:
+       case KEY_LEFT:
                req = REQ_SPREV_FIELD;
                break;
        case '\t':
                tab = true;
                /* fall through */
        case KEY_DOWN:
+       case KEY_RIGHT:
                req = REQ_SNEXT_FIELD;
                break;
        case KEY_PPAGE:
@@ -1082,7 +1150,6 @@ bool widgetset_process_key(struct nc_widgetset *set, int key)
                break;
        }
 
-       widget = field_userptr(field);
        if (req) {
                widget_focus_change(widget, field, false);
                form_driver(set->form, req);
@@ -1107,10 +1174,7 @@ bool widgetset_process_key(struct nc_widgetset *set, int key)
                return true;
        }
 
-       if (!widget->process_key)
-               return false;
-
-       return widget->process_key(widget, set->form, key);
+       return false;
 }
 
 static int widgetset_destructor(void *ptr)
@@ -1161,10 +1225,8 @@ void widgetset_post(struct nc_widgetset *set)
        post_form(set->form);
        form_driver(set->form, REQ_END_FIELD);
 
-       if (set->cur_field) {
+       if (set->cur_field)
                set_current_field(set->form, set->cur_field);
-               field = set->cur_field;
-       }
 
        field = current_field(set->form);
        widget = field_userptr(field);