From baa0341bd52aa96db9eb9dde37b20c50ef3f48b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ed=C3=AAnis=20Freindorfer=20Azevedo?= Date: Thu, 7 May 2020 17:01:19 -0300 Subject: [PATCH] Input refactor. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- src/gb/GB.cpp | 2 - src/gba/GBA.cpp | 2 - src/wx/CMakeLists.txt | 2 + src/wx/cmdevents.cpp | 19 ++--- src/wx/guiinit.cpp | 117 ++++++++++++++-------------- src/wx/opts.cpp | 147 ++++++++++++++++++------------------ src/wx/opts.h | 2 +- src/wx/panel.cpp | 24 ++---- src/wx/strutils.cpp | 20 +++++ src/wx/strutils.h | 5 ++ src/wx/viewsupt.cpp | 3 +- src/wx/widgets/joyedit.cpp | 82 +++++++++++--------- src/wx/widgets/keyedit.cpp | 91 ++++++++++++++-------- src/wx/widgets/wx/joyedit.h | 9 ++- src/wx/widgets/wx/keyedit.h | 11 ++- src/wx/wxhead.h | 5 +- src/wx/wxutil.cpp | 84 +++++++++++++++++++++ src/wx/wxutil.h | 59 +++++++++++++++ src/wx/wxvbam.cpp | 47 +++++++++--- src/wx/wxvbam.h | 24 +++++- src/wx/xrc/AccelConfig.xrc | 2 +- 21 files changed, 508 insertions(+), 249 deletions(-) create mode 100644 src/wx/wxutil.cpp create mode 100644 src/wx/wxutil.h diff --git a/src/gb/GB.cpp b/src/gb/GB.cpp index ca265db0..5527c039 100644 --- a/src/gb/GB.cpp +++ b/src/gb/GB.cpp @@ -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; diff --git a/src/gba/GBA.cpp b/src/gba/GBA.cpp index 996d42b4..95e61ee0 100644 --- a/src/gba/GBA.cpp +++ b/src/gba/GBA.cpp @@ -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; diff --git a/src/wx/CMakeLists.txt b/src/wx/CMakeLists.txt index 9361127a..cf9fb518 100644 --- a/src/wx/CMakeLists.txt +++ b/src/wx/CMakeLists.txt @@ -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 diff --git a/src/wx/cmdevents.cpp b/src/wx/cmdevents.cpp index 02342607..3b5da32d 100644 --- a/src/wx/cmdevents.cpp +++ b/src/wx/cmdevents.cpp @@ -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); } diff --git a/src/wx/guiinit.cpp b/src/wx/guiinit.cpp index d5168a2c..e8bff7e0 100644 --- a/src/wx/guiinit.cpp +++ b/src/wx/guiinit.cpp @@ -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(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(d, "Assign"); accel_config_handler.remb = SafeXRCCTRL(d, "Remove"); - accel_config_handler.key = SafeXRCCTRL(d, "Shortcut"); + accel_config_handler.key = SafeXRCCTRL(d, "Shortcut"); accel_config_handler.curas = SafeXRCCTRL(d, "AlreadyThere"); accel_config_handler.key->MoveBeforeInTabOrder(accel_config_handler.asb); accel_config_handler.key->SetMultikey(0); diff --git a/src/wx/opts.cpp b/src/wx/opts.cpp index 8045208a..3f925163 100644 --- a/src/wx/opts.cpp +++ b/src/wx/opts.cpp @@ -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()); diff --git a/src/wx/opts.h b/src/wx/opts.h index 04526573..5ee2e05d 100644 --- a/src/wx/opts.h +++ b/src/wx/opts.h @@ -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. diff --git a/src/wx/panel.cpp b/src/wx/panel.cpp index 132b0e8d..21ac8702 100644 --- a/src/wx/panel.cpp +++ b/src/wx/panel.cpp @@ -16,6 +16,7 @@ #include "drawing.h" #include "filters.h" #include "wxvbam.h" +#include "wxutil.h" #ifdef __WXMSW__ #include @@ -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 diff --git a/src/wx/strutils.cpp b/src/wx/strutils.cpp index 726d2e3b..86b01aed 100644 --- a/src/wx/strutils.cpp +++ b/src/wx/strutils.cpp @@ -1,4 +1,5 @@ #include "strutils.h" +#include // 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); } diff --git a/src/wx/strutils.h b/src/wx/strutils.h index 4bf865a7..c8e3e43f 100644 --- a/src/wx/strutils.h +++ b/src/wx/strutils.h @@ -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); diff --git a/src/wx/viewsupt.cpp b/src/wx/viewsupt.cpp index 3ea9b88b..570b5d82 100644 --- a/src/wx/viewsupt.cpp +++ b/src/wx/viewsupt.cpp @@ -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) { diff --git a/src/wx/widgets/joyedit.cpp b/src/wx/widgets/joyedit.cpp index 6376228e..9bbd7c69 100644 --- a/src/wx/widgets/joyedit.cpp +++ b/src/wx/widgets/joyedit.cpp @@ -1,4 +1,6 @@ +#include #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; } diff --git a/src/wx/widgets/keyedit.cpp b/src/wx/widgets/keyedit.cpp index 75b72726..93ed7e3a 100644 --- a/src/wx/widgets/keyedit.cpp +++ b/src/wx/widgets/keyedit.cpp @@ -1,9 +1,16 @@ +#include #include #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; } diff --git a/src/wx/widgets/wx/joyedit.h b/src/wx/widgets/wx/joyedit.h index 5cece169..36040ad5 100644 --- a/src/wx/widgets/wx/joyedit.h +++ b/src/wx/widgets/wx/joyedit.h @@ -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&); diff --git a/src/wx/widgets/wx/keyedit.h b/src/wx/widgets/wx/keyedit.h index be8435f0..2405ae2b 100644 --- a/src/wx/widgets/wx/keyedit.h +++ b/src/wx/widgets/wx/keyedit.h @@ -8,8 +8,9 @@ #include #include #include +#include "wxutil.h" -typedef std::vector wxAcceleratorEntry_v; +typedef std::vector 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") }, diff --git a/src/wx/wxhead.h b/src/wx/wxhead.h index 60d2c89a..da237796 100644 --- a/src/wx/wxhead.h +++ b/src/wx/wxhead.h @@ -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')); diff --git a/src/wx/wxutil.cpp b/src/wx/wxutil.cpp new file mode 100644 index 00000000..42e2fece --- /dev/null +++ b/src/wx/wxutil.cpp @@ -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; +} diff --git a/src/wx/wxutil.h b/src/wx/wxutil.h new file mode 100644 index 00000000..80d7b691 --- /dev/null +++ b/src/wx/wxutil.h @@ -0,0 +1,59 @@ +#ifndef _WX_UTIL_H +#define _WX_UTIL_H + +#include + +int getKeyboardKeyCode(wxKeyEvent& event); + +#include + +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 +#include +#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 keysMap; +}; + +#endif diff --git a/src/wx/wxvbam.cpp b/src/wx/wxvbam.cpp index d3c6c206..9c0d8243 100644 --- a/src/wx/wxvbam.cpp +++ b/src/wx/wxvbam.cpp @@ -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++) diff --git a/src/wx/wxvbam.h b/src/wx/wxvbam.h index 0d386152..7138d94d 100644 --- a/src/wx/wxvbam.h +++ b/src/wx/wxvbam.h @@ -33,6 +33,7 @@ #include "../gba/Sound.h" #include "wxlogdebug.h" +#include "wxutil.h" template 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: diff --git a/src/wx/xrc/AccelConfig.xrc b/src/wx/xrc/AccelConfig.xrc index 53aa82ed..6dce2955 100644 --- a/src/wx/xrc/AccelConfig.xrc +++ b/src/wx/xrc/AccelConfig.xrc @@ -99,7 +99,7 @@ 5 - + wxALL|wxEXPAND