-
-
Notifications
You must be signed in to change notification settings - Fork 311
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor accelerator / global shortcuts handling
* Removes wxAcceleratorEntryUnicode and assorted arrays in favor of a new class, `config::Shortcuts`, which handles UserInput assignment to commands and resolution at runtime. `config::Shortcuts` also handles the INI user configuration in a backwards-compatible way. Runtime resolution of UserInput to command is also now logarithmic rather than linear. * The same shortcut can no longer be assigned to 2 different commands, which fixes #158. * Moves the `AccelConfig` dialog to its own dedicated class.
- Loading branch information
Showing
21 changed files
with
1,346 additions
and
1,362 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
#include "config/shortcuts.h" | ||
|
||
#include <wx/xrc/xmlres.h> | ||
|
||
#define VBAM_SHORTCUTS_INTERNAL_INCLUDE | ||
#include "config/internal/shortcuts-internal.h" | ||
#undef VBAM_SHORTCUTS_INTERNAL_INCLUDE | ||
|
||
namespace config { | ||
namespace internal { | ||
|
||
const std::unordered_map<int, UserInput>& DefaultShortcuts() { | ||
static const std::unordered_map<int, UserInput> kDefaultShortcuts = { | ||
{XRCID("CheatsList"), UserInput('C', wxMOD_CMD)}, | ||
{XRCID("NextFrame"), UserInput('N', wxMOD_CMD)}, | ||
// this was annoying people A LOT #334 | ||
//{wxID_EXIT, UserInput(WXK_ESCAPE, wxMOD_NONE)}, | ||
// this was annoying people #298 | ||
//{wxID_EXIT, UserInput('X', wxMOD_CMD)}, | ||
|
||
{wxID_EXIT, UserInput('Q', wxMOD_CMD)}, | ||
{wxID_CLOSE, UserInput('W', wxMOD_CMD)}, | ||
// load most recent is more commonly used than load state | ||
// {XRCID("Load"), UserInput('L', wxMOD_CMD)}, | ||
{XRCID("LoadGameRecent"), UserInput('L', wxMOD_CMD)}, | ||
{XRCID("LoadGame01"), UserInput(WXK_F1, wxMOD_NONE)}, | ||
{XRCID("LoadGame02"), UserInput(WXK_F2, wxMOD_NONE)}, | ||
{XRCID("LoadGame03"), UserInput(WXK_F3, wxMOD_NONE)}, | ||
{XRCID("LoadGame04"), UserInput(WXK_F4, wxMOD_NONE)}, | ||
{XRCID("LoadGame05"), UserInput(WXK_F5, wxMOD_NONE)}, | ||
{XRCID("LoadGame06"), UserInput(WXK_F6, wxMOD_NONE)}, | ||
{XRCID("LoadGame07"), UserInput(WXK_F7, wxMOD_NONE)}, | ||
{XRCID("LoadGame08"), UserInput(WXK_F8, wxMOD_NONE)}, | ||
{XRCID("LoadGame09"), UserInput(WXK_F9, wxMOD_NONE)}, | ||
{XRCID("LoadGame10"), UserInput(WXK_F10, wxMOD_NONE)}, | ||
{XRCID("Pause"), UserInput(WXK_PAUSE, wxMOD_NONE)}, | ||
{XRCID("Pause"), UserInput('P', wxMOD_CMD)}, | ||
{XRCID("Reset"), UserInput('R', wxMOD_CMD)}, | ||
// add shortcuts for original size multiplier #415 | ||
{XRCID("SetSize1x"), UserInput('1', wxMOD_NONE)}, | ||
{XRCID("SetSize2x"), UserInput('2', wxMOD_NONE)}, | ||
{XRCID("SetSize3x"), UserInput('3', wxMOD_NONE)}, | ||
{XRCID("SetSize4x"), UserInput('4', wxMOD_NONE)}, | ||
{XRCID("SetSize5x"), UserInput('5', wxMOD_NONE)}, | ||
{XRCID("SetSize6x"), UserInput('6', wxMOD_NONE)}, | ||
// save oldest is more commonly used than save other | ||
// {XRCID("Save"), UserInput('S', wxMOD_CMD)}, | ||
{XRCID("SaveGameOldest"), UserInput('S', wxMOD_CMD)}, | ||
{XRCID("SaveGame01"), UserInput(WXK_F1, wxMOD_SHIFT)}, | ||
{XRCID("SaveGame02"), UserInput(WXK_F2, wxMOD_SHIFT)}, | ||
{XRCID("SaveGame03"), UserInput(WXK_F3, wxMOD_SHIFT)}, | ||
{XRCID("SaveGame04"), UserInput(WXK_F4, wxMOD_SHIFT)}, | ||
{XRCID("SaveGame05"), UserInput(WXK_F5, wxMOD_SHIFT)}, | ||
{XRCID("SaveGame06"), UserInput(WXK_F6, wxMOD_SHIFT)}, | ||
{XRCID("SaveGame07"), UserInput(WXK_F7, wxMOD_SHIFT)}, | ||
{XRCID("SaveGame08"), UserInput(WXK_F8, wxMOD_SHIFT)}, | ||
{XRCID("SaveGame09"), UserInput(WXK_F9, wxMOD_SHIFT)}, | ||
{XRCID("SaveGame10"), UserInput(WXK_F10, wxMOD_SHIFT)}, | ||
// I prefer the SDL ESC key binding | ||
// {XRCID("ToggleFullscreen"), UserInput(WXK_ESCAPE, wxMOD_NONE)}, | ||
// alt-enter is more standard anyway | ||
{XRCID("ToggleFullscreen"), UserInput(WXK_RETURN, wxMOD_ALT)}, | ||
{XRCID("JoypadAutofireA"), UserInput('1', wxMOD_ALT)}, | ||
{XRCID("JoypadAutofireB"), UserInput('2', wxMOD_ALT)}, | ||
{XRCID("JoypadAutofireL"), UserInput('3', wxMOD_ALT)}, | ||
{XRCID("JoypadAutofireR"), UserInput('4', wxMOD_ALT)}, | ||
{XRCID("VideoLayersBG0"), UserInput('1', wxMOD_CMD)}, | ||
{XRCID("VideoLayersBG1"), UserInput('2', wxMOD_CMD)}, | ||
{XRCID("VideoLayersBG2"), UserInput('3', wxMOD_CMD)}, | ||
{XRCID("VideoLayersBG3"), UserInput('4', wxMOD_CMD)}, | ||
{XRCID("VideoLayersOBJ"), UserInput('5', wxMOD_CMD)}, | ||
{XRCID("VideoLayersWIN0"), UserInput('6', wxMOD_CMD)}, | ||
{XRCID("VideoLayersWIN1"), UserInput('7', wxMOD_CMD)}, | ||
{XRCID("VideoLayersOBJWIN"), UserInput('8', wxMOD_CMD)}, | ||
{XRCID("Rewind"), UserInput('B', wxMOD_CMD)}, | ||
// following are not in standard menus | ||
// FILExx are filled in when recent menu is filled | ||
{wxID_FILE1, UserInput(WXK_F1, wxMOD_CMD)}, | ||
{wxID_FILE2, UserInput(WXK_F2, wxMOD_CMD)}, | ||
{wxID_FILE3, UserInput(WXK_F3, wxMOD_CMD)}, | ||
{wxID_FILE4, UserInput(WXK_F4, wxMOD_CMD)}, | ||
{wxID_FILE5, UserInput(WXK_F5, wxMOD_CMD)}, | ||
{wxID_FILE6, UserInput(WXK_F6, wxMOD_CMD)}, | ||
{wxID_FILE7, UserInput(WXK_F7, wxMOD_CMD)}, | ||
{wxID_FILE8, UserInput(WXK_F8, wxMOD_CMD)}, | ||
{wxID_FILE9, UserInput(WXK_F9, wxMOD_CMD)}, | ||
{wxID_FILE10, UserInput(WXK_F10, wxMOD_CMD)}, | ||
{XRCID("VideoLayersReset"), UserInput('0', wxMOD_CMD)}, | ||
{XRCID("ChangeFilter"), UserInput('G', wxMOD_CMD)}, | ||
{XRCID("ChangeIFB"), UserInput('I', wxMOD_CMD)}, | ||
{XRCID("IncreaseVolume"), UserInput(WXK_NUMPAD_ADD, wxMOD_NONE)}, | ||
{XRCID("DecreaseVolume"), UserInput(WXK_NUMPAD_SUBTRACT, wxMOD_NONE)}, | ||
{XRCID("ToggleSound"), UserInput(WXK_NUMPAD_ENTER, wxMOD_NONE)}, | ||
}; | ||
return kDefaultShortcuts; | ||
} | ||
|
||
UserInput DefaultShortcutForCommand(int command) { | ||
const auto& iter = DefaultShortcuts().find(command); | ||
if (iter != DefaultShortcuts().end()) { | ||
return iter->second; | ||
} | ||
return UserInput(); | ||
} | ||
|
||
} // namespace internal | ||
} // namespace config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
#ifndef VBAM_SHORTCUTS_INTERNAL_INCLUDE | ||
#error "Do not include "config/internal/shortcuts-internal.h" outside of the implementation." | ||
#endif | ||
|
||
#include <set> | ||
#include <unordered_map> | ||
|
||
#include "config/user-input.h" | ||
|
||
namespace config { | ||
namespace internal { | ||
|
||
// Returns the map of commands to their default shortcut. | ||
const std::unordered_map<int, UserInput>& DefaultShortcuts(); | ||
|
||
// Returns the default shortcut for the given `command`. | ||
// Returns an Invalid UserInput if there is no default shortcut for `command`. | ||
UserInput DefaultShortcutForCommand(int command); | ||
|
||
} // namespace internal | ||
} // namespace config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
#include "config/shortcuts.h" | ||
|
||
#include <wx/string.h> | ||
#include <wx/translation.h> | ||
#include <wx/xrc/xmlres.h> | ||
|
||
#include "config/user-input.h" | ||
|
||
#define VBAM_SHORTCUTS_INTERNAL_INCLUDE | ||
#include "config/internal/shortcuts-internal.h" | ||
#undef VBAM_SHORTCUTS_INTERNAL_INCLUDE | ||
|
||
namespace config { | ||
|
||
namespace { | ||
|
||
int NoopCommand() { | ||
static const int noop = XRCID("NOOP"); | ||
return noop; | ||
} | ||
|
||
} // namespace | ||
|
||
Shortcuts::Shortcuts() { | ||
// Set up default shortcuts. | ||
for (const auto& iter : internal::DefaultShortcuts()) { | ||
AssignInputToCommand(iter.second, iter.first); | ||
} | ||
} | ||
|
||
Shortcuts::Shortcuts(const std::unordered_map<int, std::set<UserInput>>& command_to_inputs, | ||
const std::map<UserInput, int>& input_to_command, | ||
const std::map<UserInput, int>& disabled_defaults) | ||
: command_to_inputs_(command_to_inputs.begin(), command_to_inputs.end()), | ||
input_to_command_(input_to_command.begin(), input_to_command.end()), | ||
disabled_defaults_(disabled_defaults.begin(), disabled_defaults.end()) {} | ||
|
||
std::vector<std::pair<int, wxString>> Shortcuts::GetConfiguration() const { | ||
std::vector<std::pair<int, wxString>> config; | ||
config.reserve(command_to_inputs_.size() + 1); | ||
|
||
if (!disabled_defaults_.empty()) { | ||
std::set<UserInput> noop_inputs; | ||
for (const auto& iter : disabled_defaults_) { | ||
noop_inputs.insert(iter.first); | ||
} | ||
config.push_back(std::make_pair(NoopCommand(), UserInput::SpanToConfigString(noop_inputs))); | ||
} | ||
|
||
for (const auto& iter : command_to_inputs_) { | ||
std::set<UserInput> inputs; | ||
for (const auto& input : iter.second) { | ||
if (internal::DefaultShortcutForCommand(iter.first) != input) { | ||
// Not a default input. | ||
inputs.insert(input); | ||
} | ||
} | ||
if (!inputs.empty()) { | ||
config.push_back(std::make_pair(iter.first, UserInput::SpanToConfigString(inputs))); | ||
} | ||
} | ||
|
||
return config; | ||
} | ||
|
||
std::set<UserInput> Shortcuts::InputsForCommand(int command) const { | ||
if (command == NoopCommand()) { | ||
std::set<UserInput> noop_inputs; | ||
for (const auto& iter : disabled_defaults_) { | ||
noop_inputs.insert(iter.first); | ||
} | ||
return noop_inputs; | ||
} | ||
|
||
auto iter = command_to_inputs_.find(command); | ||
if (iter == command_to_inputs_.end()) { | ||
return {}; | ||
} | ||
return iter->second; | ||
} | ||
|
||
int Shortcuts::CommandForInput(const UserInput& input) const { | ||
const auto iter = input_to_command_.find(input); | ||
if (iter == input_to_command_.end()) { | ||
return 0; | ||
} | ||
return iter->second; | ||
} | ||
|
||
std::set<wxJoystick> Shortcuts::Joysticks() const { | ||
std::set<wxJoystick> output; | ||
for (const auto& iter : command_to_inputs_) { | ||
for (const UserInput& user_input : iter.second) { | ||
output.insert(user_input.joystick()); | ||
} | ||
} | ||
return output; | ||
} | ||
|
||
Shortcuts Shortcuts::Clone() const { | ||
return Shortcuts(this->command_to_inputs_, this->input_to_command_, this->disabled_defaults_); | ||
} | ||
|
||
void Shortcuts::AssignInputToCommand(const UserInput& input, int command) { | ||
if (command == NoopCommand()) { | ||
// "Assigning to Noop" means unassinging the default binding. | ||
UnassignDefaultBinding(input); | ||
return; | ||
} | ||
|
||
// Remove the existing binding if it exists. | ||
auto iter = input_to_command_.find(input); | ||
if (iter != input_to_command_.end()) { | ||
UnassignInput(input); | ||
} | ||
|
||
auto disabled_iter = disabled_defaults_.find(input); | ||
if (disabled_iter != disabled_defaults_.end()) { | ||
int original_command = disabled_iter->second; | ||
if (original_command == command) { | ||
// Restoring a disabled input. Remove from the disabled set. | ||
disabled_defaults_.erase(disabled_iter); | ||
} | ||
// Then, just continue normally. | ||
} | ||
|
||
command_to_inputs_[command].emplace(input); | ||
input_to_command_[input] = command; | ||
} | ||
|
||
void Shortcuts::UnassignInput(const UserInput& input) { | ||
assert(input); | ||
|
||
auto iter = input_to_command_.find(input); | ||
if (iter == input_to_command_.end()) { | ||
// Input not found, nothing to do. | ||
return; | ||
} | ||
|
||
if (internal::DefaultShortcutForCommand(iter->second) == input) { | ||
// Unassigning a default binding has some special handling. | ||
UnassignDefaultBinding(input); | ||
return; | ||
} | ||
|
||
// Otherwise, just remove it from the 2 maps. | ||
auto command_iter = command_to_inputs_.find(iter->second); | ||
assert(command_iter != command_to_inputs_.end()); | ||
|
||
command_iter->second.erase(input); | ||
if (command_iter->second.empty()) { | ||
// Remove empty set. | ||
command_to_inputs_.erase(command_iter); | ||
} | ||
input_to_command_.erase(iter); | ||
} | ||
|
||
void Shortcuts::UnassignDefaultBinding(const UserInput& input) { | ||
auto input_iter = input_to_command_.find(input); | ||
if (input_iter == input_to_command_.end()) { | ||
// This can happen if the INI file provided by the user has an invalid | ||
// option. In this case, just silently ignore it. | ||
return; | ||
} | ||
|
||
if (internal::DefaultShortcutForCommand(input_iter->second) != input) { | ||
// As above, we have already removed the default binding, ignore it. | ||
return; | ||
} | ||
|
||
auto command_iter = command_to_inputs_.find(input_iter->second); | ||
assert(command_iter != command_to_inputs_.end()); | ||
|
||
command_iter->second.erase(input); | ||
if (command_iter->second.empty()) { | ||
command_to_inputs_.erase(command_iter); | ||
} | ||
|
||
disabled_defaults_[input] = input_iter->second; | ||
input_to_command_.erase(input_iter); | ||
} | ||
|
||
} // namespace config |
Oops, something went wrong.