74ce6afb136a6ba18f518664df1b8a237b5da8dc
[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 INFO_ROW                20
49 #define ALERT_ROW               20
50 #define INSTRUCTION_ROW         22
51 #define INSTRUCTION_PAD "     "
52
53 /** Layout of text within a setting widget */
54 struct setting_row {
55         char start[0];
56         char pad1[1];
57         char name[15];
58         char pad2[1];
59         char value[60];
60         char pad3[1];
61         char nul;
62 } __attribute__ (( packed ));
63
64 /** A setting widget */
65 struct setting_widget {
66         /** Settings block */
67         struct settings *settings;
68         /** Configuration setting */
69         struct setting *setting;
70         /** Screen row */
71         unsigned int row;
72         /** Screen column */
73         unsigned int col;
74         /** Edit box widget used for editing setting */
75         struct edit_box editbox;
76         /** Editing in progress flag */
77         int editing;
78         /** Buffer for setting's value */
79         char value[256]; /* enough size for a DHCP string */
80 };
81
82 /** Number of registered configuration settings */
83 #define NUM_SETTINGS table_num_entries ( SETTINGS )
84
85 static void load_setting ( struct setting_widget *widget ) __nonnull;
86 static int save_setting ( struct setting_widget *widget ) __nonnull;
87 static void init_setting ( struct setting_widget *widget,
88                            struct settings *settings,
89                            struct setting *setting,
90                            unsigned int row, unsigned int col ) __nonnull;
91 static void draw_setting ( struct setting_widget *widget ) __nonnull;
92 static int edit_setting ( struct setting_widget *widget, int key ) __nonnull;
93 static void init_setting_index ( struct setting_widget *widget,
94                                  struct settings *settings,
95                                  unsigned int index ) __nonnull;
96 static void vmsg ( unsigned int row, const char *fmt, va_list args ) __nonnull;
97 static void msg ( unsigned int row, const char *fmt, ... ) __nonnull;
98 static void valert ( const char *fmt, va_list args ) __nonnull;
99 static void alert ( const char *fmt, ... ) __nonnull;
100 static void draw_info_row ( struct setting *setting ) __nonnull;
101 static int main_loop ( struct settings *settings ) __nonnull;
102
103 /**
104  * Load setting widget value from configuration settings
105  *
106  * @v widget            Setting widget
107  *
108  */
109 static void load_setting ( struct setting_widget *widget ) {
110
111         /* Mark as not editing */
112         widget->editing = 0;
113
114         /* Read current setting value */
115         if ( fetchf_setting ( widget->settings, widget->setting,
116                               widget->value, sizeof ( widget->value ) ) < 0 ) {
117                 widget->value[0] = '\0';
118         }       
119
120         /* Initialise edit box */
121         init_editbox ( &widget->editbox, widget->value,
122                        sizeof ( widget->value ), NULL, widget->row,
123                        ( widget->col + offsetof ( struct setting_row, value )),
124                        sizeof ( ( ( struct setting_row * ) NULL )->value ), 0);
125 }
126
127 /**
128  * Save setting widget value back to configuration settings
129  *
130  * @v widget            Setting widget
131  */
132 static int save_setting ( struct setting_widget *widget ) {
133         return storef_setting ( widget->settings, widget->setting,
134                                 widget->value );
135 }
136
137 /**
138  * Initialise setting widget
139  *
140  * @v widget            Setting widget
141  * @v settings          Settings block
142  * @v setting           Configuration setting
143  * @v row               Screen row
144  * @v col               Screen column
145  */
146 static void init_setting ( struct setting_widget *widget,
147                            struct settings *settings,
148                            struct setting *setting,
149                            unsigned int row, unsigned int col ) {
150
151         /* Initialise widget structure */
152         memset ( widget, 0, sizeof ( *widget ) );
153         widget->settings = settings;
154         widget->setting = setting;
155         widget->row = row;
156         widget->col = col;
157
158         /* Read current setting value */
159         load_setting ( widget );
160 }
161
162 /**
163  * Draw setting widget
164  *
165  * @v widget            Setting widget
166  */
167 static void draw_setting ( struct setting_widget *widget ) {
168         struct setting_row row;
169         unsigned int len;
170         unsigned int curs_col;
171         char *value;
172
173         /* Fill row with spaces */
174         memset ( &row, ' ', sizeof ( row ) );
175         row.nul = '\0';
176
177         /* Construct dot-padded name */
178         memset ( row.name, '.', sizeof ( row.name ) );
179         len = strlen ( widget->setting->name );
180         if ( len > sizeof ( row.name ) )
181                 len = sizeof ( row.name );
182         memcpy ( row.name, widget->setting->name, len );
183
184         /* Construct space-padded value */
185         value = widget->value;
186         if ( ! *value )
187                 value = "<not specified>";
188         len = strlen ( value );
189         if ( len > sizeof ( row.value ) )
190                 len = sizeof ( row.value );
191         memcpy ( row.value, value, len );
192         curs_col = ( widget->col + offsetof ( typeof ( row ), value )
193                      + len );
194
195         /* Print row */
196         mvprintw ( widget->row, widget->col, "%s", row.start );
197         move ( widget->row, curs_col );
198         if ( widget->editing )
199                 draw_editbox ( &widget->editbox );
200 }
201
202 /**
203  * Edit setting widget
204  *
205  * @v widget            Setting widget
206  * @v key               Key pressed by user
207  * @ret key             Key returned to application, or zero
208  */
209 static int edit_setting ( struct setting_widget *widget, int key ) {
210         widget->editing = 1;
211         return edit_editbox ( &widget->editbox, key );
212 }
213
214 /**
215  * Initialise setting widget by index
216  *
217  * @v widget            Setting widget
218  * @v settings          Settings block
219  * @v index             Index of setting with settings list
220  */
221 static void init_setting_index ( struct setting_widget *widget,
222                                  struct settings *settings,
223                                  unsigned int index ) {
224         struct setting *all_settings = table_start ( SETTINGS );
225
226         init_setting ( widget, settings, &all_settings[index],
227                        ( SETTINGS_LIST_ROW + index ), SETTINGS_LIST_COL );
228 }
229
230 /**
231  * Print message centred on specified row
232  *
233  * @v row               Row
234  * @v fmt               printf() format string
235  * @v args              printf() argument list
236  */
237 static void vmsg ( unsigned int row, const char *fmt, va_list args ) {
238         char buf[COLS];
239         size_t len;
240
241         len = vsnprintf ( buf, sizeof ( buf ), fmt, args );
242         mvprintw ( row, ( ( COLS - len ) / 2 ), "%s", buf );
243 }
244
245 /**
246  * Print message centred on specified row
247  *
248  * @v row               Row
249  * @v fmt               printf() format string
250  * @v ..                printf() arguments
251  */
252 static void msg ( unsigned int row, const char *fmt, ... ) {
253         va_list args;
254
255         va_start ( args, fmt );
256         vmsg ( row, fmt, args );
257         va_end ( args );
258 }
259
260 /**
261  * Clear message on specified row
262  *
263  * @v row               Row
264  */
265 static void clearmsg ( unsigned int row ) {
266         move ( row, 0 );
267         clrtoeol();
268 }
269
270 /**
271  * Print alert message
272  *
273  * @v fmt               printf() format string
274  * @v args              printf() argument list
275  */
276 static void valert ( const char *fmt, va_list args ) {
277         clearmsg ( ALERT_ROW );
278         color_set ( CPAIR_ALERT, NULL );
279         vmsg ( ALERT_ROW, fmt, args );
280         sleep ( 2 );
281         color_set ( CPAIR_NORMAL, NULL );
282         clearmsg ( ALERT_ROW );
283 }
284
285 /**
286  * Print alert message
287  *
288  * @v fmt               printf() format string
289  * @v ...               printf() arguments
290  */
291 static void alert ( const char *fmt, ... ) {
292         va_list args;
293
294         va_start ( args, fmt );
295         valert ( fmt, args );
296         va_end ( args );
297 }
298
299 /**
300  * Draw title row
301  */
302 static void draw_title_row ( void ) {
303         attron ( A_BOLD );
304         msg ( TITLE_ROW, "gPXE option configuration console" );
305         attroff ( A_BOLD );
306 }
307
308 /**
309  * Draw information row
310  *
311  * @v setting           Current configuration setting
312  */
313 static void draw_info_row ( struct setting *setting ) {
314         clearmsg ( INFO_ROW );
315         attron ( A_BOLD );
316         msg ( INFO_ROW, "%s - %s", setting->name, setting->description );
317         attroff ( A_BOLD );
318 }
319
320 /**
321  * Draw instruction row
322  *
323  * @v editing           Editing in progress flag
324  */
325 static void draw_instruction_row ( int editing ) {
326         clearmsg ( INSTRUCTION_ROW );
327         if ( editing ) {
328                 msg ( INSTRUCTION_ROW,
329                       "Enter - accept changes" INSTRUCTION_PAD
330                       "Ctrl-C - discard changes" );
331         } else {
332                 msg ( INSTRUCTION_ROW,
333                       "Ctrl-X - exit configuration utility" );
334         }
335 }
336
337 static int main_loop ( struct settings *settings ) {
338         struct setting_widget widget;
339         unsigned int current = 0;
340         unsigned int next;
341         int i;
342         int key;
343         int rc;
344
345         /* Print initial screen content */
346         draw_title_row();
347         color_set ( CPAIR_NORMAL, NULL );
348         for ( i = ( NUM_SETTINGS - 1 ) ; i >= 0 ; i-- ) {
349                 init_setting_index ( &widget, settings, i );
350                 draw_setting ( &widget );
351         }
352
353         while ( 1 ) {
354                 /* Redraw information and instruction rows */
355                 draw_info_row ( widget.setting );
356                 draw_instruction_row ( widget.editing );
357
358                 /* Redraw current setting */
359                 color_set ( ( widget.editing ? CPAIR_EDIT : CPAIR_SELECT ),
360                             NULL );
361                 draw_setting ( &widget );
362                 color_set ( CPAIR_NORMAL, NULL );
363
364                 key = getkey();
365                 if ( widget.editing ) {
366                         key = edit_setting ( &widget, key );
367                         switch ( key ) {
368                         case CR:
369                         case LF:
370                                 if ( ( rc = save_setting ( &widget ) ) != 0 ) {
371                                         alert ( " Could not set %s: %s ",
372                                                 widget.setting->name,
373                                                 strerror ( rc ) );
374                                 }
375                                 /* Fall through */
376                         case CTRL_C:
377                                 load_setting ( &widget );
378                                 break;
379                         default:
380                                 /* Do nothing */
381                                 break;
382                         }
383                 } else {
384                         next = current;
385                         switch ( key ) {
386                         case KEY_DOWN:
387                                 if ( next < ( NUM_SETTINGS - 1 ) )
388                                         next++;
389                                 break;
390                         case KEY_UP:
391                                 if ( next > 0 )
392                                         next--;
393                                 break;
394                         case CTRL_X:
395                                 return 0;
396                         default:
397                                 edit_setting ( &widget, key );
398                                 break;
399                         }       
400                         if ( next != current ) {
401                                 draw_setting ( &widget );
402                                 init_setting_index ( &widget, settings, next );
403                                 current = next;
404                         }
405                 }
406         }
407         
408 }
409
410 int settings_ui ( struct settings *settings ) {
411         int rc;
412
413         initscr();
414         start_color();
415         init_pair ( CPAIR_NORMAL, COLOR_WHITE, COLOR_BLUE );
416         init_pair ( CPAIR_SELECT, COLOR_WHITE, COLOR_RED );
417         init_pair ( CPAIR_EDIT, COLOR_BLACK, COLOR_CYAN );
418         init_pair ( CPAIR_ALERT, COLOR_WHITE, COLOR_RED );
419         color_set ( CPAIR_NORMAL, NULL );
420         erase();
421         
422         rc = main_loop ( settings );
423
424         endwin();
425
426         return rc;
427 }