You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

menu_ui.c 8.5KB

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