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