ui/ncurses: Add simple ncurses form widget set
authorJeremy Kerr <jk@ozlabs.org>
Mon, 21 Oct 2013 06:09:43 +0000 (14:09 +0800)
committerJeremy Kerr <jk@ozlabs.org>
Wed, 13 Nov 2013 09:55:31 +0000 (17:55 +0800)
We're doing a lot of the same stuff with forms, and we'll to implement a
lot of fields with the upcoming configuration interface.

This change introduces a simple ncurses widget set.

Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
ui/ncurses/Makefile.am
ui/ncurses/nc-widgets.c [new file with mode: 0644]
ui/ncurses/nc-widgets.h [new file with mode: 0644]

index ac4ca4643ce638f2ddee568784385f60258ebe38..a341780281b467cd04f2cbaee7483238c8863fc8 100644 (file)
@@ -38,7 +38,9 @@ libpbnc_la_SOURCES = \
        nc-scr.c \
        nc-scr.h \
        nc-sysinfo.c \
-       nc-sysinfo.h
+       nc-sysinfo.h \
+       nc-widgets.c \
+       nc-widgets.h
 
 sbin_PROGRAMS = petitboot-nc
 
diff --git a/ui/ncurses/nc-widgets.c b/ui/ncurses/nc-widgets.c
new file mode 100644 (file)
index 0000000..d17034d
--- /dev/null
@@ -0,0 +1,728 @@
+/*
+ *  Copyright (C) 2013 IBM Corporation
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <ctype.h>
+
+#include <talloc/talloc.h>
+#include <types/types.h>
+#include <log/log.h>
+#include <util/util.h>
+
+#include "config.h"
+#include "nc-cui.h"
+#include "nc-widgets.h"
+
+#undef move
+
+#define to_checkbox(w) container_of(w, struct nc_widget_checkbox, widget)
+#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)
+
+static const char *checkbox_checked_str = "[*]";
+static const char *checkbox_unchecked_str = "[ ]";
+
+static const char *select_selected_str = "(*)";
+static const char *select_unselected_str = "( )";
+
+struct nc_widgetset {
+       WINDOW  *mainwin;
+       WINDOW  *subwin;
+       FORM    *form;
+       FIELD   **fields;
+       int     n_fields, n_alloc_fields;
+       void    (*widget_focus)(struct nc_widget *, void *);
+       void    *widget_focus_arg;
+       FIELD   *cur_field;
+};
+
+struct nc_widget {
+       FIELD   *field;
+       bool    (*process_key)(struct nc_widget *, FORM *, int);
+       void    (*set_visible)(struct nc_widget *, bool);
+       void    (*move)(struct nc_widget *, int, int);
+       int     focussed_attr;
+       int     unfocussed_attr;
+       int     height;
+       int     width;
+       int     x;
+       int     y;
+};
+
+struct nc_widget_label {
+       struct nc_widget        widget;
+       const char              *text;
+};
+
+struct nc_widget_checkbox {
+       struct nc_widget        widget;
+       bool                    checked;
+};
+
+struct nc_widget_textbox {
+       struct nc_widget        widget;
+};
+
+struct nc_widget_select {
+       struct nc_widget        widget;
+       struct select_option {
+               char            *str;
+               int             val;
+               FIELD           *field;
+       } *options;
+       int                     top, left, size;
+       int                     n_options, selected_option;
+       struct nc_widgetset     *set;
+       void                    (*on_change)(void *, int);
+       void                    *on_change_arg;
+};
+
+struct nc_widget_button {
+       struct nc_widget        widget;
+       void                    (*click)(void *arg);
+       void                    *arg;
+};
+
+static void widgetset_add_field(struct nc_widgetset *set, FIELD *field);
+
+static bool process_key_nop(struct nc_widget *widget __attribute__((unused)),
+               FORM *form __attribute((unused)),
+               int key __attribute__((unused)))
+{
+       return false;
+}
+
+static void field_set_visible(FIELD *field, bool visible)
+{
+       int opts = field_opts(field) & ~O_VISIBLE;
+       if (visible)
+               opts |= O_VISIBLE;
+       set_field_opts(field, opts);
+}
+
+static void field_move(FIELD *field, int y, int x)
+{
+       move_field(field, y, x);
+}
+
+static int label_destructor(void *ptr)
+{
+       struct nc_widget_label *label = ptr;
+       free_field(label->widget.field);
+       return 0;
+}
+
+
+struct nc_widget_label *widget_new_label(struct nc_widgetset *set,
+               int y, int x, char *str)
+{
+       struct nc_widget_label *label;
+       FIELD *f;
+       int len;
+
+       len = strlen(str);
+
+       label = talloc_zero(set, struct nc_widget_label);
+       label->widget.height = 1;
+       label->widget.width = len;
+       label->widget.x = x;
+       label->widget.y = y;
+       label->widget.process_key = process_key_nop;
+       label->widget.field = f = new_field(1, len, y, x, 0, 0);
+       label->widget.focussed_attr = A_NORMAL;
+       label->widget.unfocussed_attr = A_NORMAL;
+
+       field_opts_off(f, O_ACTIVE);
+       set_field_buffer(f, 0, str);
+       set_field_userptr(f, &label->widget);
+
+       widgetset_add_field(set, label->widget.field);
+       talloc_set_destructor(label, label_destructor);
+
+       return label;
+}
+
+bool widget_checkbox_get_value(struct nc_widget_checkbox *checkbox)
+{
+       return checkbox->checked;
+}
+
+static void checkbox_set_buffer(struct nc_widget_checkbox *checkbox)
+{
+       const char *str;
+       str = checkbox->checked ? checkbox_checked_str : checkbox_unchecked_str;
+       set_field_buffer(checkbox->widget.field, 0, str);
+}
+
+static bool checkbox_process_key(struct nc_widget *widget,
+               FORM *form __attribute__((unused)), int key)
+{
+       struct nc_widget_checkbox *checkbox = to_checkbox(widget);
+
+       if (key != ' ')
+               return false;
+
+       checkbox->checked = !checkbox->checked;
+       checkbox_set_buffer(checkbox);
+
+       return true;
+}
+
+static int checkbox_destructor(void *ptr)
+{
+       struct nc_widget_checkbox *checkbox = ptr;
+       free_field(checkbox->widget.field);
+       return 0;
+}
+
+struct nc_widget_checkbox *widget_new_checkbox(struct nc_widgetset *set,
+               int y, int x, bool checked)
+{
+       struct nc_widget_checkbox *checkbox;
+       FIELD *f;
+
+       checkbox = talloc_zero(set, struct nc_widget_checkbox);
+       checkbox->checked = checked;
+       checkbox->widget.height = 1;
+       checkbox->widget.width = strlen(checkbox_checked_str);
+       checkbox->widget.x = x;
+       checkbox->widget.y = y;
+       checkbox->widget.process_key = checkbox_process_key;
+       checkbox->widget.focussed_attr = A_REVERSE;
+       checkbox->widget.unfocussed_attr = A_NORMAL;
+       checkbox->widget.field = f = new_field(1, strlen(checkbox_checked_str),
+                       y, x, 0, 0);
+
+       field_opts_off(f, O_EDIT);
+       set_field_userptr(f, &checkbox->widget);
+       checkbox_set_buffer(checkbox);
+
+       widgetset_add_field(set, checkbox->widget.field);
+       talloc_set_destructor(checkbox, checkbox_destructor);
+
+       return checkbox;
+}
+
+static char *strip_string(char *str)
+{
+       int len, i;
+
+       len = strlen(str);
+
+       /* clear trailing space */
+       for (i = len - 1; i >= 0; i--) {
+               if (!isspace(str[i]))
+                       break;
+               str[i] = '\0';
+       }
+
+       /* increment str past leading space */
+       for (i = 0; i < len; i++) {
+               if (str[i] == '\0' || !isspace(str[i]))
+                       break;
+       }
+
+       return str + i;
+}
+
+char *widget_textbox_get_value(struct nc_widget_textbox *textbox)
+{
+       char *str = field_buffer(textbox->widget.field, 0);
+       return str ? strip_string(str) : NULL;
+}
+
+static bool textbox_process_key(
+               struct nc_widget *widget __attribute__((unused)),
+               FORM *form, int key)
+{
+       switch (key) {
+       case KEY_HOME:
+               form_driver(form, REQ_BEG_FIELD);
+               break;
+       case KEY_END:
+               form_driver(form, REQ_END_FIELD);
+               break;
+       case KEY_LEFT:
+               form_driver(form, REQ_LEFT_CHAR);
+               break;
+       case KEY_RIGHT:
+               form_driver(form, REQ_RIGHT_CHAR);
+               break;
+       case KEY_BACKSPACE:
+               if (form_driver(form, REQ_LEFT_CHAR) != E_OK)
+                       break;
+               /* fall through */
+       case KEY_DC:
+               form_driver(form, REQ_DEL_CHAR);
+               break;
+       default:
+               form_driver(form, key);
+               break;
+       }
+
+       return true;
+}
+
+static int textbox_destructor(void *ptr)
+{
+       struct nc_widget_textbox *textbox = ptr;
+       free_field(textbox->widget.field);
+       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 *textbox;
+       FIELD *f;
+
+       textbox = talloc_zero(set, struct nc_widget_textbox);
+       textbox->widget.height = 1;
+       textbox->widget.width = len;
+       textbox->widget.x = x;
+       textbox->widget.y = y;
+       textbox->widget.process_key = textbox_process_key;
+       textbox->widget.field = f = new_field(1, len, y, x, 0, 0);
+       textbox->widget.focussed_attr = A_REVERSE;
+       textbox->widget.unfocussed_attr = A_UNDERLINE;
+
+       field_opts_off(f, O_STATIC | O_WRAP | O_BLANK);
+       set_field_buffer(f, 0, str);
+       set_field_back(f, textbox->widget.unfocussed_attr);
+       set_field_userptr(f, &textbox->widget);
+
+       widgetset_add_field(set, textbox->widget.field);
+       talloc_set_destructor(textbox, textbox_destructor);
+
+       return textbox;
+}
+
+static void select_option_change(struct select_option *opt, bool selected)
+{
+       const char *str;
+
+       str = selected ? select_selected_str : select_unselected_str;
+
+       memcpy(opt->str, str, strlen(str));
+       set_field_buffer(opt->field, 0, opt->str);
+}
+
+static bool select_process_key(struct nc_widget *w, FORM *form, int key)
+{
+       struct nc_widget_select *select = to_select(w);
+       struct select_option *new_opt, *old_opt;
+       int i, new_idx;
+       FIELD *field;
+
+       switch (key) {
+       case ' ':
+       case KEY_ENTER:
+               break;
+       default:
+               return false;
+       }
+
+       field = current_field(form);
+       new_opt = NULL;
+
+       for (i = 0; i < select->n_options; i++) {
+               if (select->options[i].field == field) {
+                       new_opt = &select->options[i];
+                       new_idx = i;
+                       break;
+               }
+       }
+
+       if (!new_opt)
+               return true;
+
+       if (new_idx == select->selected_option)
+               return true;
+
+       old_opt = &select->options[select->selected_option];
+
+       select_option_change(old_opt, false);
+       select_option_change(new_opt, true);
+
+       select->selected_option = new_idx;
+
+       if (select->on_change)
+               select->on_change(select->on_change_arg, new_opt->val);
+
+       return true;
+}
+
+static void select_set_visible(struct nc_widget *widget, bool visible)
+{
+       struct nc_widget_select *select = to_select(widget);
+       int i;
+
+       for (i = 0; i < select->n_options; i++)
+               field_set_visible(select->options[i].field, visible);
+}
+
+static void select_move(struct nc_widget *widget, int y, int x)
+{
+       struct nc_widget_select *select = to_select(widget);
+       int i;
+
+       for (i = 0; i < select->n_options; i++)
+               field_move(select->options[i].field, y + i, x);
+}
+
+static int select_destructor(void *ptr)
+{
+       struct nc_widget_select *select = ptr;
+       int i;
+
+       for (i = 0; i < select->n_options; i++)
+               free_field(select->options[i].field);
+
+       return 0;
+}
+
+struct nc_widget_select *widget_new_select(struct nc_widgetset *set,
+               int y, int x, int len)
+{
+       struct nc_widget_select *select;
+
+       select = talloc_zero(set, struct nc_widget_select);
+       select->widget.width = len;
+       select->widget.height = 0;
+       select->widget.x = x;
+       select->widget.y = y;
+       select->widget.process_key = select_process_key;
+       select->widget.set_visible = select_set_visible;
+       select->widget.move = select_move;
+       select->widget.focussed_attr = A_REVERSE;
+       select->widget.unfocussed_attr = A_NORMAL;
+       select->top = y;
+       select->left = x;
+       select->size = len;
+       select->set = set;
+
+       talloc_set_destructor(select, select_destructor);
+
+       return select;
+}
+
+void widget_select_add_option(struct nc_widget_select *select, int value,
+               const char *text, bool selected)
+{
+       const char *str;
+       FIELD *f;
+       int i;
+
+       /* if we never see an option with selected set, we want the first
+        * one to be selected */
+       if (select->n_options == 0)
+               selected = true;
+       else if (selected)
+               select_option_change(&select->options[select->selected_option],
+                                       false);
+
+       if (selected) {
+               select->selected_option = select->n_options;
+               str = select_selected_str;
+       } else
+               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].field = f = new_field(1, select->size,
+                                               select->top + i,
+                                               select->left, 0, 0);
+
+       field_opts_off(f, O_WRAP | O_EDIT);
+       set_field_userptr(f, &select->widget);
+       set_field_buffer(f, 0, select->options[i].str);
+
+       widgetset_add_field(select->set, f);
+}
+
+int widget_select_get_value(struct nc_widget_select *select)
+{
+       return select->options[select->selected_option].val;
+}
+
+int widget_select_height(struct nc_widget_select *select)
+{
+       return select->n_options;
+}
+
+void widget_select_on_change(struct nc_widget_select *select,
+               void (*on_change)(void *, int), void *arg)
+{
+       select->on_change = on_change;
+       select->on_change_arg = arg;
+}
+
+static bool button_process_key(struct nc_widget *widget,
+               FORM *form __attribute__((unused)), int key)
+{
+       struct nc_widget_button *button = to_button(widget);
+
+       if (!button->click)
+               return false;
+
+       switch (key) {
+       case ' ':
+       case '\r':
+       case '\n':
+               button->click(button->arg);
+               return true;
+       }
+
+       return false;
+}
+
+static int button_destructor(void *ptr)
+{
+       struct nc_widget_button *button = ptr;
+       free_field(button->widget.field);
+       return 0;
+}
+
+struct nc_widget_button *widget_new_button(struct nc_widgetset *set,
+               int y, int x, int size, const char *str,
+               void (*click)(void *), void *arg)
+{
+       struct nc_widget_button *button;
+       char *text;
+       FIELD *f;
+       int idx, len;
+
+       button = talloc_zero(set, struct nc_widget_button);
+       button->widget.height = 1;
+       button->widget.width = size;
+       button->widget.x = x;
+       button->widget.y = y;
+       button->widget.field = f = new_field(1, size + 2, y, x, 0, 0);
+       button->widget.process_key = button_process_key;
+       button->widget.focussed_attr = A_REVERSE;
+       button->widget.unfocussed_attr = A_NORMAL;
+       button->click = click;
+       button->arg = arg;
+
+       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;
+
+       text = talloc_array(button, char, size + 3);
+       memset(text, ' ', size + 2);
+       memcpy(text + idx + 1, str, len);
+       text[0] = '[';
+       text[size + 1] = ']';
+       text[size + 2] = '\0';
+
+       set_field_buffer(f, 0, text);
+
+       widgetset_add_field(set, button->widget.field);
+       talloc_set_destructor(button, button_destructor);
+
+       return button;
+}
+
+static void widget_focus_change(struct nc_widget *widget, FIELD *field,
+               bool focussed)
+{
+       int attr = focussed ? widget->focussed_attr : widget->unfocussed_attr;
+       set_field_back(field, attr);
+}
+
+bool widgetset_process_key(struct nc_widgetset *set, int key)
+{
+       struct nc_widget *widget;
+       FIELD *field;
+       int req = 0;
+
+       field = current_field(set->form);
+       assert(field);
+
+       /* handle field change events */
+       switch (key) {
+       case KEY_BTAB:
+       case KEY_UP:
+               req = REQ_PREV_FIELD;
+               break;
+       case '\t':
+       case KEY_DOWN:
+               req = REQ_NEXT_FIELD;
+               break;
+       case KEY_PPAGE:
+               req = REQ_FIRST_FIELD;
+               break;
+       case KEY_NPAGE:
+               req = REQ_LAST_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);
+               field = current_field(set->form);
+               widget = field_userptr(field);
+               widget_focus_change(widget, field, true);
+               if (set->widget_focus)
+                       set->widget_focus(widget, set->widget_focus_arg);
+               return true;
+       }
+
+       if (!widget->process_key)
+               return false;
+
+       return widget->process_key(widget, set->form, key);
+}
+
+static int widgetset_destructor(void *ptr)
+{
+       struct nc_widgetset *set = ptr;
+       free_form(set->form);
+       return 0;
+}
+
+struct nc_widgetset *widgetset_create(void *ctx, WINDOW *main, WINDOW *sub)
+{
+       struct nc_widgetset *set;
+
+       set = talloc_zero(ctx, struct nc_widgetset);
+       set->n_alloc_fields = 8;
+       set->mainwin = main;
+       set->subwin = sub;
+       set->fields = talloc_array(set, FIELD *, set->n_alloc_fields);
+       talloc_set_destructor(set, widgetset_destructor);
+
+       return set;
+}
+
+void widgetset_set_widget_focus(struct nc_widgetset *set,
+               widget_focus_cb cb, void *arg)
+{
+       set->widget_focus = cb;
+       set->widget_focus_arg = arg;
+}
+
+void widgetset_post(struct nc_widgetset *set)
+{
+       struct nc_widget *widget;
+       FIELD *field;
+
+       set->form = new_form(set->fields);
+       set_form_win(set->form, set->mainwin);
+       set_form_sub(set->form, set->subwin);
+       post_form(set->form);
+       form_driver(set->form, REQ_END_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);
+       widget_focus_change(widget, field, true);
+       if (set->widget_focus)
+               set->widget_focus(widget, set->widget_focus_arg);
+}
+
+void widgetset_unpost(struct nc_widgetset *set)
+{
+       set->cur_field = current_field(set->form);
+       unpost_form(set->form);
+       free_form(set->form);
+       set->form = NULL;
+}
+
+static void widgetset_add_field(struct nc_widgetset *set, FIELD *field)
+{
+       if (set->n_fields == set->n_alloc_fields - 1) {
+               set->n_alloc_fields *= 2;
+               set->fields = talloc_realloc(set, set->fields,
+                               FIELD *, set->n_alloc_fields);
+       }
+
+       set->n_fields++;
+       set->fields[set->n_fields - 1] = field;
+       set->fields[set->n_fields] = NULL;
+}
+
+#define DECLARE_BASEFN(type) \
+       struct nc_widget *widget_ ## type ## _base              \
+               (struct nc_widget_ ## type *w)                  \
+       { return &w->widget; }
+
+DECLARE_BASEFN(textbox);
+DECLARE_BASEFN(checkbox);
+DECLARE_BASEFN(select);
+DECLARE_BASEFN(label);
+DECLARE_BASEFN(button);
+
+void widget_set_visible(struct nc_widget *widget, bool visible)
+{
+       if (widget->set_visible)
+               widget->set_visible(widget, visible);
+       else
+               field_set_visible(widget->field, visible);
+}
+
+void widget_move(struct nc_widget *widget, int y, int x)
+{
+       if (widget->move)
+               widget->move(widget, y, x);
+       else
+               field_move(widget->field, y, x);
+
+       widget->x = x;
+       widget->y = y;
+}
+
+int widget_height(struct nc_widget *widget)
+{
+       return widget->height;
+}
+
+int widget_width(struct nc_widget *widget)
+{
+       return widget->width;
+}
+
+int widget_x(struct nc_widget *widget)
+{
+       return widget->x;
+}
+
+int widget_y(struct nc_widget *widget)
+{
+       return widget->y;
+}
+
diff --git a/ui/ncurses/nc-widgets.h b/ui/ncurses/nc-widgets.h
new file mode 100644 (file)
index 0000000..53f1583
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ *  Copyright (C) 2013 IBM Corporation
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef NC_WIDGETS_H
+
+struct nc_widgetset;
+struct nc_widget_label;
+struct nc_widget_checkbox;
+struct nc_widget_textbox;
+struct nc_widget_button;
+
+struct nc_widget_label *widget_new_label(struct nc_widgetset *set,
+               int y, int x, char *str);
+struct nc_widget_checkbox *widget_new_checkbox(struct nc_widgetset *set,
+               int y, int x, bool checked);
+struct nc_widget_textbox *widget_new_textbox(struct nc_widgetset *set,
+               int y, int x, int len, char *str);
+struct nc_widget_select *widget_new_select(struct nc_widgetset *set,
+               int y, int x, int len);
+struct nc_widget_button *widget_new_button(struct nc_widgetset *set,
+               int y, int x, int size, const char *str,
+               void (*click)(void *), void *arg);
+
+void widget_select_add_option(struct nc_widget_select *select, int value,
+               const char *text, bool selected);
+
+void widget_select_on_change(struct nc_widget_select *select,
+               void (*on_change)(void *arg, int value), void *arg);
+
+char *widget_textbox_get_value(struct nc_widget_textbox *textbox);
+bool widget_checkbox_get_value(struct nc_widget_checkbox *checkbox);
+int widget_select_get_value(struct nc_widget_select *select);
+int widget_select_height(struct nc_widget_select *select);
+
+/* generic widget API */
+struct nc_widget *widget_textbox_base(struct nc_widget_textbox *textbox);
+struct nc_widget *widget_checkbox_base(struct nc_widget_checkbox *checkbox);
+struct nc_widget *widget_select_base(struct nc_widget_select *select);
+struct nc_widget *widget_label_base(struct nc_widget_label *label);
+struct nc_widget *widget_button_base(struct nc_widget_button *button);
+
+void widget_move(struct nc_widget *widget, int y, int x);
+void widget_set_visible(struct nc_widget *widget, bool visible);
+int widget_height(struct nc_widget *widget);
+int widget_width(struct nc_widget *widget);
+int widget_y(struct nc_widget *widget);
+int widget_x(struct nc_widget *widget);
+
+/* widgetset API */
+typedef void (*widget_focus_cb)(struct nc_widget *widget, void *arg);
+struct nc_widgetset *widgetset_create(void *ctx, WINDOW *main, WINDOW *sub);
+void widgetset_set_widget_focus(struct nc_widgetset *set,
+               widget_focus_cb cb, void *arg);
+void widgetset_post(struct nc_widgetset *set);
+void widgetset_unpost(struct nc_widgetset *set);
+bool widgetset_process_key(struct nc_widgetset *set, int key);
+
+#endif /* NC_WIDGETS_H */
+