[Settings] Add setting deletion (Ctrl-D) interface
[gpxe.git] / src / hci / tui / settings_ui.c
1 /*
2  * Copyright (C) 2006 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., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 FILE_LICENCE ( GPL2_OR_LATER );
20
21 #include <stdio.h>
22 #include <stdarg.h>
23 #include <unistd.h>
24 #include <string.h>
25 #include <curses.h>
26 #include <console.h>
27 #include <gpxe/settings.h>
28 #include <gpxe/editbox.h>
29 #include <gpxe/keys.h>
30 #include <gpxe/settings_ui.h>
31
32 /** @file
33  *
34  * Option configuration console
35  *
36  */
37
38 /* Colour pairs */
39 #define CPAIR_NORMAL    1
40 #define CPAIR_SELECT    2
41 #define CPAIR_EDIT      3
42 #define CPAIR_ALERT     4
43
44 /* Screen layout */
45 #define TITLE_ROW               1
46 #define SETTINGS_LIST_ROW       3
47 #define SETTINGS_LIST_COL       1
48 #define SETTINGS_LIST_ROWS      16
49 #define INFO_ROW                20
50 #define ALERT_ROW               20
51 #define INSTRUCTION_ROW         22
52 #define INSTRUCTION_PAD "     "
53
54 /** Layout of text within a setting widget */
55 struct setting_row {
56         char start[0];
57         char pad1[1];
58         char name[15];
59         char pad2[1];
60         char value[60];
61         char pad3[1];
62         char nul;
63 } __attribute__ (( packed ));
64
65 /** A setting widget */
66 struct setting_widget {
67         /** Settings block */
68         struct settings *settings;
69         /** Index of the first visible setting, for scrolling. */
70         unsigned int first_visible;
71         /** Configuration setting */
72         struct setting *setting;
73         /** Screen row */
74         unsigned int row;
75         /** Screen column */
76         unsigned int col;
77         /** Edit box widget used for editing setting */
78         struct edit_box editbox;
79         /** Editing in progress flag */
80         int editing;
81         /** Buffer for setting's value */
82         char value[256]; /* enough size for a DHCP string */
83 };
84
85 /** Number of registered configuration settings */
86 #define NUM_SETTINGS table_num_entries ( SETTINGS )
87
88 static void load_setting ( struct setting_widget *widget ) __nonnull;
89 static int save_setting ( struct setting_widget *widget ) __nonnull;
90 static void init_widget ( struct setting_widget *widget,
91                            struct settings *settings ) __nonnull;
92 static void draw_setting ( struct setting_widget *widget ) __nonnull;
93 static int edit_setting ( struct setting_widget *widget, int key ) __nonnull;
94 static void select_setting ( struct setting_widget *widget,
95                              unsigned int index ) __nonnull;
96 static void reveal ( struct setting_widget *widget, unsigned int n) __nonnull;
97 static void vmsg ( unsigned int row, const char *fmt, va_list args ) __nonnull;
98 static void msg ( unsigned int row, const char *fmt, ... ) __nonnull;
99 static void valert ( const char *fmt, va_list args ) __nonnull;
100 static void alert ( const char *fmt, ... ) __nonnull;
101 static void draw_info_row ( struct setting *setting ) __nonnull;
102 static int main_loop ( struct settings *settings ) __nonnull;
103
104 /**
105  * Load setting widget value from configuration settings
106  *
107  * @v widget            Setting widget
108  *
109  */
110 static void load_setting ( struct setting_widget *widget ) {
111
112         /* Mark as not editing */
113         widget->editing = 0;
114
115         /* Read current setting value */
116         if ( fetchf_setting ( widget->settings, widget->setting,
117                               widget->value, sizeof ( widget->value ) ) < 0 ) {
118                 widget->value[0] = '\0';
119         }       
120
121         /* Initialise edit box */
122         init_editbox ( &widget->editbox, widget->value,
123                        sizeof ( widget->value ), NULL, widget->row,
124                        ( widget->col + offsetof ( struct setting_row, value )),
125                        sizeof ( ( ( struct setting_row * ) NULL )->value ), 0);
126 }
127
128 /**
129  * Save setting widget value back to configuration settings
130  *
131  * @v widget            Setting widget
132  */
133 static int save_setting ( struct setting_widget *widget ) {
134         return storef_setting ( widget->settings, widget->setting,
135                                 widget->value );
136 }
137
138 /**
139  * Initialise the scrolling setting widget, drawing initial display.
140  *
141  * @v widget            Setting widget
142  * @v settings          Settings block
143  */
144 static void init_widget ( struct setting_widget *widget,
145                           struct settings *settings ) {
146         memset ( widget, 0, sizeof ( *widget ) );
147         widget->settings = settings;
148         widget->first_visible = SETTINGS_LIST_ROWS;
149         reveal ( widget, 0 );
150 }
151
152 /**
153  * Draw setting widget
154  *
155  * @v widget            Setting widget
156  */
157 static void draw_setting ( struct setting_widget *widget ) {
158         struct setting_row row;
159         unsigned int len;
160         unsigned int curs_col;
161         char *value;
162
163         /* Fill row with spaces */
164         memset ( &row, ' ', sizeof ( row ) );
165         row.nul = '\0';
166
167         /* Construct dot-padded name */
168         memset ( row.name, '.', sizeof ( row.name ) );
169         len = strlen ( widget->setting->name );
170         if ( len > sizeof ( row.name ) )
171                 len = sizeof ( row.name );
172         memcpy ( row.name, widget->setting->name, len );
173
174         /* Construct space-padded value */
175         value = widget->value;
176         if ( ! *value )
177                 value = "<not specified>";
178         len = strlen ( value );
179         if ( len > sizeof ( row.value ) )
180                 len = sizeof ( row.value );
181         memcpy ( row.value, value, len );
182         curs_col = ( widget->col + offsetof ( typeof ( row ), value )
183                      + len );
184
185         /* Print row */
186         mvprintw ( widget->row, widget->col, "%s", row.start );
187         move ( widget->row, curs_col );
188         if ( widget->editing )
189                 draw_editbox ( &widget->editbox );
190 }
191
192 /**
193  * Edit setting widget
194  *
195  * @v widget            Setting widget
196  * @v key               Key pressed by user
197  * @ret key             Key returned to application, or zero
198  */
199 static int edit_setting ( struct setting_widget *widget, int key ) {
200         widget->editing = 1;
201         return edit_editbox ( &widget->editbox, key );
202 }
203
204 /**
205  * Select a setting for display updates, by index.
206  *
207  * @v widget            Setting widget
208  * @v settings          Settings block
209  * @v index             Index of setting with settings list
210  */
211 static void select_setting ( struct setting_widget *widget,
212                              unsigned int index ) {
213         struct setting *all_settings = table_start ( SETTINGS );
214         unsigned int skip = offsetof ( struct setting_widget, setting );
215
216         /* Reset the widget, preserving static state. */
217         memset ( ( char * ) widget + skip, 0, sizeof ( *widget ) - skip );
218         widget->setting = &all_settings[index];
219         widget->row = SETTINGS_LIST_ROW + index - widget->first_visible;
220         widget->col = SETTINGS_LIST_COL;
221
222         /* Read current setting value */
223         load_setting ( widget );
224 }
225
226 /**
227  * Print message centred on specified row
228  *
229  * @v row               Row
230  * @v fmt               printf() format string
231  * @v args              printf() argument list
232  */
233 static void vmsg ( unsigned int row, const char *fmt, va_list args ) {
234         char buf[COLS];
235         size_t len;
236
237         len = vsnprintf ( buf, sizeof ( buf ), fmt, args );
238         mvprintw ( row, ( ( COLS - len ) / 2 ), "%s", buf );
239 }
240
241 /**
242  * Print message centred on specified row
243  *
244  * @v row               Row
245  * @v fmt               printf() format string
246  * @v ..                printf() arguments
247  */
248 static void msg ( unsigned int row, const char *fmt, ... ) {
249         va_list args;
250
251         va_start ( args, fmt );
252         vmsg ( row, fmt, args );
253         va_end ( args );
254 }
255
256 /**
257  * Clear message on specified row
258  *
259  * @v row               Row
260  */
261 static void clearmsg ( unsigned int row ) {
262         move ( row, 0 );
263         clrtoeol();
264 }
265
266 /**
267  * Print alert message
268  *
269  * @v fmt               printf() format string
270  * @v args              printf() argument list
271  */
272 static void valert ( const char *fmt, va_list args ) {
273         clearmsg ( ALERT_ROW );
274         color_set ( CPAIR_ALERT, NULL );
275         vmsg ( ALERT_ROW, fmt, args );
276         sleep ( 2 );
277         color_set ( CPAIR_NORMAL, NULL );
278         clearmsg ( ALERT_ROW );
279 }
280
281 /**
282  * Print alert message
283  *
284  * @v fmt               printf() format string
285  * @v ...               printf() arguments
286  */
287 static void alert ( const char *fmt, ... ) {
288         va_list args;
289
290         va_start ( args, fmt );
291         valert ( fmt, args );
292         va_end ( args );
293 }
294
295 /**
296  * Draw title row
297  */
298 static void draw_title_row ( void ) {
299         attron ( A_BOLD );
300         msg ( TITLE_ROW, "gPXE option configuration console" );
301         attroff ( A_BOLD );
302 }
303
304 /**
305  * Draw information row
306  *
307  * @v setting           Current configuration setting
308  */
309 static void draw_info_row ( struct setting *setting ) {
310         clearmsg ( INFO_ROW );
311         attron ( A_BOLD );
312         msg ( INFO_ROW, "%s - %s", setting->name, setting->description );
313         attroff ( A_BOLD );
314 }
315
316 /**
317  * Draw instruction row
318  *
319  * @v editing           Editing in progress flag
320  */
321 static void draw_instruction_row ( int editing ) {
322         clearmsg ( INSTRUCTION_ROW );
323         if ( editing ) {
324                 msg ( INSTRUCTION_ROW,
325                       "Enter - accept changes" INSTRUCTION_PAD
326                       "Ctrl-C - discard changes" );
327         } else {
328                 msg ( INSTRUCTION_ROW,
329                       "Ctrl-D - delete setting" INSTRUCTION_PAD
330                       "Ctrl-X - exit configuration utility" );
331         }
332 }
333
334 /**
335  * Reveal a setting by index: Scroll the setting list to reveal the
336  * specified setting.
337  *
338  * @widget      The main loop's display widget.
339  * @n           The index of the setting to reveal.
340  */
341 static void reveal ( struct setting_widget *widget, unsigned int n)
342 {
343         unsigned int i;
344
345         /* Simply return if setting N is already on-screen. */
346         if ( n - widget->first_visible < SETTINGS_LIST_ROWS )
347                 return;
348         
349         /* Jump scroll to make the specified setting visible. */
350         while ( widget->first_visible < n )
351                 widget->first_visible += SETTINGS_LIST_ROWS;
352         while ( widget->first_visible > n )
353                 widget->first_visible -= SETTINGS_LIST_ROWS;
354         
355         /* Draw elipses before and/or after the settings list to
356            represent any invisible settings. */
357         mvaddstr ( SETTINGS_LIST_ROW - 1,
358                    SETTINGS_LIST_COL + 1,
359                    widget->first_visible > 0 ? "..." : "   " );
360         mvaddstr ( SETTINGS_LIST_ROW + SETTINGS_LIST_ROWS,
361                    SETTINGS_LIST_COL + 1,
362                    ( widget->first_visible + SETTINGS_LIST_ROWS < NUM_SETTINGS
363                      ? "..."
364                      : "   " ) );
365         
366         /* Draw visible settings. */
367         for ( i = 0; i < SETTINGS_LIST_ROWS; i++ ) {
368                 if ( widget->first_visible + i < NUM_SETTINGS ) {
369                         select_setting ( widget, widget->first_visible + i );
370                         draw_setting ( widget );
371                 } else {
372                         clearmsg ( SETTINGS_LIST_ROW + i );
373                 }
374         }
375
376         /* Set the widget to the current row, which will be redrawn
377            appropriately by the main loop. */
378         select_setting ( widget, n );
379 }
380
381 static int main_loop ( struct settings *settings ) {
382         struct setting_widget widget;
383         unsigned int current = 0;
384         unsigned int next;
385         int key;
386         int rc;
387
388         /* Print initial screen content */
389         draw_title_row();
390         color_set ( CPAIR_NORMAL, NULL );
391         init_widget ( &widget, settings );
392         
393         while ( 1 ) {
394                 /* Redraw information and instruction rows */
395                 draw_info_row ( widget.setting );
396                 draw_instruction_row ( widget.editing );
397
398                 /* Redraw current setting */
399                 color_set ( ( widget.editing ? CPAIR_EDIT : CPAIR_SELECT ),
400                             NULL );
401                 draw_setting ( &widget );
402                 color_set ( CPAIR_NORMAL, NULL );
403
404                 key = getkey();
405                 if ( widget.editing ) {
406                         key = edit_setting ( &widget, key );
407                         switch ( key ) {
408                         case CR:
409                         case LF:
410                                 if ( ( rc = save_setting ( &widget ) ) != 0 ) {
411                                         alert ( " Could not set %s: %s ",
412                                                 widget.setting->name,
413                                                 strerror ( rc ) );
414                                 }
415                                 /* Fall through */
416                         case CTRL_C:
417                                 load_setting ( &widget );
418                                 break;
419                         default:
420                                 /* Do nothing */
421                                 break;
422                         }
423                 } else {
424                         next = current;
425                         switch ( key ) {
426                         case KEY_DOWN:
427                                 if ( next < ( NUM_SETTINGS - 1 ) )
428                                         reveal ( &widget, ++next );
429                                 break;
430                         case KEY_UP:
431                                 if ( next > 0 )
432                                         reveal ( &widget, --next ) ;
433                                 break;
434                         case CTRL_D:
435                                 delete_setting ( widget.settings,
436                                                  widget.setting );
437                                 select_setting ( &widget, next );
438                                 draw_setting ( &widget );
439                                 break;
440                         case CTRL_X:
441                                 return 0;
442                         default:
443                                 edit_setting ( &widget, key );
444                                 break;
445                         }       
446                         if ( next != current ) {
447                                 draw_setting ( &widget );
448                                 select_setting ( &widget, next );
449                                 current = next;
450                         }
451                 }
452         }
453         
454 }
455
456 int settings_ui ( struct settings *settings ) {
457         int rc;
458
459         initscr();
460         start_color();
461         init_pair ( CPAIR_NORMAL, COLOR_WHITE, COLOR_BLUE );
462         init_pair ( CPAIR_SELECT, COLOR_WHITE, COLOR_RED );
463         init_pair ( CPAIR_EDIT, COLOR_BLACK, COLOR_CYAN );
464         init_pair ( CPAIR_ALERT, COLOR_WHITE, COLOR_RED );
465         color_set ( CPAIR_NORMAL, NULL );
466         erase();
467         
468         rc = main_loop ( settings );
469
470         endwin();
471
472         return rc;
473 }