refactor key mapping using hash map

key mapping now allows key + modifier combinations
This commit is contained in:
thrust26 2019-05-25 15:12:34 +02:00
parent a2a3844d3d
commit fc79665d3a
14 changed files with 326 additions and 88 deletions

167
src/common/KeyMap.cxx Normal file
View File

@ -0,0 +1,167 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "KeyMap.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyMap::KeyMap(void)
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void KeyMap::add(const Event::Type event, const Mapping& input)
{
myMap[convertMod(input)] = event;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void KeyMap::add(const Event::Type event, const int mode, const int key, const int mod)
{
add(event, Mapping(mode, key, mod));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void KeyMap::erase(const Mapping& input)
{
myMap.erase(convertMod(input));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void KeyMap::erase(const int mode, const int key, const int mod)
{
erase(Mapping(mode, key, mod));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Event::Type KeyMap::get(const Mapping& input) const
{
auto find = myMap.find(convertMod(input));
if (find != myMap.end())
return find->second;
return Event::Type::NoType;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Event::Type KeyMap::get(const int mode, const int key, const int mod) const
{
return get(Mapping(mode, key, mod));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string KeyMap::getEventMappingDesc(const Event::Type event, const int mode) const
{
ostringstream buf;
#ifndef BSPF_MACOS
string modifier = "Ctrl";
#else
string modifier = "Cmd";
#endif
for (auto item : myMap)
{
if (item.second == event && item.first.mode == mode)
{
if (buf.str() != "")
buf << ", ";
if (item.first.mod & StellaMod::KBDM_CTRL) buf << modifier << "+";
if (item.first.mod & StellaMod::KBDM_ALT) buf << "Alt+";
if (item.first.mod & StellaMod::KBDM_SHIFT) buf << "Shift+";
buf << StellaKeyName::forKey(item.first.key);
}
}
return buf.str();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string KeyMap::saveMapping(const int mode) const
{
ostringstream buf;
for (auto item : myMap)
{
if (item.first.mode == mode)
{
if (buf.str() != "")
buf << "|";
buf << item.second << ":" << item.first.key << "," << item.first.mod;
}
}
return buf.str();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int KeyMap::loadMapping(string& list, const int mode)
{
// Since istringstream swallows whitespace, we have to make the
// delimiters be spaces
std::replace(list.begin(), list.end(), '|', ' ');
std::replace(list.begin(), list.end(), ':', ' ');
std::replace(list.begin(), list.end(), ',', ' ');
istringstream buf(list);
int event, key, mod, i = 0;
while (buf >> event && buf >> key && buf >> mod && ++i)
add(Event::Type(event), mode, key, mod);
return i;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string KeyMap::getDesc(const Mapping& input) const
{
return "TODO";
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string KeyMap::getDesc(const int mode, const int key, const int mod) const
{
return getDesc(Mapping(mode, key, mod));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void KeyMap::eraseMode(const int mode)
{
for (auto item : myMap)
if (item.first.mode == mode)
erase(item.first);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void KeyMap::eraseEvent(const Event::Type event, const int mode)
{
for (auto item : myMap)
if (item.second == event && item.first.mode == mode)
erase(item.first);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyMap::Mapping KeyMap::convertMod(const Mapping& input) const
{
Mapping i = input;
// limit to modifiers we want to support
i.mod = StellaMod(i.mod & (StellaMod::KBDM_SHIFT | StellaMod::KBDM_ALT | StellaMod::KBDM_CTRL));
// merge left and right modifiers
if (i.mod & KBDM_CTRL) i.mod = StellaMod(i.mod | KBDM_CTRL);
if (i.mod & KBDM_SHIFT) i.mod = StellaMod(i.mod | KBDM_SHIFT);
if (i.mod & KBDM_ALT) i.mod = StellaMod(i.mod | KBDM_ALT);
return i;
}

102
src/common/KeyMap.hxx Normal file
View File

@ -0,0 +1,102 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef KEYMAP_HXX
#define KEYMAP_HXX
#include "Event.hxx"
#include "EventHandlerConstants.hxx"
/**
This class handles keyboard mappings in Stella.
@author Thomas Jentzsch
*/
class KeyMap
{
public:
struct Mapping
{
EventMode mode;
StellaKey key;
StellaMod mod;
Mapping() : mode(EventMode(0)), key(StellaKey(0)), mod(StellaMod(0)) { }
Mapping(const Mapping& k) : mode(k.mode), key(k.key), mod(k.mod) { }
explicit Mapping(EventMode mode, StellaKey key, StellaMod mod = StellaMod::KBDM_NONE)
: mode(mode), key(key), mod(mod) { }
explicit Mapping(int mode, int key, int mod = StellaMod::KBDM_NONE)
: mode(EventMode(mode)), key(StellaKey(key)), mod(StellaMod(mod)) { }
bool operator==(const Mapping& other) const
{
return (mode == other.mode
&& key == other.key
&& mod == other.mod);
}
};
KeyMap();
virtual ~KeyMap() = default;
/** Add new mapping for given event */
void add(const Event::Type event, const Mapping& input);
void add(const Event::Type event, const int mode, const int key, const int mod = StellaMod::KBDM_NONE);
/** Erase mapping */
void erase(const Mapping& input);
void erase(const int mode, const int key, const int mod = StellaMod::KBDM_NONE);
/** Get event for mapping */
Event::Type get(const Mapping& input) const;
Event::Type get(const int mode, const int key, const int mod = StellaMod::KBDM_NONE) const;
/** Get the mapping(s) description for given event and mode */
string getEventMappingDesc(const Event::Type event, const int mode) const;
/** Get mapping description */
string getDesc(const Mapping& input) const;
string getDesc(const int mode, const int key, const int mod = StellaMod::KBDM_NONE) const;
string saveMapping(const int mode) const;
int loadMapping(string& list, const int mode);
/** Erase all mappings for given mode */
void eraseMode(const int mode);
/** Erase given event's mapping for given mode */
void eraseEvent(const Event::Type event, const int mode);
/** clear all mappings for a modes */
// void clear() { myMap.clear(); }
size_t size() { return myMap.size(); }
private:
//** Convert modifiers */
Mapping convertMod(const Mapping& input) const;
struct KeyHash {
size_t operator()(const Mapping& k)const {
return std::hash<long long>()(((long long)k.mode)
^ (((long long)k.key) << 16)
^ (((long long)k.mod) << 32));
}
};
std::unordered_map<Mapping, Event::Type, KeyHash> myMap;
};
#endif

View File

@ -26,6 +26,7 @@
#include "TIASurface.hxx"
#include "PNGLibrary.hxx"
#include "PKeyboardHandler.hxx"
#include "KeyMap.hxx"
#ifdef DEBUGGER_SUPPORT
#include "Debugger.hxx"
@ -43,33 +44,12 @@ PhysicalKeyboardHandler::PhysicalKeyboardHandler(
myAltKeyCounter(0),
myUseCtrlKeyFlag(myOSystem.settings().getBool("ctrlcombo"))
{
// Since istringstream swallows whitespace, we have to make the
// delimiters be spaces
string list = myOSystem.settings().getString("keymap");
replace(list.begin(), list.end(), ':', ' ');
istringstream buf(list);
string list = myOSystem.settings().getString("keymap_emu");
int i = myKeyMap.loadMapping(list, kEmulationMode);
list = myOSystem.settings().getString("keymap_ui");
i += myKeyMap.loadMapping(list, kMenuMode);
IntArray map;
int value;
Event::Type e;
// Get event count, which should be the first int in the list
buf >> value;
e = Event::Type(value);
if(e == Event::LastType)
while(buf >> value)
map.push_back(value);
// Only fill the key mapping array if the data is valid
if(e == Event::LastType && map.size() == KBDK_LAST * kNumModes)
{
// Fill the keymap table with events
auto ev = map.cbegin();
for(int mode = 0; mode < kNumModes; ++mode)
for(int i = 0; i < KBDK_LAST; ++i)
myKeyTable[i][mode] = Event::Type(*ev++);
}
else
if (!i)
{
setDefaultMapping(Event::NoType, kEmulationMode);
setDefaultMapping(Event::NoType, kMenuMode);
@ -83,16 +63,13 @@ void PhysicalKeyboardHandler::setDefaultMapping(Event::Type event, EventMode mod
// Otherwise, only reset the given event
bool eraseAll = (event == Event::NoType);
if(eraseAll)
{
// Erase all mappings
for(int i = 0; i < KBDK_LAST; ++i)
myKeyTable[i][mode] = Event::NoType;
}
// Erase all mappings of given mode
myKeyMap.eraseMode(mode);
auto setDefaultKey = [&](StellaKey key, Event::Type k_event)
auto setDefaultKey = [&](StellaKey key, Event::Type k_event, StellaMod mod = StellaMod::KBDM_NONE)
{
if(eraseAll || k_event == event)
myKeyTable[key][mode] = k_event;
myKeyMap.add(k_event, mode, key, mod);
};
switch(mode)
@ -213,53 +190,33 @@ void PhysicalKeyboardHandler::setDefaultMapping(Event::Type event, EventMode mod
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PhysicalKeyboardHandler::eraseMapping(Event::Type event, EventMode mode)
{
for(int i = 0; i < KBDK_LAST; ++i)
// This key cannot be remapped
if(myKeyTable[i][mode] == event && !(i == KBDK_TAB && mode == EventMode::kMenuMode))
myKeyTable[i][mode] = Event::NoType;
// This key cannot be remapped
if (event != Event::UINavNext || mode != EventMode::kMenuMode)
myKeyMap.eraseEvent(event, mode);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PhysicalKeyboardHandler::saveMapping()
{
// Iterate through the keymap table and create a colon-separated list
// Prepend the event count, so we can check it on next load
ostringstream keybuf;
keybuf << Event::LastType;
for(int mode = 0; mode < kNumModes; ++mode)
for(int i = 0; i < KBDK_LAST; ++i)
keybuf << ":" << myKeyTable[i][mode];
myOSystem.settings().setValue("keymap", keybuf.str());
myOSystem.settings().setValue("keymap_emu", myKeyMap.saveMapping(kEmulationMode));
myOSystem.settings().setValue("keymap_ui", myKeyMap.saveMapping(kMenuMode));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string PhysicalKeyboardHandler::getMappingDesc(Event::Type event, EventMode mode) const
{
ostringstream buf;
for(int k = 0; k < KBDK_LAST; ++k)
{
if(myKeyTable[k][mode] == event)
{
if(buf.str() != "")
buf << ", ";
buf << StellaKeyName::forKey(StellaKey(k));
}
}
return buf.str();
return myKeyMap.getEventMappingDesc(event, mode);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool PhysicalKeyboardHandler::addMapping(Event::Type event, EventMode mode,
StellaKey key)
StellaKey key, StellaMod mod)
{
// These keys cannot be remapped
if((key == KBDK_TAB && mode == EventMode::kMenuMode) || Event::isAnalog(event))
return false;
else
myKeyTable[key][mode] = event;
myKeyMap.add(event, mode, key, mod);
return true;
}
@ -317,7 +274,7 @@ void PhysicalKeyboardHandler::handleEvent(StellaKey key, StellaMod mod, bool pre
}
// Handle keys which switch eventhandler state
if(!pressed && myHandler.changeStateByEvent(myKeyTable[key][kEmulationMode]))
if (!pressed && myHandler.changeStateByEvent(myKeyMap.get(kEmulationMode, key, mod)))
return;
}
@ -325,15 +282,15 @@ void PhysicalKeyboardHandler::handleEvent(StellaKey key, StellaMod mod, bool pre
switch(estate)
{
case EventHandlerState::EMULATION:
myHandler.handleEvent(myKeyTable[key][kEmulationMode], pressed);
myHandler.handleEvent(myKeyMap.get(kEmulationMode, key, mod), pressed);
break;
case EventHandlerState::PAUSE:
switch(myKeyTable[key][kEmulationMode])
switch (myKeyMap.get(kEmulationMode, key, mod))
{
case Event::TakeSnapshot:
case Event::DebuggerMode:
myHandler.handleEvent(myKeyTable[key][kEmulationMode], pressed);
myHandler.handleEvent(myKeyMap.get(kEmulationMode, key, mod), pressed);
break;
default:

View File

@ -26,6 +26,7 @@ class Event;
#include "bspf.hxx"
#include "EventHandlerConstants.hxx"
#include "KeyMap.hxx"
/**
This class handles all physical keyboard-related operations in Stella.
@ -41,6 +42,7 @@ class Event;
class PhysicalKeyboardHandler
{
public:
PhysicalKeyboardHandler(OSystem& system, EventHandler& handler, Event& event);
void setDefaultMapping(Event::Type type, EventMode mode);
@ -49,13 +51,13 @@ class PhysicalKeyboardHandler
string getMappingDesc(Event::Type, EventMode mode) const;
/** Bind a physical keyboard event to a virtual event/action. */
bool addMapping(Event::Type event, EventMode mode, StellaKey key);
bool addMapping(Event::Type event, EventMode mode, StellaKey key, StellaMod mod);
/** Handle a physical keyboard event. */
void handleEvent(StellaKey key, StellaMod mod, bool pressed);
Event::Type eventForKey(StellaKey key, EventMode mode) const {
return myKeyTable[key][mode];
return myKeyMap.get(mode, key);
}
/** See comments on 'myAltKeyCounter' for more information. */
@ -72,11 +74,8 @@ class PhysicalKeyboardHandler
EventHandler& myHandler;
Event& myEvent;
// Array of key events, indexed by StellaKey
Event::Type myKeyTable[KBDK_LAST][kNumModes];
// Array of mod keys, indexed by StellaKey
// TODO - uncomment when this is ready
//StellaMod myModKeyTable[KBDK_LAST][kNumModes];
// Hashmap of key events
KeyMap myKeyMap;
// Sometimes key combos with the Alt key become 'stuck' after the
// window changes state, and we want to ignore that event

View File

@ -6,6 +6,7 @@ MODULE_OBJS := \
src/common/FBSurfaceSDL2.o \
src/common/FrameBufferSDL2.o \
src/common/FSNodeZIP.o \
src/common/KeyMap.o \
src/common/Logger.o \
src/common/main.o \
src/common/MouseControl.o \

View File

@ -848,9 +848,9 @@ void EventHandler::removePhysicalJoystickFromDatabase(const string& name)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EventHandler::addKeyMapping(Event::Type event, EventMode mode, StellaKey key)
bool EventHandler::addKeyMapping(Event::Type event, EventMode mode, StellaKey key, StellaMod mod)
{
bool mapped = myPKeyHandler->addMapping(event, mode, key);
bool mapped = myPKeyHandler->addMapping(event, mode, key, mod);
if(mapped)
setActionMappings(mode);

View File

@ -181,8 +181,9 @@ class EventHandler
@param event The event we are remapping
@param mode The mode where this event is active
@param key The key to bind to this event
@param mod The modifier to bind to this event
*/
bool addKeyMapping(Event::Type event, EventMode mode, StellaKey key);
bool addKeyMapping(Event::Type event, EventMode mode, StellaKey key, StellaMod mod);
/**
Bind a physical joystick axis direction to an event/action and regenerate

View File

@ -82,7 +82,8 @@ Settings::Settings()
setPermanent(AudioSettings::SETTING_STEREO, AudioSettings::DEFAULT_STEREO);
// Input event options
setPermanent("keymap", "");
setPermanent("keymap_emu", "");
setPermanent("keymap_ui", "");
setPermanent("joymap", "");
setPermanent("combomap", "");
setPermanent("joydeadzone", "13");

View File

@ -254,14 +254,14 @@ void EventMappingWidget::enableButtons(bool state)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EventMappingWidget::handleKeyDown(StellaKey key, StellaMod mod)
bool EventMappingWidget::handleKeyUp(StellaKey key, StellaMod mod)
{
// Remap keys in remap mode
if(myRemapStatus && myActionSelected >= 0)
if (myRemapStatus && myActionSelected >= 0)
{
Event::Type event =
instance().eventHandler().eventAtIndex(myActionSelected, myEventMode);
if(instance().eventHandler().addKeyMapping(event, myEventMode, key))
if (instance().eventHandler().addKeyMapping(event, myEventMode, key, mod))
stopRemapping();
}
return true;

View File

@ -57,7 +57,7 @@ class EventMappingWidget : public Widget, public CommandSender
kComboCmd = 'cmbo'
};
bool handleKeyDown(StellaKey key, StellaMod mod) override;
bool handleKeyUp(StellaKey key, StellaMod mod) override;
void handleJoyDown(int stick, int button) override;
void handleJoyAxis(int stick, int axis, int value) override;
bool handleJoyHat(int stick, int hat, JoyHat value) override;

View File

@ -448,15 +448,15 @@ void InputDialog::setDefaults()
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void InputDialog::handleKeyDown(StellaKey key, StellaMod mod)
void InputDialog::handleKeyUp(StellaKey key, StellaMod mod)
{
// Remap key events in remap mode, otherwise pass to parent dialog
if(myEmulEventMapper->remapMode())
myEmulEventMapper->handleKeyDown(key, mod);
else if(myMenuEventMapper->remapMode())
myMenuEventMapper->handleKeyDown(key, mod);
if (myEmulEventMapper->remapMode())
myEmulEventMapper->handleKeyUp(key, mod);
else if (myMenuEventMapper->remapMode())
myMenuEventMapper->handleKeyUp(key, mod);
else
Dialog::handleKeyDown(key, mod);
Dialog::handleKeyUp(key, mod);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -43,7 +43,7 @@ class InputDialog : public Dialog
virtual ~InputDialog();
private:
void handleKeyDown(StellaKey key, StellaMod mod) override;
void handleKeyUp(StellaKey key, StellaMod mod) override;
void handleJoyDown(int stick, int button) override;
void handleJoyAxis(int stick, int axis, int value) override;
bool handleJoyHat(int stick, int hat, JoyHat value) override;

View File

@ -374,8 +374,9 @@
<ClCompile Include="..\common\EventHandlerSDL2.cxx" />
<ClCompile Include="..\common\FBSurfaceSDL2.cxx" />
<ClCompile Include="..\common\FpsMeter.cxx" />
<ClCompile Include="..\common\FrameBufferSDL2.cxx" />
<ClCompile Include="..\common\FrameBufferSDL2.cxx" />
<ClCompile Include="..\common\FSNodeZIP.cxx" />
<ClCompile Include="..\common\KeyMap.cxx" />
<ClCompile Include="..\common\Logger.cxx" />
<ClCompile Include="..\common\main.cxx" />
<ClCompile Include="..\common\MouseControl.cxx" />
@ -1072,6 +1073,7 @@
<ClInclude Include="..\common\FrameBufferSDL2.hxx" />
<ClInclude Include="..\common\FSNodeFactory.hxx" />
<ClInclude Include="..\common\FSNodeZIP.hxx" />
<ClInclude Include="..\common\KeyMap.hxx" />
<ClInclude Include="..\common\LinkedObjectPool.hxx" />
<ClInclude Include="..\common\Logger.hxx" />
<ClInclude Include="..\common\MediaFactory.hxx" />

View File

@ -903,6 +903,9 @@
<ClCompile Include="..\common\PKeyboardHandler.cxx">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\common\KeyMap.cxx">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\emucore\tia\Audio.cxx">
<Filter>Source Files\emucore\tia</Filter>
</ClCompile>
@ -1994,12 +1997,17 @@
<ClInclude Include="..\gui\R77HelpDialog.hxx">
<Filter>Header Files\gui</Filter>
</ClInclude>
<ClInclude Include="..\common\KeyMap.hxx">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="stella.ico">
<Filter>Resource Files</Filter>
</None>
<None Include="..\emucore\tia\frame-manager\module.mk" />
<Filter>Source Files</Filter>
</None>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="stella.rc">