From dc118c53696af6a0b1a8ee78fc9a4d28a217fb21 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 15 May 2024 14:10:33 +0100 Subject: [hci] Provide a general concept of a text widget set Create a generic abstraction of a text widget, refactor the existing editable text box widget to use this abstraction, add an implementation of a non-editable text label widget, and generalise the login user interface to use this generic widget abstraction. Signed-off-by: Michael Brown --- src/hci/mucurses/widgets/editbox.c | 65 ++++++++-------- src/hci/mucurses/widgets/label.c | 77 +++++++++++++++++++ src/hci/tui/login_ui.c | 106 +++++++++----------------- src/hci/tui/settings_ui.c | 14 ++-- src/hci/tui/widget_ui.c | 143 +++++++++++++++++++++++++++++++++++ src/include/ipxe/editbox.h | 50 +++++------- src/include/ipxe/errfile.h | 1 + src/include/ipxe/label.h | 42 +++++++++++ src/include/ipxe/widget.h | 151 +++++++++++++++++++++++++++++++++++++ 9 files changed, 509 insertions(+), 140 deletions(-) create mode 100644 src/hci/mucurses/widgets/label.c create mode 100644 src/hci/tui/widget_ui.c create mode 100644 src/include/ipxe/label.h create mode 100644 src/include/ipxe/widget.h diff --git a/src/hci/mucurses/widgets/editbox.c b/src/hci/mucurses/widgets/editbox.c index fddd274..79e7cdd 100644 --- a/src/hci/mucurses/widgets/editbox.c +++ b/src/hci/mucurses/widgets/editbox.c @@ -25,6 +25,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include +#include #include /** @file @@ -36,38 +37,15 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define EDITBOX_MIN_CHARS 3 /** - * Initialise text box widget - * - * @v box Editable text box widget - * @v buf Dynamically allocated string buffer - * @v win Containing window - * @v row Row - * @v col Starting column - * @v width Width - * @v flags Flags - */ -void init_editbox ( struct edit_box *box, char **buf, - WINDOW *win, unsigned int row, unsigned int col, - unsigned int width, unsigned int flags ) { - memset ( box, 0, sizeof ( *box ) ); - init_editstring ( &box->string, buf ); - box->string.cursor = ( *buf ? strlen ( *buf ) : 0 ); - box->win = ( win ? win : stdscr ); - box->row = row; - box->col = col; - box->width = width; - box->flags = flags; -} - -/** * Draw text box widget * - * @v box Editable text box widget - * + * @v widgets Text widget set + * @v widget Text widget */ -void draw_editbox ( struct edit_box *box ) { +static void draw_editbox ( struct widgets *widgets, struct widget *widget ) { + struct edit_box *box = container_of ( widget, struct edit_box, widget ); const char *content = *(box->string.buf); - size_t width = box->width; + size_t width = widget->width; char buf[ width + 1 ]; signed int cursor_offset, underflow, overflow, first; size_t len; @@ -93,15 +71,36 @@ void draw_editbox ( struct edit_box *box ) { len = ( content ? ( strlen ( content ) - first ) : 0 ); if ( len > width ) len = width; - if ( box->flags & EDITBOX_STARS ) { + if ( widget->flags & WIDGET_SECRET ) { memset ( buf, '*', len ); } else { memcpy ( buf, ( content + first ), len ); } /* Print box content and move cursor */ - if ( ! box->win ) - box->win = stdscr; - mvwprintw ( box->win, box->row, box->col, "%s", buf ); - wmove ( box->win, box->row, ( box->col + cursor_offset ) ); + color_set ( CPAIR_EDIT, NULL ); + mvwprintw ( widgets->win, widget->row, widget->col, "%s", buf ); + wmove ( widgets->win, widget->row, ( widget->col + cursor_offset ) ); + color_set ( CPAIR_NORMAL, NULL ); } + +/** + * Edit text box widget + * + * @v widgets Text widget set + * @v widget Text widget + * @v key Key pressed by user + * @ret key Key returned to application, or zero + */ +static int edit_editbox ( struct widgets *widgets __unused, + struct widget *widget, int key ) { + struct edit_box *box = container_of ( widget, struct edit_box, widget ); + + return edit_string ( &box->string, key ); +} + +/** Text box widget operations */ +struct widget_operations editbox_operations = { + .draw = draw_editbox, + .edit = edit_editbox, +}; diff --git a/src/hci/mucurses/widgets/label.c b/src/hci/mucurses/widgets/label.c new file mode 100644 index 0000000..29645a6 --- /dev/null +++ b/src/hci/mucurses/widgets/label.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 Michael Brown . + * + * 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; either version 2 of the + * License, or any later version. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include + +/** @file + * + * Text label widget + * + */ + +/** + * Draw text label widget + * + * @v widgets Text widget set + * @v widget Text widget + */ +static void draw_label ( struct widgets *widgets, struct widget *widget ) { + struct label *label = container_of ( widget, struct label, widget ); + unsigned int width = widget->width; + unsigned int col = widget->col; + const char *text = label->text; + + /* Centre label if width is non-zero */ + if ( width ) + col += ( ( width - strlen ( text ) ) / 2 ); + + /* Print label content */ + attron ( A_BOLD ); + mvwprintw ( widgets->win, widget->row, col, "%s", text ); + attroff ( A_BOLD ); +} + +/** + * Edit text label widget + * + * @v widgets Text widget set + * @v widget Text widget + * @v key Key pressed by user + * @ret key Key returned to application, or zero + */ +static int edit_label ( struct widgets *widgets __unused, + struct widget *widget __unused, int key ) { + + /* Cannot be edited */ + return key; +} + +/** Text label widget operations */ +struct widget_operations label_operations = { + .draw = draw_label, + .edit = edit_label, +}; diff --git a/src/hci/tui/login_ui.c b/src/hci/tui/login_ui.c index bd24991..b76afb9 100644 --- a/src/hci/tui/login_ui.c +++ b/src/hci/tui/login_ui.c @@ -35,6 +35,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include #include #include @@ -45,90 +46,55 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define USERNAME_ROW ( ( LINES / 2U ) - 2U ) #define PASSWORD_LABEL_ROW ( ( LINES / 2U ) + 2U ) #define PASSWORD_ROW ( ( LINES / 2U ) + 4U ) -#define LABEL_COL ( ( COLS / 2U ) - 4U ) -#define EDITBOX_COL ( ( COLS / 2U ) - 10U ) -#define EDITBOX_WIDTH 20U +#define WIDGET_COL ( ( COLS / 2U ) - 10U ) +#define WIDGET_WIDTH 20U int login_ui ( void ) { char *username; char *password; - struct edit_box username_box; - struct edit_box password_box; - struct edit_box *current_box = &username_box; - int key; - int rc = -EINPROGRESS; + struct { + struct widgets widgets; + struct label username_label; + struct label password_label; + struct edit_box username_box; + struct edit_box password_box; + } widgets; + int rc; /* Fetch current setting values */ fetchf_setting_copy ( NULL, &username_setting, NULL, NULL, &username ); fetchf_setting_copy ( NULL, &password_setting, NULL, NULL, &password ); - /* Initialise UI */ - initscr(); - start_color(); - init_editbox ( &username_box, &username, NULL, USERNAME_ROW, - EDITBOX_COL, EDITBOX_WIDTH, 0 ); - init_editbox ( &password_box, &password, NULL, PASSWORD_ROW, - EDITBOX_COL, EDITBOX_WIDTH, EDITBOX_STARS ); + /* Construct user interface */ + memset ( &widgets, 0, sizeof ( widgets ) ); + init_widgets ( &widgets.widgets, NULL ); + init_label ( &widgets.username_label, USERNAME_LABEL_ROW, WIDGET_COL, + WIDGET_WIDTH, "Username" ); + init_label ( &widgets.password_label, PASSWORD_LABEL_ROW, WIDGET_COL, + WIDGET_WIDTH, "Password" ); + init_editbox ( &widgets.username_box, USERNAME_ROW, WIDGET_COL, + WIDGET_WIDTH, 0, &username ); + init_editbox ( &widgets.password_box, PASSWORD_ROW, WIDGET_COL, + WIDGET_WIDTH, WIDGET_SECRET, &password ); + add_widget ( &widgets.widgets, &widgets.username_label.widget ); + add_widget ( &widgets.widgets, &widgets.password_label.widget ); + add_widget ( &widgets.widgets, &widgets.username_box.widget ); + add_widget ( &widgets.widgets, &widgets.password_box.widget ); - /* Draw initial UI */ - color_set ( CPAIR_NORMAL, NULL ); - erase(); - attron ( A_BOLD ); - mvprintw ( USERNAME_LABEL_ROW, LABEL_COL, "Username:" ); - mvprintw ( PASSWORD_LABEL_ROW, LABEL_COL, "Password:" ); - attroff ( A_BOLD ); - color_set ( CPAIR_EDIT, NULL ); - draw_editbox ( &username_box ); - draw_editbox ( &password_box ); - - /* Main loop */ - while ( rc == -EINPROGRESS ) { - - draw_editbox ( current_box ); - - key = getkey ( 0 ); - switch ( key ) { - case KEY_DOWN: - current_box = &password_box; - break; - case KEY_UP: - current_box = &username_box; - break; - case TAB: - current_box = ( ( current_box == &username_box ) ? - &password_box : &username_box ); - break; - case KEY_ENTER: - if ( current_box == &username_box ) { - current_box = &password_box; - } else { - rc = 0; - } - break; - case CTRL_C: - case ESC: - rc = -ECANCELED; - break; - default: - edit_editbox ( current_box, key ); - break; - } - } - - /* Terminate UI */ - color_set ( CPAIR_NORMAL, NULL ); - erase(); - endwin(); + /* Present user interface */ + if ( ( rc = widget_ui ( &widgets.widgets ) ) != 0 ) + goto err_ui; /* Store settings on successful completion */ - if ( rc == 0 ) - rc = storef_setting ( NULL, &username_setting, username ); - if ( rc == 0 ) - rc = storef_setting ( NULL, &password_setting, password ); + if ( ( rc = storef_setting ( NULL, &username_setting, username ) ) !=0) + goto err_store_username; + if ( ( rc = storef_setting ( NULL, &password_setting, password ) ) !=0) + goto err_store_password; - /* Free setting values */ + err_store_username: + err_store_password: + err_ui: free ( username ); free ( password ); - return rc; } diff --git a/src/hci/tui/settings_ui.c b/src/hci/tui/settings_ui.c index 95cbd77..53bf24d 100644 --- a/src/hci/tui/settings_ui.c +++ b/src/hci/tui/settings_ui.c @@ -108,6 +108,8 @@ struct settings_ui { struct jump_scroller scroll; /** Current row */ struct settings_ui_row row; + /** Widget set used for editing setting */ + struct widgets widgets; }; /** @@ -164,10 +166,11 @@ static unsigned int select_setting_row ( struct settings_ui *ui, } /* Initialise edit box */ - init_editbox ( &ui->row.editbox, &ui->row.buf, NULL, ui->row.row, + memset ( &ui->row.editbox, 0, sizeof ( ui->row.editbox ) ); + init_editbox ( &ui->row.editbox, ui->row.row, ( SETTINGS_LIST_COL + offsetof ( typeof ( *text ), u.setting.value ) ), - sizeof ( text->u.setting.value ), 0 ); + sizeof ( text->u.setting.value ), 0, &ui->row.buf ); return count; } @@ -250,7 +253,7 @@ static void draw_setting_row ( struct settings_ui *ui ) { static int edit_setting ( struct settings_ui *ui, int key ) { assert ( ui->row.setting.name != NULL ); ui->row.editing = 1; - return edit_editbox ( &ui->row.editbox, key ); + return edit_widget ( &ui->widgets, &ui->row.editbox.widget, key ); } /** @@ -454,6 +457,7 @@ static int main_loop ( struct settings *settings ) { /* Print initial screen content */ color_set ( CPAIR_NORMAL, NULL ); memset ( &ui, 0, sizeof ( ui ) ); + init_widgets ( &ui.widgets, NULL ); select_settings ( &ui, settings ); while ( 1 ) { @@ -477,9 +481,7 @@ static int main_loop ( struct settings *settings ) { assert ( ui.row.setting.name != NULL ); /* Redraw edit box */ - color_set ( CPAIR_EDIT, NULL ); - draw_editbox ( &ui.row.editbox ); - color_set ( CPAIR_NORMAL, NULL ); + draw_widget ( &ui.widgets, &ui.row.editbox.widget ); /* Process keypress */ key = edit_setting ( &ui, getkey ( 0 ) ); diff --git a/src/hci/tui/widget_ui.c b/src/hci/tui/widget_ui.c new file mode 100644 index 0000000..5079eef --- /dev/null +++ b/src/hci/tui/widget_ui.c @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2024 Michael Brown . + * + * 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; either version 2 of the + * License, or any later version. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * Text widget UI + * + */ + +#include +#include +#include +#include + +/** + * Find editable widget in widget set + * + * @v widgets Text widget set + * @v index Editable widget index + * @ret widget Editable widget, or NULL + */ +static struct widget * find_widget ( struct widgets *widgets, + unsigned int index ) { + struct widget *widget; + + list_for_each_entry ( widget, &widgets->list, list ) { + if ( ! ( widget->flags & WIDGET_EDITABLE ) ) + continue; + if ( index-- == 0 ) + return widget; + } + return NULL; +} + +/** + * Text widget user interface main loop + * + * @v widgets Text widget set + * @ret rc Return status code + */ +static int widget_ui_loop ( struct widgets *widgets ) { + struct widget *widget; + unsigned int current; + unsigned int count; + int key; + + /* Draw all widgets */ + list_for_each_entry ( widget, &widgets->list, list ) + draw_widget ( widgets, widget ); + + /* Count editable widgets */ + count = 0; + while ( find_widget ( widgets, count ) != NULL ) + count++; + + /* Main loop */ + current = 0; + while ( 1 ) { + + /* Identify current widget */ + widget = find_widget ( widgets, current ); + if ( ! widget ) + return -ENOENT; + + /* Redraw current widget */ + draw_widget ( widgets, widget ); + + /* Process keypress */ + key = edit_widget ( widgets, widget, getkey ( 0 ) ); + switch ( key ) { + case KEY_UP: + if ( current > 0 ) + current--; + break; + case KEY_DOWN: + if ( ++current == count ) + current--; + break; + case TAB: + if ( ++current == count ) + current = 0; + break; + case KEY_ENTER: + current++; + if ( current >= count ) + return 0; + break; + case CTRL_C: + case ESC: + return -ECANCELED; + default: + /* Do nothing for unrecognised keys or edit errors */ + break; + } + } +} + +/** + * Present text widget user interface + * + * @v widgets Text widget set + * @ret rc Return status code + */ +int widget_ui ( struct widgets *widgets ) { + int rc; + + /* Initialise UI */ + initscr(); + start_color(); + color_set ( CPAIR_NORMAL, NULL ); + erase(); + + /* Run main loop */ + rc = widget_ui_loop ( widgets ); + + /* Terminate UI */ + color_set ( CPAIR_NORMAL, NULL ); + endwin(); + + return rc; +} diff --git a/src/include/ipxe/editbox.h b/src/include/ipxe/editbox.h index c14bbdc..1f62485 100644 --- a/src/include/ipxe/editbox.h +++ b/src/include/ipxe/editbox.h @@ -11,51 +11,39 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include +#include /** An editable text box widget */ struct edit_box { + /** Text widget */ + struct widget widget; /** Editable string */ struct edit_string string; - /** Containing window */ - WINDOW *win; - /** Row */ - unsigned int row; - /** Starting column */ - unsigned int col; - /** Width */ - unsigned int width; /** First displayed character */ unsigned int first; - /** Flags */ - unsigned int flags; }; -/** Editable text box widget flags */ -enum edit_box_flags { - /** Show stars instead of contents (for password widgets) */ - EDITBOX_STARS = 0x0001, -}; - -extern void init_editbox ( struct edit_box *box, char **buf, - WINDOW *win, unsigned int row, unsigned int col, - unsigned int width, unsigned int flags ) - __attribute__ (( nonnull (1, 2) )); -extern void draw_editbox ( struct edit_box *box ) __nonnull; -static inline int edit_editbox ( struct edit_box *box, int key ) __nonnull; +extern struct widget_operations editbox_operations; /** - * Edit text box widget + * Initialise text box widget * * @v box Editable text box widget - * @v key Key pressed by user - * @ret key Key returned to application, or zero - * - * You must call draw_editbox() to update the display after calling - * edit_editbox(). - * + * @v row Row + * @v col Starting column + * @v width Width + * @v flags Flags + * @v buf Dynamically allocated string buffer */ -static inline int edit_editbox ( struct edit_box *box, int key ) { - return edit_string ( &box->string, key ); +static inline __attribute__ (( always_inline )) void +init_editbox ( struct edit_box *box, unsigned int row, unsigned int col, + unsigned int width, unsigned int flags, char **buf ) { + + init_widget ( &box->widget, &editbox_operations, row, col, + width, ( flags | WIDGET_EDITABLE ) ); + init_editstring ( &box->string, buf ); + if ( *buf ) + box->string.cursor = strlen ( *buf ); } #endif /* _IPXE_EDITBOX_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 96981dd..a9ce656 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -417,6 +417,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_x25519 ( ERRFILE_OTHER | 0x005f0000 ) #define ERRFILE_des ( ERRFILE_OTHER | 0x00600000 ) #define ERRFILE_editstring ( ERRFILE_OTHER | 0x00610000 ) +#define ERRFILE_widget_ui ( ERRFILE_OTHER | 0x00620000 ) /** @} */ diff --git a/src/include/ipxe/label.h b/src/include/ipxe/label.h new file mode 100644 index 0000000..48e36cb --- /dev/null +++ b/src/include/ipxe/label.h @@ -0,0 +1,42 @@ +#ifndef _IPXE_LABEL_H +#define _IPXE_LABEL_H + +/** @file + * + * Text label widget + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include + +/** A text label widget */ +struct label { + /** Text widget */ + struct widget widget; + /** Label text */ + const char *text; +}; + +extern struct widget_operations label_operations; + +/** + * Initialise text label widget + * + * @v label Text label widget + * @v row Row + * @v col Starting column + * @v width Width + * @v text Label text + */ +static inline __attribute__ (( always_inline )) void +init_label ( struct label *label, unsigned int row, unsigned int col, + unsigned int width, const char *text ) { + + init_widget ( &label->widget, &label_operations, row, col, width, 0 ); + label->text = text; +} + +#endif /* _IPXE_LABEL_H */ diff --git a/src/include/ipxe/widget.h b/src/include/ipxe/widget.h new file mode 100644 index 0000000..fe93b9f --- /dev/null +++ b/src/include/ipxe/widget.h @@ -0,0 +1,151 @@ +#ifndef _IPXE_WIDGET_H +#define _IPXE_WIDGET_H + +/** @file + * + * Text widgets + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include + +/** A text widget set */ +struct widgets { + /** List of widgets (in tab order) */ + struct list_head list; + /** Containing window */ + WINDOW *win; +}; + +/** A text widget */ +struct widget { + /** List of widgets (in tab order) */ + struct list_head list; + /** Widget operations */ + struct widget_operations *op; + + /** Row */ + unsigned int row; + /** Starting column */ + unsigned int col; + /** Width */ + unsigned int width; + /** Flags */ + unsigned int flags; +}; + +/** Text widget flags */ +enum widget_flags { + /** Widget may have input focus */ + WIDGET_EDITABLE = 0x0001, + /** Widget contains a secret */ + WIDGET_SECRET = 0x0002, +}; + +/** Text widget operations */ +struct widget_operations { + /** + * Draw widget + * + * @v widgets Text widget set + * @v widget Text widget + */ + void ( * draw ) ( struct widgets *widgets, struct widget *widget ); + /** + * Edit widget + * + * @v widgets Text widget set + * @v widget Text widget + * @v key Key pressed by user + * @ret key Key returned to application, or zero + * + * This will not update the display: you must call the draw() + * method to ensure that any changes to an editable widget are + * displayed to the user. + */ + int ( * edit ) ( struct widgets *widgets, struct widget *widget, + int key ); +}; + +/** + * Initialise text widget set + * + * @v widgets Text widget set + * @v win Containing window + */ +static inline __attribute__ (( always_inline )) void +init_widgets ( struct widgets *widgets, WINDOW *win ) { + + INIT_LIST_HEAD ( &widgets->list ); + widgets->win = ( win ? win : stdscr ); +} + +/** + * Initialise text widget + * + * @v widget Text widget + * @v op Text widget operations + * @v row Row + * @v col Starting column + * @v width Width + */ +static inline __attribute__ (( always_inline )) void +init_widget ( struct widget *widget, struct widget_operations *op, + unsigned int row, unsigned int col, unsigned int width, + unsigned int flags ) { + + widget->op = op; + widget->row = row; + widget->col = col; + widget->width = width; + widget->flags = flags; +} + +/** + * Append text widget + * + * @v widgets Text widget set + * @v widget Text widget + */ +static inline __attribute__ (( always_inline )) void +add_widget ( struct widgets *widgets, struct widget *widget ) { + + list_add_tail ( &widget->list, &widgets->list ); +} + +/** + * Draw text widget + * + * @v widgets Text widget set + * @v widget Text widget + */ +static inline __attribute__ (( always_inline )) void +draw_widget ( struct widgets *widgets, struct widget *widget ) { + + widget->op->draw ( widgets, widget ); +} + +/** + * Edit text widget + * + * @v widgets Text widget set + * @v widget Text widget + * @v key Key pressed by user + * @ret key Key returned to application, or zero + * + * This will not update the display: you must call draw_widget() to + * ensure that any changes to an editable widget are displayed to the + * user. + */ +static inline __attribute__ (( always_inline )) int +edit_widget ( struct widgets *widgets, struct widget *widget, int key ) { + + return widget->op->edit ( widgets, widget, key ); +} + +extern int widget_ui ( struct widgets *widgets ); + +#endif /* _IPXE_WIDGET_H */ -- cgit v1.1