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
);