2 * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
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.
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.
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
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.
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
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>
46 #define MENU_ROWS ( LINES - 2U - MENU_ROW )
47 #define MENU_COLS ( COLS - 2U )
50 /** A menu user interface */
55 struct jump_scroller scroll;
56 /** Timeout (0=indefinite) */
57 unsigned long timeout;
61 * Return a numbered menu item
65 * @ret item Menu item, or NULL
67 static struct menu_item * menu_item ( struct menu *menu, unsigned int index ) {
68 struct menu_item *item;
70 list_for_each_entry ( item, &menu->items, list ) {
79 * Draw a numbered menu item
81 * @v ui Menu user interface
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 */
93 /* Move to start of row */
94 row_offset = ( index - ui->scroll.first );
95 move ( ( MENU_ROW + row_offset ), MENU_COL );
98 item = menu_item ( ui->menu, index );
101 /* Draw separators in a different colour */
103 color_set ( CPAIR_SEPARATOR, NULL );
105 /* Highlight if this is the selected item */
106 if ( index == ui->scroll.current ) {
107 color_set ( CPAIR_SELECT, NULL );
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 ) );
118 memcpy ( ( buf + MENU_PAD ), item->text, len );
120 /* Add timeout if applicable */
122 snprintf ( timeout_buf, sizeof ( timeout_buf ), "(%ld)",
123 ( ( ui->timeout + TICKS_PER_SEC - 1 ) /
125 if ( ( index == ui->scroll.current ) && ( ui->timeout != 0 ) ) {
126 memcpy ( ( buf + MENU_COLS - MENU_PAD - timeout_len ),
127 timeout_buf, timeout_len );
131 printw ( "%s", buf );
133 /* Reset attributes */
134 color_set ( CPAIR_NORMAL, NULL );
138 /* Clear row if there is no corresponding menu item */
142 /* Move cursor back to start of row */
143 move ( ( MENU_ROW + row_offset ), MENU_COL );
147 * Draw the current block of menu items
149 * @v ui Menu user interface
151 static void draw_menu_items ( struct menu_ui *ui ) {
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 );
162 /* Draw visible items */
163 for ( i = 0 ; i < MENU_ROWS ; i++ )
164 draw_menu_item ( ui, ( ui->scroll.first + i ) );
170 * @v ui Menu user interface
171 * @ret selected Selected item
172 * @ret rc Return status code
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;
185 /* Record current selection */
186 previous = ui->scroll.current;
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;
196 key = getkey ( timeout );
198 /* Choose default if we finally time out */
199 if ( ui->timeout == 0 )
202 /* Cancel any timeout */
205 /* Handle scroll keys */
206 move = jump_scroll_key ( &ui->scroll, key );
208 /* Handle other keys */
220 list_for_each_entry ( item, &ui->menu->items,
222 if ( ! ( item->shortcut &&
223 ( item->shortcut == key ) ) ) {
227 ui->scroll.current = i;
238 /* Move selection, if applicable */
240 move = jump_scroll_move ( &ui->scroll, move );
241 item = menu_item ( ui->menu, ui->scroll.current );
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 );
254 /* Record selection */
255 item = menu_item ( ui->menu, ui->scroll.current );
256 assert ( item != NULL );
257 assert ( item->label != NULL );
260 } while ( ( rc == 0 ) && ! chosen );
269 * @v timeout Timeout period, in ticks (0=indefinite)
270 * @ret selected Selected item
271 * @ret rc Return status code
273 int show_menu ( struct menu *menu, unsigned long timeout,
274 const char *select, struct menu_item **selected ) {
275 struct menu_item *item;
277 char buf[ MENU_COLS + 1 /* NUL */ ];
278 int labelled_count = 0;
282 memset ( &ui, 0, sizeof ( ui ) );
284 ui.scroll.rows = MENU_ROWS;
285 ui.timeout = timeout;
286 list_for_each_entry ( item, &menu->items, list ) {
288 if ( ! labelled_count )
289 ui.scroll.current = ui.scroll.count;
292 if ( strcmp ( select, item->label ) == 0 )
293 ui.scroll.current = ui.scroll.count;
295 if ( item->is_default )
296 ui.scroll.current = ui.scroll.count;
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.
309 /* Initialise screen */
312 color_set ( CPAIR_NORMAL, NULL );
316 /* Draw initial content */
318 snprintf ( buf, sizeof ( buf ), "%s", ui.menu->title );
319 mvprintw ( TITLE_ROW, ( ( COLS - strlen ( buf ) ) / 2 ), "%s", buf );
321 jump_scroll ( &ui.scroll );
322 draw_menu_items ( &ui );
323 draw_menu_item ( &ui, ui.scroll.current );
325 /* Enter main loop */
326 rc = menu_loop ( &ui, selected );
327 assert ( *selected );