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