/* * Copyright (C) 2012 Michael Brown . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * You can also choose to distribute this program under the terms of * the Unmodified Binary Distribution Licence (as given in the file * COPYING.UBDL), provided that you have satisfied its requirements. */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** @file * * Menu interface * */ #include #include #include #include #include #include #include #include #include /* Screen layout */ #define TITLE_ROW 1U #define MENU_ROW 3U #define MENU_COL 1U #define MENU_ROWS ( LINES - 2U - MENU_ROW ) #define MENU_COLS ( COLS - 2U ) #define MENU_PAD 2U /** A menu user interface */ struct menu_ui { /** Menu */ struct menu *menu; /** Jump scroller */ struct jump_scroller scroll; /** Timeout (0=indefinite) */ unsigned long timeout; }; /** * Return a numbered menu item * * @v menu Menu * @v index Index * @ret item Menu item, or NULL */ static struct menu_item * menu_item ( struct menu *menu, unsigned int index ) { struct menu_item *item; list_for_each_entry ( item, &menu->items, list ) { if ( index-- == 0 ) return item; } return NULL; } /** * Draw a numbered menu item * * @v ui Menu user interface * @v index Index */ static void draw_menu_item ( struct menu_ui *ui, unsigned int index ) { struct menu_item *item; unsigned int row_offset; char buf[ MENU_COLS + 1 /* NUL */ ]; char timeout_buf[6]; /* "(xxx)" + NUL */ size_t timeout_len; size_t max_len; size_t len; /* Move to start of row */ row_offset = ( index - ui->scroll.first ); move ( ( MENU_ROW + row_offset ), MENU_COL ); /* Get menu item */ item = menu_item ( ui->menu, index ); if ( item ) { /* Draw separators in a different colour */ if ( ! item->label ) color_set ( CPAIR_SEPARATOR, NULL ); /* Highlight if this is the selected item */ if ( index == ui->scroll.current ) { color_set ( CPAIR_SELECT, NULL ); attron ( A_BOLD ); } /* Construct row */ memset ( buf, ' ', ( sizeof ( buf ) - 1 ) ); buf[ sizeof ( buf ) -1 ] = '\0'; len = strlen ( item->text ); max_len = ( sizeof ( buf ) - 1 /* NUL */ - ( 2 * MENU_PAD ) ); if ( len > max_len ) len = max_len; memcpy ( ( buf + MENU_PAD ), item->text, len ); /* Add timeout if applicable */ timeout_len = snprintf ( timeout_buf, sizeof ( timeout_buf ), "(%ld)", ( ( ui->timeout + TICKS_PER_SEC - 1 ) / TICKS_PER_SEC ) ); if ( ( index == ui->scroll.current ) && ( ui->timeout != 0 ) ) { memcpy ( ( buf + MENU_COLS - MENU_PAD - timeout_len ), timeout_buf, timeout_len ); } /* Print row */ printw ( "%s", buf ); /* Reset attributes */ color_set ( CPAIR_NORMAL, NULL ); attroff ( A_BOLD ); } else { /* Clear row if there is no corresponding menu item */ clrtoeol(); } /* Move cursor back to start of row */ move ( ( MENU_ROW + row_offset ), MENU_COL ); } /** * Draw the current block of menu items * * @v ui Menu user interface */ static void draw_menu_items ( struct menu_ui *ui ) { unsigned int i; /* Draw ellipses before and/or after the list as necessary */ color_set ( CPAIR_SEPARATOR, NULL ); mvaddstr ( ( MENU_ROW - 1 ), ( MENU_COL + MENU_PAD ), ( jump_scroll_is_first ( &ui->scroll ) ? " " : "..." ) ); mvaddstr ( ( MENU_ROW + MENU_ROWS ), ( MENU_COL + MENU_PAD ), ( jump_scroll_is_last ( &ui->scroll ) ? " " : "..." ) ); color_set ( CPAIR_NORMAL, NULL ); /* Draw visible items */ for ( i = 0 ; i < MENU_ROWS ; i++ ) draw_menu_item ( ui, ( ui->scroll.first + i ) ); } /** * Menu main loop * * @v ui Menu user interface * @ret selected Selected item * @ret rc Return status code */ static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) { struct menu_item *item; unsigned long timeout; unsigned int previous; int key; int i; int move; int chosen = 0; int rc = 0; do { /* Record current selection */ previous = ui->scroll.current; /* Calculate timeout as remainder of current second */ timeout = ( ui->timeout % TICKS_PER_SEC ); if ( ( timeout == 0 ) && ( ui->timeout != 0 ) ) timeout = TICKS_PER_SEC; ui->timeout -= timeout; /* Get key */ move = 0; key = getkey ( timeout ); if ( key < 0 ) { /* Choose default if we finally time out */ if ( ui->timeout == 0 ) chosen = 1; } else { /* Cancel any timeout */ ui->timeout = 0; /* Handle scroll keys */ move = jump_scroll_key ( &ui->scroll, key ); /* Handle other keys */ switch ( key ) { case ESC: case CTRL_C: rc = -ECANCELED; break; case CR: case LF: chosen = 1; break; default: i = 0; list_for_each_entry ( item, &ui->menu->items, list ) { if ( ! ( item->shortcut && ( item->shortcut == key ) ) ) { i++; continue; } ui->scroll.current = i; if ( item->label ) { chosen = 1; } else { move = +1; } } break; } } /* Move selection, if applicable */ while ( move ) { move = jump_scroll_move ( &ui->scroll, move ); item = menu_item ( ui->menu, ui->scroll.current ); if ( item->label ) break; } /* Redraw selection if necessary */ if ( ( ui->scroll.current != previous ) || ( timeout != 0 ) ) { draw_menu_item ( ui, previous ); if ( jump_scroll ( &ui->scroll ) ) draw_menu_items ( ui ); draw_menu_item ( ui, ui->scroll.current ); } /* Record selection */ item = menu_item ( ui->menu, ui->scroll.current ); assert ( item != NULL ); assert ( item->label != NULL ); *selected = item; } while ( ( rc == 0 ) && ! chosen ); return rc; } /** * Show menu * * @v menu Menu * @v timeout Timeout period, in ticks (0=indefinite) * @ret selected Selected item * @ret rc Return status code */ int show_menu ( struct menu *menu, unsigned long timeout, const char *select, struct menu_item **selected ) { struct menu_item *item; struct menu_ui ui; char buf[ MENU_COLS + 1 /* NUL */ ]; int labelled_count = 0; int rc; /* Initialise UI */ memset ( &ui, 0, sizeof ( ui ) ); ui.menu = menu; ui.scroll.rows = MENU_ROWS; ui.timeout = timeout; list_for_each_entry ( item, &menu->items, list ) { if ( item->label ) { if ( ! labelled_count ) ui.scroll.current = ui.scroll.count; labelled_count++; if ( select ) { if ( strcmp ( select, item->label ) == 0 ) ui.scroll.current = ui.scroll.count; } else { if ( item->is_default ) ui.scroll.current = ui.scroll.count; } } ui.scroll.count++; } if ( ! labelled_count ) { /* Menus with no labelled items cannot be selected * from, and will seriously confuse the navigation * logic. Refuse to display any such menus. */ return -ENOENT; } /* Initialise screen */ initscr(); start_color(); color_set ( CPAIR_NORMAL, NULL ); curs_set ( 0 ); erase(); /* Draw initial content */ attron ( A_BOLD ); snprintf ( buf, sizeof ( buf ), "%s", ui.menu->title ); mvprintw ( TITLE_ROW, ( ( COLS - strlen ( buf ) ) / 2 ), "%s", buf ); attroff ( A_BOLD ); jump_scroll ( &ui.scroll ); draw_menu_items ( &ui ); draw_menu_item ( &ui, ui.scroll.current ); /* Enter main loop */ rc = menu_loop ( &ui, selected ); assert ( *selected ); /* Clear screen */ endwin(); return rc; }