diff --git a/src/wx/CMakeLists.txt b/src/wx/CMakeLists.txt index 33b4c421..a3e952a3 100644 --- a/src/wx/CMakeLists.txt +++ b/src/wx/CMakeLists.txt @@ -73,6 +73,7 @@ set(VBAM_WX_COMMON widgets/checkedlistctrl.h widgets/client-data.h widgets/dpi-support.h + widgets/event-handler-provider.h widgets/group-check-box.cpp widgets/group-check-box.h widgets/keep-on-top-styler.cpp @@ -87,6 +88,8 @@ set(VBAM_WX_COMMON widgets/user-input-event.h widgets/sdl-poller.cpp widgets/sdl-poller.h + widgets/shortcut-menu-bar.cpp + widgets/shortcut-menu-bar.h widgets/utils.cpp widgets/utils.h widgets/webupdatedef.h diff --git a/src/wx/config/user-input.cpp b/src/wx/config/user-input.cpp index c9278112..62f58114 100644 --- a/src/wx/config/user-input.cpp +++ b/src/wx/config/user-input.cpp @@ -230,12 +230,16 @@ JoyId JoyId::Invalid() { return JoyId(kInvalidSdlIndex); } -wxString JoyId::ToString() const { +wxString JoyId::ToConfigString() const { return wxString::Format("Joy%d", sdl_index_ + 1); } -wxString JoyInput::ToString() const { - const wxString joy_string = joy_.ToString(); +wxString JoyId::ToLocalizedString() const { + return wxString::Format(_("Joystick %d"), sdl_index_ + 1); +} + +wxString JoyInput::ToConfigString() const { + const wxString joy_string = joy_.ToConfigString(); switch (control_) { case JoyControl::AxisPlus: return wxString::Format("%s-Axis%d+", joy_string, control_index_); @@ -258,6 +262,30 @@ wxString JoyInput::ToString() const { return wxEmptyString; } +wxString JoyInput::ToLocalizedString() const { + const wxString joy_string = joy_.ToLocalizedString(); + switch (control_) { + case JoyControl::AxisPlus: + return wxString::Format(_("%s: Axis %d+"), joy_string, control_index_); + case JoyControl::AxisMinus: + return wxString::Format(_("%s: Axis %d-"), joy_string, control_index_); + case JoyControl::Button: + return wxString::Format(_("%s: Button %d"), joy_string, control_index_); + case JoyControl::HatNorth: + return wxString::Format(_("%s: Hat %d North"), joy_string, control_index_); + case JoyControl::HatSouth: + return wxString::Format(_("%s: Hat %d South"), joy_string, control_index_); + case JoyControl::HatWest: + return wxString::Format(_("%s: Hat %d West"), joy_string, control_index_); + case JoyControl::HatEast: + return wxString::Format(_("%s: Hat %d East"), joy_string, control_index_); + } + + // Unreachable. + assert(false); + return wxEmptyString; +} + wxString KeyboardInput::ToConfigString() const { // Handle the modifier case separately. if (KeyIsModifier(key_)) { @@ -291,10 +319,6 @@ wxString KeyboardInput::ToLocalizedString() const { } 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; } @@ -336,7 +360,7 @@ wxString UserInput::ToConfigString() const { case Device::Keyboard: return keyboard_input().ToConfigString(); case Device::Joystick: - return joy_input().ToString(); + return joy_input().ToConfigString(); } // Unreachable. @@ -351,7 +375,7 @@ wxString UserInput::ToLocalizedString() const { case Device::Keyboard: return keyboard_input().ToLocalizedString(); case Device::Joystick: - return joy_input().ToString(); + return joy_input().ToLocalizedString(); } // Unreachable. diff --git a/src/wx/config/user-input.h b/src/wx/config/user-input.h index 5fb50aa9..3a9c1b2e 100644 --- a/src/wx/config/user-input.h +++ b/src/wx/config/user-input.h @@ -70,7 +70,8 @@ public: constexpr explicit JoyId(int sdl_index) : sdl_index_(sdl_index){}; ~JoyId() = default; - wxString ToString() const; + wxString ToConfigString() const; + wxString ToLocalizedString() const; constexpr bool operator==(const JoyId& other) const { return sdl_index_ == other.sdl_index_; } constexpr bool operator!=(const JoyId& other) const { return sdl_index_ != other.sdl_index_; } @@ -100,7 +101,8 @@ public: constexpr JoyControl control() const { return control_; } constexpr uint8_t control_index() const { return control_index_; } - wxString ToString() const; + wxString ToConfigString() const; + wxString ToLocalizedString() const; constexpr bool operator==(const JoyInput& other) const { return joy_ == other.joy_ && control_ == other.control_ && diff --git a/src/wx/panel.cpp b/src/wx/panel.cpp index cd6365a3..71a3ee72 100644 --- a/src/wx/panel.cpp +++ b/src/wx/panel.cpp @@ -165,7 +165,6 @@ GameArea::GameArea() config::OptionID::kSoundBuffers, config::OptionID::kSoundDSoundHWAccel, config::OptionID::kSoundUpmix}, [&](config::Option*) { schedule_audio_restart_ = true; }) { - this->SetClientObject(new widgets::UserInputEventSender(this)); SetSizer(new wxBoxSizer(wxVERTICAL)); // all renderers prefer 32-bit // well, "simple" prefers 24-bit, but that's not available for filters @@ -1461,7 +1460,6 @@ DrawingPanel::DrawingPanel(wxWindow* parent, int _width, int _height) , wxPanel(parent, wxID_ANY, wxPoint(0, 0), parent->GetClientSize(), wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) { - this->SetClientObject(new widgets::UserInputEventSender(this)); } void DrawingPanelBase::DrawingPanelInit() @@ -2193,7 +2191,6 @@ GLDrawingPanel::GLDrawingPanel(wxWindow* parent, int _width, int _height) , wxglc(parent, wxID_ANY, glopts, wxPoint(0, 0), parent->GetClientSize(), wxFULL_REPAINT_ON_RESIZE | wxWANTS_CHARS) { - this->SetClientObject(new widgets::UserInputEventSender(this)); widgets::RequestHighResolutionOpenGlSurfaceForWindow(this); SetContext(); } diff --git a/src/wx/viewsupt.cpp b/src/wx/viewsupt.cpp index b6b608d5..6f767ccf 100644 --- a/src/wx/viewsupt.cpp +++ b/src/wx/viewsupt.cpp @@ -1,38 +1,11 @@ #include "wx/viewsupt.h" #include "wx/config/option-proxy.h" +#include "wx/config/user-input.h" #include "wx/wxvbam.h" namespace Viewers { -namespace { - -int getKeyboardKeyCode(const wxKeyEvent& event) { - const int key_code = event.GetKeyCode(); - if (key_code > WXK_START) { - return key_code; - } - int uc = event.GetUnicodeKey(); - if (uc != WXK_NONE) { - if (uc < 32) { // not all control chars - switch (uc) { - case WXK_BACK: - case WXK_TAB: - case WXK_RETURN: - case WXK_ESCAPE: - return uc; - default: - return WXK_NONE; - } - } - return uc; - } else { - return event.GetKeyCode(); - } -} - -} // namespace - void Viewer::CloseDlg(wxCloseEvent& ev) { (void)ev; // unused params @@ -339,12 +312,11 @@ void MemView::Refit() NULL, this); disp.Connect(wxEVT_LEFT_UP, wxMouseEventHandler(MemView::MouseEvent), NULL, this); - disp.Connect(wxEVT_CHAR, wxKeyEventHandler(MemView::KeyEvent), - NULL, this); disp.SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX)); sb.Create(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL); sb.SetScrollbar(0, 15, 500, 15); + disp.Bind(VBAM_EVT_USER_INPUT, &MemView::KeyEvent, this); } wxClientDC dc(&disp); @@ -443,17 +415,28 @@ void MemView::ShowCaret() disp.SetFocus(); } -void MemView::KeyEvent(wxKeyEvent& ev) +void MemView::KeyEvent(widgets::UserInputEvent& ev) { - uint32_t k = getKeyboardKeyCode(ev); - int nnib = 2 << fmt; + const nonstd::optional opt_user_input = ev.FirstReleasedInput(); + if (opt_user_input == nonstd::nullopt) { + return; + } - switch (k) { + const config::UserInput& user_input = opt_user_input.value(); + if (!user_input.is_keyboard()) { + return; + } + + const wxKeyCode key = user_input.keyboard_input().key(); + const wxKeyModifier mod = user_input.keyboard_input().mod(); + + int nnib = 2 << fmt; + switch (key) { case WXK_RIGHT: case WXK_NUMPAD_RIGHT: if (isasc) selnib += 2; - else if (ev.GetModifiers() == wxMOD_SHIFT) + else if (mod == wxMOD_SHIFT) selnib += 2 << fmt; else if (!(selnib % nnib)) selnib += nnib + nnib - 1; @@ -475,7 +458,7 @@ void MemView::KeyEvent(wxKeyEvent& ev) case WXK_NUMPAD_LEFT: if (isasc) selnib -= 2; - else if (ev.GetModifiers() == wxMOD_SHIFT) + else if (mod == wxMOD_SHIFT) selnib -= 2 << fmt; else if (!(++selnib % nnib)) selnib -= nnib * 2; @@ -506,7 +489,7 @@ void MemView::KeyEvent(wxKeyEvent& ev) break; default: - if (k > 0x7f || (isasc && !isprint(k)) || (!isasc && !isxdigit(k))) { + if (key > 0x7f || (isasc && !isprint(key)) || (!isasc && !isxdigit(key))) { ev.Skip(); return; } @@ -537,10 +520,10 @@ void MemView::KeyEvent(wxKeyEvent& ev) if (isasc) { mask = 0xff << bno * 8; - val = k << bno * 8; + val = key << bno * 8; } else { mask = 8 * (0xf << bno) + 4 * nibno; - val = isdigit(k) ? k - '0' : tolower(k) + 10 - 'a'; + val = isdigit(key) ? key - '0' : tolower(key) + 10 - 'a'; val <<= bno * 8 + nibno * 4; } diff --git a/src/wx/viewsupt.h b/src/wx/viewsupt.h index 67b8f51a..f827dc3f 100644 --- a/src/wx/viewsupt.h +++ b/src/wx/viewsupt.h @@ -1,6 +1,8 @@ #ifndef VBAM_WX_VIEWSUPT_H_ #define VBAM_WX_VIEWSUPT_H_ +#include + #include #include #include @@ -14,7 +16,7 @@ #include #include -#include // for uint32_t +#include "wx/widgets/user-input-event.h" // avoid exporting too much stuff namespace Viewers { @@ -218,7 +220,7 @@ protected: int addrlen; void MouseEvent(wxMouseEvent& ev); - void KeyEvent(wxKeyEvent& ev); + void KeyEvent(widgets::UserInputEvent& ev); // the subwidgets wxPanel disp; wxScrollBar sb; diff --git a/src/wx/widgets/event-handler-provider.h b/src/wx/widgets/event-handler-provider.h new file mode 100644 index 00000000..e27c2f29 --- /dev/null +++ b/src/wx/widgets/event-handler-provider.h @@ -0,0 +1,21 @@ +#ifndef VBAM_WX_WIDGETS_EVENT_HANDLER_PROVIDER_H_ +#define VBAM_WX_WIDGETS_EVENT_HANDLER_PROVIDER_H_ + +// Forward declaration. +class wxEvtHandler; + +namespace widgets { + +// Abstract interface to provide the currently active event handler to use for +// user input events. +class EventHandlerProvider { +public: + virtual ~EventHandlerProvider() = default; + + // Returns the currently active event handler to use for user input events. + virtual wxEvtHandler* event_handler() = 0; +}; + +} // namespace widgets + +#endif // VBAM_WX_WIDGETS_EVENT_HANDLER_PROVIDER_H_ diff --git a/src/wx/widgets/sdl-poller.cpp b/src/wx/widgets/sdl-poller.cpp index c7194094..e2734e5a 100644 --- a/src/wx/widgets/sdl-poller.cpp +++ b/src/wx/widgets/sdl-poller.cpp @@ -300,12 +300,14 @@ void JoyState::Notify() { SetRumble(rumbling_); } -SdlPoller::SdlPoller(const EventHandlerProvider handler_provider) +SdlPoller::SdlPoller(EventHandlerProvider* const handler_provider) : enable_game_controller_(OPTION(kSDLGameControllerMode)), handler_provider_(handler_provider), game_controller_enabled_observer_( config::OptionID::kSDLGameControllerMode, [this](config::Option* option) { ReconnectControllers(option->GetBool()); }) { + assert(handler_provider); + wxTimer::Start(50); SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS); SDL_GameControllerEventState(SDL_ENABLE); @@ -406,7 +408,7 @@ void SdlPoller::Notify() { } if (!event_data.empty()) { - wxEvtHandler* handler = handler_provider_(); + wxEvtHandler* handler = handler_provider_->event_handler(); if (handler) { handler->QueueEvent(new UserInputEvent(std::move(event_data))); } diff --git a/src/wx/widgets/sdl-poller.h b/src/wx/widgets/sdl-poller.h index a375bc09..79bd84ef 100644 --- a/src/wx/widgets/sdl-poller.h +++ b/src/wx/widgets/sdl-poller.h @@ -1,7 +1,6 @@ #ifndef WX_WIDGETS_SDL_POLLER_H_ #define WX_WIDGETS_SDL_POLLER_H_ -#include #include #include @@ -9,6 +8,7 @@ #include #include "wx/config/option-observer.h" +#include "wx/widgets/event-handler-provider.h" // Forward declarations. class wxEvtHandler; @@ -18,16 +18,13 @@ namespace widgets { // Forward declaration. class JoyState; -// Provider for the event handler to send the events to. -using EventHandlerProvider = std::function; - // The SDL worker is responsible for handling SDL events and firing the // appropriate `UserInputEvent` for joysticks. // It is used to fire `UserInputEvent` for joysticks. This class should be kept // as a singleton owned by the application object. class SdlPoller final : public wxTimer { public: - explicit SdlPoller(const EventHandlerProvider handler_provider); + explicit SdlPoller(EventHandlerProvider* const handler_provider); ~SdlPoller() final; // Disable copy and copy assignment. @@ -59,7 +56,7 @@ private: bool enable_game_controller_ = false; // The provider of event handlers to send the events to. - const EventHandlerProvider handler_provider_; + EventHandlerProvider* const handler_provider_; // Observer for the game controller enabled option. const config::OptionsObserver game_controller_enabled_observer_; diff --git a/src/wx/widgets/shortcut-menu-bar.cpp b/src/wx/widgets/shortcut-menu-bar.cpp new file mode 100644 index 00000000..384b6482 --- /dev/null +++ b/src/wx/widgets/shortcut-menu-bar.cpp @@ -0,0 +1,14 @@ +#include "wx/widgets/shortcut-menu-bar.h" + +#include + +namespace widgets { + +void ShortcutMenuBar::SetAcceleratorTable(const wxAcceleratorTable& /*accel*/) { + // Do nothing. We don't want to set up accelerators on the menu bar. + wxMenuBar::SetAcceleratorTable(wxNullAcceleratorTable); +} + +wxIMPLEMENT_DYNAMIC_CLASS(ShortcutMenuBar, wxMenuBar); + +} // namespace widgets diff --git a/src/wx/widgets/shortcut-menu-bar.h b/src/wx/widgets/shortcut-menu-bar.h new file mode 100644 index 00000000..c2e775b6 --- /dev/null +++ b/src/wx/widgets/shortcut-menu-bar.h @@ -0,0 +1,23 @@ +#ifndef VBAM_WIDGETS_SHORTCUT_MENU_BAR_H_ +#define VBAM_WIDGETS_SHORTCUT_MENU_BAR_H_ + +#include + +namespace widgets { + +// A menu bar with no accelerator table. This is used to prevent the menu bar +// from stealing keyboard shortcuts from the main window. +class ShortcutMenuBar : public wxMenuBar { +public: + ShortcutMenuBar() = default; + ~ShortcutMenuBar() override = default; + + // wxMenuBar implementation. + void SetAcceleratorTable(const wxAcceleratorTable& accel) override; + + wxDECLARE_DYNAMIC_CLASS(ShortcutMenuBar); +}; + +} // namespace widgets + +#endif // VBAM_WIDGETS_SHORTCUT_MENU_BAR_H_ diff --git a/src/wx/widgets/user-input-ctrl.cpp b/src/wx/widgets/user-input-ctrl.cpp index 85041b02..cad8f15c 100644 --- a/src/wx/widgets/user-input-ctrl.cpp +++ b/src/wx/widgets/user-input-ctrl.cpp @@ -30,7 +30,6 @@ bool UserInputCtrl::Create(wxWindow* parent, const wxSize& size, long style, const wxString& name) { - this->SetClientObject(new UserInputEventSender(this)); this->Bind(VBAM_EVT_USER_INPUT, &UserInputCtrl::OnUserInput, this); this->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent& event) { last_focus_time_ = wxGetUTCTimeMillis(); diff --git a/src/wx/widgets/user-input-event.cpp b/src/wx/widgets/user-input-event.cpp index 6723efb4..a5a518e2 100644 --- a/src/wx/widgets/user-input-event.cpp +++ b/src/wx/widgets/user-input-event.cpp @@ -151,20 +151,27 @@ wxEvent* UserInputEvent::Clone() const { return new UserInputEvent(this->data_); } -UserInputEventSender::UserInputEventSender(wxWindow* const window) - : window_(window) { - assert(window); - - // Attach the event handlers. - window_->Bind(wxEVT_KEY_DOWN, &UserInputEventSender::OnKeyDown, this); - window_->Bind(wxEVT_KEY_UP, &UserInputEventSender::OnKeyUp, this); - window_->Bind(wxEVT_SET_FOCUS, &UserInputEventSender::Reset, this, window_->GetId()); - window_->Bind(wxEVT_KILL_FOCUS, &UserInputEventSender::Reset, this, window_->GetId()); +KeyboardInputSender::KeyboardInputSender(EventHandlerProvider* const handler_provider) + : handler_provider_(handler_provider) { + assert(handler_provider_); } -UserInputEventSender::~UserInputEventSender() = default; +KeyboardInputSender::~KeyboardInputSender() = default; -void UserInputEventSender::OnKeyDown(wxKeyEvent& event) { +void KeyboardInputSender::ProcessKeyEvent(wxKeyEvent& event) { + if (!handler_provider_->event_handler()) { + // No event handler to send the event to. + return; + } + + if (event.GetEventType() == wxEVT_KEY_DOWN) { + OnKeyDown(event); + } else if (event.GetEventType() == wxEVT_KEY_UP) { + OnKeyUp(event); + } +} + +void KeyboardInputSender::OnKeyDown(wxKeyEvent& event) { // Stop propagation of the event. event.Skip(false); @@ -214,10 +221,10 @@ void UserInputEventSender::OnKeyDown(wxKeyEvent& event) { } } - wxQueueEvent(window_, new UserInputEvent(std::move(event_data))); + wxQueueEvent(handler_provider_->event_handler(), new UserInputEvent(std::move(event_data))); } -void UserInputEventSender::OnKeyUp(wxKeyEvent& event) { +void KeyboardInputSender::OnKeyUp(wxKeyEvent& event) { // Stop propagation of the event. event.Skip(false); @@ -297,17 +304,8 @@ void UserInputEventSender::OnKeyUp(wxKeyEvent& event) { for (const auto& data : event_data) { active_mod_inputs_.erase(data.input.keyboard_input()); } - wxQueueEvent(window_, new UserInputEvent(std::move(event_data))); -} -void UserInputEventSender::Reset(wxFocusEvent& event) { - // Reset the internal state. - active_keys_.clear(); - active_mods_.clear(); - active_mod_inputs_.clear(); - - // Let the event propagate. - event.Skip(); + wxQueueEvent(handler_provider_->event_handler(), new UserInputEvent(std::move(event_data))); } } // namespace widgets diff --git a/src/wx/widgets/user-input-event.h b/src/wx/widgets/user-input-event.h index 354011c6..a232de5c 100644 --- a/src/wx/widgets/user-input-event.h +++ b/src/wx/widgets/user-input-event.h @@ -10,6 +10,7 @@ #include #include "wx/config/user-input.h" +#include "wx/widgets/event-handler-provider.h" namespace widgets { @@ -53,34 +54,32 @@ private: }; // Object that is used to fire user input events when a key is pressed or -// released. To use this object, attach it to a wxWindow and listen for -// the VBAM_EVT_USER_INPUT_DOWN and VBAM_EVT_USER_INPUT_UP events. -class UserInputEventSender final : public wxClientData { +// released. This class should be kept as a singleton owned by the application +// object. It is meant to be used in the FilterEvent() method of the app. +class KeyboardInputSender final : public wxClientData { public: - // `window` must not be nullptr. Will assert otherwise. - // `window` must outlive this object. - explicit UserInputEventSender(wxWindow* const window); - ~UserInputEventSender() override; + explicit KeyboardInputSender(EventHandlerProvider* const handler_provider); + ~KeyboardInputSender() override; // Disable copy and copy assignment. - UserInputEventSender(const UserInputEventSender&) = delete; - UserInputEventSender& operator=(const UserInputEventSender&) = delete; + KeyboardInputSender(const KeyboardInputSender&) = delete; + KeyboardInputSender& operator=(const KeyboardInputSender&) = delete; + + // Processes the provided key event and sends the appropriate user input + // event to the current event handler. + void ProcessKeyEvent(wxKeyEvent& event); private: // Keyboard event handlers. void OnKeyDown(wxKeyEvent& event); void OnKeyUp(wxKeyEvent& event); - // Resets the internal state. Called on focus in/out. - void Reset(wxFocusEvent& event); - std::unordered_set active_keys_; std::unordered_set active_mods_; std::unordered_set active_mod_inputs_; - // The wxWindow this object is attached to. - // Must outlive this object. - wxWindow* const window_; + // The provider of event handlers to send the events to. + EventHandlerProvider* const handler_provider_; }; } // namespace widgets diff --git a/src/wx/wxvbam.cpp b/src/wx/wxvbam.cpp index a165d453..a2682bfe 100644 --- a/src/wx/wxvbam.cpp +++ b/src/wx/wxvbam.cpp @@ -64,11 +64,6 @@ void ResetMenuItemAccelerator(wxMenuItem* menu_item) { wxGetApp().bindings()->InputsForCommand( config::ShortcutCommand(menu_item->GetId())); for (const config::UserInput& user_input : user_inputs) { - if (user_input.device() != config::UserInput::Device::Keyboard) { - // Cannot use joystick keybinding as text without wx assertion error. - continue; - } - new_label.append('\t'); new_label.append(user_input.ToLocalizedString()); break; @@ -252,7 +247,8 @@ wxvbamApp::wxvbamApp() frame(nullptr), using_wayland(false), emulated_gamepad_(std::bind(&wxvbamApp::bindings, this)), - sdl_poller_(std::bind(&wxvbamApp::GetJoyEventHandler, this)) {} + sdl_poller_(this), + keyboard_input_sender_(this) {} const wxString wxvbamApp::GetPluginsDir() { @@ -826,7 +822,7 @@ wxvbamApp::~wxvbamApp() { #endif } -wxEvtHandler* wxvbamApp::GetJoyEventHandler() { +wxEvtHandler* wxvbamApp::event_handler() { // Use the active window, if any. wxWindow* focused_window = wxWindow::FindFocus(); if (focused_window) { @@ -842,7 +838,7 @@ wxEvtHandler* wxvbamApp::GetJoyEventHandler() { return nullptr; } - if (OPTION(kUIAllowJoystickBackgroundInput)) { + if (OPTION(kUIAllowJoystickBackgroundInput) || OPTION(kUIAllowKeyboardBackgroundInput)) { // Use the game panel, if the background polling option is enabled. return panel->panel->GetWindow()->GetEventHandler(); } @@ -990,53 +986,6 @@ void MainFrame::OnSize(wxSizeEvent& event) OPTION(kGeomFullScreen) = IsFullScreen(); } -int MainFrame::FilterEvent(wxEvent& event) { - if (menus_opened || dialog_opened) { - return wxEventFilter::Event_Skip; - } - - if (event.GetEventType() != VBAM_EVT_USER_INPUT) { - // We only treat "VBAM_EVT_USER_INPUT" events here. - return wxEventFilter::Event_Skip; - } - - widgets::UserInputEvent& user_input_event = static_cast(event); - const config::Bindings* bindings = wxGetApp().bindings(); - int command_id = wxID_NONE; - nonstd::optional user_input; - - for (const auto& event_data : user_input_event.data()) { - if (!event_data.pressed) { - // We only treat key press events here. - continue; - } - - const nonstd::optional command = - bindings->CommandForInput(event_data.input); - if (command != nonstd::nullopt && command->is_shortcut()) { - // Associated shortcut command found. - command_id = command->shortcut().id(); - user_input.emplace(event_data.input); - break; - } - } - - if (command_id == wxID_NONE) { - // No associated command found. - return wxEventFilter::Event_Skip; - } - - // Execute the associated shortcut command. - wxCommandEvent command_event(wxEVT_COMMAND_MENU_SELECTED, command_id); - command_event.SetEventObject(this); - this->GetEventHandler()->ProcessEvent(command_event); - - // Filter out the processed input so it is not processed again. This also - // prevents us from firing 2 commands if the user presses "Ctrl+1" and has - // a shortcut for both "Ctrl+1" and "1". Only one will be handled here. - return user_input_event.FilterProcessedInput(user_input.value()); -} - wxString MainFrame::GetGamePath(wxString path) { wxString game_path = path; @@ -1376,15 +1325,57 @@ void MainFrame::IdentifyRom() } } -// global event filter -// apparently required for win32; just setting accel table still misses -// a few keys (e.g. only ctrl-x works for exit, but not esc & ctrl-q; -// ctrl-w does not work for close). It's possible another entity is -// grabbing those keys, but I can't track it down. int wxvbamApp::FilterEvent(wxEvent& event) { - if (frame) { - return frame->FilterEvent(event); + if (!frame) { + // Ignore early events. + return wxEventFilter::Event_Skip; } - return wxApp::FilterEvent(event); + + if (event.GetEventType() == wxEVT_KEY_DOWN || event.GetEventType() == wxEVT_KEY_UP) { + // Handle keyboard input events here. No control will receive them. + keyboard_input_sender_.ProcessKeyEvent(static_cast(event)); + return wxEventFilter::Event_Processed; + } + + if (!frame->CanProcessShortcuts()) { + return wxEventFilter::Event_Skip; + } + + if (event.GetEventType() != VBAM_EVT_USER_INPUT) { + // We only treat "VBAM_EVT_USER_INPUT" events here. + return wxEventFilter::Event_Skip; + } + + widgets::UserInputEvent& user_input_event = static_cast(event); + int command_id = wxID_NONE; + nonstd::optional user_input; + + for (const auto& event_data : user_input_event.data()) { + if (!event_data.pressed) { + // We only treat key press events here. + continue; + } + + const nonstd::optional command = + bindings_.CommandForInput(event_data.input); + if (command != nonstd::nullopt && command->is_shortcut()) { + // Associated shortcut command found. + command_id = command->shortcut().id(); + user_input.emplace(event_data.input); + break; + } + } + + if (command_id == wxID_NONE) { + // No associated command found. + return wxEventFilter::Event_Skip; + } + + // Queue the associated shortcut command. + wxCommandEvent* command_event = new wxCommandEvent(wxEVT_COMMAND_MENU_SELECTED, command_id); + command_event->SetEventObject(this); + frame->GetEventHandler()->QueueEvent(command_event); + + return user_input_event.FilterProcessedInput(user_input.value()); } diff --git a/src/wx/wxvbam.h b/src/wx/wxvbam.h index 1d5fd800..48661b82 100644 --- a/src/wx/wxvbam.h +++ b/src/wx/wxvbam.h @@ -18,6 +18,7 @@ #include "wx/config/option.h" #include "wx/dialogs/base-dialog.h" #include "wx/widgets/dpi-support.h" +#include "wx/widgets/event-handler-provider.h" #include "wx/widgets/keep-on-top-styler.h" #include "wx/widgets/sdl-poller.h" #include "wx/widgets/user-input-event.h" @@ -59,7 +60,7 @@ inline std::string ToString(const wxChar* aString) class MainFrame; -class wxvbamApp final : public wxApp { +class wxvbamApp final : public wxApp, public widgets::EventHandlerProvider { public: wxvbamApp(); @@ -133,8 +134,8 @@ protected: int console_status = 0; private: - // Returns the currently active event handler to use for user input events. - wxEvtHandler* GetJoyEventHandler(); + // EventHandlerProvider implementation. + wxEvtHandler* event_handler() override; config::Bindings bindings_; config::EmulatedGamepad emulated_gamepad_; @@ -143,6 +144,7 @@ private: char* home = nullptr; widgets::SdlPoller sdl_poller_; + widgets::KeyboardInputSender keyboard_input_sender_; // Main configuration file. wxFileName config_file_; @@ -200,8 +202,6 @@ public: void GetMenuOptionBool(const wxString& menuName, bool* field); void SetMenuOption(const wxString& menuName, bool value); - int FilterEvent(wxEvent& event); - GameArea* GetPanel() { return panel; @@ -209,6 +209,8 @@ public: wxString GetGamePath(wxString path); + bool CanProcessShortcuts() const { return !menus_opened && !dialog_opened; } + // wxMSW pauses the game for menu popups and modal dialogs, but wxGTK // does not. It's probably desirable to pause the game. To do this for // dialogs, use this function instead of dlg->ShowModal() diff --git a/src/wx/xrc/MainMenu.xrc b/src/wx/xrc/MainMenu.xrc index 1b86acc6..994a8c45 100644 --- a/src/wx/xrc/MainMenu.xrc +++ b/src/wx/xrc/MainMenu.xrc @@ -1,6 +1,6 @@ - +