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