[Settings] Enable jump scroll in config UI
[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-X - exit configuration utility" );
330         }
331 }
332
333 /**
334  * Reveal a setting by index: Scroll the setting list to reveal the
335  * specified setting.
336  *
337  * @widget      The main loop's display widget.
338  * @n           The index of the setting to reveal.
339  */
340 static void reveal ( struct setting_widget *widget, unsigned int n)
341 {
342         unsigned int i;
343
344         /* Simply return if setting N is already on-screen. */
345         if ( n - widget->first_visible < SETTINGS_LIST_ROWS )
346                 return;
347         
348         /* Jump scroll to make the specified setting visible. */
349         while ( widget->first_visible < n )
350                 widget->first_visible += SETTINGS_LIST_ROWS;
351         while ( widget->first_visible > n )
352                 widget->first_visible -= SETTINGS_LIST_ROWS;
353         
354         /* Draw elipses before and/or after the settings list to
355            represent any invisible settings. */
356         mvaddstr ( SETTINGS_LIST_ROW - 1,
357                    SETTINGS_LIST_COL + 1,
358                    widget->first_visible > 0 ? "..." : "   " );
359         mvaddstr ( SETTINGS_LIST_ROW + SETTINGS_LIST_ROWS,
360                    SETTINGS_LIST_COL + 1,
361                    ( widget->first_visible + SETTINGS_LIST_ROWS < NUM_SETTINGS
362                      ? "..."
363                      : "   " ) );
364         
365         /* Draw visible settings. */
366         for ( i = 0; i < SETTINGS_LIST_ROWS; i++ ) {
367                 if ( widget->first_visible + i < NUM_SETTINGS ) {
368                         select_setting ( widget, widget->first_visible + i );
369                         draw_setting ( widget );
370                 } else {
371                         clearmsg ( SETTINGS_LIST_ROW + i );
372                 }
373         }
374
375         /* Set the widget to the current row, which will be redrawn
376            appropriately by the main loop. */
377         select_setting ( widget, n );
378 }
379
380 static int main_loop ( struct settings *settings ) {
381         struct setting_widget widget;
382         unsigned int current = 0;
383         unsigned int next;
384         int key;
385         int rc;
386
387         /* Print initial screen content */
388         draw_title_row();
389         color_set ( CPAIR_NORMAL, NULL );
390         init_widget ( &widget, settings );
391         
392         while ( 1 ) {
393                 /* Redraw information and instruction rows */
394                 draw_info_row ( widget.setting );
395                 draw_instruction_row ( widget.editing );
396
397                 /* Redraw current setting */
398                 color_set ( ( widget.editing ? CPAIR_EDIT : CPAIR_SELECT ),
399                             NULL );
400                 draw_setting ( &widget );
401                 color_set ( CPAIR_NORMAL, NULL );
402
403                 key = getkey();
404                 if ( widget.editing ) {
405                         key = edit_setting ( &widget, key );
406                         switch ( key ) {
407                         case CR:
408                         case LF:
409                                 if ( ( rc = save_setting ( &widget ) ) != 0 ) {
410                                         alert ( " Could not set %s: %s ",
411                                                 widget.setting->name,
412                                                 strerror ( rc ) );
413                                 }
414                                 /* Fall through */
415                         case CTRL_C:
416                                 load_setting ( &widget );
417                                 break;
418                         default:
419                                 /* Do nothing */
420                                 break;
421                         }
422                 } else {
423                         next = current;
424                         switch ( key ) {
425                         case KEY_DOWN:
426                                 if ( next < ( NUM_SETTINGS - 1 ) )
427                                         reveal ( &widget, ++next );
428                                 break;
429                         case KEY_UP:
430                                 if ( next > 0 )
431                                         reveal ( &widget, --next ) ;
432                                 break;
433                         case CTRL_X:
434                                 return 0;
435                         default:
436                                 edit_setting ( &widget, key );
437                                 break;
438                         }       
439                         if ( next != current ) {
440                                 draw_setting ( &widget );
441                                 select_setting ( &widget, next );
442                                 current = next;
443                         }
444                 }
445         }
446         
447 }
448
449 int settings_ui ( struct settings *settings ) {
450         int rc;
451
452         initscr();
453         start_color();
454         init_pair ( CPAIR_NORMAL, COLOR_WHITE, COLOR_BLUE );
455         init_pair ( CPAIR_SELECT, COLOR_WHITE, COLOR_RED );
456         init_pair ( CPAIR_EDIT, COLOR_BLACK, COLOR_CYAN );
457         init_pair ( CPAIR_ALERT, COLOR_WHITE, COLOR_RED );
458         color_set ( CPAIR_NORMAL, NULL );
459         erase();
460         
461         rc = main_loop ( settings );
462
463         endwin();
464
465         return rc;
466 }