From 43e9c8147f74fba794fd280fa0fdb57af8b968f4 Mon Sep 17 00:00:00 2001 From: Jeremy Kerr Date: Mon, 21 Oct 2013 14:09:43 +0800 Subject: ui/ncurses: Add simple ncurses form widget set 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 --- ui/ncurses/Makefile.am | 4 +- ui/ncurses/nc-widgets.c | 728 ++++++++++++++++++++++++++++++++++++++++++++++++ ui/ncurses/nc-widgets.h | 73 +++++ 3 files changed, 804 insertions(+), 1 deletion(-) create mode 100644 ui/ncurses/nc-widgets.c create mode 100644 ui/ncurses/nc-widgets.h (limited to 'ui') diff --git a/ui/ncurses/Makefile.am b/ui/ncurses/Makefile.am index ac4ca46..a341780 100644 --- a/ui/ncurses/Makefile.am +++ b/ui/ncurses/Makefile.am @@ -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 index 0000000..d17034d --- /dev/null +++ b/ui/ncurses/nc-widgets.c @@ -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 +#include + +#include +#include +#include +#include + +#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 index 0000000..53f1583 --- /dev/null +++ b/ui/ncurses/nc-widgets.h @@ -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 */ + -- cgit v1.2.1