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 7.9KB

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