diff --git a/client/eventsSDL/InputSourceKeyboard.cpp b/client/eventsSDL/InputSourceKeyboard.cpp index 0901dc420..03abbbc43 100644 --- a/client/eventsSDL/InputSourceKeyboard.cpp +++ b/client/eventsSDL/InputSourceKeyboard.cpp @@ -33,6 +33,8 @@ InputSourceKeyboard::InputSourceKeyboard() void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key) { + std::string keyName = SDL_GetKeyName(key.keysym.sym); + logGlobal->trace("keyboard: key '%s' pressed", keyName); assert(key.state == SDL_PRESSED); if (SDL_IsTextInputActive() == SDL_TRUE) @@ -85,8 +87,7 @@ void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key) return; } - auto shortcutsVector = GH.shortcuts().translateKeycode(key.keysym.sym); - + auto shortcutsVector = GH.shortcuts().translateKeycode(keyName); GH.events().dispatchShortcutPressed(shortcutsVector); } @@ -95,6 +96,9 @@ void InputSourceKeyboard::handleEventKeyUp(const SDL_KeyboardEvent & key) if(key.repeat != 0) return; // ignore periodic event resends + std::string keyName = SDL_GetKeyName(key.keysym.sym); + logGlobal->trace("keyboard: key '%s' released", keyName); + if (SDL_IsTextInputActive() == SDL_TRUE) { if (key.keysym.sym >= ' ' && key.keysym.sym < 0x80) @@ -103,7 +107,7 @@ void InputSourceKeyboard::handleEventKeyUp(const SDL_KeyboardEvent & key) assert(key.state == SDL_RELEASED); - auto shortcutsVector = GH.shortcuts().translateKeycode(key.keysym.sym); + auto shortcutsVector = GH.shortcuts().translateKeycode(keyName); GH.events().dispatchShortcutReleased(shortcutsVector); } diff --git a/client/gui/ShortcutHandler.cpp b/client/gui/ShortcutHandler.cpp index 839a3fc99..680a9ef34 100644 --- a/client/gui/ShortcutHandler.cpp +++ b/client/gui/ShortcutHandler.cpp @@ -12,149 +12,40 @@ #include "ShortcutHandler.h" #include "Shortcut.h" -#include -std::vector ShortcutHandler::translateKeycode(SDL_Keycode key) const +#include "../../lib/json/JsonUtils.h" + +ShortcutHandler::ShortcutHandler() { - static const std::multimap keyToShortcut = { - {SDLK_RETURN, EShortcut::GLOBAL_ACCEPT }, - {SDLK_KP_ENTER, EShortcut::GLOBAL_ACCEPT }, - {SDLK_ESCAPE, EShortcut::GLOBAL_CANCEL }, - {SDLK_RETURN, EShortcut::GLOBAL_RETURN }, - {SDLK_KP_ENTER, EShortcut::GLOBAL_RETURN }, - {SDLK_ESCAPE, EShortcut::GLOBAL_RETURN }, - {SDLK_F4, EShortcut::GLOBAL_FULLSCREEN }, - {SDLK_BACKSPACE, EShortcut::GLOBAL_BACKSPACE }, - {SDLK_TAB, EShortcut::GLOBAL_MOVE_FOCUS }, - {SDLK_o, EShortcut::GLOBAL_OPTIONS }, - {SDLK_LEFT, EShortcut::MOVE_LEFT }, - {SDLK_RIGHT, EShortcut::MOVE_RIGHT }, - {SDLK_UP, EShortcut::MOVE_UP }, - {SDLK_DOWN, EShortcut::MOVE_DOWN }, - {SDLK_HOME, EShortcut::MOVE_FIRST }, - {SDLK_END, EShortcut::MOVE_LAST }, - {SDLK_PAGEUP, EShortcut::MOVE_PAGE_UP }, - {SDLK_PAGEDOWN, EShortcut::MOVE_PAGE_DOWN }, - {SDLK_1, EShortcut::SELECT_INDEX_1 }, - {SDLK_2, EShortcut::SELECT_INDEX_2 }, - {SDLK_3, EShortcut::SELECT_INDEX_3 }, - {SDLK_4, EShortcut::SELECT_INDEX_4 }, - {SDLK_5, EShortcut::SELECT_INDEX_5 }, - {SDLK_6, EShortcut::SELECT_INDEX_6 }, - {SDLK_7, EShortcut::SELECT_INDEX_7 }, - {SDLK_8, EShortcut::SELECT_INDEX_8 }, - {SDLK_n, EShortcut::MAIN_MENU_NEW_GAME }, - {SDLK_l, EShortcut::MAIN_MENU_LOAD_GAME }, - {SDLK_h, EShortcut::MAIN_MENU_HIGH_SCORES }, - {SDLK_c, EShortcut::MAIN_MENU_CREDITS }, - {SDLK_q, EShortcut::MAIN_MENU_QUIT }, - {SDLK_b, EShortcut::MAIN_MENU_BACK }, - {SDLK_s, EShortcut::MAIN_MENU_SINGLEPLAYER }, - {SDLK_m, EShortcut::MAIN_MENU_MULTIPLAYER }, - {SDLK_c, EShortcut::MAIN_MENU_CAMPAIGN }, - {SDLK_t, EShortcut::MAIN_MENU_TUTORIAL }, - {SDLK_s, EShortcut::MAIN_MENU_CAMPAIGN_SOD }, - {SDLK_r, EShortcut::MAIN_MENU_CAMPAIGN_ROE }, - {SDLK_a, EShortcut::MAIN_MENU_CAMPAIGN_AB }, - {SDLK_c, EShortcut::MAIN_MENU_CAMPAIGN_CUSTOM }, - {SDLK_b, EShortcut::LOBBY_BEGIN_GAME }, - {SDLK_RETURN, EShortcut::LOBBY_BEGIN_GAME }, - {SDLK_KP_ENTER, EShortcut::LOBBY_BEGIN_GAME }, - {SDLK_l, EShortcut::LOBBY_LOAD_GAME }, - {SDLK_RETURN, EShortcut::LOBBY_LOAD_GAME }, - {SDLK_KP_ENTER, EShortcut::LOBBY_LOAD_GAME }, - {SDLK_s, EShortcut::LOBBY_SAVE_GAME }, - {SDLK_RETURN, EShortcut::LOBBY_SAVE_GAME }, - {SDLK_KP_ENTER, EShortcut::LOBBY_SAVE_GAME }, - {SDLK_r, EShortcut::LOBBY_RANDOM_MAP }, - {SDLK_h, EShortcut::LOBBY_HIDE_CHAT }, - {SDLK_a, EShortcut::LOBBY_ADDITIONAL_OPTIONS }, - {SDLK_s, EShortcut::LOBBY_SELECT_SCENARIO }, - {SDLK_e, EShortcut::GAME_END_TURN }, - {SDLK_l, EShortcut::GAME_LOAD_GAME }, - {SDLK_s, EShortcut::GAME_SAVE_GAME }, - {SDLK_r, EShortcut::GAME_RESTART_GAME }, - {SDLK_m, EShortcut::GAME_TO_MAIN_MENU }, - {SDLK_q, EShortcut::GAME_QUIT_GAME }, - {SDLK_b, EShortcut::GAME_OPEN_MARKETPLACE }, - {SDLK_g, EShortcut::GAME_OPEN_THIEVES_GUILD }, - {SDLK_TAB, EShortcut::GAME_ACTIVATE_CONSOLE }, - {SDLK_o, EShortcut::ADVENTURE_GAME_OPTIONS }, - {SDLK_F6, EShortcut::ADVENTURE_TOGGLE_GRID }, - {SDLK_z, EShortcut::ADVENTURE_SET_HERO_ASLEEP }, - {SDLK_w, EShortcut::ADVENTURE_SET_HERO_AWAKE }, - {SDLK_m, EShortcut::ADVENTURE_MOVE_HERO }, - {SDLK_SPACE, EShortcut::ADVENTURE_VISIT_OBJECT }, - {SDLK_KP_1, EShortcut::ADVENTURE_MOVE_HERO_SW }, - {SDLK_KP_2, EShortcut::ADVENTURE_MOVE_HERO_SS }, - {SDLK_KP_3, EShortcut::ADVENTURE_MOVE_HERO_SE }, - {SDLK_KP_4, EShortcut::ADVENTURE_MOVE_HERO_WW }, - {SDLK_KP_6, EShortcut::ADVENTURE_MOVE_HERO_EE }, - {SDLK_KP_7, EShortcut::ADVENTURE_MOVE_HERO_NW }, - {SDLK_KP_8, EShortcut::ADVENTURE_MOVE_HERO_NN }, - {SDLK_KP_9, EShortcut::ADVENTURE_MOVE_HERO_NE }, - {SDLK_DOWN, EShortcut::ADVENTURE_MOVE_HERO_SS }, - {SDLK_LEFT, EShortcut::ADVENTURE_MOVE_HERO_WW }, - {SDLK_RIGHT, EShortcut::ADVENTURE_MOVE_HERO_EE }, - {SDLK_UP, EShortcut::ADVENTURE_MOVE_HERO_NN }, - {SDLK_RETURN, EShortcut::ADVENTURE_VIEW_SELECTED }, - {SDLK_KP_ENTER, EShortcut::ADVENTURE_VIEW_SELECTED }, - // {SDLK_, EShortcut::ADVENTURE_NEXT_OBJECT }, - {SDLK_t, EShortcut::ADVENTURE_NEXT_TOWN }, - {SDLK_h, EShortcut::ADVENTURE_NEXT_HERO }, - // {SDLK_, EShortcut::ADVENTURE_FIRST_TOWN }, - // {SDLK_, EShortcut::ADVENTURE_FIRST_HERO }, - {SDLK_i, EShortcut::ADVENTURE_VIEW_SCENARIO }, - {SDLK_d, EShortcut::ADVENTURE_DIG_GRAIL }, - {SDLK_p, EShortcut::ADVENTURE_VIEW_PUZZLE }, - {SDLK_v, EShortcut::ADVENTURE_VIEW_WORLD }, - {SDLK_1, EShortcut::ADVENTURE_VIEW_WORLD_X1 }, - {SDLK_2, EShortcut::ADVENTURE_VIEW_WORLD_X2 }, - {SDLK_4, EShortcut::ADVENTURE_VIEW_WORLD_X4 }, - {SDLK_u, EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL}, - {SDLK_k, EShortcut::ADVENTURE_KINGDOM_OVERVIEW}, - {SDLK_q, EShortcut::ADVENTURE_QUEST_LOG }, - {SDLK_c, EShortcut::ADVENTURE_CAST_SPELL }, - {SDLK_g, EShortcut::ADVENTURE_THIEVES_GUILD }, - {SDLK_KP_PLUS, EShortcut::ADVENTURE_ZOOM_IN }, - {SDLK_KP_MINUS, EShortcut::ADVENTURE_ZOOM_OUT }, - {SDLK_BACKSPACE, EShortcut::ADVENTURE_ZOOM_RESET }, - {SDLK_q, EShortcut::BATTLE_TOGGLE_QUEUE }, - {SDLK_f, EShortcut::BATTLE_USE_CREATURE_SPELL }, - {SDLK_s, EShortcut::BATTLE_SURRENDER }, - {SDLK_r, EShortcut::BATTLE_RETREAT }, - {SDLK_a, EShortcut::BATTLE_AUTOCOMBAT }, - {SDLK_e, EShortcut::BATTLE_END_WITH_AUTOCOMBAT}, - {SDLK_c, EShortcut::BATTLE_CAST_SPELL }, - {SDLK_w, EShortcut::BATTLE_WAIT }, - {SDLK_d, EShortcut::BATTLE_DEFEND }, - {SDLK_SPACE, EShortcut::BATTLE_DEFEND }, - {SDLK_UP, EShortcut::BATTLE_CONSOLE_UP }, - {SDLK_DOWN, EShortcut::BATTLE_CONSOLE_DOWN }, - {SDLK_SPACE, EShortcut::BATTLE_TACTICS_NEXT }, - {SDLK_RETURN, EShortcut::BATTLE_TACTICS_END }, - {SDLK_KP_ENTER, EShortcut::BATTLE_TACTICS_END }, - {SDLK_s, EShortcut::BATTLE_SELECT_ACTION }, - {SDLK_i, EShortcut::BATTLE_TOGGLE_HEROES_STATS}, - {SDLK_t, EShortcut::TOWN_OPEN_TAVERN }, - {SDLK_SPACE, EShortcut::TOWN_SWAP_ARMIES }, - {SDLK_END, EShortcut::RECRUITMENT_MAX }, - {SDLK_HOME, EShortcut::RECRUITMENT_MIN }, - {SDLK_u, EShortcut::RECRUITMENT_UPGRADE }, - {SDLK_a, EShortcut::RECRUITMENT_UPGRADE_ALL }, - {SDLK_u, EShortcut::RECRUITMENT_UPGRADE_ALL }, - {SDLK_h, EShortcut::KINGDOM_HEROES_TAB }, - {SDLK_t, EShortcut::KINGDOM_TOWNS_TAB }, - {SDLK_d, EShortcut::HERO_DISMISS }, - {SDLK_c, EShortcut::HERO_COMMANDER }, - {SDLK_l, EShortcut::HERO_LOOSE_FORMATION }, - {SDLK_t, EShortcut::HERO_TIGHT_FORMATION }, - {SDLK_b, EShortcut::HERO_TOGGLE_TACTICS }, - {SDLK_a, EShortcut::SPELLBOOK_TAB_ADVENTURE }, - {SDLK_c, EShortcut::SPELLBOOK_TAB_COMBAT } - }; + const JsonNode config = JsonUtils::assembleFromFiles("config/shortcutsConfig"); - auto range = keyToShortcut.equal_range(key); + for (auto const & entry : config["keyboard"].Struct()) + { + std::string shortcutName = entry.first; + EShortcut shortcutID = findShortcut(shortcutName); + + if (shortcutID == EShortcut::NONE) + { + logGlobal->warn("Unknown shortcut '%s' found when loading shortcuts config!", shortcutName); + continue; + } + + if (entry.second.isString()) + { + mappedShortcuts.emplace(entry.second.String(), shortcutID); + } + + if (entry.second.isVector()) + { + for (auto const & entryVector : entry.second.Vector()) + mappedShortcuts.emplace(entryVector.String(), shortcutID); + } + } +} + +std::vector ShortcutHandler::translateKeycode(const std::string & key) const +{ + auto range = mappedShortcuts.equal_range(key); // FIXME: some code expects calls to keyPressed / captureThisKey even without defined hotkeys if (range.first == range.second) diff --git a/client/gui/ShortcutHandler.h b/client/gui/ShortcutHandler.h index 46dc9937c..3b37b7223 100644 --- a/client/gui/ShortcutHandler.h +++ b/client/gui/ShortcutHandler.h @@ -11,13 +11,15 @@ #pragma once enum class EShortcut; -using SDL_Keycode = int32_t; class ShortcutHandler { + std::multimap mappedShortcuts; public: + ShortcutHandler(); + /// returns list of shortcuts assigned to provided SDL keycode - std::vector translateKeycode(SDL_Keycode key) const; + std::vector translateKeycode(const std::string & key) const; /// attempts to find shortcut by its unique identifier. Returns EShortcut::NONE on failure EShortcut findShortcut(const std::string & identifier ) const; diff --git a/config/shortcutsConfig.json b/config/shortcutsConfig.json new file mode 100644 index 000000000..b0a97aaec --- /dev/null +++ b/config/shortcutsConfig.json @@ -0,0 +1,128 @@ +// This file defines all shortcuts used by VCMI +// For modders: create file with same name (Content/config/shortcutsConfig.json) to modify this set in your mod +// For players: create file Documents/vcmi/config/shortcutsConfig.json to modify this set (other platforms: check "User data location" in Launcher) +// +// When creating your own config, you can remove all hotkeys that you have not changed and game will read them from this file +{ + "keyboard" : { + "globalAccept": [ "Return", "Keypad Enter"], + "globalCancel": "Escape", + "globalReturn": [ "Escape", "Return", "Keypad Enter"], + "globalFullscreen": "F4", + "globalOptions": "O", + "globalBackspace": "Backspace", + "globalMoveFocus": "Tab", + "moveLeft": "Left", + "moveRight": "Right", + "moveUp": "Up", + "moveDown": "Down", + "moveFirst": "Home", + "moveLast": "End", + "movePageUp": "PageUp", + "movePageDown": "PageDown", + "selectIndex1": "1", + "selectIndex2": "2", + "selectIndex3": "3", + "selectIndex4": "4", + "selectIndex5": "5", + "selectIndex6": "6", + "selectIndex7": "7", + "selectIndex8": "8", + "mainMenuNewGame": "N", + "mainMenuLoadGame": "L", + "mainMenuHighScores": "H", + "mainMenuCredits": "C", + "mainMenuQuit": "Q", + "mainMenuBack": "B", + "mainMenuSingleplayer": "S", + "mainMenuMultiplayer": "M", + "mainMenuCampaign": "C", + "mainMenuTutorial": "T", + "mainMenuCampaignSod": "S", + "mainMenuCampaignRoe": "R", + "mainMenuCampaignAb": "A", + "mainMenuCampaignCustom": "C", + "lobbyBeginGame": [ "B", "Return", "Keypad Enter"], + "lobbyLoadGame": [ "L", "Return", "Keypad Enter"], + "lobbySaveGame": [ "S", "Return", "Keypad Enter"], + "lobbyRandomMap": "R", + "lobbyHideChat": "H", + "lobbyAdditionalOptions": "A", + "lobbySelectScenario": "S", + "gameEndTurn": "E", + "gameLoadGame": "L", + "gameSaveGame": "S", + "gameRestartGame": "R", + "gameMainMenu": "M", + "gameQuitGame": "Q", + "gameOpenMarketplace": "B", + "gameOpenThievesGuild": "G", + "gameActivateConsole": "Tab", + "adventureGameOptions": "O", + "adventureToggleGrid": "F6", + "adventureToggleSleep": [], + "adventureSetHeroAsleep": "Z", + "adventureSetHeroAwake": "W", + "adventureMoveHero": "M", + "adventureVisitObject": "Space", + "adventureMoveHeroSW": [ "Keypad 1" ], + "adventureMoveHeroSS": [ "Keypad 2", "Down" ], + "adventureMoveHeroSE": [ "Keypad 3" ], + "adventureMoveHeroWW": [ "Keypad 4", "Left" ], + "adventureMoveHeroEE": [ "Keypad 6", "Right" ], + "adventureMoveHeroNW": [ "Keypad 7" ], + "adventureMoveHeroNN": [ "Keypad 8", "Up" ], + "adventureMoveHeroNE": [ "Keypad 9" ], + "adventureViewSelected": [ "Return", "Keypad Enter"], + "adventureNextObject": [], + "adventureNextTown": "T", + "adventureNextHero": "H", + "adventureFirstTown": [], + "adventureFirstHero": [], + "adventureViewScenario": "I", + "adventureDigGrail": "D", + "adventureViewPuzzle": "P", + "adventureViewWorld": "V", + "adventureViewWorld1": "1", + "adventureViewWorld2": "2", + "adventureViewWorld4": "4", + "adventureToggleMapLevel": "U", + "adventureKingdomOverview": "K", + "adventureQuestLog": "Q", + "adventureCastSpell": "C", + "adventureThievesGuild": "G", + "adventureExitWorldView": [ "Escape", "Return", "Keypad Enter"], + "adventureZoomIn": "Keypad +", + "adventureZoomOut": "Keypad -", + "adventureZoomReset": "Backspace", + "battleToggleQueue": "Q", + "battleUseCreatureSpell": "F", + "battleSurrender": "S", + "battleRetreat": "R", + "battleAutocombat": "A", + "battleAutocombatEnd": "E", + "battleCastSpell": "C", + "battleWait": "W", + "battleDefend": [ "D", "Space"], + "battleConsoleUp": "Up", + "battleConsoleDown": "Down", + "battleTacticsNext": "Space", + "battleTacticsEnd": [ "Return", "Keypad Enter"], + "battleSelectAction": "S", + "townOpenTavern": "T", + "townSwapArmies": "Space", + "recruitmentMax": "End", + "recruitmentMin": "Home", + "recruitmentUpgrade": "U", + "recruitmentUpgradeAll": [ "A", "U" ], + "kingdomHeroesTab": "H", + "kingdomTownsTab": "T", + "heroDismiss": "D", + "heroCommander": "C", + "heroLooseFormation": "L", + "heroTightFormation": "T", + "heroToggleTactics": "B", + "spellbookTabAdventure": "A", + "spellbookTabCombat": "C" + } +}