iPXE
menu_ui.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 /** @file
00027  *
00028  * Menu interface
00029  *
00030  */
00031 
00032 #include <string.h>
00033 #include <errno.h>
00034 #include <curses.h>
00035 #include <ipxe/keys.h>
00036 #include <ipxe/timer.h>
00037 #include <ipxe/console.h>
00038 #include <ipxe/ansicol.h>
00039 #include <ipxe/jumpscroll.h>
00040 #include <ipxe/menu.h>
00041 
00042 /* Screen layout */
00043 #define TITLE_ROW       1U
00044 #define MENU_ROW        3U
00045 #define MENU_COL        1U
00046 #define MENU_ROWS       ( LINES - 2U - MENU_ROW )
00047 #define MENU_COLS       ( COLS - 2U )
00048 #define MENU_PAD        2U
00049 
00050 /** A menu user interface */
00051 struct menu_ui {
00052         /** Menu */
00053         struct menu *menu;
00054         /** Jump scroller */
00055         struct jump_scroller scroll;
00056         /** Timeout (0=indefinite) */
00057         unsigned long timeout;
00058 };
00059 
00060 /**
00061  * Return a numbered menu item
00062  *
00063  * @v menu              Menu
00064  * @v index             Index
00065  * @ret item            Menu item, or NULL
00066  */
00067 static struct menu_item * menu_item ( struct menu *menu, unsigned int index ) {
00068         struct menu_item *item;
00069 
00070         list_for_each_entry ( item, &menu->items, list ) {
00071                 if ( index-- == 0 )
00072                         return item;
00073         }
00074 
00075         return NULL;
00076 }
00077 
00078 /**
00079  * Draw a numbered menu item
00080  *
00081  * @v ui                Menu user interface
00082  * @v index             Index
00083  */
00084 static void draw_menu_item ( struct menu_ui *ui, unsigned int index ) {
00085         struct menu_item *item;
00086         unsigned int row_offset;
00087         char buf[ MENU_COLS + 1 /* NUL */ ];
00088         char timeout_buf[6]; /* "(xxx)" + NUL */
00089         size_t timeout_len;
00090         size_t max_len;
00091         size_t len;
00092 
00093         /* Move to start of row */
00094         row_offset = ( index - ui->scroll.first );
00095         move ( ( MENU_ROW + row_offset ), MENU_COL );
00096 
00097         /* Get menu item */
00098         item = menu_item ( ui->menu, index );
00099         if ( item ) {
00100 
00101                 /* Draw separators in a different colour */
00102                 if ( ! item->label )
00103                         color_set ( CPAIR_SEPARATOR, NULL );
00104 
00105                 /* Highlight if this is the selected item */
00106                 if ( index == ui->scroll.current ) {
00107                         color_set ( CPAIR_SELECT, NULL );
00108                         attron ( A_BOLD );
00109                 }
00110 
00111                 /* Construct row */
00112                 memset ( buf, ' ', ( sizeof ( buf ) - 1 ) );
00113                 buf[ sizeof ( buf ) -1 ] = '\0';
00114                 len = strlen ( item->text );
00115                 max_len = ( sizeof ( buf ) - 1 /* NUL */ - ( 2 * MENU_PAD ) );
00116                 if ( len > max_len )
00117                         len = max_len;
00118                 memcpy ( ( buf + MENU_PAD ), item->text, len );
00119 
00120                 /* Add timeout if applicable */
00121                 timeout_len =
00122                         snprintf ( timeout_buf, sizeof ( timeout_buf ), "(%ld)",
00123                                    ( ( ui->timeout + TICKS_PER_SEC - 1 ) /
00124                                      TICKS_PER_SEC ) );
00125                 if ( ( index == ui->scroll.current ) && ( ui->timeout != 0 ) ) {
00126                         memcpy ( ( buf + MENU_COLS - MENU_PAD - timeout_len ),
00127                                  timeout_buf, timeout_len );
00128                 }
00129 
00130                 /* Print row */
00131                 printw ( "%s", buf );
00132 
00133                 /* Reset attributes */
00134                 color_set ( CPAIR_NORMAL, NULL );
00135                 attroff ( A_BOLD );
00136 
00137         } else {
00138                 /* Clear row if there is no corresponding menu item */
00139                 clrtoeol();
00140         }
00141 
00142         /* Move cursor back to start of row */
00143         move ( ( MENU_ROW + row_offset ), MENU_COL );
00144 }
00145 
00146 /**
00147  * Draw the current block of menu items
00148  *
00149  * @v ui                Menu user interface
00150  */
00151 static void draw_menu_items ( struct menu_ui *ui ) {
00152         unsigned int i;
00153 
00154         /* Draw ellipses before and/or after the list as necessary */
00155         color_set ( CPAIR_SEPARATOR, NULL );
00156         mvaddstr ( ( MENU_ROW - 1 ), ( MENU_COL + MENU_PAD ),
00157                    ( jump_scroll_is_first ( &ui->scroll ) ? "   " : "..." ) );
00158         mvaddstr ( ( MENU_ROW + MENU_ROWS ), ( MENU_COL + MENU_PAD ),
00159                    ( jump_scroll_is_last ( &ui->scroll ) ? "   " : "..." ) );
00160         color_set ( CPAIR_NORMAL, NULL );
00161 
00162         /* Draw visible items */
00163         for ( i = 0 ; i < MENU_ROWS ; i++ )
00164                 draw_menu_item ( ui, ( ui->scroll.first + i ) );
00165 }
00166 
00167 /**
00168  * Menu main loop
00169  *
00170  * @v ui                Menu user interface
00171  * @ret selected        Selected item
00172  * @ret rc              Return status code
00173  */
00174 static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) {
00175         struct menu_item *item;
00176         unsigned long timeout;
00177         unsigned int previous;
00178         int key;
00179         int i;
00180         int move;
00181         int chosen = 0;
00182         int rc = 0;
00183 
00184         do {
00185                 /* Record current selection */
00186                 previous = ui->scroll.current;
00187 
00188                 /* Calculate timeout as remainder of current second */
00189                 timeout = ( ui->timeout % TICKS_PER_SEC );
00190                 if ( ( timeout == 0 ) && ( ui->timeout != 0 ) )
00191                         timeout = TICKS_PER_SEC;
00192                 ui->timeout -= timeout;
00193 
00194                 /* Get key */
00195                 move = 0;
00196                 key = getkey ( timeout );
00197                 if ( key < 0 ) {
00198                         /* Choose default if we finally time out */
00199                         if ( ui->timeout == 0 )
00200                                 chosen = 1;
00201                 } else {
00202                         /* Cancel any timeout */
00203                         ui->timeout = 0;
00204 
00205                         /* Handle scroll keys */
00206                         move = jump_scroll_key ( &ui->scroll, key );
00207 
00208                         /* Handle other keys */
00209                         switch ( key ) {
00210                         case ESC:
00211                         case CTRL_C:
00212                                 rc = -ECANCELED;
00213                                 break;
00214                         case CR:
00215                         case LF:
00216                                 chosen = 1;
00217                                 break;
00218                         default:
00219                                 i = 0;
00220                                 list_for_each_entry ( item, &ui->menu->items,
00221                                                       list ) {
00222                                         if ( ! ( item->shortcut &&
00223                                                  ( item->shortcut == key ) ) ) {
00224                                                 i++;
00225                                                 continue;
00226                                         }
00227                                         ui->scroll.current = i;
00228                                         if ( item->label ) {
00229                                                 chosen = 1;
00230                                         } else {
00231                                                 move = +1;
00232                                         }
00233                                 }
00234                                 break;
00235                         }
00236                 }
00237 
00238                 /* Move selection, if applicable */
00239                 while ( move ) {
00240                         move = jump_scroll_move ( &ui->scroll, move );
00241                         item = menu_item ( ui->menu, ui->scroll.current );
00242                         if ( item->label )
00243                                 break;
00244                 }
00245 
00246                 /* Redraw selection if necessary */
00247                 if ( ( ui->scroll.current != previous ) || ( timeout != 0 ) ) {
00248                         draw_menu_item ( ui, previous );
00249                         if ( jump_scroll ( &ui->scroll ) )
00250                                 draw_menu_items ( ui );
00251                         draw_menu_item ( ui, ui->scroll.current );
00252                 }
00253 
00254                 /* Record selection */
00255                 item = menu_item ( ui->menu, ui->scroll.current );
00256                 assert ( item != NULL );
00257                 assert ( item->label != NULL );
00258                 *selected = item;
00259 
00260         } while ( ( rc == 0 ) && ! chosen );
00261 
00262         return rc;
00263 }
00264 
00265 /**
00266  * Show menu
00267  *
00268  * @v menu              Menu
00269  * @v timeout           Timeout period, in ticks (0=indefinite)
00270  * @ret selected        Selected item
00271  * @ret rc              Return status code
00272  */
00273 int show_menu ( struct menu *menu, unsigned long timeout,
00274                 const char *select, struct menu_item **selected ) {
00275         struct menu_item *item;
00276         struct menu_ui ui;
00277         char buf[ MENU_COLS + 1 /* NUL */ ];
00278         int labelled_count = 0;
00279         int rc;
00280 
00281         /* Initialise UI */
00282         memset ( &ui, 0, sizeof ( ui ) );
00283         ui.menu = menu;
00284         ui.scroll.rows = MENU_ROWS;
00285         ui.timeout = timeout;
00286         list_for_each_entry ( item, &menu->items, list ) {
00287                 if ( item->label ) {
00288                         if ( ! labelled_count )
00289                                 ui.scroll.current = ui.scroll.count;
00290                         labelled_count++;
00291                         if ( select ) {
00292                                 if ( strcmp ( select, item->label ) == 0 )
00293                                         ui.scroll.current = ui.scroll.count;
00294                         } else {
00295                                 if ( item->is_default )
00296                                         ui.scroll.current = ui.scroll.count;
00297                         }
00298                 }
00299                 ui.scroll.count++;
00300         }
00301         if ( ! labelled_count ) {
00302                 /* Menus with no labelled items cannot be selected
00303                  * from, and will seriously confuse the navigation
00304                  * logic.  Refuse to display any such menus.
00305                  */
00306                 return -ENOENT;
00307         }
00308 
00309         /* Initialise screen */
00310         initscr();
00311         start_color();
00312         color_set ( CPAIR_NORMAL, NULL );
00313         curs_set ( 0 );
00314         erase();
00315 
00316         /* Draw initial content */
00317         attron ( A_BOLD );
00318         snprintf ( buf, sizeof ( buf ), "%s", ui.menu->title );
00319         mvprintw ( TITLE_ROW, ( ( COLS - strlen ( buf ) ) / 2 ), "%s", buf );
00320         attroff ( A_BOLD );
00321         jump_scroll ( &ui.scroll );
00322         draw_menu_items ( &ui );
00323         draw_menu_item ( &ui, ui.scroll.current );
00324 
00325         /* Enter main loop */
00326         rc = menu_loop ( &ui, selected );
00327         assert ( *selected );
00328 
00329         /* Clear screen */
00330         endwin();
00331 
00332         return rc;
00333 }