const std::string &input_context::handle_input( const int timeout ) { inp_mngr.set_timeout( timeout ); const std::string &result = handle_input(); inp_mngr.reset_timeout(); return result; }
void input_context::display_menu() { inp_mngr.reset_timeout(); // Shamelessly stolen from help.cpp input_context ctxt( "HELP_KEYBINDINGS" ); ctxt.register_action( "UP", _( "Scroll up" ) ); ctxt.register_action( "DOWN", _( "Scroll down" ) ); ctxt.register_action( "PAGE_DOWN" ); ctxt.register_action( "PAGE_UP" ); ctxt.register_action( "REMOVE" ); ctxt.register_action( "ADD_LOCAL" ); ctxt.register_action( "ADD_GLOBAL" ); ctxt.register_action( "QUIT" ); ctxt.register_action( "ANY_INPUT" ); if( category != "HELP_KEYBINDINGS" ) { // avoiding inception! ctxt.register_action( "HELP_KEYBINDINGS" ); } std::string hotkeys = ctxt.get_available_single_char_hotkeys( display_help_hotkeys ); int maxwidth = max( FULL_SCREEN_WIDTH, TERMX ); int width = min( 80, maxwidth ); int maxheight = max( FULL_SCREEN_HEIGHT, TERMY ); int height = min( maxheight, ( int ) hotkeys.size() + LEGEND_HEIGHT + BORDER_SPACE ); catacurses::window w_help = catacurses::newwin( height - 2, width - 2, maxheight / 2 - height / 2, maxwidth / 2 - width / 2 ); // has the user changed something? bool changed = false; // keybindings before the user changed anything. input_manager::t_action_contexts old_action_contexts( inp_mngr.action_contexts ); // current status: adding/removing/showing keybindings enum { s_remove, s_add, s_add_global, s_show } status = s_show; // copy of registered_actions, but without the ANY_INPUT and COORDINATE, which should not be shown std::vector<std::string> org_registered_actions( registered_actions ); org_registered_actions.erase( std::remove_if( org_registered_actions.begin(), org_registered_actions.end(), []( const std::string & a ) { return a == ANY_INPUT || a == COORDINATE; } ), org_registered_actions.end() ); // colors of the keybindings static const nc_color global_key = c_light_gray; static const nc_color local_key = c_light_green; static const nc_color unbound_key = c_light_red; // (vertical) scroll offset size_t scroll_offset = 0; // height of the area usable for display of keybindings, excludes headers & borders const size_t display_height = height - LEGEND_HEIGHT - BORDER_SPACE; // -2 for the border // width of the legend const size_t legwidth = width - 4 - BORDER_SPACE; // keybindings help std::ostringstream legend; legend << "<color_" << string_from_color( unbound_key ) << ">" << _( "Unbound keys" ) << "</color>\n"; legend << "<color_" << string_from_color( local_key ) << ">" << _( "Keybinding active only on this screen" ) << "</color>\n"; legend << "<color_" << string_from_color( global_key ) << ">" << _( "Keybinding active globally" ) << "</color>\n"; legend << _( "Press - to remove keybinding\nPress + to add local keybinding\nPress = to add global keybinding\n" ); std::vector<std::string> filtered_registered_actions = org_registered_actions; std::string filter_phrase; std::string action; long raw_input_char = 0; string_input_popup spopup; spopup.window( w_help, 4, 8, legwidth ) .max_length( legwidth ) .context( ctxt ); while( true ) { werase( w_help ); draw_border( w_help, BORDER_COLOR, _( "Keybindings" ), c_light_red ); draw_scrollbar( w_help, scroll_offset, display_height, filtered_registered_actions.size(), 10, 0, c_white, true ); fold_and_print( w_help, 1, 2, legwidth, c_white, legend.str() ); for( size_t i = 0; i + scroll_offset < filtered_registered_actions.size() && i < display_height; i++ ) { const std::string &action_id = filtered_registered_actions[i + scroll_offset]; bool overwrite_default; const action_attributes &attributes = inp_mngr.get_action_attributes( action_id, category, &overwrite_default ); char invlet; if( i < hotkeys.size() ) { invlet = hotkeys[i]; } else { invlet = ' '; } if( status == s_add_global && overwrite_default ) { // We're trying to add a global, but this action has a local // defined, so gray out the invlet. mvwprintz( w_help, i + 10, 2, c_dark_gray, "%c ", invlet ); } else if( status == s_add || status == s_add_global ) { mvwprintz( w_help, i + 10, 2, c_blue, "%c ", invlet ); } else if( status == s_remove ) { mvwprintz( w_help, i + 10, 2, c_blue, "%c ", invlet ); } else { mvwprintz( w_help, i + 10, 2, c_blue, " " ); } nc_color col; if( attributes.input_events.empty() ) { col = unbound_key; } else if( overwrite_default ) { col = local_key; } else { col = global_key; } mvwprintz( w_help, i + 10, 4, col, "%s: ", get_action_name( action_id ).c_str() ); mvwprintz( w_help, i + 10, 52, col, "%s", get_desc( action_id ).c_str() ); } // spopup.query_string() will call wrefresh( w_help ) catacurses::refresh(); spopup.text( filter_phrase ); if( status == s_show ) { filter_phrase = spopup.query_string( false ); action = ctxt.input_to_action( ctxt.get_raw_input() ); } else { spopup.query_string( false, true ); action = ctxt.handle_input(); } raw_input_char = ctxt.get_raw_input().get_first_input(); filtered_registered_actions = filter_strings_by_phrase( org_registered_actions, filter_phrase ); if( scroll_offset > filtered_registered_actions.size() ) { scroll_offset = 0; } if( filtered_registered_actions.empty() && action != "QUIT" ) { continue; } // In addition to the modifiable hotkeys, we also check for hardcoded // keys, e.g. '+', '-', '=', in order to prevent the user from // entering an unrecoverable state. if( action == "ADD_LOCAL" || raw_input_char == '+' ) { status = s_add; } else if( action == "ADD_GLOBAL" || raw_input_char == '=' ) { status = s_add_global; } else if( action == "REMOVE" || raw_input_char == '-' ) { status = s_remove; } else if( action == "ANY_INPUT" ) { const size_t hotkey_index = hotkeys.find_first_of( raw_input_char ); if( hotkey_index == std::string::npos ) { continue; } const size_t action_index = hotkey_index + scroll_offset; if( action_index >= filtered_registered_actions.size() ) { continue; } const std::string &action_id = filtered_registered_actions[action_index]; // Check if this entry is local or global. bool is_local = false; inp_mngr.get_action_attributes( action_id, category, &is_local ); const std::string name = get_action_name( action_id ); if( status == s_remove && ( !get_option<bool>( "QUERY_KEYBIND_REMOVAL" ) || query_yn( _( "Clear keys for %s?" ), name.c_str() ) ) ) { // If it's global, reset the global actions. std::string category_to_access = category; if( !is_local ) { category_to_access = default_context_id; } inp_mngr.remove_input_for_action( action_id, category_to_access ); changed = true; } else if( status == s_add_global && is_local ) { // Disallow adding global actions to an action that already has a local defined. popup( _( "There are already local keybindings defined for this action, please remove them first." ) ); } else if( status == s_add || status == s_add_global ) { const long newbind = popup_getkey( _( "New key for %s:" ), name.c_str() ); const input_event new_event( newbind, CATA_INPUT_KEYBOARD ); if( action_uses_input( action_id, new_event ) ) { popup_getkey( _( "This key is already used for %s." ), name.c_str() ); status = s_show; continue; } const std::string conflicts = get_conflicts( new_event ); const bool has_conflicts = !conflicts.empty(); bool resolve_conflicts = false; if( has_conflicts ) { resolve_conflicts = query_yn( _( "This key conflicts with %s. Remove this key from the conflicting command(s), and continue?" ), conflicts.c_str() ); } if( !has_conflicts || resolve_conflicts ) { if( resolve_conflicts ) { clear_conflicting_keybindings( new_event ); } // We might be adding a local or global action. std::string category_to_access = category; if( status == s_add_global ) { category_to_access = default_context_id; } inp_mngr.add_input_for_action( action_id, category_to_access, new_event ); changed = true; } } status = s_show; } else if( action == "DOWN" ) { if( filtered_registered_actions.size() > display_height && scroll_offset < filtered_registered_actions.size() - display_height ) { scroll_offset++; } } else if( action == "UP" ) { if( scroll_offset > 0 ) { scroll_offset--; } } else if( action == "PAGE_DOWN" ) { if( scroll_offset + display_height < filtered_registered_actions.size() ) { scroll_offset += std::min( display_height, filtered_registered_actions.size() - display_height - scroll_offset ); } else if( filtered_registered_actions.size() > display_height ) { scroll_offset = 0; } } else if( action == "PAGE_UP" ) { if( scroll_offset >= display_height ) { scroll_offset -= display_height; } else if( scroll_offset > 0 ) { scroll_offset = 0; } else if( filtered_registered_actions.size() > display_height ) { scroll_offset = filtered_registered_actions.size() - display_height; } } else if( action == "QUIT" ) { if( status != s_show ) { status = s_show; } else { break; } } else if( action == "HELP_KEYBINDINGS" ) { // update available hotkeys in case they've changed hotkeys = ctxt.get_available_single_char_hotkeys( display_help_hotkeys ); } } if( changed && query_yn( _( "Save changes?" ) ) ) { try { inp_mngr.save(); } catch( std::exception &err ) { popup( _( "saving keybindings failed: %s" ), err.what() ); } } else if( changed ) { inp_mngr.action_contexts.swap( old_action_contexts ); } werase( w_help ); wrefresh( w_help ); }