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
20 FILE_LICENCE ( GPL2_OR_LATER );
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>
41 #define MENU_ROWS ( LINES - 2U - MENU_ROW )
42 #define MENU_COLS ( COLS - 2U )
45 /** A menu user interface */
49 /** Number of menu items */
51 /** Currently selected item */
53 /** First visible item */
55 /** Timeout (0=indefinite) */
56 unsigned long timeout;
60 * Return a numbered menu item
64 * @ret item Menu item, or NULL
66 static struct menu_item * menu_item ( struct menu *menu, unsigned int index ) {
67 struct menu_item *item;
69 list_for_each_entry ( item, &menu->items, list ) {
78 * Draw a numbered menu item
80 * @v ui Menu user interface
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 */
92 /* Move to start of row */
93 row_offset = ( index - ui->first_visible );
94 move ( ( MENU_ROW + row_offset ), MENU_COL );
97 item = menu_item ( ui->menu, index );
100 /* Draw separators in a different colour */
102 color_set ( CPAIR_SEPARATOR, NULL );
104 /* Highlight if this is the selected item */
105 if ( index == ui->selected ) {
106 color_set ( CPAIR_SELECT, NULL );
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 ) );
117 memcpy ( ( buf + MENU_PAD ), item->text, len );
119 /* Add timeout if applicable */
121 snprintf ( timeout_buf, sizeof ( timeout_buf ), "(%ld)",
122 ( ( ui->timeout + TICKS_PER_SEC - 1 ) /
124 if ( ( index == ui->selected ) && ( ui->timeout != 0 ) ) {
125 memcpy ( ( buf + MENU_COLS - MENU_PAD - timeout_len ),
126 timeout_buf, timeout_len );
130 printw ( "%s", buf );
132 /* Reset attributes */
133 color_set ( CPAIR_NORMAL, NULL );
137 /* Clear row if there is no corresponding menu item */
141 /* Move cursor back to start of row */
142 move ( ( MENU_ROW + row_offset ), MENU_COL );
146 * Draw the current block of menu items
148 * @v ui Menu user interface
150 static void draw_menu_items ( struct menu_ui *ui ) {
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;
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 ) ?
166 color_set ( CPAIR_NORMAL, NULL );
168 /* Draw visible items */
169 for ( i = 0 ; i < MENU_ROWS ; i++ )
170 draw_menu_item ( ui, ( ui->first_visible + i ) );
176 * @v ui Menu user interface
177 * @ret selected Selected item
178 * @ret rc Return status code
180 static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) {
181 struct menu_item *item;
182 unsigned long timeout;
192 /* Record current selection */
193 current = ui->selected;
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;
203 key = getkey ( timeout );
205 /* Choose default if we finally time out */
206 if ( ui->timeout == 0 )
209 /* Cancel any timeout */
221 move = ( ui->first_visible - ui->selected - 1 );
224 move = ( ui->first_visible - ui->selected
243 list_for_each_entry ( item, &ui->menu->items,
245 if ( ! ( item->shortcut &&
246 ( item->shortcut == key ) ) ) {
261 /* Move selection, if applicable */
263 ui->selected += move;
264 if ( ui->selected < 0 ) {
267 } else if ( ui->selected >= ( int ) ui->count ) {
268 ui->selected = ( ui->count - 1 );
271 item = menu_item ( ui->menu, ui->selected );
274 move = ( ( move > 0 ) ? +1 : -1 );
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 );
286 /* Record selection */
287 item = menu_item ( ui->menu, ui->selected );
288 assert ( item != NULL );
289 assert ( item->label != NULL );
292 } while ( ( rc == 0 ) && ! chosen );
301 * @v timeout Timeout period, in ticks (0=indefinite)
302 * @ret selected Selected item
303 * @ret rc Return status code
305 int show_menu ( struct menu *menu, unsigned long timeout,
306 const char *select, struct menu_item **selected ) {
307 struct menu_item *item;
309 char buf[ MENU_COLS + 1 /* NUL */ ];
310 int labelled_count = 0;
314 memset ( &ui, 0, sizeof ( ui ) );
316 ui.timeout = timeout;
317 list_for_each_entry ( item, &menu->items, list ) {
319 if ( ! labelled_count )
320 ui.selected = ui.count;
323 if ( strcmp ( select, item->label ) == 0 )
324 ui.selected = ui.count;
326 if ( item->is_default )
327 ui.selected = ui.count;
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.
340 /* Initialise screen */
343 color_set ( CPAIR_NORMAL, NULL );
347 /* Draw initial content */
349 snprintf ( buf, sizeof ( buf ), "%s", ui.menu->title );
350 mvprintw ( TITLE_ROW, ( ( COLS - strlen ( buf ) ) / 2 ), "%s", buf );
352 draw_menu_items ( &ui );
353 draw_menu_item ( &ui, ui.selected );
355 /* Enter main loop */
356 rc = menu_loop ( &ui, selected );
357 assert ( *selected );