]> git.ozlabs.org Git - petitboot/blobdiff - ui/ncurses/nc-widgets.c
ui/ncurses: Add nc-auth and authenticate when required.
[petitboot] / ui / ncurses / nc-widgets.c
index 2829040b46ba236a7ad01e9e1dd828d0199ac7d9..afd56d71cf90b79d83ee8a07fd4bdfc466e1b508 100644 (file)
 #  error "Curses form.h not found."
 #endif
 
-#include <string.h>
+#include <arpa/inet.h>
 #include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
 
 #include <talloc/talloc.h>
 #include <types/types.h>
 #include <log/log.h>
 #include <util/util.h>
+#include <i18n/i18n.h>
+#include <fold/fold.h>
+#include <url/url.h>
 
 #include "nc-cui.h"
 #include "nc-widgets.h"
@@ -61,6 +66,7 @@
 #define to_textbox(w) container_of(w, struct nc_widget_textbox, widget)
 #define to_button(w) container_of(w, struct nc_widget_button, widget)
 #define to_select(w) container_of(w, struct nc_widget_select, widget)
+#define to_subset(w) container_of(w, struct nc_widget_subset, widget)
 
 static const char *checkbox_checked_str = "[*]";
 static const char *checkbox_unchecked_str = "[ ]";
@@ -79,7 +85,9 @@ struct nc_widgetset {
        FIELD   *cur_field;
 
        /* custom validators */
-       FIELDTYPE *ipv4_multi_type;
+       FIELDTYPE *ip_multi_type;
+       FIELDTYPE *ip_type;
+       FIELDTYPE *url_type;
 };
 
 struct nc_widget {
@@ -112,12 +120,31 @@ struct nc_widget_textbox {
        struct nc_widget        widget;
 };
 
+struct nc_widget_subset {
+       struct nc_widget        widget;
+       int                     *active;
+       int                     n_active;
+       struct subset_option {
+               char            *str;
+               int             val;
+               FIELD           *field;
+       } *options;
+       int                     n_options;
+       int                     top, left, size;
+       struct nc_widgetset     *set;
+       void                    (*on_change)(void *, int);
+       void                    *on_change_arg;
+       void                    (*screen_cb)(void *,
+                                       struct nc_widget_subset *, int);
+};
+
 struct nc_widget_select {
        struct nc_widget        widget;
        struct select_option {
                char            *str;
                int             val;
                FIELD           *field;
+               int             lines;
        } *options;
        int                     top, left, size;
        int                     n_options, selected_option;
@@ -155,6 +182,11 @@ static void field_set_visible(FIELD *field, bool visible)
        set_field_opts(field, opts);
 }
 
+static bool field_visible(FIELD *field)
+{
+       return (field_opts(field) & O_VISIBLE) == O_VISIBLE;
+}
+
 static void field_move(FIELD *field, int y, int x)
 {
        move_field(field, y, x);
@@ -310,6 +342,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;
@@ -325,8 +365,8 @@ static int textbox_destructor(void *ptr)
        return 0;
 }
 
-struct nc_widget_textbox *widget_new_textbox(struct nc_widgetset *set,
-               int y, int x, int len, char *str)
+struct nc_widget_textbox *widget_new_textbox_hidden(struct nc_widgetset *set,
+               int y, int x, int len, char *str, bool hide_input)
 {
        struct nc_widget_textbox *textbox;
        FIELD *f;
@@ -343,6 +383,8 @@ struct nc_widget_textbox *widget_new_textbox(struct nc_widgetset *set,
        textbox->widget.unfocussed_attr = A_UNDERLINE;
 
        field_opts_off(f, O_STATIC | O_WRAP | O_BLANK);
+       if (hide_input)
+               field_opts_off(f, O_PUBLIC);
        set_field_buffer(f, 0, str);
        set_field_back(f, textbox->widget.unfocussed_attr);
        set_field_userptr(f, &textbox->widget);
@@ -353,60 +395,446 @@ struct nc_widget_textbox *widget_new_textbox(struct nc_widgetset *set,
        return textbox;
 }
 
+struct nc_widget_textbox *widget_new_textbox(struct nc_widgetset *set,
+               int y, int x, int len, char *str)
+{
+       return widget_new_textbox_hidden(set, y, x, len, str, false);
+}
+
+void widget_textbox_set_fixed_size(struct nc_widget_textbox *textbox)
+{
+       field_opts_on(textbox->widget.field, O_STATIC);
+}
+
 void widget_textbox_set_validator_integer(struct nc_widget_textbox *textbox,
                long min, long max)
 {
        set_field_type(textbox->widget.field, TYPE_INTEGER, 1, min, max);
 }
 
-void widget_textbox_set_validator_ipv4(struct nc_widget_textbox *textbox)
+static bool check_url_field(FIELD *field,
+               const void *arg __attribute__((unused)))
+{
+       return is_url(field_buffer(field, 0));
+}
+
+void widget_textbox_set_validator_url(struct nc_widget_textbox *textbox)
 {
-       set_field_type(textbox->widget.field, TYPE_IPV4);
+       if (!textbox->set->url_type)
+               textbox->set->url_type = new_fieldtype(check_url_field, NULL);
+
+       set_field_type(textbox->widget.field, textbox->set->url_type);
 }
 
-static bool check_ipv4_multi_char(int c,
+static bool check_ip_field(FIELD *field,
                const void *arg __attribute__((unused)))
+{
+       char *str;
+       int rc;
+
+       str = strip_string(field_buffer(field, 0));
+
+       rc = addr_scheme(str);
+
+       return (rc == AF_INET || rc == AF_INET6);
+}
+
+
+static bool check_ipv6_multi_char(int c)
+{
+       return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ||
+              c == ':' || c == ' ';
+}
+
+static bool check_ipv4_multi_char(int c)
 {
        return isdigit(c) || c == '.' || c == ' ';
 }
 
-static bool check_ipv4_multi_field(FIELD *field,
+static bool check_ip_multi_field(FIELD *field,
                const void *arg __attribute__((unused)))
 {
-       char *buf = field_buffer(field, 0);
-       unsigned int ip[4];
-       int n, len;
-
-       while (*buf != '\0') {
-               n = sscanf(buf, "%u.%u.%u.%u%n",
-                               &ip[0], &ip[1], &ip[2], &ip[3], &len);
-               if (n != 4)
-                       return false;
-
-               if (ip[0] > 255 || ip[1] > 255 || ip[2] > 255 || ip[3] > 255)
-                       return false;
-
-               for (buf += len; *buf != '\0'; buf++) {
-                       if (isspace(*buf))
-                               continue;
-                       else if (isdigit(*buf))
-                               break;
-                       else
-                               return false;
+       char *buf, *tok, *saveptr;
+       bool result;
+       int type;
+
+       /* Use strdup directly since we can't easily reach a talloc parent */
+       buf = strdup(strip_string(field_buffer(field, 0)));
+       if (!buf)
+               /* We tried */
+               return true;
+
+       result = false;
+       tok = strtok_r(buf, " ", &saveptr);
+       if (!tok || *tok == '\0')
+               goto err;
+
+       while (tok) {
+               type = addr_scheme(tok);
+               if (!(type == AF_INET || type == AF_INET6))
+                       goto err;
+
+               tok = strtok_r(NULL, " ", &saveptr);
+       }
+       result = true;
+
+err:
+       free(buf);
+       return result;
+}
+
+static bool check_ip_multi_char(int c, const void *arg __attribute__((unused)))
+{
+       return (check_ipv4_multi_char(c) || check_ipv6_multi_char(c));
+}
+
+void widget_textbox_set_validator_ip(struct nc_widget_textbox *textbox)
+{
+       if (!textbox->set->ip_type) {
+               textbox->set->ip_type = new_fieldtype(check_ip_field, NULL);
+       }
+       set_field_type(textbox->widget.field, textbox->set->ip_type);
+}
+
+/*
+ * In a perfect world we would use link_fieldtype() but segfaults do not
+ * enhance the user experience.
+ */
+void widget_textbox_set_validator_ip_multi(struct nc_widget_textbox *textbox)
+{
+       if (!textbox->set->ip_multi_type) {
+               textbox->set->ip_multi_type = new_fieldtype(
+                                               check_ip_multi_field,
+                                               check_ip_multi_char);
+       }
+       set_field_type(textbox->widget.field, textbox->set->ip_multi_type);
+}
+
+static void subset_update_order(struct nc_widget_subset *subset)
+{
+       char *str;
+       int i, val;
+
+       for (i = 0; i < subset->n_active; i++) {
+               val = subset->active[i];
+               str = talloc_asprintf(subset, "(%d) %s",
+                                     i, subset->options[val].str);
+               set_field_buffer(subset->options[val].field, 0,
+                                str);
+               talloc_free(str);
+       }
+}
+
+static void widget_focus_change(struct nc_widget *widget, FIELD *field,
+               bool focussed);
+
+static void subset_delete_active(struct nc_widget_subset *subset, int idx)
+{
+       bool last = idx == (subset->n_active - 1);
+       struct nc_widgetset *set = subset->set;
+       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) {
+               if (last)
+                       val = subset->active[idx - 1];
+               else
+                       val = subset->active[idx + 1];
+               set->cur_field = subset->options[val].field;
+       } else {
+               for (i = 0; i < set->n_fields; i++)
+                       if (field_visible(set->fields[i])) {
+                               opts = field_opts(set->fields[i]);
+                               if ((opts & O_ACTIVE) == O_ACTIVE) {
+                                       set->cur_field = set->fields[i];
+                                       break;
+                               }
+                       }
+       }
+
+       set_current_field(set->form, set->cur_field);
+       widget = field_userptr(set->cur_field);
+       widget_focus_change(widget, set->cur_field, true);
+       if (set->widget_focus)
+               set->widget_focus(widget, set->widget_focus_arg);
+
+       /* Update active array */
+       rem = sizeof(int) * (subset->n_active - idx - 1);
+       val = subset->active[idx];
+       field_set_visible(subset->options[val].field, false);
+       if (rem)
+               memmove(&subset->active[idx], &subset->active[idx + 1], rem);
+       subset->n_active--;
+       subset->active = talloc_realloc(subset, subset->active,
+                                        int, subset->n_active);
+
+       subset->widget.height = subset->n_active;
+}
+
+static bool subset_process_key(struct nc_widget *w, FORM *form, int key)
+{
+       struct nc_widget_subset *subset = to_subset(w);
+       int i, val, opt_idx = -1;
+       FIELD *field;
+
+       if (key != '-' && key != '+' && key != KEY_DC && key != KEY_BACKSPACE)
+               return false;
+
+       field = current_field(form);
+
+       for (i = 0; i < subset->n_active; i++) {
+               val = subset->active[i];
+               if (subset->options[val].field == field) {
+                       opt_idx = i;
+                       break;
                }
        }
 
+       if (opt_idx < 0)
+               return false;
+
+       if (key == KEY_DC || key == KEY_BACKSPACE)
+               subset_delete_active(subset, opt_idx);
+
+       if (key == '-') {
+               if (opt_idx == 0)
+                       return true;
+
+               val = subset->active[opt_idx];
+               subset->active[opt_idx] = subset->active[opt_idx - 1];
+               subset->active[opt_idx - 1] = val;
+       }
+
+       if (key == '+') {
+               if (opt_idx >= subset->n_active - 1)
+                       return true;
+
+               val = subset->active[opt_idx];
+               subset->active[opt_idx] = subset->active[opt_idx + 1];
+               subset->active[opt_idx + 1] = val;
+       }
+
+       subset_update_order(subset);
+
+       if (subset->on_change)
+               subset->on_change(subset->on_change_arg, 0);
+
        return true;
 }
 
-void widget_textbox_set_validator_ipv4_multi(struct nc_widget_textbox *textbox)
+static void subset_set_visible(struct nc_widget *widget, bool visible)
 {
-       if (!textbox->set->ipv4_multi_type) {
-               textbox->set->ipv4_multi_type = new_fieldtype(
-                               check_ipv4_multi_field,
-                               check_ipv4_multi_char);
+       struct nc_widget_subset *subset = to_subset(widget);
+       int i, val;
+
+       for (i = 0; i < subset->n_active; i++) {
+               val = subset->active[i];
+               field_set_visible(subset->options[val].field, visible);
        }
-       set_field_type(textbox->widget.field, textbox->set->ipv4_multi_type);
+}
+
+static void subset_move(struct nc_widget *widget, int y, int x)
+{
+       struct nc_widget_subset *subset = to_subset(widget);
+       int i, val;
+
+       for (i = 0; i < subset->n_active; i++) {
+               val = subset->active[i];
+               field_move(subset->options[val].field, y + i , x);
+       }
+}
+
+static void subset_field_focus(struct nc_widget *widget, FIELD *field)
+{
+       struct nc_widget_subset *subset = to_subset(widget);
+       int i, val;
+
+       for (i = 0; i < subset->n_active; i++) {
+               val = subset->active[i];
+               if (field == subset->options[val].field) {
+                       widget->focus_y = i;
+                       return;
+               }
+       }
+}
+
+static int subset_destructor(void *ptr)
+{
+       struct nc_widget_subset *subset = ptr;
+       int i;
+
+       for (i = 0; i < subset->n_options; i++)
+               free_field(subset->options[i].field);
+
+       return 0;
+}
+
+struct nc_widget_subset *widget_new_subset(struct nc_widgetset *set,
+               int y, int x, int len, void *screen_cb)
+{
+       struct nc_widget_subset *subset;
+
+       subset = talloc_zero(set, struct nc_widget_subset);
+       subset->widget.width = len;
+       subset->widget.height = 0;
+       subset->widget.x = x;
+       subset->widget.y = y;
+       subset->widget.process_key = subset_process_key;
+       subset->widget.set_visible = subset_set_visible;
+       subset->widget.move = subset_move;
+       subset->widget.field_focus = subset_field_focus;
+       subset->widget.focussed_attr = A_REVERSE;
+       subset->widget.unfocussed_attr = A_NORMAL;
+       subset->top = y;
+       subset->left = x;
+       subset->size = len;
+       subset->set = set;
+       subset->n_active = subset->n_options = 0;
+       subset->active = NULL;
+       subset->options = NULL;
+       subset->screen_cb = screen_cb;
+
+       talloc_set_destructor(subset, subset_destructor);
+
+       return subset;
+}
+
+void widget_subset_add_option(struct nc_widget_subset *subset, const char *text)
+{
+       FIELD *f;
+       int i;
+
+       i = subset->n_options++;
+       subset->options = talloc_realloc(subset, subset->options,
+                                        struct subset_option, i + 1);
+
+       subset->options[i].str = talloc_strdup(subset->options, text);
+
+       subset->options[i].field = f = new_field(1, subset->size, subset->top + i,
+                                                subset->left, 0, 0);
+
+       field_opts_off(f, O_WRAP | O_EDIT);
+       set_field_userptr(f, &subset->widget);
+       set_field_buffer(f, 0, subset->options[i].str);
+       field_set_visible(f, false);
+       widgetset_add_field(subset->set, f);
+}
+
+void widget_subset_make_active(struct nc_widget_subset *subset, int idx)
+{
+       int i;
+
+       for (i = 0; i < subset->n_active; i++)
+               if (subset->active[i] == idx) {
+                       pb_debug("%s: Index %d already active\n", __func__, idx);
+                       return;
+               }
+
+       i = subset->n_active++;
+       subset->widget.height = subset->n_active;
+       subset->active = talloc_realloc(subset, subset->active,
+                                       int, i + 1);
+       subset->active[i] = idx;
+
+       subset_update_order(subset);
+}
+
+int widget_subset_get_order(void *ctx, unsigned int **order,
+               struct nc_widget_subset *subset)
+{
+       unsigned int *buf = talloc_array(ctx, unsigned int, subset->n_active);
+       int i;
+
+       for (i = 0; i < subset->n_active; i++)
+               buf[i] = subset->active[i];
+
+       *order = buf;
+       return i;
+}
+
+void widget_subset_show_inactive(struct nc_widget_subset *subset,
+               struct nc_widget_select *select)
+{
+       bool active = false, first = true;
+       int i, j;
+
+       for (i = 0; i < subset->n_options; i++) {
+               active = false;
+               for (j = 0; j < subset->n_active; j++)
+                       if (subset->active[j] == i)
+                               active = true;
+
+               if (active)
+                       continue;
+
+               widget_select_add_option(select, i,
+                                        subset->options[i].str, first);
+               if (first)
+                       first = false;
+       }
+}
+
+int widget_subset_n_inactive(struct nc_widget_subset *subset)
+{
+       return subset->n_options - subset->n_active;
+}
+
+int widget_subset_height(struct nc_widget_subset *subset)
+{
+       return subset->n_active;
+}
+
+void widget_subset_on_change(struct nc_widget_subset *subset,
+               void (*on_change)(void *, int), void *arg)
+{
+       subset->on_change = on_change;
+       subset->on_change_arg = arg;
+}
+
+void widget_subset_drop_options(struct nc_widget_subset *subset)
+{
+       struct nc_widgetset *set = subset->set;
+       int i;
+
+       for (i = 0; i < subset->n_options; i++) {
+               FIELD *field = subset->options[i].field;
+               widgetset_remove_field(set, field);
+               if (field == set->cur_field)
+                       set->cur_field = NULL;
+               free_field(subset->options[i].field);
+       }
+
+       talloc_free(subset->options);
+       talloc_free(subset->active);
+       subset->options = NULL;
+       subset->active = NULL;
+       subset->n_options = 0;
+       subset->n_active = 0;
+       subset->widget.height = 0;
+       subset->widget.focus_y = 0;
+}
+
+void widget_subset_clear_active(struct nc_widget_subset *subset)
+{
+       int i;
+
+       for (i = 0; i < subset->n_options; i++)
+               field_set_visible(subset->options[i].field, false);
+
+       talloc_free(subset->active);
+       subset->active = NULL;
+       subset->n_active = 0;
+       subset->widget.height = 0;
+       subset->widget.focus_y = 0;
+}
+
+void widget_subset_callback(void *arg,
+               struct nc_widget_subset *subset, int idx)
+{
+       subset->screen_cb(arg, subset, idx);
 }
 
 static void select_option_change(struct select_option *opt, bool selected)
@@ -471,21 +899,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;
        }
 }
@@ -527,10 +959,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;
 
@@ -549,23 +1018,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)
@@ -577,7 +1052,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,
@@ -635,16 +1110,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;
@@ -654,17 +1131,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);
 
@@ -674,7 +1162,7 @@ struct nc_widget_button *widget_new_button(struct nc_widgetset *set,
        return button;
 }
 
-static void widget_focus_change(struct nc_widget *widget, FIELD *field,
+void widget_focus_change(struct nc_widget *widget, FIELD *field,
                bool focussed)
 {
        int attr = focussed ? widget->focussed_attr : widget->unfocussed_attr;
@@ -684,36 +1172,64 @@ static void widget_focus_change(struct nc_widget *widget, FIELD *field,
 bool widgetset_process_key(struct nc_widgetset *set, int key)
 {
        struct nc_widget *widget;
-       FIELD *field;
+       FIELD *field, *tmp;
        int req = 0;
+       bool tab;
 
        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 */
        switch (key) {
        case KEY_BTAB:
+               tab = true;
+               /* fall through */
        case KEY_UP:
-               req = REQ_PREV_FIELD;
+               req = REQ_SPREV_FIELD;
                break;
        case '\t':
+               tab = true;
+               /* fall through */
        case KEY_DOWN:
-               req = REQ_NEXT_FIELD;
+               req = REQ_SNEXT_FIELD;
                break;
        case KEY_PPAGE:
-               req = REQ_FIRST_FIELD;
+               req = REQ_SFIRST_FIELD;
                break;
        case KEY_NPAGE:
-               req = REQ_LAST_FIELD;
+               req = REQ_SLAST_FIELD;
+               break;
+       case KEY_LEFT:
+               req = REQ_LEFT_FIELD;
+               break;
+       case KEY_RIGHT:
+               req = REQ_RIGHT_FIELD;
                break;
        }
 
-       widget = field_userptr(field);
        if (req) {
                widget_focus_change(widget, field, false);
                form_driver(set->form, req);
-               form_driver(set->form, REQ_END_FIELD);
+
+               /* if we're doing a tabbed-field-change, skip until we
+                * see the next widget */
+               tmp = field;
                field = current_field(set->form);
+
+               for (; tab && tmp != field && field_userptr(field) == widget;) {
+                       form_driver(set->form, req);
+                       field = current_field(set->form);
+               }
+
+               form_driver(set->form, REQ_END_FIELD);
                widget = field_userptr(field);
                widget_focus_change(widget, field, true);
                if (widget->field_focus)
@@ -723,18 +1239,19 @@ 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)
 {
        struct nc_widgetset *set = ptr;
        free_form(set->form);
-       if (set->ipv4_multi_type)
-               free_fieldtype(set->ipv4_multi_type);
+       if (set->ip_type)
+               free_fieldtype(set->ip_type);
+       if (set->ip_multi_type)
+               free_fieldtype(set->ip_multi_type);
+       if (set->url_type)
+               free_fieldtype(set->url_type);
        return 0;
 }
 
@@ -777,10 +1294,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);
@@ -834,6 +1349,7 @@ static void widgetset_remove_field(struct nc_widgetset *set, FIELD *field)
 
 DECLARE_BASEFN(textbox);
 DECLARE_BASEFN(checkbox);
+DECLARE_BASEFN(subset);
 DECLARE_BASEFN(select);
 DECLARE_BASEFN(label);
 DECLARE_BASEFN(button);
@@ -855,6 +1371,10 @@ void widget_move(struct nc_widget *widget, int y, int x)
 
        widget->x = x;
        widget->y = y;
+
+       if (x + widget->width > COLS)
+               pb_debug("%s: Widget at %d,%d runs over pad! (%d)", __func__,
+                      y, x, x + widget->width);
 }
 
 int widget_height(struct nc_widget *widget)