mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-23 06:44:38 +01:00
* create STR_7000: "or" * declare STR_OR * enable localisation via LanguageGetString() * Update ShortcutManager.cpp * Update changelog.txt --------- Co-authored-by: Tulio Leao <tupaschoal@gmail.com>
450 lines
14 KiB
C++
450 lines
14 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2025 OpenRCT2 developers
|
|
*
|
|
* For a complete list of all authors, please refer to contributors.md
|
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
|
*
|
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
|
*****************************************************************************/
|
|
|
|
#include "ShortcutManager.h"
|
|
|
|
#include "ShortcutIds.h"
|
|
|
|
#include <SDL.h>
|
|
#include <openrct2-ui/UiStringIds.h>
|
|
#include <openrct2/Context.h>
|
|
#include <openrct2/PlatformEnvironment.h>
|
|
#include <openrct2/core/Console.hpp>
|
|
#include <openrct2/core/DataSerialiser.h>
|
|
#include <openrct2/core/FileStream.h>
|
|
#include <openrct2/core/FileSystem.hpp>
|
|
#include <openrct2/core/Json.hpp>
|
|
#include <openrct2/core/String.hpp>
|
|
#include <openrct2/ui/WindowManager.h>
|
|
|
|
using namespace OpenRCT2::Ui;
|
|
|
|
std::string_view RegisteredShortcut::GetTopLevelGroup() const
|
|
{
|
|
auto fullstopIndex = Id.find('.');
|
|
if (fullstopIndex != std::string::npos)
|
|
{
|
|
return std::string_view(Id.c_str(), fullstopIndex);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
std::string_view RegisteredShortcut::GetGroup() const
|
|
{
|
|
auto fullstopIndex = Id.find_last_of('.');
|
|
if (fullstopIndex != std::string::npos)
|
|
{
|
|
return std::string_view(Id.c_str(), fullstopIndex);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
bool RegisteredShortcut::Matches(const InputEvent& e) const
|
|
{
|
|
if (IsSuitableInputEvent(e))
|
|
{
|
|
auto result = std::find_if(
|
|
Current.begin(), Current.end(), [e](const ShortcutInput& action) { return action.Matches(e); });
|
|
return result != Current.end();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool RegisteredShortcut::IsSuitableInputEvent(const InputEvent& e) const
|
|
{
|
|
// Do not intercept button releases
|
|
if (e.State == InputEventState::Release)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (e.DeviceKind == InputDeviceKind::Mouse)
|
|
{
|
|
// Do not allow LMB or RMB to be shortcut
|
|
if (e.Button == 0 || e.Button == 1)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else if (e.DeviceKind == InputDeviceKind::Keyboard)
|
|
{
|
|
// Do not allow modifier keys alone
|
|
switch (e.Button)
|
|
{
|
|
case SDLK_LCTRL:
|
|
case SDLK_RCTRL:
|
|
case SDLK_LSHIFT:
|
|
case SDLK_RSHIFT:
|
|
case SDLK_LALT:
|
|
case SDLK_RALT:
|
|
case SDLK_LGUI:
|
|
case SDLK_RGUI:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string RegisteredShortcut::GetDisplayString() const
|
|
{
|
|
std::string result;
|
|
auto numChords = Current.size();
|
|
for (size_t i = 0; i < numChords; i++)
|
|
{
|
|
const auto& kc = Current[i];
|
|
result += kc.ToLocalisedString();
|
|
if (i < numChords - 1)
|
|
{
|
|
result += " ";
|
|
result += LanguageGetString(STR_OR);
|
|
result += " ";
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
ShortcutManager::ShortcutManager(IPlatformEnvironment& env)
|
|
: _env(env)
|
|
{
|
|
RegisterDefaultShortcuts();
|
|
}
|
|
|
|
void ShortcutManager::RegisterShortcut(RegisteredShortcut&& shortcut)
|
|
{
|
|
if (!shortcut.Id.empty() && GetShortcut(shortcut.Id) == nullptr)
|
|
{
|
|
auto id = std::make_unique<std::string>(shortcut.Id);
|
|
auto idView = std::string_view(*id);
|
|
_ids.push_back(std::move(id));
|
|
shortcut.OrderIndex = Shortcuts.size();
|
|
Shortcuts[idView] = shortcut;
|
|
}
|
|
}
|
|
|
|
RegisteredShortcut* ShortcutManager::GetShortcut(std::string_view id)
|
|
{
|
|
auto result = Shortcuts.find(id);
|
|
return result == Shortcuts.end() ? nullptr : &result->second;
|
|
}
|
|
|
|
void ShortcutManager::RemoveShortcut(std::string_view id)
|
|
{
|
|
Shortcuts.erase(id);
|
|
_ids.erase(
|
|
std::remove_if(_ids.begin(), _ids.end(), [id](const std::unique_ptr<std::string>& x) { return *x == id; }), _ids.end());
|
|
}
|
|
|
|
bool ShortcutManager::IsPendingShortcutChange() const
|
|
{
|
|
return !_pendingShortcutChange.empty();
|
|
}
|
|
|
|
void ShortcutManager::SetPendingShortcutChange(std::string_view id)
|
|
{
|
|
_pendingShortcutChange = id;
|
|
}
|
|
|
|
void ShortcutManager::ProcessEvent(const InputEvent& e)
|
|
{
|
|
if (!IsPendingShortcutChange())
|
|
{
|
|
for (const auto& shortcut : Shortcuts)
|
|
{
|
|
if (shortcut.second.Matches(e))
|
|
{
|
|
shortcut.second.Action();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto shortcut = GetShortcut(_pendingShortcutChange);
|
|
if (shortcut != nullptr && shortcut->IsSuitableInputEvent(e))
|
|
{
|
|
auto shortcutInput = ShortcutInput::FromInputEvent(e);
|
|
if (shortcutInput.has_value())
|
|
{
|
|
shortcut->Current.clear();
|
|
shortcut->Current.push_back(std::move(shortcutInput.value()));
|
|
}
|
|
_pendingShortcutChange.clear();
|
|
|
|
auto* windowMgr = Ui::GetWindowManager();
|
|
windowMgr->CloseByClass(WindowClass::ChangeKeyboardShortcut);
|
|
SaveUserBindings();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ShortcutManager::ProcessEventForSpecificShortcut(const InputEvent& e, std::string_view id)
|
|
{
|
|
auto shortcut = GetShortcut(id);
|
|
if (shortcut != nullptr && shortcut->Matches(e))
|
|
{
|
|
shortcut->Action();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ShortcutManager::LoadUserBindings()
|
|
{
|
|
try
|
|
{
|
|
auto path = fs::u8path(_env.GetFilePath(PathId::configShortcuts));
|
|
if (fs::exists(path))
|
|
{
|
|
LoadUserBindings(path);
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
Console::WriteLine("Importing legacy shortcuts...");
|
|
auto legacyPath = fs::u8path(_env.GetFilePath(PathId::configShortcutsLegacy));
|
|
if (fs::exists(legacyPath))
|
|
{
|
|
LoadLegacyBindings(legacyPath);
|
|
SaveUserBindings();
|
|
Console::WriteLine("Legacy shortcuts imported");
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
Console::Error::WriteLine("Unable to import legacy shortcut bindings: %s", e.what());
|
|
}
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
Console::Error::WriteLine("Unable to load shortcut bindings: %s", e.what());
|
|
}
|
|
}
|
|
|
|
std::optional<ShortcutInput> ShortcutManager::ConvertLegacyBinding(uint16_t binding)
|
|
{
|
|
constexpr uint16_t kNullBinding = 0xFFFF;
|
|
constexpr uint16_t kShift = 0x100;
|
|
constexpr uint16_t kCtrl = 0x200;
|
|
constexpr uint16_t kAlt = 0x400;
|
|
constexpr uint16_t kCmd = 0x800;
|
|
|
|
if (binding == kNullBinding)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
ShortcutInput result;
|
|
result.Kind = InputDeviceKind::Keyboard;
|
|
if (binding & kShift)
|
|
result.Modifiers |= KMOD_SHIFT;
|
|
if (binding & kCtrl)
|
|
result.Modifiers |= KMOD_CTRL;
|
|
if (binding & kAlt)
|
|
result.Modifiers |= KMOD_ALT;
|
|
if (binding & kCmd)
|
|
result.Modifiers |= KMOD_GUI;
|
|
result.Button = SDL_GetKeyFromScancode(static_cast<SDL_Scancode>(binding & 0xFF));
|
|
return result;
|
|
}
|
|
|
|
void ShortcutManager::LoadLegacyBindings(const fs::path& path)
|
|
{
|
|
constexpr int32_t kSupportedFileVersion = 1;
|
|
constexpr int32_t kMaxLegacyShortcuts = 85;
|
|
|
|
auto fs = FileStream(path, FileMode::open);
|
|
auto version = fs.ReadValue<uint16_t>();
|
|
if (version == kSupportedFileVersion)
|
|
{
|
|
for (size_t i = 0; i < kMaxLegacyShortcuts; i++)
|
|
{
|
|
auto value = fs.ReadValue<uint16_t>();
|
|
auto shortcutId = GetLegacyShortcutId(i);
|
|
if (!shortcutId.empty())
|
|
{
|
|
auto shortcut = GetShortcut(shortcutId);
|
|
if (shortcut != nullptr)
|
|
{
|
|
shortcut->Current.clear();
|
|
auto input = ConvertLegacyBinding(value);
|
|
if (input.has_value())
|
|
{
|
|
shortcut->Current.push_back(std::move(input.value()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShortcutManager::LoadUserBindings(const fs::path& path)
|
|
{
|
|
auto root = Json::ReadFromFile(path.u8string());
|
|
if (root.is_object())
|
|
{
|
|
for (auto it = root.begin(); it != root.end(); ++it)
|
|
{
|
|
const auto& key = it.key();
|
|
const auto& value = it.value();
|
|
|
|
const auto& shortcut = GetShortcut(key);
|
|
if (shortcut != nullptr)
|
|
{
|
|
shortcut->Current.clear();
|
|
if (value.is_string())
|
|
{
|
|
shortcut->Current.emplace_back(value.get<std::string>());
|
|
}
|
|
else if (value.is_array())
|
|
{
|
|
for (auto& subValue : value)
|
|
{
|
|
shortcut->Current.emplace_back(subValue.get<std::string>());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShortcutManager::SaveUserBindings()
|
|
{
|
|
try
|
|
{
|
|
auto path = fs::u8path(_env.GetFilePath(PathId::configShortcuts));
|
|
SaveUserBindings(path);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
Console::Error::WriteLine("Unable to save shortcut bindings: %s", e.what());
|
|
}
|
|
}
|
|
|
|
void ShortcutManager::SaveUserBindings(const fs::path& path)
|
|
{
|
|
json_t root;
|
|
if (fs::exists(path))
|
|
{
|
|
root = Json::ReadFromFile(path.u8string());
|
|
}
|
|
|
|
for (const auto& shortcut : Shortcuts)
|
|
{
|
|
auto& jShortcut = root[shortcut.second.Id];
|
|
if (shortcut.second.Current.size() == 1)
|
|
{
|
|
jShortcut = shortcut.second.Current[0].ToString();
|
|
}
|
|
else
|
|
{
|
|
jShortcut = nlohmann::json::array();
|
|
for (const auto& binding : shortcut.second.Current)
|
|
{
|
|
jShortcut.push_back(binding.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
Json::WriteToFile(path.u8string(), root);
|
|
}
|
|
|
|
std::string_view ShortcutManager::GetLegacyShortcutId(size_t index)
|
|
{
|
|
static constexpr std::string_view _legacyMap[] = {
|
|
ShortcutId::kInterfaceCloseTop,
|
|
ShortcutId::kInterfaceCloseAll,
|
|
ShortcutId::kInterfaceCancelConstruction,
|
|
ShortcutId::kInterfacePause,
|
|
ShortcutId::kViewGeneralZoomOut,
|
|
ShortcutId::kViewGeneralZoomIn,
|
|
ShortcutId::kViewGeneralRotateClockwise,
|
|
ShortcutId::kViewGeneralRotateAnticlockwise,
|
|
ShortcutId::kInterfaceRotateConstruction,
|
|
ShortcutId::kViewToggleUnderground,
|
|
ShortcutId::kViewToggleBaseLand,
|
|
ShortcutId::kViewToggleVerticalLand,
|
|
ShortcutId::kViewToggleRides,
|
|
ShortcutId::kViewToggleScenery,
|
|
ShortcutId::kViewToggleSupports,
|
|
ShortcutId::kViewToggleGuests,
|
|
ShortcutId::kViewToggleLandHeightMarkers,
|
|
ShortcutId::kViewToggleTrackHeightMarkers,
|
|
ShortcutId::kViewToggleFootpathHeightMarkers,
|
|
ShortcutId::kInterfaceOpenLand,
|
|
ShortcutId::kInterfaceOpenWater,
|
|
ShortcutId::kInterfaceOpenScenery,
|
|
ShortcutId::kInterfaceOpenFootpaths,
|
|
ShortcutId::kInterfaceOpenNewRide,
|
|
ShortcutId::kInterfaceOpenFinances,
|
|
ShortcutId::kInterfaceOpenResearch,
|
|
ShortcutId::kInterfaceOpenRides,
|
|
ShortcutId::kInterfaceOpenPark,
|
|
ShortcutId::kInterfaceOpenGuests,
|
|
ShortcutId::kInterfaceOpenStaff,
|
|
ShortcutId::kInterfaceOpenMessages,
|
|
ShortcutId::kInterfaceOpenMap,
|
|
ShortcutId::kInterfaceScreenshot,
|
|
ShortcutId::kInterfaceDecreaseSpeed,
|
|
ShortcutId::kInterfaceIncreaseSpeed,
|
|
ShortcutId::kInterfaceOpenCheats,
|
|
ShortcutId::kInterfaceToggleToolbars,
|
|
ShortcutId::kViewScrollUp,
|
|
ShortcutId::kViewScrollLeft,
|
|
ShortcutId::kViewScrollDown,
|
|
ShortcutId::kViewScrollRight,
|
|
ShortcutId::kInterfaceMultiplayerChat,
|
|
ShortcutId::kInterfaceSaveGame,
|
|
ShortcutId::kInterfaceShowOptions,
|
|
ShortcutId::kInterfaceMute,
|
|
ShortcutId::kInterfaceScaleToggleWindowMode,
|
|
ShortcutId::kInterfaceMultiplayerShow,
|
|
std::string_view(),
|
|
ShortcutId::kDebugTogglePaintDebugWindow,
|
|
ShortcutId::kViewToggleFootpaths,
|
|
ShortcutId::kWindowRideConstructionTurnLeft,
|
|
ShortcutId::kWindowRideConstructionTurnRight,
|
|
ShortcutId::kWindowRideConstructionDefault,
|
|
ShortcutId::kWindowRideConstructionSlopeDown,
|
|
ShortcutId::kWindowRideConstructionSlopeUp,
|
|
ShortcutId::kWindowRideConstructionChainLift,
|
|
ShortcutId::kWindowRideConstructionBankLeft,
|
|
ShortcutId::kWindowRideConstructionBankRight,
|
|
ShortcutId::kWindowRideConstructionPrevious,
|
|
ShortcutId::kWindowRideConstructionNext,
|
|
ShortcutId::kWindowRideConstructionBuild,
|
|
ShortcutId::kWindowRideConstructionDemolish,
|
|
ShortcutId::kInterfaceLoadGame,
|
|
ShortcutId::kInterfaceClearScenery,
|
|
ShortcutId::kViewToggleGridlines,
|
|
ShortcutId::kViewToggleCutAway,
|
|
ShortcutId::kViewToggleFootpathIssues,
|
|
ShortcutId::kInterfaceOpenTileInspector,
|
|
ShortcutId::kDebugAdvanceTick,
|
|
ShortcutId::kInterfaceSceneryPicker,
|
|
ShortcutId::kInterfaceScaleIncrease,
|
|
ShortcutId::kInterfaceScaleDecrease,
|
|
ShortcutId::kWindowTileInspectorToggleInvisibility,
|
|
ShortcutId::kWindowTileInspectorCopy,
|
|
ShortcutId::kWindowTileInspectorPaste,
|
|
ShortcutId::kWindowTileInspectorRemove,
|
|
ShortcutId::kWindowTileInspectorMoveUp,
|
|
ShortcutId::kWindowTileInspectorMoveDown,
|
|
ShortcutId::kWindowTileInspectorIncreaseX,
|
|
ShortcutId::kWindowTileInspectorDecreaseX,
|
|
ShortcutId::kWindowTileInspectorIncreaseY,
|
|
ShortcutId::kWindowTileInspectorDecreaseY,
|
|
ShortcutId::kWindowTileInspectorIncreaseHeight,
|
|
ShortcutId::kWindowTileInspectorDecreaseHeight,
|
|
ShortcutId::kInterfaceDisableClearance,
|
|
};
|
|
return index < std::size(_legacyMap) ? _legacyMap[index] : std::string_view();
|
|
}
|