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