void input_context::display_help() { inp_mngr.set_timeout(-1); // Shamelessly stolen from help.cpp WINDOW *w_help = newwin(FULL_SCREEN_HEIGHT - 2, FULL_SCREEN_WIDTH - 2, 1 + (int)((TERMY > FULL_SCREEN_HEIGHT) ? (TERMY - FULL_SCREEN_HEIGHT) / 2 : 0), 1 + (int)((TERMX > FULL_SCREEN_WIDTH) ? (TERMX - FULL_SCREEN_WIDTH) / 2 : 0)); // 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); std::vector<std::string>::iterator any_input = std::find(org_registered_actions.begin(), org_registered_actions.end(), ANY_INPUT); if (any_input != org_registered_actions.end()) { org_registered_actions.erase(any_input); } std::vector<std::string>::iterator coordinate = std::find(org_registered_actions.begin(), org_registered_actions.end(), COORDINATE); if (coordinate != org_registered_actions.end()) { org_registered_actions.erase(coordinate); } // colors of the keybindings static const nc_color global_key = c_ltgray; static const nc_color local_key = c_ltgreen; static const nc_color unbound_key = c_ltred; // (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 = FULL_SCREEN_HEIGHT - 9 - 2; // -2 for the border // width of the legend const size_t legwidth = FULL_SCREEN_WIDTH - 4 - 2; // 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"); 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); while(true) { werase(w_help); draw_border(w_help); draw_scrollbar(w_help, scroll_offset, display_height, org_registered_actions.size() - display_height, 8); mvwprintz(w_help, 0, (FULL_SCREEN_WIDTH - utf8_width(_("Keybindings"))) / 2 - 1, c_ltred, " %s ", _("Keybindings")); fold_and_print(w_help, 1, 2, legwidth, c_white, legend.str()); for (size_t i = 0; i + scroll_offset < org_registered_actions.size() && i < display_height; i++) { const std::string &action_id = org_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 + 8, 2, c_dkgray, "%c ", invlet); } else if (status == s_add || status == s_add_global) { mvwprintz(w_help, i + 8, 2, c_blue, "%c ", invlet); } else if (status == s_remove) { mvwprintz(w_help, i + 8, 2, c_blue, "%c ", invlet); } else { mvwprintz(w_help, i + 8, 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 + 8, 4, col, "%s: ", get_action_name(action_id).c_str()); mvwprintz(w_help, i + 8, 52, col, "%s", get_desc(action_id).c_str()); } wrefresh(w_help); refresh(); // 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. const std::string action = ctxt.handle_input(); const long raw_input_char = ctxt.get_raw_input().get_first_input(); 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 (status == s_show || hotkey_index == std::string::npos ) { continue; } const size_t action_index = hotkey_index + scroll_offset; if( action_index >= org_registered_actions.size() ) { continue; } const std::string &action_id = org_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 && (!OPTIONS["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); 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 (scroll_offset < org_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 < org_registered_actions.size() ) { scroll_offset += std::min(display_height, org_registered_actions.size() - display_height - scroll_offset); } else if( org_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( org_registered_actions.size() > display_height ) { scroll_offset = org_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()); } catch(std::string &err) { popup(_("saving keybindings failed: %s"), err.c_str()); } } else if(changed) { inp_mngr.action_contexts.swap(old_action_contexts); } werase(w_help); wrefresh(w_help); delwin(w_help); }
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 ); }