Convert wxJoyKeyCtrl to UserInputControl

This replaces all uses of wxJoyKeyCtrl with a new custom widget,
UserInputControl. Internally, this class keeps track of UserInputs
associated with the current control, allowing direct access to the
UserInputs, rather than going through string conversions.

Acceleration handling is simplified by going through UserInput -
converted to the key, mod, joy triplet for now - rather than handling
string comparisons.
This commit is contained in:
Fabrice de Gans 2023-05-03 22:19:15 -07:00 committed by Rafael Kitover
parent b9694e1900
commit b139572424
25 changed files with 1455 additions and 1600 deletions

File diff suppressed because it is too large Load Diff

View File

@ -736,7 +736,7 @@ add_custom_command(
OUTPUT cmdtab.cpp cmdhandlers.h cmd-evtable.h
COMMAND ${CMAKE_COMMAND} -D OUTDIR=${CMAKE_CURRENT_BINARY_DIR} -P copy-events.cmake
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS cmdevents.cpp
DEPENDS cmdevents.cpp copy-events.cmake
)
@ -770,11 +770,10 @@ set(
dialogs/game-boy-config.cpp
widgets/group-check-box.cpp
widgets/keep-on-top-styler.cpp
widgets/keyedit.cpp
widgets/joyedit.cpp
widgets/option-validator.cpp
widgets/render-plugin.cpp
widgets/sdljoy.cpp
widgets/user-input-ctrl.cpp
widgets/wxmisc.cpp
# probably ought to be in common
../sdl/text.cpp
@ -823,8 +822,7 @@ set(
widgets/keep-on-top-styler.h
widgets/option-validator.h
widgets/render-plugin.h
widgets/wx/keyedit.h
widgets/wx/joyedit.h
widgets/user-input-ctrl.h
widgets/wx/sdljoy.h
widgets/wx/webupdatedef.h
widgets/wx/wxmisc.h

View File

@ -8,6 +8,7 @@
#include <limits>
#include <wx/log.h>
#include <wx/translation.h>
#include "../System.h"
#include "../gba/Sound.h"

View File

@ -1,15 +1,274 @@
#include "config/user-input.h"
#include <map>
#include <wx/accel.h>
#include <wx/log.h>
#include <wx/regex.h>
#include <wx/translation.h>
#include "strutils.h"
#include "wx/joyedit.h"
#include "wxutil.h"
namespace config {
namespace {
struct KeyOverride {
wxString config_name;
wxString display_name;
};
static const std::map<wxKeyCode, KeyOverride> kKeyCodeOverrides = {
{WXK_BACK, {"BACK", _("Backspace")}},
{WXK_DELETE, {"DELETE", _("Delete")}},
{WXK_PAGEUP, {"PAGEUP", _("Page Up")}},
{WXK_PAGEDOWN, {"PAGEDOWN", _("Page Down")}},
{WXK_NUMLOCK, {"NUM_LOCK", _("Num Lock")}},
{WXK_SCROLL, {"SCROLL_LOCK", _("Scroll Lock")}},
// Numpad.
{WXK_NUMPAD_SPACE, {"KP_SPACE", _("Num Space")}},
{WXK_NUMPAD_TAB, {"KP_TAB", _("Num Tab")}},
{WXK_NUMPAD_ENTER, {"KP_ENTER", _("Num Enter")}},
{WXK_NUMPAD_HOME, {"KP_HOME", _("Num Home")}},
{WXK_NUMPAD_LEFT, {"KP_LEFT", _("Num left")}},
{WXK_NUMPAD_UP, {"KP_UP", _("Num Up")}},
{WXK_NUMPAD_RIGHT, {"KP_RIGHT", _("Num Right")}},
{WXK_NUMPAD_DOWN, {"KP_DOWN", _("Num Down")}},
{WXK_NUMPAD_PAGEUP, {"KP_PAGEUP", _("Num PageUp")}},
{WXK_NUMPAD_PAGEDOWN, {"KP_PAGEDOWN", _("Num PageDown")}},
{WXK_NUMPAD_END, {"KP_END", _("Num End")}},
{WXK_NUMPAD_BEGIN, {"KP_BEGIN", _("Num Begin")}},
{WXK_NUMPAD_INSERT, {"KP_INSERT", _("Num Insert")}},
{WXK_NUMPAD_DELETE, {"KP_DELETE", _("Num Delete")}},
{WXK_NUMPAD_EQUAL, {"KP_EQUAL", _("Num =")}},
{WXK_NUMPAD_MULTIPLY, {"KP_MULTIPLY", _("Num *")}},
{WXK_NUMPAD_ADD, {"KP_ADD", _("Num +")}},
{WXK_NUMPAD_SEPARATOR, {"KP_SEPARATOR", _("Num ,")}},
{WXK_NUMPAD_SUBTRACT, {"KP_SUBTRACT", _("Num -")}},
{WXK_NUMPAD_DECIMAL, {"KP_DECIMAL", _("Num .")}},
{WXK_NUMPAD_DIVIDE, {"KP_DIVIDE", _("Num /")}},
#if wxCHECK_VERSION(3, 1, 0)
// Media keys.
{WXK_VOLUME_MUTE, {"VOL_MUTE", _("Volume Mute")}},
{WXK_VOLUME_DOWN, {"VOL_DOWN", _("Volume Down")}},
{WXK_VOLUME_UP, {"VOL_UP", _("Volume Up")}},
{WXK_MEDIA_NEXT_TRACK, {"MEDIA_NEXT_TRACK", _("Next Track")}},
{WXK_MEDIA_PREV_TRACK, {"MEDIA_PREV_TRACK", _("Previous Track")}},
{WXK_MEDIA_STOP, {"MEDIA_STOP", _("Stop")}},
{WXK_MEDIA_PLAY_PAUSE, {"MEDIA_PLAY_PAUSE", _("Play/Pause")}},
#endif // wxCHECK_VERSION(3, 1, 0)
};
bool KeyIsModifier(int key) {
return key == WXK_CONTROL || key == WXK_SHIFT || key == WXK_ALT || key == WXK_RAW_CONTROL;
}
wxString ModToConfigString(int mod) {
wxString config_string;
if (mod & wxMOD_ALT) {
config_string += "ALT+";
}
if (mod & wxMOD_CONTROL) {
config_string += "CTRL+";
}
#ifdef __WXMAC__
if (mod & wxMOD_RAW_CONTROL) {
config_string += "RAWCTRL+";
}
#endif
if (mod & wxMOD_SHIFT) {
config_string += "SHIFT+";
}
if (mod & wxMOD_META) {
config_string += "META+";
}
return config_string;
}
wxString ModToLocalizedString(int mod) {
wxString config_string;
if (mod & wxMOD_ALT) {
config_string += _("Alt+");
}
if (mod & wxMOD_CONTROL) {
config_string += _("Ctrl+");
}
#ifdef __WXMAC__
if (mod & wxMOD_RAW_CONTROL) {
config_string += _("Rawctrl+");
}
#endif
if (mod & wxMOD_SHIFT) {
config_string += _("Shift+");
}
if (mod & wxMOD_META) {
config_string += _("Meta+");
}
return config_string;
}
wxString KeyboardInputToConfigString(int mod, int key) {
// Handle the modifier case separately.
if (KeyIsModifier(key)) {
return wxString::Format("%d:%d", key, mod);
}
// Custom overrides.
const auto iter = kKeyCodeOverrides.find(static_cast<wxKeyCode>(key));
if (iter != kKeyCodeOverrides.end()) {
return ModToConfigString(mod) + iter->second.config_name;
}
const wxString accel_string = wxAcceleratorEntry(mod, key).ToRawString().MakeUpper();
if (!accel_string.IsAscii()) {
// Unicode handling.
return wxString::Format("%d:%d", key, mod);
}
return accel_string;
}
wxString KeyboardInputToLocalizedString(int mod, int key) {
static const std::map<int, wxString> kStandaloneModifiers = {
{WXK_ALT, _("Alt")},
{WXK_SHIFT, _("Shift")},
#ifdef __WXMAC__
{WXK_RAW_CONTROL, _("Ctrl")},
{WXK_COMMAND, _("Cmd")},
#else
{WXK_CONTROL, _("Ctrl")},
#endif
};
const auto iter = kStandaloneModifiers.find(key);
if (iter != kStandaloneModifiers.end()) {
return iter->second;
}
// Custom overrides.
const auto iter2 = kKeyCodeOverrides.find(static_cast<wxKeyCode>(key));
if (iter2 != kKeyCodeOverrides.end()) {
return ModToLocalizedString(mod) + iter2->second.display_name;
}
return wxAcceleratorEntry(mod, key).ToString();
}
int StringToInt(const wxString& string) {
int ret = 0;
for (const auto& c : string) {
ret = ret * 10 + (c - '0');
}
return ret;
}
UserInput StringToUserInput(const wxString& string) {
// Regex used to parse joystick input.
static const wxRegEx kJoyRegex("^Joy([0-9]+)-", wxRE_EXTENDED | wxRE_ICASE);
static const wxRegEx kAxisRegex("^Axis([0-9]+)[-+]$", wxRE_EXTENDED | wxRE_ICASE);
static const wxRegEx kButtonRegex("^Button([0-9]+)$", wxRE_EXTENDED | wxRE_ICASE);
static const wxRegEx kHatRegex(
"^Hat([0-9]+)"
"((N|North|U|Up|NE|NorthEast|UR|UpRight)|"
"(S|South|D|Down|SW|SouthWest|DL|DownLeft)|"
"(E|East|R|Right|SE|SouthEast|DR|DownRight)|"
"(W|West|L|Left|NW|NorthWest|UL|UpLeft))$",
wxRE_EXTENDED | wxRE_ICASE);
// Standalone modifiers do not get parsed properly by wxWidgets.
static const std::map<wxString, UserInput> kStandaloneModifiers = {
{"ALT", UserInput(WXK_ALT, wxMOD_ALT)},
{"SHIFT", UserInput(WXK_SHIFT, wxMOD_SHIFT)},
{"RAWCTRL", UserInput(WXK_RAW_CONTROL, wxMOD_RAW_CONTROL)},
{"RAW_CTRL", UserInput(WXK_RAW_CONTROL, wxMOD_RAW_CONTROL)},
{"RAWCONTROL", UserInput(WXK_RAW_CONTROL, wxMOD_RAW_CONTROL)},
{"RAW_CONTROL", UserInput(WXK_RAW_CONTROL, wxMOD_RAW_CONTROL)},
{"CTRL", UserInput(WXK_CONTROL, wxMOD_CONTROL)},
{"CONTROL", UserInput(WXK_CONTROL, wxMOD_CONTROL)},
};
if (string.empty()) {
return UserInput();
}
if (kJoyRegex.Matches(string)) {
// Joystick.
size_t start, length;
kJoyRegex.GetMatch(&start, &length, 1);
const int joy = StringToInt(string.Mid(start, length));
const wxString remainder = string.Mid(start + length + 1);
if (kAxisRegex.Matches(remainder)) {
kAxisRegex.GetMatch(&start, &length, 1);
const int key = StringToInt(remainder.Mid(start, length));
const int mod =
remainder[start + length] == '+' ? wxJoyControl::AxisPlus : wxJoyControl::AxisMinus;
return UserInput(key, mod, joy);
}
if (kButtonRegex.Matches(remainder)) {
kButtonRegex.GetMatch(&start, &length, 1);
const int key = StringToInt(remainder.Mid(start, length));
return UserInput(key, wxJoyControl::Button, joy);
}
if (kHatRegex.Matches(remainder)) {
kHatRegex.GetMatch(&start, &length, 1);
const int key = StringToInt(remainder.Mid(start, length));
if (kHatRegex.GetMatch(&start, &length, 3)) {
return UserInput(key, wxJoyControl::HatNorth, joy);
} else if (kHatRegex.GetMatch(&start, &length, 4)) {
return UserInput(key, wxJoyControl::HatSouth, joy);
} else if (kHatRegex.GetMatch(&start, &length, 5)) {
return UserInput(key, wxJoyControl::HatEast, joy);
} else if (kHatRegex.GetMatch(&start, &length, 6)) {
return UserInput(key, wxJoyControl::HatWest, joy);
}
}
// Invalid.
return UserInput();
}
// Not a joystick.
const auto pair = strutils::split(string, ":");
long mod, key;
if (pair.size() == 2 && pair[0].ToLong(&key) && pair[1].ToLong(&mod)) {
// Pair of integers, likely a unicode input.
return UserInput(key, mod);
}
wxString upper(string);
upper.MakeUpper();
const auto iter = kStandaloneModifiers.find(upper);
if (iter != kStandaloneModifiers.end()) {
// Stand-alone modifier key.
return iter->second;
}
// Need to disable logging to parse the string.
const wxLogNull disable_logging;
wxAcceleratorEntry accel;
if (accel.FromString(string)) {
// This is weird, but keycode events are sent as "uppercase", but the
// accelerator parsing always considers them "lowercase", so we force
// an uppercase conversion here.
int key = accel.GetKeyCode();
if (key < WXK_START && wxIslower(key)) {
key = wxToupper(key);
}
return UserInput(key, accel.GetFlags());
}
// Invalid.
return UserInput();
}
} // namespace
UserInput::UserInput(const wxKeyEvent& event)
: UserInput(Device::Keyboard,
event.GetModifiers(),
getKeyboardKeyCode(event),
0) {}
: UserInput(Device::Keyboard, event.GetModifiers(), getKeyboardKeyCode(event), 0) {}
UserInput::UserInput(const wxJoyEvent& event)
: UserInput(Device::Joystick,
@ -18,41 +277,90 @@ UserInput::UserInput(const wxJoyEvent& event)
event.joystick().player_index()) {}
// static
std::set<UserInput> UserInput::FromString(const wxString& string) {
std::set<UserInput> UserInput::FromConfigString(const wxString& string) {
std::set<UserInput> user_inputs;
if (string.empty()) {
return user_inputs;
}
for (const auto& token : strutils::split_with_sep(string, wxT(","))) {
int mod, key, joy;
if (!wxJoyKeyTextCtrl::ParseString(token, token.size(), mod, key,
joy)) {
for (const auto& token : strutils::split_with_sep(string, ",")) {
UserInput user_input = StringToUserInput(token);
if (!user_input) {
user_inputs.clear();
return user_inputs;
}
user_inputs.emplace(UserInput(key, mod, joy));
user_inputs.emplace(std::move(user_input));
}
return user_inputs;
}
// static
wxString UserInput::SpanToString(const std::set<UserInput>& user_inputs,
bool is_config) {
wxString UserInput::SpanToConfigString(const std::set<UserInput>& user_inputs) {
wxString config_string;
if (user_inputs.empty()) {
return config_string;
}
for (const UserInput& user_input : user_inputs) {
config_string += user_input.ToString(is_config) + wxT(',');
config_string += user_input.ToConfigString() + ',';
}
return config_string.SubString(0, config_string.size() - 2);
}
wxString UserInput::ToString(bool is_config) const {
// TODO: Move the implementation here once all callers have been updated.
return wxJoyKeyTextCtrl::ToString(mod_, key_, joy_, is_config);
wxString UserInput::ToConfigString() const {
switch (device_) {
case Device::Invalid:
return wxEmptyString;
case Device::Keyboard:
return KeyboardInputToConfigString(mod_, key_);
case Device::Joystick:
wxString key;
switch (mod_) {
case wxJoyControl::AxisPlus:
key = wxString::Format(("Axis%d+"), key_);
break;
case wxJoyControl::AxisMinus:
key = wxString::Format(("Axis%d-"), key_);
break;
case wxJoyControl::Button:
key = wxString::Format(("Button%d"), key_);
break;
case wxJoyControl::HatNorth:
key = wxString::Format(("Hat%dN"), key_);
break;
case wxJoyControl::HatSouth:
key = wxString::Format(("Hat%dS"), key_);
break;
case wxJoyControl::HatWest:
key = wxString::Format(("Hat%dW"), key_);
break;
case wxJoyControl::HatEast:
key = wxString::Format(("Hat%dE"), key_);
break;
}
return wxString::Format("Joy%d-%s", joy_, key);
}
// Unreachable.
assert(false);
return wxEmptyString;
}
wxString UserInput::ToLocalizedString() const {
switch (device_) {
case Device::Invalid:
return wxEmptyString;
case Device::Keyboard:
return KeyboardInputToLocalizedString(mod_, key_);
case Device::Joystick:
// Same as Config string.
return ToConfigString();
}
// Unreachable.
assert(false);
return wxEmptyString;
}
} // namespace config

View File

@ -23,14 +23,13 @@ public:
enum class Device { Invalid = 0, Keyboard, Joystick, Last = Joystick };
// Constructor from a configuration string. Returns empty set on failure.
static std::set<UserInput> FromString(const wxString& string);
static std::set<UserInput> FromConfigString(const wxString& string);
// Converts a set of UserInput into a configuration string. This
// recomputes the configuration string every time and should not be used
// for comparison purposes.
// TODO: Replace std::set with std::span when the code base uses C++20.
static wxString SpanToString(const std::set<UserInput>& user_inputs,
bool is_config = false);
static wxString SpanToConfigString(const std::set<UserInput>& user_inputs);
// Invalid UserInput, mainly used for comparison.
UserInput() : UserInput(Device::Invalid, 0, 0, 0) {}
@ -42,19 +41,28 @@ public:
UserInput(const wxJoyEvent& event);
// TODO: Remove this once all uses have been removed.
UserInput(int key = 0, int mod = 0, int joy = 0)
UserInput(int key, int mod = 0, int joy = 0)
: UserInput(joy == 0 ? Device::Keyboard : Device::Joystick,
mod,
key,
joy) {}
// Converts to a configuration string.
wxString ToString(bool is_config = false) const;
Device device() const { return device_; }
// Converts to a configuration string for saving.
wxString ToConfigString() const;
// Converts to a localized string for display.
wxString ToLocalizedString() const;
wxJoystick joystick() const { return joystick_; }
constexpr bool is_valid() const { return device_ != Device::Invalid; }
constexpr operator bool() const { return is_valid(); }
int key() const { return key_; }
int mod() const { return mod_; }
unsigned joy() const { return joy_; }
constexpr bool operator==(const UserInput& other) const {
return device_ == other.device_ && mod_ == other.mod_ &&
key_ == other.key_ && joy_ == other.joy_;

View File

@ -13,12 +13,12 @@ STRING(REGEX MATCHALL "\nEVT_HANDLER([^\")]|\"[^\"]*\")*\\)" MW "${MW}")
# cmdtab.cpp is a table of cmd-id-name/cmd-name pairs
# sorted for binary searching
FILE(WRITE "${CMDTAB}" "// Generated from cmdevents.cpp; do not edit\n#include \"wxvbam.h\"\n\nstruct cmditem cmdtab[] = {\n")
FILE(WRITE "${CMDTAB}" "// Generated from cmdevents.cpp; do not edit\n#include <wx/xrc/xmlres.h>\n\n#include \"wxvbam.h\"\n#include \"wxhead.h\"\n\nstruct cmditem cmdtab[] = {\n")
SET(EVLINES )
FOREACH(EV ${MW})
# stripping the wxID_ makes it look better, but it's still all-caps
STRING(REGEX REPLACE "^[^\"]*\\((wxID_|)([^,]*),[^\"]*(\"[^\"]*\")[^,)]*(,[^)]*|).*"
" new_cmditem(wxT(\"\\2\"), wxTRANSLATE(\\3), XRCID(\"\\1\\2\")\\4 )"
" new_cmditem(\"\\2\", \\3, XRCID(\"\\1\\2\")\\4 )"
EV "${EV}")
STRING(REGEX REPLACE "XRCID\\(\"(wxID_[^\"]*)\"\\)" "\\1" EV ${EV})
LIST(APPEND EVLINES "${EV},\n")

View File

@ -12,15 +12,25 @@
#include <stdexcept>
#include <typeinfo>
#include <wx/checkbox.h>
#include <wx/checkedlistctrl.h>
#include <wx/choice.h>
#include <wx/clrpicker.h>
#include <wx/dialog.h>
#include <wx/dir.h>
#include <wx/filehistory.h>
#include <wx/filepicker.h>
#include <wx/menu.h>
#include <wx/msgdlg.h>
#include <wx/progdlg.h>
#include <wx/radiobut.h>
#include <wx/scrolwin.h>
#include <wx/slider.h>
#include <wx/spinctrl.h>
#include <wx/stockitem.h>
#include <wx/tokenzr.h>
#include <wx/txtstrm.h>
#include <wx/valtext.h>
#include <wx/wfstream.h>
#include "../gba/CheatSearch.h"
@ -33,6 +43,8 @@
#include "dialogs/game-boy-config.h"
#include "opts.h"
#include "widgets/option-validator.h"
#include "widgets/user-input-ctrl.h"
#include "wxhead.h"
#if defined(__WXGTK__)
#include "wayland.h"
@ -1603,24 +1615,22 @@ public:
// ClearUp for Up; ClearR for R etc
for (const config::GameKey& game_key : config::kAllGameKeys) {
const wxString control_name = config::GameKeyToString(game_key);
wxJoyKeyTextCtrl* tc = XRCCTRL_D(*p, control_name, wxJoyKeyTextCtrl);
widgets::UserInputCtrl* tc = XRCCTRL_D(*p, control_name, widgets::UserInputCtrl);
wxString singleClearButton("Clear" + control_name);
if (ev.GetId() == XRCID(singleClearButton.c_str())) {
tc->SetValue(wxEmptyString);
tc->Clear();
return;
}
}
for (const config::GameKey& game_key : config::kAllGameKeys) {
wxJoyKeyTextCtrl* tc = XRCCTRL_D(
*p, config::GameKeyToString(game_key), wxJoyKeyTextCtrl);
widgets::UserInputCtrl* tc =
XRCCTRL_D(*p, config::GameKeyToString(game_key), widgets::UserInputCtrl);
if (clear) {
tc->SetValue(wxEmptyString);
tc->Clear();
} else {
tc->SetValue(config::UserInput::SpanToString(
kDefaultBindings.find(config::GameControl(0, game_key))
->second));
tc->SetInputs(kDefaultBindings.find(config::GameControl(0, game_key))->second);
}
}
}
@ -1673,6 +1683,19 @@ static bool cmdid_lt(const wxAcceleratorEntryUnicode& a, const wxAcceleratorEntr
return a.GetCommand() < b.GetCommand();
}
class UserInputClientData : public wxClientData {
public:
explicit UserInputClientData(const config::UserInput& user_input) : user_input_(user_input) {}
explicit UserInputClientData(config::UserInput&& user_input)
: user_input_(std::move(user_input)) {}
~UserInputClientData() override = default;
const config::UserInput& user_input() const { return user_input_; }
private:
const config::UserInput user_input_;
};
// manage the accel editor dialog
static class AccelConfig_t : public wxEvtHandler {
public:
@ -1680,30 +1703,25 @@ public:
wxControlWithItems* lb;
wxAcceleratorEntry_v user_accels, accels;
wxWindow *asb, *remb;
wxJoyKeyTextCtrl* key;
widgets::UserInputCtrl* key;
wxControl* curas;
// since this is not the actual dialog, derived from wxDialog, which is
// the normal way of doing things, do init on the show event instead of
// constructor
void Init(wxShowEvent& ev)
{
#if wxCHECK_VERSION(2, 9, 0)
#define GetShow IsShown
#endif
void Init(wxShowEvent& ev) {
ev.Skip();
if (!ev.GetShow())
if (!ev.IsShown())
return;
lb->Clear();
tc->Unselect();
tc->ExpandAll();
user_accels = gopts.accels;
key->SetValue(wxT(""));
asb->Enable(false);
remb->Enable(false);
curas->SetLabel(wxT(""));
curas->SetLabel("");
accels = wxGetApp().frame->get_accels(user_accels);
}
@ -1738,18 +1756,13 @@ public:
lb->Clear();
remb->Enable(false);
asb->Enable(!key->GetValue().empty());
asb->Enable(!key->inputs().empty());
int cmd = id->val;
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());
}
config::UserInput input(accels[i].GetKeyCode(), accels[i].GetFlags(), accels[i].GetJoystick());
lb->Append(input.ToLocalizedString(), new UserInputClientData(std::move(input)));
}
}
}
@ -1771,26 +1784,23 @@ public:
return;
wxString selstr = lb->GetString(lsel);
int selmod, selkey, seljoy;
if (!wxJoyKeyTextCtrl::FromString(selstr, selmod, selkey, seljoy))
// this should never happen
return;
const config::UserInput& input =
static_cast<const UserInputClientData*>(lb->GetClientObject(lsel))->user_input();
int selmod = input.mod();
int selkey = input.key();
int seljoy = input.joy();
remb->Enable(false);
// if this key is currently in the shortcut field, clear out curas
if (selstr == key->GetValue())
curas->SetLabel(wxT(""));
curas->SetLabel("");
lb->Delete(lsel);
// 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)
|| (seljoy != 0 && i->GetUkey() == selstr))
{
for (wxAcceleratorEntry_v::iterator i = user_accels.begin(); i < user_accels.end(); ++i)
if (i->GetFlags() == selmod && i->GetKeyCode() == selkey && i->GetJoystick() == seljoy) {
user_accels.erase(i);
break;
}
@ -1799,19 +1809,16 @@ 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)
|| (seljoy != 0 && sys_accels[i].GetUkey() == selstr)) // joystick system bindings?
{
wxAcceleratorEntryUnicode ne(selstr, seljoy, selmod, selkey, XRCID("NOOP"));
if (sys_accels[i].GetFlags() == selmod && sys_accels[i].GetKeyCode() == selkey &&
sys_accels[i].GetJoystick() == seljoy) {
wxAcceleratorEntryUnicode ne(seljoy, 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)
|| (seljoy != 0 && i->GetUkey() == selstr))
{
for (wxAcceleratorEntry_v::iterator i = accels.begin(); i < accels.end(); ++i)
if (i->GetFlags() == selmod && i->GetKeyCode() == selkey &&
i->GetJoystick() == seljoy) {
accels.erase(i);
break;
}
@ -1821,7 +1828,9 @@ public:
void ResetAll(wxCommandEvent& ev)
{
(void)ev; // unused params
if (user_accels.empty() || wxMessageBox(_("This will clear all user-defined accelerators. Are you sure?"), _("Confirm"), wxYES_NO) != wxYES)
if (user_accels.empty() ||
wxMessageBox(_("This will clear all user-defined accelerators. Are you sure?"),
_("Confirm"), wxYES_NO) != wxYES)
return;
user_accels.clear();
@ -1829,8 +1838,8 @@ public:
tc->Unselect();
lb->Clear();
// rather than recomputing curas, just clear it
key->SetValue(wxT(""));
curas->SetLabel(wxT(""));
key->Clear();
curas->SetLabel("");
}
// remove old key binding, add new key binding, and update GUI
@ -1838,35 +1847,37 @@ public:
{
(void)ev; // unused params
wxTreeItemId csel = tc->GetSelection();
wxString accel = key->GetValue();
const std::set<config::UserInput>& inputs = key->inputs();
if (!csel.IsOk() || accel.empty())
if (!csel.IsOk() || inputs.empty())
return;
int acmod, ackey, acjoy;
assert(inputs.size() == 1);
if (!wxJoyKeyTextCtrl::FromString(accel, acmod, ackey, acjoy))
// this should never happen
return;
const config::UserInput& input = *inputs.begin();
const int acmod = input.mod();
const int ackey = input.key();
const int acjoy = input.joy();
for (unsigned int i = 0; i < lb->GetCount(); i++)
if (lb->GetString(i) == accel)
return; // ignore attempts to add twice
for (unsigned int i = 0; i < lb->GetCount(); i++) {
if (static_cast<UserInputClientData*>(lb->GetClientObject(i))->user_input() == input) {
// Ignore attempts to add twice.
return;
}
}
lb->Append(accel);
lb->Append(input.ToLocalizedString(), new UserInputClientData(input));
// 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 && i->GetJoystick() != acjoy)
|| (acjoy != 0 && i->GetUkey() == accel)) {
for (wxAcceleratorEntry_v::iterator i = user_accels.begin(); i < user_accels.end(); ++i)
if (i->GetFlags() == acmod && i->GetKeyCode() == ackey && i->GetJoystick() == acjoy) {
user_accels.erase(i);
break;
}
// then assign to this command
const TreeInt* id = static_cast<const TreeInt*>(tc->GetItemData(csel));
wxAcceleratorEntryUnicode ne(accel, acjoy, acmod, ackey, cmdtab[id->val].cmd_id);
wxAcceleratorEntryUnicode ne(input, cmdtab[id->val].cmd_id);
user_accels.push_back(ne);
// now assigned to this cmd...
@ -1881,31 +1892,28 @@ public:
void CheckKey(wxCommandEvent& ev)
{
(void)ev; // unused params
wxString nkey = key->GetValue();
const auto& inputs = key->inputs();
if (nkey.empty()) {
curas->SetLabel(wxT(""));
if (inputs.empty()) {
curas->SetLabel("");
asb->Enable(false);
return;
}
int acmod, ackey, acjoy;
assert(inputs.size() == 1);
const config::UserInput input = *inputs.begin();
if (!wxJoyKeyTextCtrl::FromString(nkey, acmod, ackey, acjoy)) {
// this should never happen
key->SetValue(wxT(""));
asb->Enable(false);
return;
}
const int ackey = input.key();
const int acmod = input.mod();
const int acjoy = input.joy();
asb->Enable(tc->GetSelection().IsOk());
int cmd = -1;
for (size_t i = 0; i < accels.size(); i++)
if ((accels[i].GetFlags() == acmod && accels[i].GetKeyCode() == ackey)
|| (acjoy != 0 && accels[i].GetUkey() == nkey)) {
if (accels[i].GetFlags() == acmod && accels[i].GetKeyCode() == ackey &&
accels[i].GetJoystick() == acjoy) {
int cmdid = accels[i].GetCommand();
for (cmd = 0; cmd < ncmds; cmd++)
if (cmdid == cmdtab[cmd].cmd_id)
break;
@ -1914,7 +1922,7 @@ public:
}
if (cmd < 0 || cmdtab[cmd].cmd_id == XRCID("NOOP")) {
curas->SetLabel(wxT(""));
curas->SetLabel("");
return;
}
@ -2233,8 +2241,8 @@ wxAcceleratorEntry_v MainFrame::get_accels(wxAcceleratorEntry_v user_accels)
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())
|| (ae.GetJoystick() == e->GetJoystick() && ae.GetUkey() == e->GetUkey())) {
if (ae.GetFlags() == e->GetFlags() && ae.GetKeyCode() == e->GetKeyCode() &&
ae.GetJoystick() == e->GetJoystick()) {
accels.erase(e);
break;
}
@ -2258,8 +2266,7 @@ void MainFrame::set_global_accels()
// first, zero out menu item on all accels
std::set<wxJoystick> needed_joysticks;
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()) {
if (accels[i].GetJoystick() != 0) {
needed_joysticks.insert(
wxJoystick::FromLegacyPlayerIndex(accels[i].GetJoystick()));
}
@ -2287,7 +2294,9 @@ void MainFrame::set_global_accels()
if (last_accel >= 0) {
DoSetAccel(mi, &accels[last_accel]);
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);
accels[last_accel].Set(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);
@ -2596,10 +2605,12 @@ bool MainFrame::BindControls()
e < sys_accels.end(); ++e)
if (a->GetFlags() == e->GetFlags() && a->GetKeyCode() == e->GetKeyCode()) {
if (e->GetMenuItem()) {
wxLogInfo(_("Duplicate menu accelerator: %s for %s and %s; keeping first"),
wxKeyTextCtrl::ToString(a->GetFlags(), a->GetKeyCode()).c_str(),
e->GetMenuItem()->GetItemLabelText().c_str(),
mi->GetItemLabelText().c_str());
wxLogInfo(_("Duplicate menu accelerator: %s for %s and %s; keeping "
"first"),
config::UserInput(a->GetKeyCode(), a->GetFlags())
.ToLocalizedString(),
e->GetMenuItem()->GetItemLabelText().c_str(),
mi->GetItemLabelText().c_str());
delete a;
a = 0;
} else {
@ -2610,10 +2621,12 @@ bool MainFrame::BindControls()
if (cmdtab[cmd].cmd_id == e->GetCommand())
break;
wxLogInfo(_("Menu accelerator %s for %s overrides default for %s; keeping menu"),
wxKeyTextCtrl::ToString(a->GetFlags(), a->GetKeyCode()).c_str(),
mi->GetItemLabelText().c_str(),
cmdtab[cmd].cmd.c_str());
wxLogInfo(_("Menu accelerator %s for %s overrides default for "
"%s; keeping menu"),
config::UserInput(a->GetKeyCode(), a->GetFlags())
.ToLocalizedString(),
mi->GetItemLabelText().c_str(),
cmdtab[cmd].cmd.c_str());
}
sys_accels.erase(e);
@ -3332,7 +3345,7 @@ bool MainFrame::BindControls()
for (const config::GameKey& game_key : config::kAllGameKeys) {
const wxString control_name = config::GameKeyToString(game_key);
wxJoyKeyTextCtrl* tc = XRCCTRL_D(*w, control_name, wxJoyKeyTextCtrl);
widgets::UserInputCtrl* tc = XRCCTRL_D(*w, control_name, widgets::UserInputCtrl);
CheckThrowXRCError(tc, control_name);
wxWindow* p = tc->GetParent();
@ -3341,8 +3354,7 @@ bool MainFrame::BindControls()
prev = tc;
prevp = p;
tc->SetValidator(
wxJoyKeyValidator(config::GameControl(i, game_key)));
tc->SetValidator(widgets::UserInputCtrlValidator(config::GameControl(i, game_key)));
}
JoyPadConfigHandler[i].p = w;
@ -3383,11 +3395,9 @@ 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<wxJoyKeyTextCtrl>(d, "Shortcut");
accel_config_handler.key = SafeXRCCTRL<widgets::UserInputCtrl>(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);
accel_config_handler.key->SetClearable(false);
wxTreeItemId rid = tc->AddRoot(wxT("root"));
if (menubar) {

View File

@ -6,12 +6,16 @@
#include <unordered_set>
#include <wx/defs.h>
#include <wx/filehistory.h>
#include <wx/log.h>
#include <wx/xrc/xmlres.h>
#include "config/option-observer.h"
#include "config/option-proxy.h"
#include "config/option.h"
#include "config/user-input.h"
#include "strutils.h"
#include "wxhead.h"
#include "wxvbam.h"
/*
@ -108,137 +112,137 @@ opts_t gopts;
// having the standard menu accels here means they will work even without menus
const wxAcceleratorEntryUnicode default_accels[] = {
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('C'), XRCID("CheatsList")),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('N'), XRCID("NextFrame")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, 'C', XRCID("CheatsList")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, '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
// FIXME: esc does not work on wxMSW
// this was annoying people A LOT #334
//wxAcceleratorEntry(wxMOD_NONE, WXK_ESCAPE, wxID_EXIT),
//wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_ESCAPE, wxID_EXIT),
// this was annoying people #298
//wxAcceleratorEntry(wxMOD_CMD, wxT('X'), wxID_EXIT),
//wxAcceleratorEntryUnicode(0, wxMOD_CMD, 'X', wxID_EXIT),
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('Q'), wxID_EXIT),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, 'Q', wxID_EXIT),
// FIXME: ctrl-W does not work on wxMSW
wxAcceleratorEntryUnicode(wxMOD_CMD, wxT('W'), wxID_CLOSE),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, 'W', wxID_CLOSE),
// load most recent is more commonly used than load other
//wxAcceleratorEntry(wxMOD_CMD, wxT('L'), XRCID("Load")),
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")),
//wxAcceleratorEntry(wxMOD_CMD, 'L', XRCID("Load")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, 'L', XRCID("LoadGameRecent")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_F1, XRCID("LoadGame01")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_F2, XRCID("LoadGame02")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_F3, XRCID("LoadGame03")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_F4, XRCID("LoadGame04")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_F5, XRCID("LoadGame05")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_F6, XRCID("LoadGame06")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_F7, XRCID("LoadGame07")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_F8, XRCID("LoadGame08")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_F9, XRCID("LoadGame09")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_F10, XRCID("LoadGame10")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_PAUSE, XRCID("Pause")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, 'P', XRCID("Pause")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, 'R', XRCID("Reset")),
// add shortcuts for original size multiplier #415
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")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, '1', XRCID("SetSize1x")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, '2', XRCID("SetSize2x")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, '3', XRCID("SetSize3x")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, '4', XRCID("SetSize4x")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, '5', XRCID("SetSize5x")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, '6', XRCID("SetSize6x")),
// save oldest is more commonly used than save other
//wxAcceleratorEntry(wxMOD_CMD, wxT('S'), XRCID("Save")),
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")),
//wxAcceleratorEntry(wxMOD_CMD, 'S', XRCID("Save")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, 'S', XRCID("SaveGameOldest")),
wxAcceleratorEntryUnicode(0, wxMOD_SHIFT, WXK_F1, XRCID("SaveGame01")),
wxAcceleratorEntryUnicode(0, wxMOD_SHIFT, WXK_F2, XRCID("SaveGame02")),
wxAcceleratorEntryUnicode(0, wxMOD_SHIFT, WXK_F3, XRCID("SaveGame03")),
wxAcceleratorEntryUnicode(0, wxMOD_SHIFT, WXK_F4, XRCID("SaveGame04")),
wxAcceleratorEntryUnicode(0, wxMOD_SHIFT, WXK_F5, XRCID("SaveGame05")),
wxAcceleratorEntryUnicode(0, wxMOD_SHIFT, WXK_F6, XRCID("SaveGame06")),
wxAcceleratorEntryUnicode(0, wxMOD_SHIFT, WXK_F7, XRCID("SaveGame07")),
wxAcceleratorEntryUnicode(0, wxMOD_SHIFT, WXK_F8, XRCID("SaveGame08")),
wxAcceleratorEntryUnicode(0, wxMOD_SHIFT, WXK_F9, XRCID("SaveGame09")),
wxAcceleratorEntryUnicode(0, 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
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")),
wxAcceleratorEntryUnicode(0, wxMOD_ALT, WXK_RETURN, XRCID("ToggleFullscreen")),
wxAcceleratorEntryUnicode(0, wxMOD_ALT, '1', XRCID("JoypadAutofireA")),
wxAcceleratorEntryUnicode(0, wxMOD_ALT, '2', XRCID("JoypadAutofireB")),
wxAcceleratorEntryUnicode(0, wxMOD_ALT, '3', XRCID("JoypadAutofireL")),
wxAcceleratorEntryUnicode(0, wxMOD_ALT, '4', XRCID("JoypadAutofireR")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, '1', XRCID("VideoLayersBG0")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, '2', XRCID("VideoLayersBG1")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, '3', XRCID("VideoLayersBG2")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, '4', XRCID("VideoLayersBG3")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, '5', XRCID("VideoLayersOBJ")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, '6', XRCID("VideoLayersWIN0")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, '7', XRCID("VideoLayersWIN1")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, '8', XRCID("VideoLayersOBJWIN")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, 'B', XRCID("Rewind")),
// following are not in standard menus
// FILExx are filled in when recent menu is filled
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_CMD, wxT('I'), XRCID("ChangeIFB")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_NUMPAD_ADD, XRCID("IncreaseVolume")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_NUMPAD_SUBTRACT, XRCID("DecreaseVolume")),
wxAcceleratorEntryUnicode(wxMOD_NONE, WXK_NUMPAD_ENTER, XRCID("ToggleSound"))
wxAcceleratorEntryUnicode(0, wxMOD_CMD, WXK_F1, wxID_FILE1),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, WXK_F2, wxID_FILE2),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, WXK_F3, wxID_FILE3),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, WXK_F4, wxID_FILE4),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, WXK_F5, wxID_FILE5),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, WXK_F6, wxID_FILE6),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, WXK_F7, wxID_FILE7),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, WXK_F8, wxID_FILE8),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, WXK_F9, wxID_FILE9),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, WXK_F10, wxID_FILE10),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, '0', XRCID("VideoLayersReset")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, 'G', XRCID("ChangeFilter")),
wxAcceleratorEntryUnicode(0, wxMOD_CMD, 'I', XRCID("ChangeIFB")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_NUMPAD_ADD, XRCID("IncreaseVolume")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_NUMPAD_SUBTRACT, XRCID("DecreaseVolume")),
wxAcceleratorEntryUnicode(0, wxMOD_NONE, WXK_NUMPAD_ENTER, XRCID("ToggleSound"))
};
const int num_def_accels = sizeof(default_accels) / sizeof(default_accels[0]);
const std::map<config::GameControl, std::set<config::UserInput>> kDefaultBindings = {
{ config::GameControl(0, config::GameKey::Up), {
WJKB(wxT('W')),
WJKB('W'),
WJKB(11, wxJoyControl::Button, 1),
WJKB(1, wxJoyControl::AxisMinus, 1),
WJKB(3, wxJoyControl::AxisMinus, 1),
}},
{ config::GameControl(0, config::GameKey::Down), {
WJKB(wxT('S')),
WJKB('S'),
WJKB(12, wxJoyControl::Button, 1),
WJKB(1, wxJoyControl::AxisPlus, 1),
WJKB(3, wxJoyControl::AxisPlus, 1),
}},
{ config::GameControl(0, config::GameKey::Left), {
WJKB(wxT('A')),
WJKB('A'),
WJKB(13, wxJoyControl::Button, 1),
WJKB(0, wxJoyControl::AxisMinus, 1),
WJKB(2, wxJoyControl::AxisMinus, 1),
}},
{ config::GameControl(0, config::GameKey::Right), {
WJKB(wxT('D')),
WJKB('D'),
WJKB(14, wxJoyControl::Button, 1),
WJKB(0, wxJoyControl::AxisPlus, 1),
WJKB(2, wxJoyControl::AxisPlus, 1),
}},
{ config::GameControl(0, config::GameKey::A), {
WJKB(wxT('L')),
WJKB('L'),
WJKB(0, wxJoyControl::Button, 1),
}},
{ config::GameControl(0, config::GameKey::B), {
WJKB(wxT('K')),
WJKB('K'),
WJKB(1, wxJoyControl::Button, 1),
}},
{ config::GameControl(0, config::GameKey::L), {
WJKB(wxT('I')),
WJKB('I'),
WJKB(2, wxJoyControl::Button, 1),
WJKB(9, wxJoyControl::Button, 1),
WJKB(4, wxJoyControl::AxisPlus, 1),
}},
{ config::GameControl(0, config::GameKey::R), {
WJKB(wxT('O')),
WJKB('O'),
WJKB(3, wxJoyControl::Button, 1),
WJKB(10, wxJoyControl::Button, 1),
WJKB(5, wxJoyControl::AxisPlus, 1),
@ -542,12 +546,12 @@ void load_opts(bool first_time_launch) {
for (auto& iter : gopts.game_control_bindings) {
const wxString optname = iter.first.ToString();
if (cfg->Read(optname, &s)) {
iter.second = config::UserInput::FromString(s);
iter.second = config::UserInput::FromConfigString(s);
if (!s.empty() && iter.second.empty()) {
wxLogWarning(_("Invalid key binding %s for %s"), s.c_str(), optname.c_str());
}
} else {
s = config::UserInput::SpanToString(iter.second);
s = config::UserInput::SpanToConfigString(iter.second);
cfg->Write(optname, s);
}
}
@ -562,14 +566,14 @@ void load_opts(bool first_time_launch) {
kbopt.append(cmdtab[i].cmd);
if (cfg->Read(kbopt, &s) && s.size()) {
wxAcceleratorEntry_v val = wxJoyKeyTextCtrl::ToAccelFromString(s);
if (!val.size())
auto inputs = config::UserInput::FromConfigString(s);
if (inputs.empty()) {
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].GetUkey(), val[j].GetJoystick(), val[j].GetFlags(), val[j].GetKeyCode(), cmdtab[i].cmd_id);
} else {
wxAcceleratorEntry_v val;
for (const auto& input : inputs) {
val.push_back(wxAcceleratorEntryUnicode(input, cmdtab[i].cmd_id));
}
gopts.accels.insert(gopts.accels.end(), val.begin(), val.end());
}
}
@ -634,13 +638,13 @@ void update_opts() {
// a different ordering than the one that will be eventually saved, but this
// is nothing to worry about.
bool game_bindings_changed = false;
for (auto &iter : gopts.game_control_bindings) {
for (const auto &iter : gopts.game_control_bindings) {
wxString option_name = iter.first.ToString();
std::set<config::UserInput> saved_config =
config::UserInput::FromString(cfg->Read(option_name, ""));
config::UserInput::FromConfigString(cfg->Read(option_name, ""));
if (saved_config != iter.second) {
game_bindings_changed = true;
cfg->Write(option_name, config::UserInput::SpanToString(iter.second));
cfg->Write(option_name, config::UserInput::SpanToConfigString(iter.second));
}
}
if (game_bindings_changed) {
@ -701,7 +705,11 @@ void update_opts() {
break;
wxAcceleratorEntry_v nv(i, j);
wxString nvs = wxJoyKeyTextCtrl::FromAccelToString(nv, wxT(','), true);
std::set<config::UserInput> user_inputs;
for (const auto& accel : nv) {
user_inputs.insert(config::UserInput(accel.GetKeyCode(), accel.GetFlags(), accel.GetJoystick()));
}
wxString nvs = config::UserInput::SpanToConfigString(user_inputs);
if (nvs != cfg->Read(command))
cfg->Write(command, nvs);
@ -796,9 +804,10 @@ void opt_set(const wxString& name, const wxString& val) {
}
if (!val.empty()) {
auto aval = wxJoyKeyTextCtrl::ToAccelFromString(val);
for (size_t i = 0; i < aval.size(); i++) {
aval[i].Set(aval[i].GetUkey(), aval[i].GetJoystick(), aval[i].GetFlags(), aval[i].GetKeyCode(), cmd->cmd_id);
auto inputs = config::UserInput::FromConfigString(val);
wxAcceleratorEntry_v aval;
for (const auto& input : inputs) {
aval.push_back(wxAcceleratorEntryUnicode(input, cmd->cmd_id));
}
if (!aval.size()) {
wxLogWarning(_("Invalid key binding %s for %s"), val.c_str(), name.c_str());
@ -818,7 +827,7 @@ void opt_set(const wxString& name, const wxString& val) {
gopts.game_control_bindings[game_control.value()].clear();
} else {
gopts.game_control_bindings[game_control.value()] =
config::UserInput::FromString(val);
config::UserInput::FromConfigString(val);
}
return;
}

View File

@ -8,7 +8,7 @@
#include "config/game-control.h"
#include "config/user-input.h"
#include "wx/keyedit.h"
#include "wxutil.h"
// Forward declaration.
class wxFileHistory;

View File

@ -36,7 +36,6 @@
#include "filters.h"
#include "wayland.h"
#include "widgets/render-plugin.h"
#include "wx/joyedit.h"
#include "wxutil.h"
#include "wxvbam.h"

View File

@ -1,262 +0,0 @@
#include "wx/joyedit.h"
#include <wx/tokenzr.h>
#include "config/user-input.h"
#include "opts.h"
#include "strutils.h"
#include "wx/sdljoy.h"
// FIXME: suppport analog/digital flag on per-axis basis
IMPLEMENT_DYNAMIC_CLASS(wxJoyKeyTextCtrl, wxKeyTextCtrl)
BEGIN_EVENT_TABLE(wxJoyKeyTextCtrl, wxKeyTextCtrl)
EVT_SDLJOY(wxJoyKeyTextCtrl::OnJoy)
END_EVENT_TABLE()
void wxJoyKeyTextCtrl::OnJoy(wxJoyEvent& event)
{
static wxLongLong last_event = 0;
// Filter consecutive axis motions within 300ms, as this adds two bindings
// +1/-1 instead of the one intended.
if ((event.control() == wxJoyControl::AxisPlus ||
event.control() == wxJoyControl::AxisMinus) &&
wxGetUTCTimeMillis() - last_event < 300) {
return;
}
last_event = wxGetUTCTimeMillis();
// Control was unpressed, ignore.
if (!event.pressed())
return;
wxString nv = config::UserInput(event).ToString();
if (nv.empty())
return;
if (multikey) {
wxString ov = GetValue();
if (!ov.empty())
nv = ov + multikey + nv;
}
SetValue(nv);
if (keyenter)
Navigate();
}
wxString wxJoyKeyTextCtrl::ToString(int mod, int key, int joy, bool isConfig)
{
if (!joy)
return wxKeyTextCtrl::ToString(mod, key, isConfig);
wxString s;
// Note: wx translates unconditionally (2.8.12, 2.9.1)!
// So any strings added below must also be translated unconditionally
s.Printf(("Joy%d-"), joy);
wxString mk;
switch (mod) {
case wxJoyControl::AxisPlus:
mk.Printf(("Axis%d+"), key);
break;
case wxJoyControl::AxisMinus:
mk.Printf(("Axis%d-"), key);
break;
case wxJoyControl::Button:
mk.Printf(("Button%d"), key);
break;
case wxJoyControl::HatNorth:
mk.Printf(("Hat%dN"), key);
break;
case wxJoyControl::HatSouth:
mk.Printf(("Hat%dS"), key);
break;
case wxJoyControl::HatWest:
mk.Printf(("Hat%dW"), key);
break;
case wxJoyControl::HatEast:
mk.Printf(("Hat%dE"), key);
break;
}
s += mk;
return s;
}
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;
ret += key;
}
return ret;
}
#include <wx/regex.h>
// only parse regex once
// Note: wx translates unconditionally (2.8.12, 2.9.1)!
// So any strings added below must also be translated unconditionally
// \1 is joy #
static wxRegEx joyre;
// \1 is axis# and \2 is + or -
static wxRegEx axre;
// \1 is button#
static wxRegEx butre;
// \1 is hat#, \3 is N, \4 is S, \5 is E, \6 is W, \7 is NE, \8 is SE,
// \9 is SW, \10 is NW
static wxRegEx hatre;
// use of static wxRegeEx is not thread-safe
static wxCriticalSection recs;
// wx provides no atoi for wxChar
// this is not a universal function; assumes valid number
static int simple_atoi(const wxString& s, int len)
{
int ret = 0;
for (int i = 0; i < len; i++)
ret = ret * 10 + (int)(s[i] - wxT('0'));
return ret;
}
static void CompileRegex()
{
// \1 is joy #
joyre.Compile(("^Joy([0-9]+)[-+]"), wxRE_EXTENDED | wxRE_ICASE);
// \1 is axis# and \2 is + or -
axre.Compile(("Axis([0-9]+)([+-])"), wxRE_EXTENDED | wxRE_ICASE);
// \1 is button#
butre.Compile(("Button([0-9]+)"), wxRE_EXTENDED | wxRE_ICASE);
// \1 is hat#, \3 is N, \4 is S, \5 is E, \6 is W
// This used to support diagonals as discrete input. For compatibility
// reasons, these have been moved a 1/8 turn counter-clockwise.
hatre.Compile(("Hat([0-9]+)"
"((N|North|U|Up|NE|NorthEast|UR|UpRight)|"
"(S|South|D|Down|SW|SouthWest|DL|DownLeft)|"
"(E|East|R|Right|SE|SouthEast|DR|DownRight)|"
"(W|West|L|Left|NW|NorthWest|UL|UpLeft))"),
wxRE_EXTENDED | wxRE_ICASE);
}
static bool ParseJoy(const wxString& s, int len, int& mod, int& key, int& joy)
{
mod = key = joy = 0;
if (!len)
return false;
wxCriticalSectionLocker lk(recs);
size_t b, l;
CompileRegex();
if (!joyre.Matches(s) || !joyre.GetMatch(&b, &l) || b)
return false;
const wxString p = s.Mid(l);
size_t alen = len - l;
joyre.GetMatch(&b, &l, 1);
joy = simple_atoi(s.Mid(b), l);
#define is_ctrl(re) re.Matches(p) && re.GetMatch(&b, &l) && l == alen && !b
if (is_ctrl(axre)) {
axre.GetMatch(&b, &l, 1);
key = simple_atoi(p.Mid(b), l);
axre.GetMatch(&b, &l, 2);
mod = p[b] == wxT('+') ? wxJoyControl::AxisPlus : wxJoyControl::AxisMinus;
} else if (is_ctrl(butre)) {
butre.GetMatch(&b, &l, 1);
key = simple_atoi(p.Mid(b), l);
mod = wxJoyControl::Button;
} else if (is_ctrl(hatre)) {
hatre.GetMatch(&b, &l, 1);
key = simple_atoi(p.Mid(b), l);
#define check_dir(n, d) if (hatre.GetMatch(&b, &l, n) && l > 0) mod = wxJoyControl::Hat##d
check_dir(3, North);
else check_dir(4, South);
else check_dir(5, East);
else check_dir(6, West);
} else {
joy = 0;
return false;
}
return true;
}
bool wxJoyKeyTextCtrl::ParseString(const wxString& s, int len, int& mod, int& key, int& joy)
{
if (ParseJoy(s, len, mod, key, joy))
return true;
return wxKeyTextCtrl::ParseString(s, len, mod, key);
}
bool wxJoyKeyTextCtrl::FromString(const wxString& s, int& mod, int& key, int& joy)
{
return ParseString(s, s.size(), 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;
for (const auto& token : strutils::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;
}
IMPLEMENT_CLASS(wxJoyKeyValidator, wxValidator)
bool wxJoyKeyValidator::TransferToWindow()
{
wxJoyKeyTextCtrl* jk = wxDynamicCast(GetWindow(), wxJoyKeyTextCtrl);
if (!jk)
return false;
jk->SetValue(config::UserInput::SpanToString(gopts.game_control_bindings[val_]));
return true;
}
bool wxJoyKeyValidator::TransferFromWindow()
{
wxJoyKeyTextCtrl* jk = wxDynamicCast(GetWindow(), wxJoyKeyTextCtrl);
if (!jk)
return false;
gopts.game_control_bindings[val_] = config::UserInput::FromString(jk->GetValue());
return true;
}

View File

@ -1,342 +0,0 @@
#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()
void wxKeyTextCtrl::OnKeyDown(wxKeyEvent& event)
{
lastmod = event.GetModifiers();
lastkey = getKeyboardKeyCode(event);
if (lastkey != WXK_NONE) // not a control character
{
KeyboardInputMap::AddMap(ToCandidateString(lastmod, lastkey), lastkey, lastmod);
}
}
void wxKeyTextCtrl::OnKeyUp(wxKeyEvent& event)
{
(void)event; // unused param
int mod = lastmod;
int key = lastkey;
lastmod = lastkey = 0;
// key is only 0 if we missed the keydown event
// or if we are being shipped pseudo keypress events
// either way, just ignore
if (!key)
return;
// use unmodified backspace to clear last key, if enabled
// if blank or backspace is modified, add normally instead
if (clearable && !mod && key == WXK_BACK && !GetValue().empty()) {
wxString val = GetValue();
size_t lastkey = val.rfind(multikey);
if (lastkey && lastkey != wxString::npos) {
// if this was actually a ,-accel, delete instead
if (lastkey == val.size() - 1) {
lastkey = val.rfind(multikey, lastkey - 1);
if (lastkey == wxString::npos)
lastkey = 0;
}
val.resize(lastkey);
SetValue(val);
} else
Clear();
} else {
wxString nv = ToCandidateString(mod, key);
if (nv.empty())
return;
if (multikey) {
wxString ov = GetValue();
if (!ov.empty())
nv = ov + multikey + nv;
}
SetValue(nv);
}
if (keyenter)
Navigate();
}
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) && 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;
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.ToRawString();
if (char_override || mod_override) {
size_t l = s.rfind(wxT('-'));
if (l == wxString::npos)
l = 0;
else
l++;
s.erase(l);
switch (key) {
case WXK_SHIFT:
s.append(_("SHIFT"));
break;
case WXK_ALT:
s.append(_("ALT"));
break;
case WXK_CONTROL:
s.append(_("CTRL"));
break;
// this is the control key on macs
#ifdef __WXMAC__
case WXK_RAW_CONTROL:
s.append(_("RAWCTRL"));
break;
#endif
default:
s.append((wxChar)key);
}
}
// on Mac, ctrl/meta become xctrl/cmd
// on other, meta is ignored
#ifndef __WXMAC__
if (mod & wxMOD_META) {
s.insert(0, _("Meta-"));
}
#endif
if (s.empty() || (key != wxT('-') && s[s.size() - 1] == wxT('-') && s != _("Num") + wxT(" -"))
|| (key != wxT('+') && s[s.size() - 1] == wxT('+') && s != _("Num") + wxT(" +")))
{
// bad key combo; probably also generates an assertion in wx
return wxEmptyString;
}
// hacky workaround for bug in wx 3.1+ not parsing key display names, or
// parsing modifiers that aren't a combo correctly
s.MakeUpper();
int keys_el_size = sizeof(keys_with_display_names)/sizeof(keys_with_display_names[0]);
for (int i = 0; i < keys_el_size; i++) {
wxString name_tr(_(keys_with_display_names[i].name));
wxString display_name_tr(_(keys_with_display_names[i].display_name));
name_tr.MakeUpper();
display_name_tr.MakeUpper();
wxString name(_(keys_with_display_names[i].name));
wxString display_name(_(keys_with_display_names[i].display_name));
name.MakeUpper();
display_name.MakeUpper();
s.Replace(display_name_tr, name_tr, true);
s.Replace(display_name, name, true);
}
return s;
}
wxString wxKeyTextCtrl::ToString(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(), isConfig);
if (key.empty())
return wxEmptyString;
ret += key;
}
return ret;
}
static bool checkForPairKeyMod(const wxString& s, int& mod, int& key)
{
long ulkey, ulmod;
// key:mod as pair
auto pair = strutils::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;
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));
wxAcceleratorEntryUnicode ae;
#ifndef __WXMAC__
#define check_meta(str) \
do { \
wxString meta = str; \
for (size_t ml = 0; (ml = a.find(meta, ml)) != wxString::npos; ml++) { \
if (!ml || a[ml - 1] == wxT('-') || a[ml - 1] == wxT('+')) { \
mod |= wxMOD_META; \
a.erase(ml, meta.size()); \
ml = -1; \
} \
} \
} while (0)
check_meta(wxT("Meta-"));
check_meta(wxT("Meta+"));
check_meta(_("Meta-"));
check_meta(_("Meta+"));
#endif
// wx disallows standalone modifiers
// unlike ToString(), this generates a debug message rather than
// an assertion error, so it's easy to ignore and expensive to avoid
// beforehand. Instead, check for them on failure
wxLogNull disable_logging;
if (!ae.FromString(a)) {
a.MakeUpper();
#define chk_str(n, k, m) \
do { \
wxString t = n; \
if (a.size() > t.size() && a.substr(a.size() - t.size()) == t) { \
a.replace(a.size() - t.size(), t.size(), wxT("F1")); \
wxString ss(s); \
if (ae.FromString(a)) { \
mod |= ae.GetFlags() | m; \
key = k; \
return true; \
} \
a.replace(a.size() - 2, 2, n); \
} \
} while (0)
chk_str(wxT("ALT"), WXK_ALT, wxMOD_ALT);
chk_str(wxT("SHIFT"), WXK_SHIFT, wxMOD_SHIFT);
chk_str(wxT("RAWCTRL"), WXK_RAW_CONTROL, wxMOD_RAW_CONTROL);
chk_str(wxT("RAW_CTRL"), WXK_RAW_CONTROL, wxMOD_RAW_CONTROL);
chk_str(wxT("RAWCONTROL"), WXK_RAW_CONTROL, wxMOD_RAW_CONTROL);
chk_str(wxT("RAW_CONTROL"), WXK_RAW_CONTROL, wxMOD_RAW_CONTROL);
chk_str(_("ALT"), WXK_ALT, wxMOD_ALT);
chk_str(_("SHIFT"), WXK_SHIFT, wxMOD_SHIFT);
chk_str(_("RAWCTRL"), WXK_RAW_CONTROL, wxMOD_RAW_CONTROL);
chk_str(_("RAW_CTRL"), WXK_RAW_CONTROL, wxMOD_RAW_CONTROL);
chk_str(_("RAWCONTROL"), WXK_RAW_CONTROL, wxMOD_RAW_CONTROL);
chk_str(_("RAW_CONTROL"), WXK_RAW_CONTROL, wxMOD_RAW_CONTROL);
chk_str(wxT("CTRL"), WXK_CONTROL, wxMOD_CONTROL);
chk_str(wxT("CONTROL"), WXK_CONTROL, wxMOD_CONTROL);
chk_str(_("CTRL"), WXK_CONTROL, wxMOD_CONTROL);
chk_str(_("CONTROL"), WXK_CONTROL, wxMOD_CONTROL);
return false;
}
mod |= ae.GetFlags();
key = ae.GetKeyCode();
// wx makes key lower-case, but key events return upper case
if (key < WXK_START && wxIslower(key))
key = wxToupper(key);
return true;
}
bool wxKeyTextCtrl::FromString(const wxString& s, int& mod, int& key)
{
return ParseString(s, s.size(), mod, key);
}
wxAcceleratorEntry_v wxKeyTextCtrl::FromString(const wxString& s, wxChar sep)
{
wxAcceleratorEntry_v ret, empty;
int mod, key;
for (const auto& token : strutils::split_with_sep(s, sep)) {
if (!ParseString(token, token.size(), mod, key))
return empty;
ret.insert(ret.begin(), wxAcceleratorEntryUnicode(mod, key));
}
return ret;
}
IMPLEMENT_CLASS(wxKeyValidator, wxValidator)
bool wxKeyValidator::TransferToWindow()
{
wxKeyTextCtrl* k = wxDynamicCast(GetWindow(), wxKeyTextCtrl);
if (!k)
return false;
k->SetValue(wxKeyTextCtrl::ToString(*val));
return true;
}
bool wxKeyValidator::TransferFromWindow()
{
wxKeyTextCtrl* k = wxDynamicCast(GetWindow(), wxKeyTextCtrl);
if (!k)
return false;
*val = wxKeyTextCtrl::FromString(k->GetValue());
return true;
}

View File

@ -58,8 +58,6 @@ wxJoyControl HatStatusToJoyControl(const uint8_t status) {
// For testing a GameController as a Joystick:
//#define SDL_IsGameController(x) false
DEFINE_EVENT_TYPE(wxEVT_JOY)
// static
wxJoystick wxJoystick::Invalid() {
return wxJoystick(kInvalidSdlIndex);
@ -107,6 +105,8 @@ wxJoyEvent::wxJoyEvent(
control_index_(control_index),
pressed_(pressed) {}
wxDEFINE_EVENT(wxEVT_JOY, wxJoyEvent);
// Represents the current state of a joystick. This class takes care of
// initializing and destroying SDL resources on construction and destruction so
// every associated SDL state for a joystick dies with this object.

View File

@ -0,0 +1,200 @@
#include "widgets/user-input-ctrl.h"
#include "config/user-input.h"
#include "opts.h"
namespace widgets {
namespace {
int FilterKeyCode(const wxKeyEvent& event) {
const wxChar keycode = event.GetUnicodeKey();
if (keycode == WXK_NONE) {
return event.GetKeyCode();
}
if (keycode < 32) {
switch (keycode) {
case WXK_BACK:
case WXK_TAB:
case WXK_RETURN:
case WXK_ESCAPE:
return keycode;
default:
return WXK_NONE;
}
}
return keycode;
}
} // namespace
extern const char UserInputCtrlNameStr[] = "userinputctrl";
UserInputCtrl::UserInputCtrl() : wxTextCtrl() {}
UserInputCtrl::UserInputCtrl(wxWindow* parent,
wxWindowID id,
const wxString& value,
const wxPoint& pos,
const wxSize& size,
long style,
const wxString& name) {
Create(parent, id, value, pos, size, style, name);
}
UserInputCtrl::~UserInputCtrl() = default;
bool UserInputCtrl::Create(wxWindow* parent,
wxWindowID id,
const wxString& value,
const wxPoint& pos,
const wxSize& size,
long style,
const wxString& name) {
return wxTextCtrl::Create(parent, id, value, pos, size, style, wxValidator(), name);
}
void UserInputCtrl::SetMultiKey(bool multikey) {
is_multikey_ = multikey;
Clear();
}
void UserInputCtrl::SetInputs(const std::set<config::UserInput>& inputs) {
inputs_ = inputs;
UpdateText();
}
void UserInputCtrl::Clear() {
inputs_.clear();
UpdateText();
}
wxIMPLEMENT_DYNAMIC_CLASS(UserInputCtrl, wxTextCtrl);
// clang-format off
wxBEGIN_EVENT_TABLE(UserInputCtrl, wxTextCtrl)
EVT_SDLJOY(UserInputCtrl::OnJoy)
EVT_KEY_DOWN(UserInputCtrl::OnKeyDown)
EVT_KEY_UP(UserInputCtrl::OnKeyUp)
wxEND_EVENT_TABLE();
// clang-format on
void UserInputCtrl::OnJoy(wxJoyEvent& event) {
static wxLongLong last_event = 0;
// Filter consecutive axis motions within 300ms, as this adds two bindings
// +1/-1 instead of the one intended.
if ((event.control() == wxJoyControl::AxisPlus || event.control() == wxJoyControl::AxisMinus) &&
wxGetUTCTimeMillis() - last_event < 300) {
return;
}
last_event = wxGetUTCTimeMillis();
// Control was unpressed, ignore.
if (!event.pressed()) {
return;
}
if (!is_multikey_) {
inputs_.clear();
}
inputs_.emplace(event);
UpdateText();
Navigate();
}
void UserInputCtrl::OnKeyDown(wxKeyEvent& event) {
last_mod_ = event.GetModifiers();
last_key_ = FilterKeyCode(event);
}
void UserInputCtrl::OnKeyUp(wxKeyEvent&) {
const int mod = last_mod_;
const int key = last_key_;
last_mod_ = last_key_ = 0;
// key is only 0 if we missed the keydown event
// or if we are being shipped pseudo keypress events
// either way, just ignore.
if (!key) {
return;
}
if (!is_multikey_) {
inputs_.clear();
}
inputs_.emplace(key, mod);
UpdateText();
Navigate();
}
void UserInputCtrl::UpdateText() {
static const wxChar kSeparator = L',';
// Build the display string.
wxString value;
for (const auto& input : inputs_) {
value += input.ToLocalizedString() + kSeparator;
}
if (value.empty()) {
SetValue(wxEmptyString);
} else {
SetValue(value.substr(0, value.size() - 1));
}
}
UserInputCtrlValidator::UserInputCtrlValidator(const config::GameControl game_control) : wxValidator(), game_control_(game_control) {}
wxObject* UserInputCtrlValidator::Clone() const {
return new UserInputCtrlValidator(game_control_);
}
bool UserInputCtrlValidator::TransferToWindow() {
UserInputCtrl* control = wxDynamicCast(GetWindow(), UserInputCtrl);
assert(control);
control->SetInputs(gopts.game_control_bindings[game_control_]);
return true;
}
bool UserInputCtrlValidator::TransferFromWindow() {
UserInputCtrl* control = wxDynamicCast(GetWindow(), UserInputCtrl);
assert(control);
gopts.game_control_bindings[game_control_] = control->inputs();
return true;
}
UserInputCtrlXmlHandler::UserInputCtrlXmlHandler() : wxXmlResourceHandler() {
AddWindowStyles();
}
wxObject* UserInputCtrlXmlHandler::DoCreateResource() {
XRC_MAKE_INSTANCE(control, UserInputCtrl)
control->Create(m_parentAsWindow, GetID(), GetText("value"), GetPosition(), GetSize(),
GetStyle() | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB, GetName());
SetupWindow(control);
if (GetBool("multikey", false)) {
control->SetMultiKey(true);
}
return control;
}
bool UserInputCtrlXmlHandler::CanHandle(wxXmlNode* node) {
return IsOfClass(node, "UserInputCtrl");
}
wxIMPLEMENT_DYNAMIC_CLASS(UserInputCtrlXmlHandler, wxXmlResourceHandler);
} // namespace widgets

View File

@ -0,0 +1,109 @@
#ifndef VBAM_WX_WIDGETS_USER_INPUT_CTRL_H_
#define VBAM_WX_WIDGETS_USER_INPUT_CTRL_H_
#include <set>
#include <wx/string.h>
#include <wx/textctrl.h>
#include <wx/validate.h>
#include <wx/xrc/xmlres.h>
#include "config/game-control.h"
#include "config/user-input.h"
#include "widgets/wx/sdljoy.h"
namespace widgets {
extern const char UserInputCtrlNameStr[];
// A custom TextCtrl that is used for input configuration. It can be configured
// for single or multi-key input. In multi-key mode, the user can press multiple
// keys to configure a single input.
// Internally, this control stores a set of UserInput objects, which is how the
// value for the field should be modified.
class UserInputCtrl : public wxTextCtrl {
public:
UserInputCtrl();
UserInputCtrl(wxWindow* parent,
wxWindowID id,
const wxString& value = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0,
const wxString& name = wxString::FromAscii(UserInputCtrlNameStr));
~UserInputCtrl() override;
bool Create(wxWindow* parent,
wxWindowID id,
const wxString& value = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0,
const wxString& name = wxString::FromAscii(UserInputCtrlNameStr));
// Sets multi-key mode on or off.
void SetMultiKey(bool multikey);
// Sets this control inputs.
void SetInputs(const std::set<config::UserInput>& inputs);
// Returns the inputs set in this control.
const std::set<config::UserInput>& inputs() const { return inputs_; }
// Clears the inputs set in this control.
void Clear() override;
wxDECLARE_DYNAMIC_CLASS(UserInputCtrl);
wxDECLARE_EVENT_TABLE();
private:
// Event handlers.
void OnJoy(wxJoyEvent& event);
void OnKeyDown(wxKeyEvent& event);
void OnKeyUp(wxKeyEvent& event);
// Updates the text in the control to reflect the current inputs.
void UpdateText();
bool is_multikey_ = false;
int last_mod_ = 0;
int last_key_ = 0;
std::set<config::UserInput> inputs_;
};
// A validator for the UserInputCtrl. This validator is used to transfer the
// GameControl data to and from the UserInputCtrl.
class UserInputCtrlValidator : public wxValidator {
public:
explicit UserInputCtrlValidator(const config::GameControl game_control);
~UserInputCtrlValidator() override = default;
wxObject* Clone() const override;
protected:
// wxValidator implementation.
bool TransferToWindow() override;
bool TransferFromWindow() override;
bool Validate(wxWindow*) override { return true; }
const config::GameControl game_control_;
};
// Handler to load the resource from an XRC file as a "UserInputCtrl" object.
class UserInputCtrlXmlHandler : public wxXmlResourceHandler {
public:
UserInputCtrlXmlHandler();
~UserInputCtrlXmlHandler() override = default;
private:
// wxXmlResourceHandler implementation.
wxObject* DoCreateResource() override;
bool CanHandle(wxXmlNode* node) override;
wxDECLARE_DYNAMIC_CLASS(wxTextCtrlXmlHandler);
};
} // namespace widgets
#endif // VBAM_WX_WIDGETS_USER_INPUT_CTRL_H_

View File

@ -1,72 +0,0 @@
#ifndef _WX_JOYKEYTEXT_H
#define _WX_JOYKEYTEXT_H
// wxJoyKeyTextCtrl: a wxTextCtrl which stores/acts on key presses and joystick
// The value is the symbolic name of the key pressed
// Supports manual clearing (bs), multiple keys in widget, automatic tab on key
#include "config/game-control.h"
#include "wx/keyedit.h"
#include "wx/sdljoy.h"
class wxJoyKeyTextCtrl : public wxKeyTextCtrl {
public:
// default constructor; required for use with xrc
// FIXME: clearable and keyenter should be style flags
wxJoyKeyTextCtrl()
: wxKeyTextCtrl()
{
}
virtual ~wxJoyKeyTextCtrl(){};
// convert mod+key to accel string, separated by -
static wxString ToString(int mod, int key, int joy, bool isConfig = false);
// parses single key string into mod+key
static bool FromString(const wxString& s, int& mod, int& key, int& joy);
// 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(wxJoyEvent&);
DECLARE_DYNAMIC_CLASS(wxJoyKeyTextCtrl);
DECLARE_EVENT_TABLE();
};
// A simple copy-only validator
class wxJoyKeyValidator : public wxValidator {
public:
wxJoyKeyValidator(const config::GameControl v)
: wxValidator()
, val_(v)
{
}
wxJoyKeyValidator(const wxJoyKeyValidator& v)
: wxValidator()
, val_(v.val_)
{
}
wxObject* Clone() const override
{
return new wxJoyKeyValidator(val_);
}
bool TransferToWindow() override;
bool TransferFromWindow() override;
bool Validate(wxWindow* p) override
{
(void)p; // unused params
return true;
}
protected:
const config::GameControl val_;
DECLARE_CLASS(wxJoyKeyValidator)
};
#endif /* WX_JOYKEYTEXT_H */

View File

@ -1,161 +0,0 @@
#ifndef _WX_KEYTEXT_H
#define _WX_KEYTEXT_H
// wxKeyTextCtrl: a wxTextCtrl which stores/acts on key presses
// The value is the symbolic name of the key pressed
// Supports manual clearing (bs), multiple keys in widget, automatic tab on key
#include <vector>
#include <wx/accel.h>
#include <wx/textctrl.h>
#include "wxutil.h"
typedef std::vector<wxAcceleratorEntryUnicode> wxAcceleratorEntry_v;
class wxKeyTextCtrl : public wxTextCtrl {
public:
// default constructor; required for use with xrc
// FIXME: clearable and keyenter should be style flags
wxKeyTextCtrl()
: wxTextCtrl()
, clearable(true)
, multikey(wxT(','))
, keyenter(true)
, lastmod(0)
, lastkey(0){};
virtual ~wxKeyTextCtrl(){};
void SetClearable(bool set = true)
{
clearable = set;
}
void SetMultikey(wxChar c = wxT(','))
{
multikey = c;
}
void SetKeyEnter(bool set = true)
{
keyenter = set;
}
bool GetClearable()
{
return clearable;
}
wxChar GetMultikey()
{
return multikey;
}
bool GetKeyEnter()
{
return keyenter;
}
// convert mod+key to accel string, separated by -
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(','), 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
// note that meta flag may be set in accelentry array item even
// where not supported for accelerators (i.e. non-mac)
// returns empty array on parse errors
static wxAcceleratorEntry_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);
protected:
void OnKeyDown(wxKeyEvent&);
void OnKeyUp(wxKeyEvent&);
bool clearable;
wxChar multikey;
bool keyenter;
// the last keydown event received; this is processed on next keyup
int lastmod, lastkey;
DECLARE_DYNAMIC_CLASS(wxKeyTextCtrl);
DECLARE_EVENT_TABLE();
};
// A simple copy-only validator
class wxKeyValidator : public wxValidator {
public:
wxKeyValidator(wxAcceleratorEntry_v* v)
: wxValidator()
, val(v)
{
}
wxKeyValidator(const wxKeyValidator& v)
: wxValidator()
, val(v.val)
{
}
wxObject* Clone() const
{
return new wxKeyValidator(val);
}
bool TransferToWindow();
bool TransferFromWindow();
bool Validate(wxWindow* p)
{
(void)p; // unused params
return true;
}
protected:
wxAcceleratorEntry_v* val;
DECLARE_CLASS(wxKeyValidator)
};
const struct {
wxKeyCode code;
wxString name;
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") },
{ WXK_SCROLL, wxTRANSLATE("Scroll_lock"), wxTRANSLATE("Scroll Lock") },
{ WXK_NUMPAD_SPACE, wxTRANSLATE("KP_Space"), wxTRANSLATE("Num Space") },
{ WXK_NUMPAD_TAB, wxTRANSLATE("KP_Tab"), wxTRANSLATE("Num Tab") },
{ WXK_NUMPAD_ENTER, wxTRANSLATE("KP_Enter"), wxTRANSLATE("Num Enter") },
{ WXK_NUMPAD_HOME, wxTRANSLATE("KP_Home"), wxTRANSLATE("Num Home") },
{ WXK_NUMPAD_LEFT, wxTRANSLATE("KP_Left"), wxTRANSLATE("Num left") },
{ WXK_NUMPAD_UP, wxTRANSLATE("KP_Up"), wxTRANSLATE("Num Up") },
{ WXK_NUMPAD_RIGHT, wxTRANSLATE("KP_Right"), wxTRANSLATE("Num Right") },
{ WXK_NUMPAD_DOWN, wxTRANSLATE("KP_Down"), wxTRANSLATE("Num Down") },
// these two are in some 3.1+ builds for whatever reason
{ WXK_NUMPAD_PAGEUP, wxTRANSLATE("KP_PageUp"), wxTRANSLATE("Num PageUp") },
{ WXK_NUMPAD_PAGEDOWN, wxTRANSLATE("KP_PageDown"), wxTRANSLATE("Num PageDown") },
{ WXK_NUMPAD_PAGEUP, wxTRANSLATE("KP_PageUp"), wxTRANSLATE("Num Page Up") },
{ WXK_NUMPAD_PAGEDOWN, wxTRANSLATE("KP_PageDown"), wxTRANSLATE("Num Page Down") },
{ WXK_NUMPAD_END, wxTRANSLATE("KP_End"), wxTRANSLATE("Num End") },
{ WXK_NUMPAD_BEGIN, wxTRANSLATE("KP_Begin"), wxTRANSLATE("Num Begin") },
{ WXK_NUMPAD_INSERT, wxTRANSLATE("KP_Insert"), wxTRANSLATE("Num Insert") },
{ WXK_NUMPAD_DELETE, wxTRANSLATE("KP_Delete"), wxTRANSLATE("Num Delete") },
{ WXK_NUMPAD_EQUAL, wxTRANSLATE("KP_Equal"), wxTRANSLATE("Num =") },
{ WXK_NUMPAD_MULTIPLY, wxTRANSLATE("KP_Multiply"), wxTRANSLATE("Num *") },
{ WXK_NUMPAD_ADD, wxTRANSLATE("KP_Add"), wxTRANSLATE("Num +") },
{ WXK_NUMPAD_SEPARATOR, wxTRANSLATE("KP_Separator"), wxTRANSLATE("Num ,") },
{ WXK_NUMPAD_SUBTRACT, wxTRANSLATE("KP_Subtract"), wxTRANSLATE("Num -") },
{ WXK_NUMPAD_DECIMAL, wxTRANSLATE("KP_Decimal"), wxTRANSLATE("Num .") },
{ WXK_NUMPAD_DIVIDE, wxTRANSLATE("KP_Divide"), wxTRANSLATE("Num /") },
};
// 2.x does not have the WXK_RAW_XXX values for Mac
#if wxMAJOR_VERSION < 3
#define WXK_RAW_CONTROL WXK_COMMAND
#define wxMOD_RAW_CONTROL wxMOD_CMD
#endif
#endif /* WX_KEYTEXT_H */

View File

@ -86,6 +86,13 @@ private:
bool pressed_;
};
wxDECLARE_EVENT(wxEVT_JOY, wxJoyEvent);
typedef void (wxEvtHandler::*wxJoyEventFunction)(wxJoyEvent&);
#define wxJoyEventHandler(func) wxEVENT_HANDLER_CAST(wxJoyEventFunction, func)
#define EVT_SDLJOY(func) \
wx__DECLARE_EVT0(wxEVT_JOY, wxJoyEventHandler(func))
// This is my own SDL-based joystick handler, since wxJoystick is brain-dead.
// It's geared towards keyboard emulation.
//
@ -145,16 +152,4 @@ private:
wxLongLong last_poll_ = wxGetUTCTimeMillis();
};
// Note: this means sdljoy can't be part of a library w/o extra work
DECLARE_LOCAL_EVENT_TYPE(wxEVT_JOY, -1)
typedef void (wxEvtHandler::*wxJoyEventFunction)(wxJoyEvent&);
#define EVT_SDLJOY(fn) \
DECLARE_EVENT_TABLE_ENTRY(wxEVT_JOY, \
wxID_ANY, \
wxID_ANY, \
(wxObjectEventFunction)(wxEventFunction)(wxCommandEventFunction) \
wxStaticCastEvent(wxJoyEventFunction, &fn), \
(wxObject*)NULL) \
,
#endif /* _WX_SDLJOY_H */

View File

@ -22,6 +22,7 @@
// filehistory.h is separate only in 2.9+
#include <wx/docview.h>
// This is necessary to build with gcc on Fedora.
using std::uint8_t;
using std::uint16_t;
using std::uint32_t;
@ -42,15 +43,6 @@ using std::int32_t;
#endif
#endif
// compatibility with MacOSX 10.5
#if !wxCHECK_VERSION(2, 8, 8)
#define AddFileWithMimeType(a, b, c, m) AddFile(a, b, c)
#define GetItemLabel GetText
#define SetItemLabel SetText
#define GetMenuLabel GetLabelTop
#define GetItemLabelText GetLabel
#endif
// compatibility with wx-2.9
// The only reason I use wxTRANSLATE at all is to get wxT as a side effect.
#if wxCHECK_VERSION(2, 9, 0)
@ -65,7 +57,7 @@ using std::int32_t;
// GetAccel is inefficent anyway (often I don't want to convert to wxAccEnt)
// This is a working replacement for SetAccel, at least.
#include "wx/keyedit.h"
#include "wxutil.h"
static inline void DoSetAccel(wxMenuItem* mi, wxAcceleratorEntryUnicode* acc)
{
@ -81,11 +73,12 @@ static inline void DoSetAccel(wxMenuItem* mi, wxAcceleratorEntryUnicode* acc)
wxString accs;
if (acc)
if (acc) {
// actually, use keyedit's ToString(), as it is more reliable
// and doesn't generate wx assertions
// accs = acc->ToString();
accs = wxKeyTextCtrl::ToString(acc->GetFlags(), acc->GetKeyCode());
accs = config::UserInput(acc->GetKeyCode(), acc->GetFlags()).ToLocalizedString();
}
if (tab != wxString::npos && accs == lab.substr(tab + 1))
return;

View File

@ -1,12 +1,10 @@
#include "wxutil.h"
#include "../common/contains.h"
int getKeyboardKeyCode(const wxKeyEvent& event)
{
int getKeyboardKeyCode(const wxKeyEvent& event) {
int uc = event.GetUnicodeKey();
if (uc != WXK_NONE) {
if (uc < 32) { // not all control chars
if (uc < 32) { // not all control chars
switch (uc) {
case WXK_BACK:
case WXK_TAB:
@ -18,76 +16,31 @@ int getKeyboardKeyCode(const wxKeyEvent& event)
}
}
return uc;
}
else {
} else {
return event.GetKeyCode();
}
}
wxAcceleratorEntryUnicode::wxAcceleratorEntryUnicode(wxAcceleratorEntry* accel)
: wxAcceleratorEntryUnicode(0,
accel->GetFlags(),
accel->GetKeyCode(),
accel->GetCommand(),
accel->GetMenuItem()) {}
wxAcceleratorEntryUnicode::wxAcceleratorEntryUnicode(wxAcceleratorEntry *accel)
: wxAcceleratorEntry(accel->GetFlags(), accel->GetKeyCode(), accel->GetCommand(), accel->GetMenuItem())
{
init(accel->GetFlags(), accel->GetKeyCode());
}
wxAcceleratorEntryUnicode::wxAcceleratorEntryUnicode(const config::UserInput& input,
int cmd,
wxMenuItem* item)
: wxAcceleratorEntryUnicode(input.joy(), input.mod(), input.key(), cmd, item) {}
wxAcceleratorEntryUnicode::wxAcceleratorEntryUnicode(int joy,
int flags,
int keyCode,
int cmd,
wxMenuItem* item)
: wxAcceleratorEntry(flags, keyCode, cmd, item), joystick(joy) {}
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;
void wxAcceleratorEntryUnicode::Set(int joy, int flags, int keyCode, int cmd, wxMenuItem* item) {
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.ToStdWstring()] = singleton->newPair(key, mod);
}
bool KeyboardInputMap::GetMap(wxString keyStr, int &key, int &mod)
{
KeyboardInputMap* singleton = getInstance();
if (contains(singleton->keysMap, keyStr.ToStdWstring())) {
key = singleton->keysMap.at(keyStr.ToStdWstring()).key;
mod = singleton->keysMap.at(keyStr.ToStdWstring()).mod;
return true;
}
return false;
}

View File

@ -1,59 +1,36 @@
#ifndef _WX_UTIL_H
#define _WX_UTIL_H
#include <wx/event.h>
int getKeyboardKeyCode(const wxKeyEvent& event);
#include <vector>
#include <wx/accel.h>
#include <wx/event.h>
#include "config/user-input.h"
int getKeyboardKeyCode(const wxKeyEvent& event);
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);
wxAcceleratorEntryUnicode(const config::UserInput& input,
int cmd = 0,
wxMenuItem* item = nullptr);
wxAcceleratorEntryUnicode(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);
void Set(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;
private:
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;
};
typedef std::vector<wxAcceleratorEntryUnicode> wxAcceleratorEntry_v;
#endif

View File

@ -16,14 +16,18 @@
#include <wx/filesys.h>
#include <wx/fs_arc.h>
#include <wx/fs_mem.h>
#include <wx/menu.h>
#include <wx/mstream.h>
#include <wx/progdlg.h>
#include <wx/protocol/http.h>
#include <wx/regex.h>
#include <wx/sstream.h>
#include <wx/stdpaths.h>
#include <wx/string.h>
#include <wx/txtstrm.h>
#include <wx/url.h>
#include <wx/wfstream.h>
#include <wx/wxcrtvararg.h>
#include <wx/zipstrm.h>
#include "../gba/remote.h"
@ -40,6 +44,8 @@
#include "strutils.h"
#include "wayland.h"
#include "widgets/group-check-box.h"
#include "widgets/user-input-ctrl.h"
#include "wxhead.h"
namespace {
static const wxString kOldConfigFileName("vbam.conf");
@ -296,6 +302,7 @@ bool wxvbamApp::OnInit() {
// maybe in future if not wxSHARED, load only builtin-needed handlers
xr->InitAllHandlers();
xr->AddHandler(new widgets::GroupCheckBoxXmlHandler());
xr->AddHandler(new widgets::UserInputCtrlXmlHandler());
wxInitAllImageHandlers();
get_config_path(config_path);
// first, load override xrcs
@ -935,18 +942,19 @@ int MainFrame::FilterEvent(wxEvent& event)
else if (event.GetEventType() == wxEVT_JOY && !menus_opened && !dialog_opened)
{
wxJoyEvent& je = (wxJoyEvent&)event;
if (!je.pressed()) return -1; // joystick button UP
wxString label = config::UserInput(je).ToString();
if (!je.pressed()) {
// joystick button UP
return -1;
}
wxAcceleratorEntry_v accels = wxGetApp().GetAccels();
for (size_t i = 0; i < accels.size(); ++i)
{
if (label == accels[i].GetUkey())
{
for (size_t i = 0; i < accels.size(); ++i) {
if (accels[i].GetJoystick() == je.joystick().player_index() &&
accels[i].GetKeyCode() == je.control_index() && accels[i].GetFlags() == je.control()) {
wxCommandEvent evh(wxEVT_COMMAND_MENU_SELECTED, accels[i].GetCommand());
evh.SetEventObject(this);
GetEventHandler()->ProcessEvent(evh);
return wxEventFilter::Event_Processed;
}
}
}
}
return wxEventFilter::Event_Skip;

View File

@ -14,8 +14,6 @@
#include "config/option-observer.h"
#include "widgets/dpi-support.h"
#include "widgets/keep-on-top-styler.h"
#include "wx/joyedit.h"
#include "wx/keyedit.h"
#include "wx/sdljoy.h"
#include "wx/wxmisc.h"
#include "wxhead.h"

View File

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

View File

@ -17,8 +17,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="Up" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="Up">
<multikey>1</multikey>
</object>
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
@ -40,8 +40,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="A" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="A">
<multikey>1</multikey>
</object>
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
@ -63,8 +63,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="Down" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="Down">
<multikey>1</multikey>
</object>
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
@ -86,8 +86,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="B" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="B">
<multikey>1</multikey>
</object>
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
@ -109,8 +109,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="Left" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="Left">
<multikey>1</multikey>
</object>
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
@ -132,8 +132,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="L" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="L">
<multikey>1</multikey>
</object>
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
@ -155,8 +155,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="Right" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="Right">
<multikey>1</multikey>
</object>
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
@ -179,8 +179,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="R" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="R">
<multikey>1</multikey>
</object>
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
@ -202,8 +202,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="Select" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="Select">
<multikey>1</multikey>
</object>
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
@ -225,8 +225,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="Start" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="Start">
<multikey>1</multikey>
</object>
<option>1</option>
<flag>wxALL|wxEXPAND</flag>
@ -257,8 +257,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="MotionUp" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="MotionUp">
<multikey>1</multikey>
</object>
<flag>wxALL|wxEXPAND</flag>
<border>5</border>
@ -279,8 +279,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="AutoA" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="AutoA">
<multikey>1</multikey>
</object>
<flag>wxALL|wxEXPAND</flag>
<border>5</border>
@ -301,8 +301,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="MotionDown" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="MotionDown">
<multikey>1</multikey>
</object>
<flag>wxALL|wxEXPAND</flag>
<border>5</border>
@ -323,8 +323,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="AutoB" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="AutoB">
<multikey>1</multikey>
</object>
<flag>wxALL|wxEXPAND</flag>
<border>5</border>
@ -345,8 +345,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="MotionLeft" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="MotionLeft">
<multikey>1</multikey>
</object>
<flag>wxALL|wxEXPAND</flag>
<border>5</border>
@ -367,8 +367,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="GS" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="GS">
<multikey>1</multikey>
</object>
<flag>wxALL|wxEXPAND</flag>
<border>5</border>
@ -389,8 +389,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="MotionRight" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="MotionRight">
<multikey>1</multikey>
</object>
<flag>wxALL|wxGROW</flag>
<border>5</border>
@ -411,8 +411,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="Speed" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="Speed">
<multikey>1</multikey>
</object>
<flag>wxALL|wxEXPAND</flag>
<border>5</border>
@ -433,8 +433,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="MotionIn" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="MotionIn">
<multikey>1</multikey>
</object>
<flag>wxALL|wxEXPAND</flag>
<border>5</border>
@ -464,8 +464,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="MotionOut" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="MotionOut">
<multikey>1</multikey>
</object>
<flag>wxALL|wxGROW</flag>
<border>5</border>
@ -486,8 +486,8 @@
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxTextCtrl" name="Capture" subclass="wxJoyKeyTextCtrl">
<style>wxTE_PROCESS_ENTER|wxTE_PROCESS_TAB</style>
<object class="UserInputCtrl" name="Capture">
<multikey>1</multikey>
</object>
<flag>wxALL|wxEXPAND</flag>
<border>5</border>