These changes are the raw update to qemu-2.6.
[kvmfornfv.git] / qemu / roms / ipxe / src / hci / tui / menu_ui.c
1 /*
2  * Copyright (C) 2012 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., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301, USA.
18  *
19  * You can also choose to distribute this program under the terms of
20  * the Unmodified Binary Distribution Licence (as given in the file
21  * COPYING.UBDL), provided that you have satisfied its requirements.
22  */
23
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
26 /** @file
27  *
28  * Menu interface
29  *
30  */
31
32 #include <string.h>
33 #include <errno.h>
34 #include <curses.h>
35 #include <ipxe/keys.h>
36 #include <ipxe/timer.h>
37 #include <ipxe/console.h>
38 #include <ipxe/ansicol.h>
39 #include <ipxe/jumpscroll.h>
40 #include <ipxe/menu.h>
41
42 /* Screen layout */
43 #define TITLE_ROW       1U
44 #define MENU_ROW        3U
45 #define MENU_COL        1U
46 #define MENU_ROWS       ( LINES - 2U - MENU_ROW )
47 #define MENU_COLS       ( COLS - 2U )
48 #define MENU_PAD        2U
49
50 /** A menu user interface */
51 struct menu_ui {
52         /** Menu */
53         struct menu *menu;
54         /** Jump scroller */
55         struct jump_scroller scroll;
56         /** Timeout (0=indefinite) */
57         unsigned long timeout;
58 };
59
60 /**
61  * Return a numbered menu item
62  *
63  * @v menu              Menu
64  * @v index             Index
65  * @ret item            Menu item, or NULL
66  */
67 static struct menu_item * menu_item ( struct menu *menu, unsigned int index ) {
68         struct menu_item *item;
69
70         list_for_each_entry ( item, &menu->items, list ) {
71                 if ( index-- == 0 )
72                         return item;
73         }
74
75         return NULL;
76 }
77
78 /**
79  * Draw a numbered menu item
80  *
81  * @v ui                Menu user interface
82  * @v index             Index
83  */
84 static void draw_menu_item ( struct menu_ui *ui, unsigned int index ) {
85         struct menu_item *item;
86         unsigned int row_offset;
87         char buf[ MENU_COLS + 1 /* NUL */ ];
88         char timeout_buf[6]; /* "(xxx)" + NUL */
89         size_t timeout_len;
90         size_t max_len;
91         size_t len;
92
93         /* Move to start of row */
94         row_offset = ( index - ui->scroll.first );
95         move ( ( MENU_ROW + row_offset ), MENU_COL );
96
97         /* Get menu item */
98         item = menu_item ( ui->menu, index );
99         if ( item ) {
100
101                 /* Draw separators in a different colour */
102                 if ( ! item->label )
103                         color_set ( CPAIR_SEPARATOR, NULL );
104
105                 /* Highlight if this is the selected item */
106                 if ( index == ui->scroll.current ) {
107                         color_set ( CPAIR_SELECT, NULL );
108                         attron ( A_BOLD );
109                 }
110
111                 /* Construct row */
112                 memset ( buf, ' ', ( sizeof ( buf ) - 1 ) );
113                 buf[ sizeof ( buf ) -1 ] = '\0';
114                 len = strlen ( item->text );
115                 max_len = ( sizeof ( buf ) - 1 /* NUL */ - ( 2 * MENU_PAD ) );
116                 if ( len > max_len )
117                         len = max_len;
118                 memcpy ( ( buf + MENU_PAD ), item->text, len );
119
120                 /* Add timeout if applicable */
121                 timeout_len =
122                         snprintf ( timeout_buf, sizeof ( timeout_buf ), "(%ld)",
123                                    ( ( ui->timeout + TICKS_PER_SEC - 1 ) /
124                                      TICKS_PER_SEC ) );
125                 if ( ( index == ui->scroll.current ) && ( ui->timeout != 0 ) ) {
126                         memcpy ( ( buf + MENU_COLS - MENU_PAD - timeout_len ),
127                                  timeout_buf, timeout_len );
128                 }
129
130                 /* Print row */
131                 printw ( "%s", buf );
132
133                 /* Reset attributes */
134                 color_set ( CPAIR_NORMAL, NULL );
135                 attroff ( A_BOLD );
136
137         } else {
138                 /* Clear row if there is no corresponding menu item */
139                 clrtoeol();
140         }
141
142         /* Move cursor back to start of row */
143         move ( ( MENU_ROW + row_offset ), MENU_COL );
144 }
145
146 /**
147  * Draw the current block of menu items
148  *
149  * @v ui                Menu user interface
150  */
151 static void draw_menu_items ( struct menu_ui *ui ) {
152         unsigned int i;
153
154         /* Draw ellipses before and/or after the list as necessary */
155         color_set ( CPAIR_SEPARATOR, NULL );
156         mvaddstr ( ( MENU_ROW - 1 ), ( MENU_COL + MENU_PAD ),
157                    ( jump_scroll_is_first ( &ui->scroll ) ? "   " : "..." ) );
158         mvaddstr ( ( MENU_ROW + MENU_ROWS ), ( MENU_COL + MENU_PAD ),
159                    ( jump_scroll_is_last ( &ui->scroll ) ? "   " : "..." ) );
160         color_set ( CPAIR_NORMAL, NULL );
161
162         /* Draw visible items */
163         for ( i = 0 ; i < MENU_ROWS ; i++ )
164                 draw_menu_item ( ui, ( ui->scroll.first + i ) );
165 }
166
167 /**
168  * Menu main loop
169  *
170  * @v ui                Menu user interface
171  * @ret selected        Selected item
172  * @ret rc              Return status code
173  */
174 static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) {
175         struct menu_item *item;
176         unsigned long timeout;
177         unsigned int previous;
178         int key;
179         int i;
180         int move;
181         int chosen = 0;
182         int rc = 0;
183
184         do {
185                 /* Record current selection */
186                 previous = ui->scroll.current;
187
188                 /* Calculate timeout as remainder of current second */
189                 timeout = ( ui->timeout % TICKS_PER_SEC );
190                 if ( ( timeout == 0 ) && ( ui->timeout != 0 ) )
191                         timeout = TICKS_PER_SEC;
192                 ui->timeout -= timeout;
193
194                 /* Get key */
195                 move = 0;
196                 key = getkey ( timeout );
197                 if ( key < 0 ) {
198                         /* Choose default if we finally time out */
199                         if ( ui->timeout == 0 )
200                                 chosen = 1;
201                 } else {
202                         /* Cancel any timeout */
203                         ui->timeout = 0;
204
205                         /* Handle scroll keys */
206                         move = jump_scroll_key ( &ui->scroll, key );
207
208                         /* Handle other keys */
209                         switch ( key ) {
210                         case ESC:
211                         case CTRL_C:
212                                 rc = -ECANCELED;
213                                 break;
214                         case CR:
215                         case LF:
216                                 chosen = 1;
217                                 break;
218                         default:
219                                 i = 0;
220                                 list_for_each_entry ( item, &ui->menu->items,
221                                                       list ) {
222                                         if ( ! ( item->shortcut &&
223                                                  ( item->shortcut == key ) ) ) {
224                                                 i++;
225                                                 continue;
226                                         }
227                                         ui->scroll.current = i;
228                                         if ( item->label ) {
229                                                 chosen = 1;
230                                         } else {
231                                                 move = +1;
232                                         }
233                                 }
234                                 break;
235                         }
236                 }
237
238                 /* Move selection, if applicable */
239                 while ( move ) {
240                         move = jump_scroll_move ( &ui->scroll, move );
241                         item = menu_item ( ui->menu, ui->scroll.current );
242                         if ( item->label )
243                                 break;
244                 }
245
246                 /* Redraw selection if necessary */
247                 if ( ( ui->scroll.current != previous ) || ( timeout != 0 ) ) {
248                         draw_menu_item ( ui, previous );
249                         if ( jump_scroll ( &ui->scroll ) )
250                                 draw_menu_items ( ui );
251                         draw_menu_item ( ui, ui->scroll.current );
252                 }
253
254                 /* Record selection */
255                 item = menu_item ( ui->menu, ui->scroll.current );
256                 assert ( item != NULL );
257                 assert ( item->label != NULL );
258                 *selected = item;
259
260         } while ( ( rc == 0 ) && ! chosen );
261
262         return rc;
263 }
264
265 /**
266  * Show menu
267  *
268  * @v menu              Menu
269  * @v timeout           Timeout period, in ticks (0=indefinite)
270  * @ret selected        Selected item
271  * @ret rc              Return status code
272  */
273 int show_menu ( struct menu *menu, unsigned long timeout,
274                 const char *select, struct menu_item **selected ) {
275         struct menu_item *item;
276         struct menu_ui ui;
277         char buf[ MENU_COLS + 1 /* NUL */ ];
278         int labelled_count = 0;
279         int rc;
280
281         /* Initialise UI */
282         memset ( &ui, 0, sizeof ( ui ) );
283         ui.menu = menu;
284         ui.scroll.rows = MENU_ROWS;
285         ui.timeout = timeout;
286         list_for_each_entry ( item, &menu->items, list ) {
287                 if ( item->label ) {
288                         if ( ! labelled_count )
289                                 ui.scroll.current = ui.scroll.count;
290                         labelled_count++;
291                         if ( select ) {
292                                 if ( strcmp ( select, item->label ) == 0 )
293                                         ui.scroll.current = ui.scroll.count;
294                         } else {
295                                 if ( item->is_default )
296                                         ui.scroll.current = ui.scroll.count;
297                         }
298                 }
299                 ui.scroll.count++;
300         }
301         if ( ! labelled_count ) {
302                 /* Menus with no labelled items cannot be selected
303                  * from, and will seriously confuse the navigation
304                  * logic.  Refuse to display any such menus.
305                  */
306                 return -ENOENT;
307         }
308
309         /* Initialise screen */
310         initscr();
311         start_color();
312         color_set ( CPAIR_NORMAL, NULL );
313         curs_set ( 0 );
314         erase();
315
316         /* Draw initial content */
317         attron ( A_BOLD );
318         snprintf ( buf, sizeof ( buf ), "%s", ui.menu->title );
319         mvprintw ( TITLE_ROW, ( ( COLS - strlen ( buf ) ) / 2 ), "%s", buf );
320         attroff ( A_BOLD );
321         jump_scroll ( &ui.scroll );
322         draw_menu_items ( &ui );
323         draw_menu_item ( &ui, ui.scroll.current );
324
325         /* Enter main loop */
326         rc = menu_loop ( &ui, selected );
327         assert ( *selected );
328
329         /* Clear screen */
330         endwin();
331
332         return rc;
333 }