Input refactor.

- Allow key shortcuts to run with loaded game.

For example, when we set `CTRL+A` for `load most recent save state` and
use `A` for some input command, holding `CTRL` and then pressing `A`
will not execute the shortcut. Instead, the key press `A` will be used
only as the input and nothing else.

With this, we use both the input and shortcut key.

- Isolate function to get keyboard key codes.

As explained on [1]:

"Using `GetUnicodeKey()` is in general the right thing to do if you are
interested in the characters typed by the user, `GetKeyCode()` should
be only used for special keys (for which `GetUnicodeKey()` returns
`WXK_NONE`)."

We also allow special keys to be mapped, hence the requirement of using
both functions.

[1] https://docs.wxwidgets.org/3.1/classwx_key_event.html

- Allow use of unicode keys for input and shortcut.

Use format `KeyCode:Modifier` for saving/loading unicode keys.

`WxWidgets=3.{0,1}` does not create an accelerator from strings with
unicode keys such as `ç` (`FromString` function). It fails with an
assertion error and stops execution. At the same time, we use the keys'
strings that are known for WxWidgets, such as `A`, `CTRL+O`,
`PAGEUP` etc.

Use both `EVT_KEY_DOWN` and `EVT_CHAR`.

`EVT_CHAR` is better than `EVT_KEY_DOWN` here because it is where the
raw key events will have been cooked using whatever recipes are in
effect from the os, locale, international keyboard settings, etc.

- Enable SDL joysticks input as key shortcuts.

Start/Stop polling joysticks on Unload/load game.

Our main loop already polls the joystick, we don't need the timer
while a game is running.

- Create function `str_split_with_sep` and use it.

For when we parse strings that may include the sep string, such as
game input and key shortcuts.
This commit is contained in:
Edênis Freindorfer Azevedo 2020-05-07 17:01:19 -03:00
parent 6b257d52f2
commit baa0341bd5
No known key found for this signature in database
GPG Key ID: 968FB6EC280C7222
21 changed files with 508 additions and 249 deletions

View File

@ -4953,7 +4953,6 @@ void gbEmulate(int ticksToStop)
if (turbo_button_pressed) {
if (!speedup_throttle_set && throttle != speedup_throttle) {
last_throttle = throttle;
//throttle = speedup_throttle;
soundSetThrottle(speedup_throttle);
speedup_throttle_set = true;
}
@ -4970,7 +4969,6 @@ void gbEmulate(int ticksToStop)
framesToSkip = speedup_frame_skip;
}
else if (speedup_throttle_set) {
//throttle = last_throttle;
soundSetThrottle(last_throttle);
speedup_throttle_set = false;

View File

@ -3799,7 +3799,6 @@ void CPULoop(int ticks)
if (turbo_button_pressed) {
if (!speedup_throttle_set && throttle != speedup_throttle) {
last_throttle = throttle;
//throttle = speedup_throttle;
soundSetThrottle(speedup_throttle);
speedup_throttle_set = true;
}
@ -3816,7 +3815,6 @@ void CPULoop(int ticks)
framesToSkip = speedup_frame_skip;
}
else if (speedup_throttle_set) {
//throttle = last_throttle;
soundSetThrottle(last_throttle);
speedup_throttle_set = false;

View File

@ -739,6 +739,7 @@ set(
viewsupt.cpp
wayland.cpp
strutils.cpp
wxutil.cpp
widgets/keyedit.cpp
widgets/joyedit.cpp
widgets/sdljoy.cpp
@ -769,6 +770,7 @@ set(
viewsupt.h
wxhead.h
wayland.h
wxutil.h
widgets/wx/keyedit.h
widgets/wx/joyedit.h
widgets/wx/sdljoy.h

View File

@ -2706,7 +2706,6 @@ EVT_HANDLER(EmulatorDirectories, "Directories...")
EVT_HANDLER(JoypadConfigure, "Joypad options...")
{
wxDialog* dlg = GetXRCDialog("JoypadConfig");
joy.Attach(NULL);
joy.Add();
if (ShowModal(dlg) == wxID_OK)
@ -2718,9 +2717,12 @@ EVT_HANDLER(JoypadConfigure, "Joypad options...")
EVT_HANDLER(Customize, "Customize UI...")
{
wxDialog* dlg = GetXRCDialog("AccelConfig");
joy.Add();
if (ShowModal(dlg) == wxID_OK)
update_opts();
SetJoystick();
}
#ifndef NO_ONLINEUPDATES
@ -3068,6 +3070,9 @@ void SetLinkTypeMenu(const char* type, int value)
mf->SetMenuOption(type, 1);
gopts.gba_link_type = value;
update_opts();
#ifndef NO_LINK
CloseLink();
#endif
mf->EnableNetworkMenu();
}
@ -3097,33 +3102,21 @@ EVT_HANDLER_MASK(LanLink, "Start Network link", CMDEN_LINK_ANY)
EVT_HANDLER(LinkType0Nothing, "Link nothing")
{
#ifndef NO_LINK
CloseLink();
#endif
SetLinkTypeMenu("LinkType0Nothing", 0);
}
EVT_HANDLER(LinkType1Cable, "Link cable")
{
#ifndef NO_LINK
CloseLink();
#endif
SetLinkTypeMenu("LinkType1Cable", 1);
}
EVT_HANDLER(LinkType2Wireless, "Link wireless")
{
#ifndef NO_LINK
CloseLink();
#endif
SetLinkTypeMenu("LinkType2Wireless", 2);
}
EVT_HANDLER(LinkType3GameCube, "Link GameCube")
{
#ifndef NO_LINK
CloseLink();
#endif
SetLinkTypeMenu("LinkType3GameCube", 3);
}

View File

@ -1695,19 +1695,6 @@ public:
}
} JoyPadConfigHandler[4];
class JoystickPoller : public wxTimer {
public:
void Notify() {
wxGetApp().frame->PollJoysticks();
}
void ShowDialog(wxShowEvent& ev) {
if (ev.IsShown())
Start(50);
else
Stop();
}
};
// manage fullscreen mode widget
// technically, it's more than a validator: it modifies the widget as well
class ScreenModeList : public wxValidator {
@ -2003,7 +1990,7 @@ static bool treeid_to_name(int id, wxString& name, wxTreeCtrl* tc,
}
// for sorting accels by command ID
static bool cmdid_lt(const wxAcceleratorEntry& a, const wxAcceleratorEntry& b)
static bool cmdid_lt(const wxAcceleratorEntryUnicode& a, const wxAcceleratorEntryUnicode& b)
{
return a.GetCommand() < b.GetCommand();
}
@ -2015,7 +2002,7 @@ public:
wxControlWithItems* lb;
wxAcceleratorEntry_v user_accels, accels;
wxWindow *asb, *remb;
wxKeyTextCtrl* key;
wxJoyKeyTextCtrl* key;
wxControl* curas;
// since this is not the actual dialog, derived from wxDialog, which is
@ -2076,10 +2063,17 @@ public:
asb->Enable(!key->GetValue().empty());
int cmd = id->val;
for (size_t i = 0; i < accels.size(); i++)
if (accels[i].GetCommand() == cmdtab[cmd].cmd_id)
lb->Append(wxKeyTextCtrl::ToString(accels[i].GetFlags(),
accels[i].GetKeyCode()));
for (size_t i = 0; i < accels.size(); ++i) {
if (accels[i].GetCommand() == cmdtab[cmd].cmd_id) {
if (accels[i].GetJoystick() == 0) {
wxString key = wxJoyKeyTextCtrl::ToCandidateString(accels[i].GetFlags(), accels[i].GetKeyCode());
lb->Append(key);
}
else {
lb->Append(accels[i].GetUkey());
}
}
}
}
// after selecting a key in key list, enable Remove button
@ -2099,10 +2093,11 @@ public:
return;
wxString selstr = lb->GetString(lsel);
int selmod, selkey;
int selmod, selkey, seljoy;
if (!wxKeyTextCtrl::FromString(selstr, selmod, selkey))
return; // this should never happen
if (!wxJoyKeyTextCtrl::FromString(selstr, selmod, selkey, seljoy))
// this should never happen
return;
remb->Enable(false);
@ -2115,7 +2110,9 @@ public:
// first drop from user accels, if applicable
for (wxAcceleratorEntry_v::iterator i = user_accels.begin();
i < user_accels.end(); ++i)
if (i->GetFlags() == selmod && i->GetKeyCode() == selkey) {
if ((i->GetFlags() == selmod && i->GetKeyCode() == selkey)
|| (seljoy != 0 && i->GetUkey() == selstr))
{
user_accels.erase(i);
break;
}
@ -2124,15 +2121,19 @@ public:
wxAcceleratorEntry_v& sys_accels = wxGetApp().frame->sys_accels;
for (size_t i = 0; i < sys_accels.size(); i++)
if (sys_accels[i].GetFlags() == selmod && sys_accels[i].GetKeyCode() == selkey) {
wxAcceleratorEntry ne(selmod, selkey, XRCID("NOOP"));
if ((sys_accels[i].GetFlags() == selmod && sys_accels[i].GetKeyCode() == selkey)
|| (seljoy != 0 && sys_accels[i].GetUkey() == selstr)) // joystick system bindings?
{
wxAcceleratorEntryUnicode ne(sys_accels[i].GetUkey(), sys_accels[i].GetJoystick(), selmod, selkey, XRCID("NOOP"));
user_accels.push_back(ne);
}
// finally, remove from accels instead of recomputing
for (wxAcceleratorEntry_v::iterator i = accels.begin();
i < accels.end(); ++i)
if (i->GetFlags() == selmod && i->GetKeyCode() == selkey) {
if ((i->GetFlags() == selmod && i->GetKeyCode() == selkey)
|| (seljoy != 0 && i->GetUkey() == selstr))
{
accels.erase(i);
break;
}
@ -2164,10 +2165,11 @@ public:
if (!csel.IsOk() || accel.empty())
return;
int acmod, ackey;
int acmod, ackey, acjoy;
if (!wxKeyTextCtrl::FromString(accel, acmod, ackey))
return; // this should never happen
if (!wxJoyKeyTextCtrl::FromString(accel, acmod, ackey, acjoy))
// this should never happen
return;
for (unsigned int i = 0; i < lb->GetCount(); i++)
if (lb->GetString(i) == accel)
@ -2178,15 +2180,17 @@ public:
// first drop from user accels, if applicable
for (wxAcceleratorEntry_v::iterator i = user_accels.begin();
i < user_accels.end(); ++i)
if (i->GetFlags() == acmod && i->GetKeyCode() == ackey) {
if ((i->GetFlags() == acmod && i->GetKeyCode() == ackey && i->GetJoystick() != acjoy)
|| (acjoy != 0 && i->GetUkey() == accel)) {
user_accels.erase(i);
break;
}
// then assign to this command
const TreeInt* id = static_cast<const TreeInt*>(tc->GetItemData(csel));
wxAcceleratorEntry ne(acmod, ackey, cmdtab[id->val].cmd_id);
wxAcceleratorEntryUnicode ne(accel, acjoy, acmod, ackey, cmdtab[id->val].cmd_id);
user_accels.push_back(ne);
// now assigned to this cmd...
wxString lab;
treeid_to_name(id->val, lab, tc, tc->GetRootItem());
@ -2207,9 +2211,9 @@ public:
return;
}
int acmod, ackey;
int acmod, ackey, acjoy;
if (!wxKeyTextCtrl::FromString(nkey, acmod, ackey)) {
if (!wxJoyKeyTextCtrl::FromString(nkey, acmod, ackey, acjoy)) {
// this should never happen
key->SetValue(wxT(""));
asb->Enable(false);
@ -2220,7 +2224,8 @@ public:
int cmd = -1;
for (size_t i = 0; i < accels.size(); i++)
if (accels[i].GetFlags() == acmod && accels[i].GetKeyCode() == ackey) {
if ((accels[i].GetFlags() == acmod && accels[i].GetKeyCode() == ackey)
|| (acjoy != 0 && accels[i].GetUkey() == nkey)) {
int cmdid = accels[i].GetCommand();
for (cmd = 0; cmd < ncmds; cmd++)
@ -2358,7 +2363,7 @@ public:
} throttle_ctrl;
static class SpeedupThrottleCtrl_t : public wxEvtHandler {
public:
public:
wxSpinCtrl* speedup_throttle_spin;
wxCheckBox* frame_skip_cb;
@ -2418,7 +2423,7 @@ public:
speedup_throttle_frame_skip = prev_frame_skip_cb = checked;
}
void Init(wxShowEvent& ev)
{
uint32_t val = 0;
@ -2547,10 +2552,11 @@ wxAcceleratorEntry_v MainFrame::get_accels(wxAcceleratorEntry_v user_accels)
// silently keep only last defined binding
// same horribly inefficent O(n*m) search for duplicates as above..
for (size_t i = 0; i < user_accels.size(); i++) {
const wxAcceleratorEntry& ae = user_accels[i];
const wxAcceleratorEntryUnicode& ae = user_accels[i];
for (wxAcceleratorEntry_v::iterator e = accels.begin(); e < accels.end(); ++e)
if (ae.GetFlags() == e->GetFlags() && ae.GetKeyCode() == e->GetKeyCode()) {
if ((ae.GetFlags() == e->GetFlags() && ae.GetKeyCode() == e->GetKeyCode())
|| (ae.GetJoystick() == e->GetJoystick() && ae.GetUkey() == e->GetUkey())) {
accels.erase(e);
break;
}
@ -2572,9 +2578,12 @@ void MainFrame::set_global_accels()
// the menus will be added now
// first, zero out menu item on all accels
for (size_t i = 0; i < accels.size(); i++)
accels[i].Set(accels[i].GetFlags(), accels[i].GetKeyCode(),
accels[i].GetCommand());
for (size_t i = 0; i < accels.size(); ++i) {
accels[i].Set(accels[i].GetUkey(), accels[i].GetJoystick(), accels[i].GetFlags(), accels[i].GetKeyCode(), accels[i].GetCommand());
if (accels[i].GetJoystick()) {
joy.Add(accels[i].GetJoystick() - 1);
}
}
// yet another O(n*m) loop. I really ought to sort the accel arrays
for (int i = 0; i < ncmds; i++) {
@ -2583,25 +2592,24 @@ void MainFrame::set_global_accels()
if (!mi)
continue;
// only *last* accelerator is made visible in menu
// only *last* accelerator is made visible in menu (non-unicode)
// and is flagged as such by setting menu item in accel
// the last is chosen so menu overrides non-menu and user overrides
// system
int cmd = cmdtab[i].cmd_id;
int last_accel = -1;
for (size_t j = 0; j < accels.size(); j++)
for (size_t j = 0; j < accels.size(); ++j)
if (cmd == accels[j].GetCommand())
last_accel = j;
if (last_accel >= 0) {
DoSetAccel(mi, &accels[last_accel]);
accels[last_accel].Set(accels[last_accel].GetFlags(),
accels[last_accel].GetKeyCode(),
accels[last_accel].GetCommand(), mi);
} else
accels[last_accel].Set(accels[last_accel].GetUkey(), accels[last_accel].GetJoystick(), accels[last_accel].GetFlags(), accels[last_accel].GetKeyCode(), accels[last_accel].GetCommand(), mi);
} else {
// clear out user-cleared menu items
DoSetAccel(mi, NULL);
}
}
// Finally, install a global accelerator table for any non-menu accels
@ -2612,7 +2620,7 @@ void MainFrame::set_global_accels()
len++;
if (len) {
wxAcceleratorEntry tab[1000];
wxAcceleratorEntryUnicode tab[1000];
for (size_t i = 0, j = 0; i < accels.size(); i++)
if (!accels[i].GetMenuItem())
@ -2627,7 +2635,7 @@ void MainFrame::set_global_accels()
// save recent accels
for (int i = 0; i < 10; i++)
recent_accel[i] = wxAcceleratorEntry();
recent_accel[i] = wxAcceleratorEntryUnicode();
for (size_t i = 0; i < accels.size(); i++)
if (accels[i].GetCommand() >= wxID_FILE1 && accels[i].GetCommand() <= wxID_FILE10)
@ -2922,7 +2930,7 @@ bool MainFrame::BindControls()
}
if (a)
sys_accels.push_back(*a);
sys_accels.push_back(wxAcceleratorEntryUnicode(a));
else
// strip from label so user isn't confused
DoSetAccel(mi, NULL);
@ -3830,13 +3838,6 @@ bool MainFrame::BindControls()
NULL, &JoyPadConfigHandler[i]);
}
// poll the joystick
JoystickPoller* jpoll = new JoystickPoller();
joyDialog->Connect(wxID_ANY, wxEVT_SHOW,
wxShowEventHandler(JoystickPoller::ShowDialog),
jpoll, jpoll);
joyDialog->Fit();
}
@ -3860,7 +3861,7 @@ bool MainFrame::BindControls()
accel_config_handler.lb = lb;
accel_config_handler.asb = SafeXRCCTRL<wxButton>(d, "Assign");
accel_config_handler.remb = SafeXRCCTRL<wxButton>(d, "Remove");
accel_config_handler.key = SafeXRCCTRL<wxKeyTextCtrl>(d, "Shortcut");
accel_config_handler.key = SafeXRCCTRL<wxJoyKeyTextCtrl>(d, "Shortcut");
accel_config_handler.curas = SafeXRCCTRL<wxControl>(d, "AlreadyThere");
accel_config_handler.key->MoveBeforeInTabOrder(accel_config_handler.asb);
accel_config_handler.key->SetMultikey(0);

View File

@ -42,9 +42,9 @@
opts_t gopts;
// having the standard menu accels here means they will work even without menus
const wxAcceleratorEntry default_accels[] = {
wxAcceleratorEntry(wxMOD_CMD, wxT('C'), XRCID("CheatsList")),
wxAcceleratorEntry(wxMOD_CMD, wxT('N'), XRCID("NextFrame")),
const wxAcceleratorEntryUnicode default_accels[] = {
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('C'), XRCID("CheatsList")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('N'), XRCID("NextFrame")),
// some ports add ctrl-q anyway, so may as well make it official
// maybe make alt-f4 universal as well...
// FIXME: ctrl-Q does not work on wxMSW
@ -56,79 +56,79 @@ const wxAcceleratorEntry default_accels[] = {
// this was annoying people #298
//wxAcceleratorEntry(wxMOD_CMD, wxT('X'), wxID_EXIT),
wxAcceleratorEntry(wxMOD_CMD, wxT('Q'), wxID_EXIT),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('Q'), wxID_EXIT),
// FIXME: ctrl-W does not work on wxMSW
wxAcceleratorEntry(wxMOD_CMD, wxT('W'), wxID_CLOSE),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('W'), wxID_CLOSE),
// load most recent is more commonly used than load other
//wxAcceleratorEntry(wxMOD_CMD, wxT('L'), XRCID("Load")),
wxAcceleratorEntry(wxMOD_CMD, wxT('L'), XRCID("LoadGameRecent")),
wxAcceleratorEntry(wxMOD_NONE, WXK_F1, XRCID("LoadGame01")),
wxAcceleratorEntry(wxMOD_NONE, WXK_F2, XRCID("LoadGame02")),
wxAcceleratorEntry(wxMOD_NONE, WXK_F3, XRCID("LoadGame03")),
wxAcceleratorEntry(wxMOD_NONE, WXK_F4, XRCID("LoadGame04")),
wxAcceleratorEntry(wxMOD_NONE, WXK_F5, XRCID("LoadGame05")),
wxAcceleratorEntry(wxMOD_NONE, WXK_F6, XRCID("LoadGame06")),
wxAcceleratorEntry(wxMOD_NONE, WXK_F7, XRCID("LoadGame07")),
wxAcceleratorEntry(wxMOD_NONE, WXK_F8, XRCID("LoadGame08")),
wxAcceleratorEntry(wxMOD_NONE, WXK_F9, XRCID("LoadGame09")),
wxAcceleratorEntry(wxMOD_NONE, WXK_F10, XRCID("LoadGame10")),
wxAcceleratorEntry(wxMOD_NONE, WXK_PAUSE, XRCID("Pause")),
wxAcceleratorEntry(wxMOD_CMD, wxT('P'), XRCID("Pause")),
wxAcceleratorEntry(wxMOD_CMD, wxT('R'), XRCID("Reset")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('L'), XRCID("LoadGameRecent")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F1, XRCID("LoadGame01")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F2, XRCID("LoadGame02")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F3, XRCID("LoadGame03")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F4, XRCID("LoadGame04")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F5, XRCID("LoadGame05")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F6, XRCID("LoadGame06")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F7, XRCID("LoadGame07")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F8, XRCID("LoadGame08")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F9, XRCID("LoadGame09")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_F10, XRCID("LoadGame10")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_PAUSE, XRCID("Pause")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('P'), XRCID("Pause")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('R'), XRCID("Reset")),
// add shortcuts for original size multiplier #415
wxAcceleratorEntry(wxMOD_NONE, wxT('1'), XRCID("SetSize1x")),
wxAcceleratorEntry(wxMOD_NONE, wxT('2'), XRCID("SetSize2x")),
wxAcceleratorEntry(wxMOD_NONE, wxT('3'), XRCID("SetSize3x")),
wxAcceleratorEntry(wxMOD_NONE, wxT('4'), XRCID("SetSize4x")),
wxAcceleratorEntry(wxMOD_NONE, wxT('5'), XRCID("SetSize5x")),
wxAcceleratorEntry(wxMOD_NONE, wxT('6'), XRCID("SetSize6x")),
wxAcceleratorEntryUnicode(wxMOD_NONE, wxT('1'), XRCID("SetSize1x")),
wxAcceleratorEntryUnicode(wxMOD_NONE, wxT('2'), XRCID("SetSize2x")),
wxAcceleratorEntryUnicode(wxMOD_NONE, wxT('3'), XRCID("SetSize3x")),
wxAcceleratorEntryUnicode(wxMOD_NONE, wxT('4'), XRCID("SetSize4x")),
wxAcceleratorEntryUnicode(wxMOD_NONE, wxT('5'), XRCID("SetSize5x")),
wxAcceleratorEntryUnicode(wxMOD_NONE, wxT('6'), XRCID("SetSize6x")),
// save oldest is more commonly used than save other
//wxAcceleratorEntry(wxMOD_CMD, wxT('S'), XRCID("Save")),
wxAcceleratorEntry(wxMOD_CMD, wxT('S'), XRCID("SaveGameOldest")),
wxAcceleratorEntry(wxMOD_SHIFT, WXK_F1, XRCID("SaveGame01")),
wxAcceleratorEntry(wxMOD_SHIFT, WXK_F2, XRCID("SaveGame02")),
wxAcceleratorEntry(wxMOD_SHIFT, WXK_F3, XRCID("SaveGame03")),
wxAcceleratorEntry(wxMOD_SHIFT, WXK_F4, XRCID("SaveGame04")),
wxAcceleratorEntry(wxMOD_SHIFT, WXK_F5, XRCID("SaveGame05")),
wxAcceleratorEntry(wxMOD_SHIFT, WXK_F6, XRCID("SaveGame06")),
wxAcceleratorEntry(wxMOD_SHIFT, WXK_F7, XRCID("SaveGame07")),
wxAcceleratorEntry(wxMOD_SHIFT, WXK_F8, XRCID("SaveGame08")),
wxAcceleratorEntry(wxMOD_SHIFT, WXK_F9, XRCID("SaveGame09")),
wxAcceleratorEntry(wxMOD_SHIFT, WXK_F10, XRCID("SaveGame10")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('S'), XRCID("SaveGameOldest")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F1, XRCID("SaveGame01")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F2, XRCID("SaveGame02")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F3, XRCID("SaveGame03")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F4, XRCID("SaveGame04")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F5, XRCID("SaveGame05")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F6, XRCID("SaveGame06")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F7, XRCID("SaveGame07")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F8, XRCID("SaveGame08")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F9, XRCID("SaveGame09")),
wxAcceleratorEntryUnicode(wxMOD_SHIFT, WXK_F10, XRCID("SaveGame10")),
// I prefer the SDL ESC key binding
//wxAcceleratorEntry(wxMOD_NONE, WXK_ESCAPE, XRCID("ToggleFullscreen"),
// alt-enter is more standard anyway
wxAcceleratorEntry(wxMOD_ALT, WXK_RETURN, XRCID("ToggleFullscreen")),
wxAcceleratorEntry(wxMOD_ALT, wxT('1'), XRCID("JoypadAutofireA")),
wxAcceleratorEntry(wxMOD_ALT, wxT('2'), XRCID("JoypadAutofireB")),
wxAcceleratorEntry(wxMOD_ALT, wxT('3'), XRCID("JoypadAutofireL")),
wxAcceleratorEntry(wxMOD_ALT, wxT('4'), XRCID("JoypadAutofireR")),
wxAcceleratorEntry(wxMOD_CMD, wxT('1'), XRCID("VideoLayersBG0")),
wxAcceleratorEntry(wxMOD_CMD, wxT('2'), XRCID("VideoLayersBG1")),
wxAcceleratorEntry(wxMOD_CMD, wxT('3'), XRCID("VideoLayersBG2")),
wxAcceleratorEntry(wxMOD_CMD, wxT('4'), XRCID("VideoLayersBG3")),
wxAcceleratorEntry(wxMOD_CMD, wxT('5'), XRCID("VideoLayersOBJ")),
wxAcceleratorEntry(wxMOD_CMD, wxT('6'), XRCID("VideoLayersWIN0")),
wxAcceleratorEntry(wxMOD_CMD, wxT('7'), XRCID("VideoLayersWIN1")),
wxAcceleratorEntry(wxMOD_CMD, wxT('8'), XRCID("VideoLayersOBJWIN")),
wxAcceleratorEntry(wxMOD_CMD, wxT('B'), XRCID("Rewind")),
wxAcceleratorEntryUnicode(wxMOD_ALT, WXK_RETURN, XRCID("ToggleFullscreen")),
wxAcceleratorEntryUnicode(wxMOD_ALT, wxT('1'), XRCID("JoypadAutofireA")),
wxAcceleratorEntryUnicode(wxMOD_ALT, wxT('2'), XRCID("JoypadAutofireB")),
wxAcceleratorEntryUnicode(wxMOD_ALT, wxT('3'), XRCID("JoypadAutofireL")),
wxAcceleratorEntryUnicode(wxMOD_ALT, wxT('4'), XRCID("JoypadAutofireR")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('1'), XRCID("VideoLayersBG0")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('2'), XRCID("VideoLayersBG1")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('3'), XRCID("VideoLayersBG2")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('4'), XRCID("VideoLayersBG3")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('5'), XRCID("VideoLayersOBJ")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('6'), XRCID("VideoLayersWIN0")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('7'), XRCID("VideoLayersWIN1")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('8'), XRCID("VideoLayersOBJWIN")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('B'), XRCID("Rewind")),
// following are not in standard menus
// FILExx are filled in when recent menu is filled
wxAcceleratorEntry(wxMOD_CMD, WXK_F1, wxID_FILE1),
wxAcceleratorEntry(wxMOD_CMD, WXK_F2, wxID_FILE2),
wxAcceleratorEntry(wxMOD_CMD, WXK_F3, wxID_FILE3),
wxAcceleratorEntry(wxMOD_CMD, WXK_F4, wxID_FILE4),
wxAcceleratorEntry(wxMOD_CMD, WXK_F5, wxID_FILE5),
wxAcceleratorEntry(wxMOD_CMD, WXK_F6, wxID_FILE6),
wxAcceleratorEntry(wxMOD_CMD, WXK_F7, wxID_FILE7),
wxAcceleratorEntry(wxMOD_CMD, WXK_F8, wxID_FILE8),
wxAcceleratorEntry(wxMOD_CMD, WXK_F9, wxID_FILE9),
wxAcceleratorEntry(wxMOD_CMD, WXK_F10, wxID_FILE10),
wxAcceleratorEntry(wxMOD_CMD, wxT('0'), XRCID("VideoLayersReset")),
wxAcceleratorEntry(wxMOD_CMD, wxT('G'), XRCID("ChangeFilter")),
wxAcceleratorEntry(wxMOD_NONE, WXK_NUMPAD_ADD, XRCID("IncreaseVolume")),
wxAcceleratorEntry(wxMOD_NONE, WXK_NUMPAD_SUBTRACT, XRCID("DecreaseVolume")),
wxAcceleratorEntry(wxMOD_NONE, WXK_NUMPAD_ENTER, XRCID("ToggleSound"))
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F1, wxID_FILE1),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F2, wxID_FILE2),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F3, wxID_FILE3),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F4, wxID_FILE4),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F5, wxID_FILE5),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F6, wxID_FILE6),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F7, wxID_FILE7),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F8, wxID_FILE8),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F9, wxID_FILE9),
wxAcceleratorEntryUnicode(wxMOD_CMD, WXK_F10, wxID_FILE10),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('0'), XRCID("VideoLayersReset")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('G'), XRCID("ChangeFilter")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_NUMPAD_ADD, XRCID("IncreaseVolume")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_NUMPAD_SUBTRACT, XRCID("DecreaseVolume")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_NUMPAD_ENTER, XRCID("ToggleSound"))
};
const int num_def_accels = sizeof(default_accels) / sizeof(default_accels[0]);
@ -669,17 +669,15 @@ void load_opts()
kbopt.append(cmdtab[i].cmd);
if (cfg->Read(kbopt, &s) && s.size()) {
wxAcceleratorEntry_v val = wxKeyTextCtrl::FromString(s);
wxAcceleratorEntry_v val = wxJoyKeyTextCtrl::ToAccelFromString(s);
if (!val.size())
wxLogWarning(_("Invalid key binding %s for %s"), s.c_str(), kbopt.c_str());
else {
for (size_t j = 0; j < val.size(); j++)
val[j].Set(val[j].GetFlags(), val[j].GetKeyCode(),
cmdtab[i].cmd_id);
val[j].Set(val[j].GetUkey(), val[j].GetJoystick(), val[j].GetFlags(), val[j].GetKeyCode(), cmdtab[i].cmd_id);
gopts.accels.insert(gopts.accels.end(),
val.begin(), val.end());
gopts.accels.insert(gopts.accels.end(), val.begin(), val.end());
}
}
}
@ -763,7 +761,7 @@ void update_opts()
wxString s, o;
wxString optname;
optname.Printf(wxT("Joypad/%d/%s"), i + 1, joynames[j].c_str());
s = wxJoyKeyTextCtrl::ToString(gopts.joykey_bindings[i][j]);
s = wxJoyKeyTextCtrl::ToString(gopts.joykey_bindings[i][j], wxT(','), true);
cfg->Read(optname, &o);
if (o != s)
@ -825,7 +823,7 @@ void update_opts()
break;
wxAcceleratorEntry_v nv(i, j);
wxString nvs = wxKeyTextCtrl::ToString(nv);
wxString nvs = wxJoyKeyTextCtrl::FromAccelToString(nv, wxT(','), true);
if (nvs != cfg->Read(command))
cfg->Write(command, nvs);
@ -953,11 +951,10 @@ bool opt_set(const wxString& name, const wxString& val)
}
if (!val.empty()) {
auto aval = wxKeyTextCtrl::FromString(val);
auto aval = wxJoyKeyTextCtrl::ToAccelFromString(val);
for (size_t i = 0; i < aval.size(); i++)
aval[i].Set(aval[i].GetFlags(), aval[i].GetKeyCode(),
cmd->cmd_id);
aval[i].Set(aval[i].GetUkey(), aval[i].GetJoystick(), aval[i].GetFlags(), aval[i].GetKeyCode(), cmd->cmd_id);
if (!aval.size())
wxLogWarning(_("Invalid key binding %s for %s"), val.c_str(), name.c_str());

View File

@ -119,7 +119,7 @@ opt_desc new_opt_desc(wxString opt = wxT(""), const char* cmd = NULL, wxString d
extern const int num_opts;
extern const wxAcceleratorEntry default_accels[];
extern const wxAcceleratorEntryUnicode default_accels[];
extern const int num_def_accels;
// call to setup default keys.

View File

@ -16,6 +16,7 @@
#include "drawing.h"
#include "filters.h"
#include "wxvbam.h"
#include "wxutil.h"
#ifdef __WXMSW__
#include <windows.h>
@ -317,6 +318,7 @@ void GameArea::LoadGame(const wxString& name)
emulating = true;
was_paused = true;
MainFrame* mf = wxGetApp().frame;
mf->StopPoll();
mf->SetJoystick();
mf->cmd_enable &= ~(CMDEN_GB | CMDEN_GBA);
mf->cmd_enable |= ONLOAD_CMDEN;
@ -567,6 +569,7 @@ void GameArea::UnloadGame(bool destruct)
mf->enable_menus();
mf->SetJoystick();
mf->ResetCheatSearch();
mf->StartPoll();
if (rewind_mem)
num_rewind_states = 0;
@ -1032,8 +1035,6 @@ void GameArea::OnIdle(wxIdleEvent& event)
wxWindow* w = panel->GetWindow();
// set up event handlers
// use both CHAR_HOOK and KEY_DOWN in case CHAR_HOOK does not work for whatever reason
w->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(GameArea::OnKeyDown), NULL, this);
w->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(GameArea::OnKeyDown), NULL, this);
w->Connect(wxEVT_KEY_UP, wxKeyEventHandler(GameArea::OnKeyUp), NULL, this);
w->Connect(wxEVT_PAINT, wxPaintEventHandler(GameArea::PaintEv), NULL, this);
@ -1313,28 +1314,19 @@ static void draw_black_background(wxWindow* win) {
void GameArea::OnKeyDown(wxKeyEvent& ev)
{
// check if the key is pressed indeed and then process it
wxKeyCode keyCode = (wxKeyCode)ev.GetKeyCode();
if (wxGetKeyState(keyCode) && process_key_press(true, ev.GetKeyCode(), ev.GetModifiers())) {
ev.Skip(false);
ev.StopPropagation();
int kc = getKeyboardKeyCode(ev);
if (process_key_press(true, kc, ev.GetModifiers())) {
wxWakeUpIdle();
}
else {
ev.Skip();
}
ev.Skip();
}
void GameArea::OnKeyUp(wxKeyEvent& ev)
{
if (process_key_press(false, ev.GetKeyCode(), ev.GetModifiers())) {
ev.Skip(false);
ev.StopPropagation();
int kc = getKeyboardKeyCode(ev);
if (process_key_press(false, kc, ev.GetModifiers())) {
wxWakeUpIdle();
}
else {
ev.Skip();
}
}
// these three are forwarded to the DrawingPanel instance

View File

@ -1,4 +1,5 @@
#include "strutils.h"
#include <wx/tokenzr.h>
// From: https://stackoverflow.com/a/7408245/262458
//
@ -21,6 +22,25 @@ wxArrayString str_split(const wxString& text, const wxString& sep) {
return tokens;
}
wxArrayString str_split_with_sep(const wxString& text, const wxString& sep)
{
wxArrayString tokens;
bool sepIsTokenToo = false;
wxStringTokenizer tokenizer(text, sep, wxTOKEN_RET_EMPTY_ALL);
while (tokenizer.HasMoreTokens()) {
wxString token = tokenizer.GetNextToken();
if (token.IsEmpty()) {
if (!sepIsTokenToo) {
sepIsTokenToo = true;
tokens.Add(sep);
}
continue;
}
tokens.Add(token);
}
return tokens;
}
size_t vec_find(wxArrayString& opts, const wxString& val) {
return opts.Index(val);
}

View File

@ -7,6 +7,11 @@
// From: https://stackoverflow.com/a/7408245/262458
wxArrayString str_split(const wxString& text, const wxString& sep);
// Same as above, but it includes the sep dir.
// If "A,,,B" is the text and "," is sep, then
// 'A', ',' and 'B' will be in the output.
wxArrayString str_split_with_sep(const wxString& text, const wxString& sep);
// From: https://stackoverflow.com/a/15099743/262458
size_t vec_find(wxArrayString& opts, const wxString& val);

View File

@ -1,6 +1,7 @@
#include "viewsupt.h"
#include "../common/ConfigManager.h"
#include "wxvbam.h"
#include "wxutil.h"
namespace Viewers {
void Viewer::CloseDlg(wxCloseEvent& ev)
@ -415,7 +416,7 @@ void MemView::ShowCaret()
void MemView::KeyEvent(wxKeyEvent& ev)
{
uint32_t k = ev.GetKeyCode();
uint32_t k = getKeyboardKeyCode(ev);
int nnib = 2 << fmt;
switch (k) {

View File

@ -1,4 +1,6 @@
#include <wx/tokenzr.h>
#include "wx/joyedit.h"
#include "strutils.h"
// FIXME: suppport analog/digital flag on per-axis basis
@ -104,10 +106,10 @@ void wxJoyKeyTextCtrl::OnJoy(wxSDLJoyEvent& event)
Navigate();
}
wxString wxJoyKeyTextCtrl::ToString(int mod, int key, int joy)
wxString wxJoyKeyTextCtrl::ToString(int mod, int key, int joy, bool isConfig)
{
if (!joy)
return wxKeyTextCtrl::ToString(mod, key);
return wxKeyTextCtrl::ToString(mod, key, isConfig);
wxString s;
// Note: wx translates unconditionally (2.8.12, 2.9.1)!
@ -165,7 +167,7 @@ wxString wxJoyKeyTextCtrl::ToString(int mod, int key, int joy)
return s;
}
wxString wxJoyKeyTextCtrl::ToString(wxJoyKeyBinding_v keys, wxChar sep)
wxString wxJoyKeyTextCtrl::ToString(wxJoyKeyBinding_v keys, wxChar sep, bool isConfig)
{
wxString ret;
@ -173,7 +175,26 @@ wxString wxJoyKeyTextCtrl::ToString(wxJoyKeyBinding_v keys, wxChar sep)
if (i > 0)
ret += sep;
wxString key = ToString(keys[i].mod, keys[i].key, keys[i].joy);
wxString key = ToString(keys[i].mod, keys[i].key, keys[i].joy, isConfig);
if (key.empty())
return wxEmptyString;
ret += key;
}
return ret;
}
wxString wxJoyKeyTextCtrl::FromAccelToString(wxAcceleratorEntry_v keys, wxChar sep, bool isConfig)
{
wxString ret;
for (size_t i = 0; i < keys.size(); i++) {
if (i > 0)
ret += sep;
wxString key = ToString(keys[i].GetFlags(), keys[i].GetKeyCode(), keys[i].GetJoystick(), isConfig);
if (key.empty())
return wxEmptyString;
@ -262,19 +283,16 @@ static bool ParseJoy(const wxString& s, int len, int& mod, int& key, int& joy)
} else if (is_ctrl(hatre)) {
hatre.GetMatch(&b, &l, 1);
key = simple_atoi(p.Mid(b), l);
#define check_dir(n, d) else if (hatre.GetMatch(&b, &l, n) && l > 0) mod = WXJB_HAT_##d
if (0)
;
#define check_dir(n, d) if (hatre.GetMatch(&b, &l, n) && l > 0) mod = WXJB_HAT_##d
check_dir(3, N);
check_dir(4, S);
check_dir(5, E);
check_dir(6, W);
check_dir(7, NE);
check_dir(8, SE);
check_dir(9, SW);
check_dir(10, NW);
else check_dir(4, S);
else check_dir(5, E);
else check_dir(6, W);
else check_dir(7, NE);
else check_dir(8, SE);
else check_dir(9, SW);
else check_dir(10, NW);
} else {
joy = 0;
return false;
@ -300,34 +318,30 @@ wxJoyKeyBinding_v wxJoyKeyTextCtrl::FromString(const wxString& s, wxChar sep)
{
wxJoyKeyBinding_v ret, empty;
int mod, key, joy;
size_t len = s.size();
if (!len)
if (s.size() == 0)
return empty;
for (size_t lastkey = len - 1; (lastkey = s.rfind(sep, lastkey)) != wxString::npos; lastkey--) {
if (lastkey == len - 1) {
// sep as accel
if (!lastkey)
break;
if (s[lastkey - 1] == wxT('-') || s[lastkey - 1] == wxT('+') || s[lastkey - 1] == sep)
continue;
}
if (!ParseString(s.Mid(lastkey + 1), len - lastkey - 1, mod, key, joy))
for (const auto& token : str_split_with_sep(s, sep)) {
if (!ParseString(token, token.size(), mod, key, joy))
return empty;
wxJoyKeyBinding jb = { key, mod, joy };
ret.insert(ret.begin(), jb);
len = lastkey;
}
return ret;
}
if (!ParseString(s, len, mod, key, joy))
wxAcceleratorEntry_v wxJoyKeyTextCtrl::ToAccelFromString(const wxString& s, wxChar sep)
{
wxAcceleratorEntry_v ret, empty;
int mod, key, joy;
if (s.size() == 0)
return empty;
wxJoyKeyBinding jb = { key, mod, joy };
ret.insert(ret.begin(), jb);
for (const auto& token : str_split_with_sep(s, sep)) {
if (!ParseString(token, token.size(), mod, key, joy))
return empty;
ret.insert(ret.begin(), wxAcceleratorEntryUnicode(token, joy, mod, key));
}
return ret;
}

View File

@ -1,9 +1,16 @@
#include <wx/tokenzr.h>
#include <wx/log.h>
#include "wx/keyedit.h"
#include "strutils.h"
IMPLEMENT_DYNAMIC_CLASS(wxKeyTextCtrl, wxTextCtrl)
BEGIN_EVENT_TABLE(wxKeyTextCtrl, wxTextCtrl)
// EVT_CHAR is better than EVT_KEY_DOWN here because it is where
// the raw key events will have been cooked using whatever recipes
// are in effect from the os, locale, international keyboard
// settings, etc. But we shouldn't need it for now, otherwise
// we would have problems detecting letter cases.
EVT_KEY_DOWN(wxKeyTextCtrl::OnKeyDown)
EVT_KEY_UP(wxKeyTextCtrl::OnKeyUp)
END_EVENT_TABLE()
@ -11,7 +18,11 @@ END_EVENT_TABLE()
void wxKeyTextCtrl::OnKeyDown(wxKeyEvent& event)
{
lastmod = event.GetModifiers();
lastkey = event.GetKeyCode();
lastkey = getKeyboardKeyCode(event);
if (lastkey != WXK_NONE) // not a control character
{
KeyboardInputMap::AddMap(ToCandidateString(lastmod, lastkey), lastkey, lastmod);
}
}
void wxKeyTextCtrl::OnKeyUp(wxKeyEvent& event)
@ -47,7 +58,7 @@ void wxKeyTextCtrl::OnKeyUp(wxKeyEvent& event)
} else
Clear();
} else {
wxString nv = ToString(mod, key);
wxString nv = ToCandidateString(mod, key);
if (nv.empty())
return;
@ -66,18 +77,35 @@ void wxKeyTextCtrl::OnKeyUp(wxKeyEvent& event)
Navigate();
}
wxString wxKeyTextCtrl::ToString(int mod, int key)
wxString wxKeyTextCtrl::ToString(int mod, int key, bool isConfig)
{
wxString s = ToCandidateString(mod, key);
// Check for unicode char. It is not possible to use it for
// wxAcceleratorEntry `FromString`
wxLogNull disable_logging;
wxAcceleratorEntryUnicode aeTest;
if (!aeTest.FromString(s) || !s.IsAscii()) {
if (!KeyboardInputMap::GetMap(s, key, mod) || isConfig) {
wxString unicodeChar;
unicodeChar.Printf("%d:%d", key, mod);
return unicodeChar;
}
}
return s;
}
wxString wxKeyTextCtrl::ToCandidateString(int mod, int key)
{
// wx ignores non-alnum printable chars
// actually, wx gives an assertion error, so it's best to filter out
// before passing to ToString()
bool char_override = key > 32 && key < WXK_START && !wxIsalnum(key);
bool char_override = key > 32 && key < WXK_START && !wxIsalnum(key) && key != WXK_DELETE;
// wx also ignores modifiers (and does not report meta at all)
bool mod_override = key == WXK_SHIFT || key == WXK_CONTROL || key == WXK_ALT || key == WXK_RAW_CONTROL;
wxAcceleratorEntry ae(mod, char_override || mod_override ? WXK_F1 : key);
wxAcceleratorEntryUnicode ae(mod, char_override || mod_override ? WXK_F1 : key);
// Note: wx translates unconditionally (2.8.12, 2.9.1)!
// So any strings added below must also be translated unconditionally
wxString s = ae.ToString();
wxString s = ae.ToRawString();
if (char_override || mod_override) {
size_t l = s.rfind(wxT('-'));
@ -150,11 +178,10 @@ wxString wxKeyTextCtrl::ToString(int mod, int key)
s.Replace(display_name_tr, name_tr, true);
s.Replace(display_name, name, true);
}
return s;
}
wxString wxKeyTextCtrl::ToString(wxAcceleratorEntry_v keys, wxChar sep)
wxString wxKeyTextCtrl::ToString(wxAcceleratorEntry_v keys, wxChar sep, bool isConfig)
{
wxString ret;
@ -162,7 +189,7 @@ wxString wxKeyTextCtrl::ToString(wxAcceleratorEntry_v keys, wxChar sep)
if (i > 0)
ret += sep;
wxString key = ToString(keys[i].GetFlags(), keys[i].GetKeyCode());
wxString key = ToString(keys[i].GetFlags(), keys[i].GetKeyCode(), isConfig);
if (key.empty())
return wxEmptyString;
@ -173,6 +200,21 @@ wxString wxKeyTextCtrl::ToString(wxAcceleratorEntry_v keys, wxChar sep)
return ret;
}
static bool checkForPairKeyMod(const wxString& s, int& mod, int& key)
{
long ulkey, ulmod;
// key:mod as pair
auto pair = str_split(s, ":");
if (pair.size() == 2 && pair[0].ToLong(&ulkey) && pair[1].ToLong(&ulmod))
{
key = (int)ulkey;
mod = (int)ulmod;
KeyboardInputMap::AddMap(wxKeyTextCtrl::ToCandidateString(mod, key), key, mod);
return true;
}
return false;
}
bool wxKeyTextCtrl::ParseString(const wxString& s, int len, int& mod, int& key)
{
mod = key = 0;
@ -180,9 +222,15 @@ bool wxKeyTextCtrl::ParseString(const wxString& s, int len, int& mod, int& key)
if (!s || !len)
return false;
if (checkForPairKeyMod(s, mod, key))
return true;
if (KeyboardInputMap::GetMap(s, key, mod))
return true;
wxString a = wxT('\t');
a.Append(s.Left(len));
wxAcceleratorEntry ae;
wxAcceleratorEntryUnicode ae;
#ifndef __WXMAC__
#define check_meta(str) \
do { \
@ -260,29 +308,12 @@ wxAcceleratorEntry_v wxKeyTextCtrl::FromString(const wxString& s, wxChar sep)
{
wxAcceleratorEntry_v ret, empty;
int mod, key;
size_t len = s.size();
for (size_t lastkey = len - 1; (lastkey = s.rfind(sep, lastkey)) != wxString::npos; lastkey--) {
if (lastkey == len - 1) {
// sep as accel
if (!lastkey)
break;
if (s[lastkey - 1] == wxT('-') || s[lastkey - 1] == wxT('+') || s[lastkey - 1] == sep)
continue;
}
if (!ParseString(s.Mid(lastkey + 1), len - lastkey - 1, mod, key))
for (const auto& token : str_split_with_sep(s, sep)) {
if (!ParseString(token, token.size(), mod, key))
return empty;
ret.insert(ret.begin(), wxAcceleratorEntry(mod, key));
len = lastkey;
ret.insert(ret.begin(), wxAcceleratorEntryUnicode(mod, key));
}
if (!ParseString(s, len, mod, key))
return empty;
ret.insert(ret.begin(), wxAcceleratorEntry(mod, key));
return ret;
}

View File

@ -51,9 +51,9 @@ public:
// convert wxSDLJoyEvent's type+val into mod (WXJB_*)
static int DigitalButton(wxSDLJoyEvent& event);
// convert mod+key to accel string, separated by -
static wxString ToString(int mod, int key, int joy);
static wxString ToString(int mod, int key, int joy, bool isConfig = false);
// convert multiple keys, separated by multikey
static wxString ToString(wxJoyKeyBinding_v keys, wxChar sep = wxT(','));
static wxString ToString(wxJoyKeyBinding_v keys, wxChar sep = wxT(','), bool isConfig = false);
// parses single key string into mod+key
static bool FromString(const wxString& s, int& mod, int& key, int& joy);
// parse multi-key string into array
@ -61,6 +61,11 @@ public:
static wxJoyKeyBinding_v FromString(const wxString& s, wxChar sep = wxT(','));
// parse a single key in given wxChar array up to given len
static bool ParseString(const wxString& s, int len, int& mod, int& key, int& joy);
// parse multi-key string into array
// returns empty array on parse errors
static wxAcceleratorEntry_v ToAccelFromString(const wxString& s, wxChar sep = wxT(','));
// convert multiple keys, separated by multikey
static wxString FromAccelToString(wxAcceleratorEntry_v keys, wxChar sep = wxT(','), bool isConfig = false);
protected:
void OnJoy(wxSDLJoyEvent&);

View File

@ -8,8 +8,9 @@
#include <vector>
#include <wx/accel.h>
#include <wx/textctrl.h>
#include "wxutil.h"
typedef std::vector<wxAcceleratorEntry> wxAcceleratorEntry_v;
typedef std::vector<wxAcceleratorEntryUnicode> wxAcceleratorEntry_v;
class wxKeyTextCtrl : public wxTextCtrl {
public:
@ -51,9 +52,12 @@ public:
}
// convert mod+key to accel string, separated by -
static wxString ToString(int mod, int key);
static wxString ToString(int mod, int key, bool isConfig = false);
// convert multiple keys, separated by multikey
static wxString ToString(wxAcceleratorEntry_v keys, wxChar sep = wxT(','));
static wxString ToString(wxAcceleratorEntry_v keys, wxChar sep = wxT(','), bool isConfig = false);
// convert mod+key to candidate accel string, separated by -
// this *should* work, but may fail for unicode chars
static wxString ToCandidateString(int mod, int key);
// parses single key string into mod+key
static bool FromString(const wxString& s, int& mod, int& key);
// parse multi-key string into accelentry array
@ -115,6 +119,7 @@ const struct {
wxString display_name;
} keys_with_display_names[] = {
{ WXK_BACK, wxTRANSLATE("Back"), wxTRANSLATE("Backspace") },
{ WXK_DELETE, wxTRANSLATE("Delete"), wxTRANSLATE("Delete") },
{ WXK_PAGEUP, wxTRANSLATE("PageUp"), wxTRANSLATE("Page Up") },
{ WXK_PAGEDOWN, wxTRANSLATE("PageDown"), wxTRANSLATE("Page Down") },
{ WXK_NUMLOCK, wxTRANSLATE("Num_lock"), wxTRANSLATE("Num Lock") },

View File

@ -65,8 +65,11 @@
#include "wx/keyedit.h"
static inline void DoSetAccel(wxMenuItem* mi, wxAcceleratorEntry* acc)
static inline void DoSetAccel(wxMenuItem* mi, wxAcceleratorEntryUnicode* acc)
{
// cannot use SDL keybinding as text without wx assertion error
if (!acc || acc->GetJoystick() != 0) return;
wxString lab = mi->GetItemLabel();
size_t tab = lab.find(wxT('\t'));

84
src/wx/wxutil.cpp Normal file
View File

@ -0,0 +1,84 @@
#include "wxutil.h"
#include "../common/contains.h"
int getKeyboardKeyCode(wxKeyEvent& event)
{
int uc = event.GetUnicodeKey();
if (uc != WXK_NONE) {
if (uc < 32)
return WXK_NONE;
return uc;
}
else {
return event.GetKeyCode();
}
}
wxAcceleratorEntryUnicode::wxAcceleratorEntryUnicode(wxAcceleratorEntry *accel)
: wxAcceleratorEntry(accel->GetFlags(), accel->GetKeyCode(), accel->GetCommand(), accel->GetMenuItem())
{
init(accel->GetFlags(), accel->GetKeyCode());
}
wxAcceleratorEntryUnicode::wxAcceleratorEntryUnicode(int flags, int keyCode, int cmd, wxMenuItem *item)
: wxAcceleratorEntry(flags, keyCode, cmd, item)
{
init(flags, keyCode);
}
wxAcceleratorEntryUnicode::wxAcceleratorEntryUnicode(wxString uKey, int joy, int flags, int keyCode, int cmd, wxMenuItem *item)
: wxAcceleratorEntry(flags, keyCode, cmd, item)
{
ukey = uKey;
joystick = joy;
}
void wxAcceleratorEntryUnicode::Set(wxString uKey, int joy, int flags, int keyCode, int cmd, wxMenuItem *item)
{
ukey = uKey;
joystick = joy;
wxAcceleratorEntry::Set(flags, keyCode, cmd, item);
}
void wxAcceleratorEntryUnicode::init(int flags, int keyCode)
{
joystick = 0;
if (!(flags == 0 && keyCode == 0)) {
ukey.Printf("%d:%d", keyCode, flags);
}
}
KeyboardInputMap* KeyboardInputMap::getInstance()
{
static KeyboardInputMap instance;
return &instance;
}
KeyboardInputMap::KeyboardInputMap(){}
void KeyboardInputMap::AddMap(wxString keyStr, int key, int mod)
{
KeyboardInputMap* singleton = getInstance();
singleton->keysMap[keyStr.wc_str()] = singleton->newPair(key, mod);
}
bool KeyboardInputMap::GetMap(wxString keyStr, int &key, int &mod)
{
KeyboardInputMap* singleton = getInstance();
if (contains(singleton->keysMap, keyStr.wc_str())) {
key = singleton->keysMap.at(keyStr.wc_str()).key;
mod = singleton->keysMap.at(keyStr.wc_str()).mod;
return true;
}
return false;
}

59
src/wx/wxutil.h Normal file
View File

@ -0,0 +1,59 @@
#ifndef _WX_UTIL_H
#define _WX_UTIL_H
#include <wx/event.h>
int getKeyboardKeyCode(wxKeyEvent& event);
#include <wx/accel.h>
class wxAcceleratorEntryUnicode : public wxAcceleratorEntry
{
public:
wxAcceleratorEntryUnicode(wxAcceleratorEntry *accel);
wxAcceleratorEntryUnicode(int flags=0, int keyCode=0, int cmd=0, wxMenuItem *item=nullptr);
wxAcceleratorEntryUnicode(wxString uKey, int joy=0, int flags=0, int keyCode=0, int cmd=0, wxMenuItem *item=nullptr);
void Set(wxString uKey, int joy, int flags, int keyCode, int cmd, wxMenuItem *item=nullptr);
int GetJoystick() const { return joystick; };
wxString GetUkey() const { return ukey; };
private:
void init(int flags, int keyCode);
wxString ukey;
int joystick;
};
#include <unordered_map>
#include <wx/string.h>
#include "widgets/wx/keyedit.h"
class KeyboardInputMap
{
public:
static KeyboardInputMap* getInstance();
static void AddMap(wxString keyStr, int key, int mod);
static bool GetMap(wxString keyStr, int &key, int &mod);
private:
KeyboardInputMap();
// We want to keep track of this pair for
// almost all keypresses.
typedef struct KeyMod {
int key;
int mod;
} KeyMod;
KeyMod newPair(int key, int mod)
{
KeyMod tmp;
tmp.key = key;
tmp.mod = mod;
return tmp;
}
// Map accel string to pair
std::unordered_map<std::wstring, KeyMod> keysMap;
};
#endif

View File

@ -720,6 +720,8 @@ MainFrame::MainFrame()
, dialog_opened(0)
, focused(false)
{
jpoll = new JoystickPoller();
this->Connect(wxID_ANY, wxEVT_SHOW, wxShowEventHandler(JoystickPoller::ShowDialog), jpoll, jpoll);
}
MainFrame::~MainFrame()
@ -846,7 +848,7 @@ int MainFrame::FilterEvent(wxEvent& event)
if (event.GetEventType() == wxEVT_KEY_DOWN && !menus_opened && !dialog_opened)
{
wxKeyEvent& ke = (wxKeyEvent&)event;
int keyCode = ke.GetKeyCode();
int keyCode = getKeyboardKeyCode(ke);
int keyMod = ke.GetModifiers();
wxAcceleratorEntry_v accels = wxGetApp().GetAccels();
for (size_t i = 0; i < accels.size(); ++i)
@ -858,6 +860,25 @@ int MainFrame::FilterEvent(wxEvent& event)
return true;
}
}
else if (event.GetEventType() == wxEVT_SDLJOY && !menus_opened && !dialog_opened)
{
wxSDLJoyEvent& je = (wxSDLJoyEvent&)event;
if (je.GetControlValue() == 0) return -1; // joystick button UP
int key = je.GetControlIndex();
int mod = wxJoyKeyTextCtrl::DigitalButton(je);
int joy = je.GetJoy() + 1;
wxString label = wxJoyKeyTextCtrl::ToString(mod, key, joy);
wxAcceleratorEntry_v accels = wxGetApp().GetAccels();
for (size_t i = 0; i < accels.size(); ++i) {
if (label == accels[i].GetUkey())
{
wxCommandEvent evh(wxEVT_COMMAND_MENU_SELECTED, accels[i].GetCommand());
evh.SetEventObject(this);
GetEventHandler()->ProcessEvent(evh);
return true;
}
}
}
return -1;
}
@ -886,31 +907,39 @@ wxString MainFrame::GetGamePath(wxString path)
void MainFrame::SetJoystick()
{
bool anyjoy = false;
/* Remove all attached joysticks to avoid errors while
* destroying and creating the GameArea `panel`. */
joy.Remove();
set_global_accels();
if (!emulating)
return;
for (int i = 0; i < 4; i++)
for (int j = 0; j < NUM_KEYS; j++) {
wxJoyKeyBinding_v b = gopts.joykey_bindings[i][j];
for (size_t k = 0; k < b.size(); k++) {
int jn = b[k].joy;
if (jn) {
if (!anyjoy) {
anyjoy = true;
joy.Attach(panel);
}
joy.Add(jn - 1);
}
}
}
}
void MainFrame::StopPoll()
{
if (jpoll && jpoll->IsRunning())
jpoll->Stop();
}
void MainFrame::StartPoll()
{
if (jpoll && !jpoll->IsRunning())
jpoll->Start();
}
void MainFrame::enable_menus()
{
for (int i = 0; i < ncmds; i++)

View File

@ -33,6 +33,7 @@
#include "../gba/Sound.h"
#include "wxlogdebug.h"
#include "wxutil.h"
template <typename T>
void CheckPointer(T pointer)
@ -203,6 +204,8 @@ class GameArea;
class LogDialog;
class JoystickPoller;
// true if pause should happen at next frame
extern bool pause_next;
@ -320,6 +323,10 @@ public:
void PollJoysticks() { joy.Poll(); }
// poll joysticks with timer
void StopPoll();
void StartPoll();
// required for building from xrc
DECLARE_DYNAMIC_CLASS(MainFrame);
// required for event handling
@ -345,9 +352,10 @@ private:
checkable_mi_array_t checkable_mi;
// recent menu item accels
wxMenu* recent;
wxAcceleratorEntry recent_accel[10];
wxAcceleratorEntryUnicode recent_accel[10];
// joystick reader
wxSDLJoy joy;
JoystickPoller* jpoll = nullptr;
// helper function for adding menu to accel editor
void add_menu_accels(wxTreeCtrl* tc, wxTreeItemId& parent, wxMenu* menu);
@ -383,6 +391,20 @@ private:
double hidpi_scale_factor;
};
// a class for polling joystick keys
class JoystickPoller : public wxTimer {
public:
void Notify() {
wxGetApp().frame->PollJoysticks();
}
void ShowDialog(wxShowEvent& ev) {
if (ev.IsShown())
Start(50);
else
Stop();
}
};
// a helper class to avoid forgetting StopModal()
class ModalPause {
public:

View File

@ -99,7 +99,7 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="Shortcut" subclass="wxKeyTextCtrl">
<object class="wxTextCtrl" name="Shortcut" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
</object>
<flag>wxALL|wxEXPAND</flag>