iPXE
form_ui.c
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2024 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301, USA.
18  *
19  * You can also choose to distribute this program under the terms of
20  * the Unmodified Binary Distribution Licence (as given in the file
21  * COPYING.UBDL), provided that you have satisfied its requirements.
22  */
23 
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25 FILE_SECBOOT ( PERMITTED );
26 
27 /** @file
28  *
29  * Text widget forms
30  *
31  */
32 
33 #include <stdlib.h>
34 #include <string.h>
35 #include <errno.h>
36 #include <ipxe/ansicol.h>
37 #include <ipxe/dynui.h>
38 #include <ipxe/jumpscroll.h>
39 #include <ipxe/settings.h>
40 #include <ipxe/editbox.h>
41 #include <ipxe/message.h>
42 
43 /** Form title row */
44 #define TITLE_ROW 1U
45 
46 /** Starting control row */
47 #define START_ROW 3U
48 
49 /** Ending control row */
50 #define END_ROW ( LINES - 3U )
51 
52 /** Instructions row */
53 #define INSTRUCTION_ROW ( LINES - 2U )
54 
55 /** Padding between instructions */
56 #define INSTRUCTION_PAD " "
57 
58 /** Input field width */
59 #define INPUT_WIDTH ( COLS / 2U )
60 
61 /** Input field column */
62 #define INPUT_COL ( ( COLS - INPUT_WIDTH ) / 2U )
63 
64 /** A form */
65 struct form {
66  /** Dynamic user interface */
67  struct dynamic_ui *dynui;
68  /** Jump scroller */
70  /** Array of form controls */
72 };
73 
74 /** A form control */
75 struct form_control {
76  /** Dynamic user interface item */
77  struct dynamic_item *item;
78  /** Settings block */
79  struct settings *settings;
80  /** Setting */
81  struct setting setting;
82  /** Label row */
83  unsigned int row;
84  /** Editable text box */
85  struct edit_box editbox;
86  /** Modifiable setting name */
87  char *name;
88  /** Modifiable setting value */
89  char *value;
90  /** Most recent error in saving */
91  int rc;
92 };
93 
94 /**
95  * Allocate form
96  *
97  * @v dynui Dynamic user interface
98  * @ret form Form, or NULL on error
99  */
100 static struct form * alloc_form ( struct dynamic_ui *dynui ) {
101  struct form *form;
102  struct form_control *control;
103  struct dynamic_item *item;
104  char *name;
105  size_t len;
106 
107  /* Calculate total length */
108  len = sizeof ( *form );
109  list_for_each_entry ( item, &dynui->items, list ) {
110  len += sizeof ( *control );
111  if ( item->name )
112  len += ( strlen ( item->name ) + 1 /* NUL */ );
113  }
114 
115  /* Allocate and initialise structure */
116  form = zalloc ( len );
117  if ( ! form )
118  return NULL;
119  control = ( ( ( void * ) form ) + sizeof ( *form ) );
120  name = ( ( ( void * ) control ) +
121  ( dynui->count * sizeof ( *control ) ) );
122  form->dynui = dynui;
123  form->controls = control;
124  list_for_each_entry ( item, &dynui->items, list ) {
125  control->item = item;
126  if ( item->name ) {
127  control->name = name;
128  name = ( stpcpy ( name, item->name ) + 1 /* NUL */ );
129  }
130  control++;
131  }
132  assert ( ( ( void * ) name ) == ( ( ( void * ) form ) + len ) );
133 
134  return form;
135 }
136 
137 /**
138  * Free form
139  *
140  * @v form Form
141  */
142 static void free_form ( struct form *form ) {
143  unsigned int i;
144 
145  /* Free input value buffers */
146  for ( i = 0 ; i < form->dynui->count ; i++ )
147  free ( form->controls[i].value );
148 
149  /* Free form */
150  free ( form );
151 }
152 
153 /**
154  * Assign form rows
155  *
156  * @v form Form
157  * @ret rc Return status code
158  */
159 static int layout_form ( struct form *form ) {
160  struct form_control *control;
161  struct dynamic_item *item;
162  unsigned int labels = 0;
163  unsigned int inputs = 0;
164  unsigned int pad_control = 0;
165  unsigned int pad_label = 0;
166  unsigned int minimum;
167  unsigned int remaining;
168  unsigned int between;
169  unsigned int row;
170  unsigned int flags;
171  unsigned int i;
172 
173  /* Count labels and inputs */
174  for ( i = 0 ; i < form->dynui->count ; i++ ) {
175  control = &form->controls[i];
176  item = control->item;
177  if ( item->text[0] )
178  labels++;
179  if ( item->name ) {
180  if ( ! inputs )
181  form->scroll.current = i;
182  inputs++;
183  if ( item->flags & DYNUI_DEFAULT )
184  form->scroll.current = i;
185  form->scroll.count = ( i + 1 );
186  }
187  }
189  DBGC ( form, "FORM %p has %d controls (%d labels, %d inputs)\n",
190  form, form->dynui->count, labels, inputs );
191 
192  /* Refuse to create forms with no inputs */
193  if ( ! inputs )
194  return -EINVAL;
195 
196  /* Calculate minimum number of rows */
197  minimum = ( labels + ( inputs * 2 /* edit box and error message */ ) );
198  remaining = ( END_ROW - START_ROW );
199  DBGC ( form, "FORM %p has %d (of %d) usable rows\n",
200  form, remaining, LINES );
201  if ( minimum > remaining )
202  return -ERANGE;
203  remaining -= minimum;
204 
205  /* Insert blank row between controls, if space exists */
206  between = ( form->dynui->count - 1 );
207  if ( between <= remaining ) {
208  pad_control = 1;
209  remaining -= between;
210  DBGC ( form, "FORM %p padding between controls\n", form );
211  }
212 
213  /* Insert blank row after label, if space exists */
214  if ( labels <= remaining ) {
215  pad_label = 1;
216  remaining -= labels;
217  DBGC ( form, "FORM %p padding after labels\n", form );
218  }
219 
220  /* Centre on screen */
221  DBGC ( form, "FORM %p has %d spare rows\n", form, remaining );
222  row = ( START_ROW + ( remaining / 2 ) );
223 
224  /* Position each control */
225  for ( i = 0 ; i < form->dynui->count ; i++ ) {
226  control = &form->controls[i];
227  item = control->item;
228  if ( item->text[0] ) {
229  control->row = row;
230  row++; /* Label text */
231  row += pad_label;
232  }
233  if ( item->name ) {
234  flags = ( ( item->flags & DYNUI_SECRET ) ?
235  WIDGET_SECRET : 0 );
236  init_editbox ( &control->editbox, row, INPUT_COL,
237  INPUT_WIDTH, flags, &control->value );
238  row++; /* Edit box */
239  row++; /* Error message (if any) */
240  }
241  row += pad_control;
242  }
243  assert ( row <= END_ROW );
244 
245  return 0;
246 }
247 
248 /**
249  * Draw form
250  *
251  * @v form Form
252  */
253 static void draw_form ( struct form *form ) {
254  struct form_control *control;
255  unsigned int i;
256 
257  /* Clear screen */
259  erase();
260 
261  /* Draw title, if any */
262  attron ( A_BOLD );
263  if ( form->dynui->title )
264  msg ( TITLE_ROW, "%s", form->dynui->title );
265  attroff ( A_BOLD );
266 
267  /* Draw controls */
268  for ( i = 0 ; i < form->dynui->count ; i++ ) {
269  control = &form->controls[i];
270 
271  /* Draw label, if any */
272  if ( control->row )
273  msg ( control->row, "%s", control->item->text );
274 
275  /* Draw input, if any */
276  if ( control->name )
277  draw_widget ( &control->editbox.widget );
278  }
279 
280  /* Draw instructions */
281  msg ( INSTRUCTION_ROW, "%s", "Ctrl-X - save changes"
282  INSTRUCTION_PAD "Ctrl-C - discard changes" );
283 }
284 
285 /**
286  * Draw (or clear) error messages
287  *
288  * @v form Form
289  */
290 static void draw_errors ( struct form *form ) {
291  struct form_control *control;
292  unsigned int row;
293  unsigned int i;
294 
295  /* Draw (or clear) errors */
296  for ( i = 0 ; i < form->dynui->count ; i++ ) {
297  control = &form->controls[i];
298 
299  /* Skip non-input controls */
300  if ( ! control->name )
301  continue;
302 
303  /* Draw or clear error message as appropriate */
304  row = ( control->editbox.widget.row + 1 );
305  if ( control->rc != 0 ) {
307  msg ( row, " %s ", strerror ( control->rc ) );
309  } else {
310  clearmsg ( row );
311  }
312  }
313 }
314 
315 /**
316  * Parse setting names
317  *
318  * @v form Form
319  * @ret rc Return status code
320  */
321 static int parse_names ( struct form *form ) {
322  struct form_control *control;
323  unsigned int i;
324  int rc;
325 
326  /* Parse all setting names */
327  for ( i = 0 ; i < form->dynui->count ; i++ ) {
328  control = &form->controls[i];
329 
330  /* Skip labels */
331  if ( ! control->name ) {
332  DBGC ( form, "FORM %p item %d is a label\n", form, i );
333  continue;
334  }
335 
336  /* Parse setting name */
337  DBGC ( form, "FORM %p item %d is for %s\n",
338  form, i, control->name );
339  if ( ( rc = parse_setting_name ( control->name,
341  &control->settings,
342  &control->setting ) ) != 0 )
343  return rc;
344 
345  /* Apply default type if necessary */
346  if ( ! control->setting.type )
347  control->setting.type = &setting_type_string;
348  }
349 
350  return 0;
351 }
352 
353 /**
354  * Load current input values
355  *
356  * @v form Form
357  */
358 static void load_values ( struct form *form ) {
359  struct form_control *control;
360  unsigned int i;
361 
362  /* Fetch all current setting values */
363  for ( i = 0 ; i < form->dynui->count ; i++ ) {
364  control = &form->controls[i];
365  if ( ! control->name )
366  continue;
367  fetchf_setting_copy ( control->settings, &control->setting,
368  NULL, &control->setting,
369  &control->value );
370  }
371 }
372 
373 /**
374  * Store current input values
375  *
376  * @v form Form
377  * @ret rc Return status code
378  */
379 static int save_values ( struct form *form ) {
380  struct form_control *control;
381  unsigned int i;
382  int rc = 0;
383 
384  /* Store all current setting values */
385  for ( i = 0 ; i < form->dynui->count ; i++ ) {
386  control = &form->controls[i];
387  if ( ! control->name )
388  continue;
389  control->rc = storef_setting ( control->settings,
390  &control->setting,
391  control->value );
392  if ( control->rc != 0 )
393  rc = control->rc;
394  }
395 
396  return rc;
397 }
398 
399 /**
400  * Submit form
401  *
402  * @v form Form
403  * @ret rc Return status code
404  */
405 static int submit_form ( struct form *form ) {
406  int rc;
407 
408  /* Attempt to save values */
409  rc = save_values ( form );
410 
411  /* Draw (or clear) errors */
412  draw_errors ( form );
413 
414  return rc;
415 }
416 
417 /**
418  * Form main loop
419  *
420  * @v form Form
421  * @ret rc Return status code
422  */
423 static int form_loop ( struct form *form ) {
424  struct jump_scroller *scroll = &form->scroll;
425  struct form_control *control;
426  struct dynamic_item *item;
427  unsigned int move;
428  unsigned int i;
429  int key;
430  int rc;
431 
432  /* Main loop */
433  while ( 1 ) {
434 
435  /* Draw current input */
436  control = &form->controls[scroll->current];
437  draw_widget ( &control->editbox.widget );
438 
439  /* Process keypress */
440  key = edit_widget ( &control->editbox.widget, getkey ( 0 ) );
441 
442  /* Handle scroll keys */
444 
445  /* Handle special keys */
446  switch ( key ) {
447  case CTRL_C:
448  case ESC:
449  /* Cancel form */
450  return -ECANCELED;
451  case KEY_ENTER:
452  /* Attempt to do the most intuitive thing when
453  * Enter is pressed. If we are on the last
454  * input, then submit the form. If we are
455  * editing an input which failed, then
456  * resubmit the form. Otherwise, move to the
457  * next input.
458  */
459  if ( ( control->rc == 0 ) &&
460  ( scroll->current < ( scroll->count - 1 ) ) ) {
461  move = SCROLL_DOWN;
462  break;
463  }
464  /* fall through */
465  case CTRL_X:
466  /* Submit form */
467  if ( ( rc = submit_form ( form ) ) == 0 )
468  return 0;
469  /* If current input is not the problem, move
470  * to the first input that needs fixing.
471  */
472  if ( control->rc == 0 ) {
473  for ( i = 0 ; i < form->dynui->count ; i++ ) {
474  if ( form->controls[i].rc != 0 ) {
475  scroll->current = i;
476  break;
477  }
478  }
479  }
480  break;
481  default:
482  /* Move to input with matching shortcut key, if any */
483  item = dynui_shortcut ( form->dynui, key );
484  if ( item ) {
485  scroll->current = item->index;
486  if ( ! item->name )
487  move = SCROLL_DOWN;
488  }
489  break;
490  }
491 
492  /* Move selection, if applicable */
493  while ( move ) {
495  control = &form->controls[scroll->current];
496  if ( control->name )
497  break;
498  }
499  }
500 }
501 
502 /**
503  * Show form
504  *
505  * @v dynui Dynamic user interface
506  * @ret rc Return status code
507  */
508 int show_form ( struct dynamic_ui *dynui ) {
509  struct form *form;
510  int rc;
511 
512  /* Allocate and initialise structure */
513  form = alloc_form ( dynui );
514  if ( ! form ) {
515  rc = -ENOMEM;
516  goto err_alloc;
517  }
518 
519  /* Parse setting names and load current values */
520  if ( ( rc = parse_names ( form ) ) != 0 )
521  goto err_parse_names;
522  load_values ( form );
523 
524  /* Lay out form on screen */
525  if ( ( rc = layout_form ( form ) ) != 0 )
526  goto err_layout;
527 
528  /* Draw initial form */
529  initscr();
530  start_color();
531  draw_form ( form );
532 
533  /* Run main loop */
534  if ( ( rc = form_loop ( form ) ) != 0 )
535  goto err_loop;
536 
537  err_loop:
539  endwin();
540  err_layout:
541  err_parse_names:
542  free_form ( form );
543  err_alloc:
544  return rc;
545 }
int getkey(unsigned long timeout)
Get single keypress.
Definition: getkey.c:72
#define KEY_ENTER
Definition: keys.h:129
const char * title
Title.
Definition: dynui.h:22
#define EINVAL
Invalid argument.
Definition: errno.h:429
struct arbelprm_rc_send_wqe rc
Definition: arbel.h:14
const char * name
Definition: ath9k_hw.c:1986
static void draw_form(struct form *form)
Draw form.
Definition: form_ui.c:253
A dynamic user interface item.
Definition: dynui.h:30
Editable text box widget.
static void draw_widget(struct widget *widget)
Draw text widget.
Definition: widget.h:87
void msg(unsigned int row, const char *fmt,...)
Print message centred on specified row.
Definition: message.c:62
int erase(void)
Completely clear the screen.
Definition: clear.c:98
static int parse_names(struct form *form)
Parse setting names.
Definition: form_ui.c:321
#define TITLE_ROW
Form title row.
Definition: form_ui.c:44
int fetchf_setting_copy(struct settings *settings, const struct setting *setting, struct settings **origin, struct setting *fetched, char **value)
Fetch copy of formatted value of setting.
Definition: settings.c:1277
Error codes.
static int attroff(int attrs)
Definition: curses.h:509
FILE_LICENCE(GPL2_OR_LATER_OR_UBDL)
Widget contains a secret.
Definition: widget.h:35
void clearmsg(unsigned int row)
Clear message on specified row.
Definition: message.c:75
#define DBGC(...)
Definition: compiler.h:505
#define start_color()
Definition: curses.h:397
int endwin(void)
Finalise console environment.
Definition: wininit.c:32
static int layout_form(struct form *form)
Assign form rows.
Definition: form_ui.c:159
FILE_SECBOOT(PERMITTED)
#define INSTRUCTION_ROW
Instructions row.
Definition: form_ui.c:53
#define LINES(...)
Define inline lines.
Definition: linebuf_test.c:44
unsigned int count
Number of user interface items.
Definition: dynui.h:26
struct dynamic_item * item
Dynamic user interface item.
Definition: form_ui.c:77
A jump scroller.
Definition: jumpscroll.h:16
Message printing.
#define ECANCELED
Operation canceled.
Definition: errno.h:344
#define CPAIR_NORMAL
Normal text.
Definition: ansicol.h:41
static int save_values(struct form *form)
Store current input values.
Definition: form_ui.c:379
struct edit_box editbox
Editable text box.
Definition: form_ui.c:85
int rc
Most recent error in saving.
Definition: form_ui.c:91
#define CTRL_X
Definition: keys.h:42
#define ENOMEM
Not enough space.
Definition: errno.h:535
char * name
Modifiable setting name.
Definition: form_ui.c:87
char * stpcpy(char *dest, const char *src)
Copy string.
Definition: string.c:327
A form.
Definition: form_ui.c:65
static void free_form(struct form *form)
Free form.
Definition: form_ui.c:142
struct list_head items
Dynamic user interface items.
Definition: dynui.h:24
WINDOW * initscr(void)
Initialise console environment.
Definition: wininit.c:18
#define START_ROW
Starting control row.
Definition: form_ui.c:47
const char * name
Name.
Definition: dynui.h:34
assert((readw(&hdr->flags) &(GTF_reading|GTF_writing))==0)
static void load_values(struct form *form)
Load current input values.
Definition: form_ui.c:358
struct dynamic_item * dynui_shortcut(struct dynamic_ui *dynui, int key)
Find dynamic user interface item by shortcut key.
Definition: dynui.c:212
#define list_for_each_entry(pos, head, member)
Iterate over entries in a list.
Definition: list.h:432
unsigned int count
Total number of items.
Definition: jumpscroll.h:20
ring len
Length.
Definition: dwmac.h:231
#define CPAIR_ALERT
Error text.
Definition: ansicol.h:53
#define CTRL_C
Definition: keys.h:21
struct settings * autovivify_child_settings(struct settings *parent, const char *name)
Find or create child settings block.
Definition: settings.c:307
static struct form * alloc_form(struct dynamic_ui *dynui)
Allocate form.
Definition: form_ui.c:100
#define ESC
Escape character.
Definition: ansiesc.h:93
static void draw_errors(struct form *form)
Draw (or clear) error messages.
Definition: form_ui.c:290
An editable text box widget.
Definition: editbox.h:18
Configuration settings.
int storef_setting(struct settings *settings, const struct setting *setting, const char *value)
Store formatted value of setting.
Definition: settings.c:1320
uint8_t flags
Flags.
Definition: ena.h:18
#define ERANGE
Result too large.
Definition: errno.h:640
char * strerror(int errno)
Retrieve string representation of error number.
Definition: strerror.c:79
static void(* free)(struct refcnt *refcnt))
Definition: refcnt.h:55
void * zalloc(size_t size)
Allocate cleared memory.
Definition: malloc.c:662
#define INPUT_COL
Input field column.
Definition: form_ui.c:62
static int form_loop(struct form *form)
Form main loop.
Definition: form_ui.c:423
unsigned int jump_scroll_move(struct jump_scroller *scroll, unsigned int move)
Move scroller.
Definition: jumpscroll.c:93
uint32_t control
Control.
Definition: myson.h:14
size_t strlen(const char *src)
Get length of string.
Definition: string.c:244
A settings block.
Definition: settings.h:133
A form control.
Definition: form_ui.c:75
struct jump_scroller scroll
Jump scroller.
Definition: form_ui.c:69
#define SCROLL_DOWN
Scroll down by one line.
Definition: jumpscroll.h:54
A setting.
Definition: settings.h:24
int show_form(struct dynamic_ui *dynui)
Show form.
Definition: form_ui.c:508
unsigned int row
Label row.
Definition: form_ui.c:83
A dynamic user interface.
Definition: dynui.h:16
static void init_editbox(struct edit_box *box, unsigned int row, unsigned int col, unsigned int width, unsigned int flags, char **buf)
Initialise text box widget.
Definition: editbox.h:40
struct form_control * controls
Array of form controls.
Definition: form_ui.c:71
#define INSTRUCTION_PAD
Padding between instructions.
Definition: form_ui.c:56
static int submit_form(struct form *form)
Submit form.
Definition: form_ui.c:405
const char * text
Text.
Definition: dynui.h:36
#define DYNUI_DEFAULT
Dynamic user interface item is default selection.
Definition: dynui.h:46
struct settings * settings
Settings block.
Definition: form_ui.c:79
static int move(int y, int x)
Definition: curses.h:594
Jump scrolling.
unsigned int rows
Maximum number of visible rows.
Definition: jumpscroll.h:18
#define color_set(cpno, opts)
Definition: curses.h:241
Dynamic user interfaces.
#define A_BOLD
Definition: curses.h:139
#define INPUT_WIDTH
Input field width.
Definition: form_ui.c:59
struct dynamic_ui * dynui
Dynamic user interface.
Definition: form_ui.c:67
unsigned int flags
Flags.
Definition: dynui.h:40
unsigned int current
Currently selected item.
Definition: jumpscroll.h:22
unsigned int index
Index.
Definition: dynui.h:38
struct list_head list
List of dynamic user interface items.
Definition: dynui.h:32
#define NULL
NULL pointer (VOID *)
Definition: Base.h:322
#define DYNUI_SECRET
Dynamic user interface item represents a secret.
Definition: dynui.h:49
String functions.
unsigned int jump_scroll_key(struct jump_scroller *scroll, int key)
Jump scrolling.
Definition: jumpscroll.c:43
static int edit_widget(struct widget *widget, int key)
Edit text widget.
Definition: widget.h:104
union @391 key
Sense key.
Definition: scsi.h:18
ANSI colours.
char * value
Modifiable setting value.
Definition: form_ui.c:89
int parse_setting_name(char *name, get_child_settings_t get_child, struct settings **settings, struct setting *setting)
Parse setting name.
Definition: settings.c:1529
static int attron(int attrs)
Definition: curses.h:513
#define END_ROW
Ending control row.
Definition: form_ui.c:50