Propagate key events (#1323)

Previously, `wxEVT_KEY_DOWN` and `wxEVT_KEY_UP` were transformed into
`VBAM_EVT_USER_INPUT` and marked as processed early. This broke a number
of controls that rely on the traditional widgets events.

To fix this, we now filter the `wxEVT_KEY_*` and `wxEVT_CHAR` events in
the only widget we use that cares about them, `UserInputCtrl` instead of
filtering them at the application level.
This commit is contained in:
Fabrice de Gans 2024-07-28 15:19:19 -07:00 committed by GitHub
parent cf5cb40cb9
commit e4ef4aa625
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 103 additions and 17 deletions

View File

@ -389,11 +389,6 @@ endif()
if(APPLE)
target_sources(visualboyadvance-m PRIVATE
macsupport.mm
widgets/dpi-support-mac.mm
)
else()
target_sources(visualboyadvance-m PRIVATE
widgets/dpi-support.cpp
)
endif()

View File

@ -188,7 +188,7 @@ public:
bool operator++(int) { return *this += 1; }
bool operator--(int) { return *this -= 1; }
bool operator+=(T value) {
const int new_value = Get() + value;
const T new_value = Get() + value;
if (new_value > Max()) {
return Set(Max());
} else {
@ -196,7 +196,7 @@ public:
}
}
bool operator-=(T value) {
const int new_value = Get() - value;
const T new_value = Get() - value;
if (new_value < Min()) {
return Set(Min());
} else {

View File

@ -8,6 +8,7 @@ target_sources(vbam-wx-widgets
PRIVATE
# from external source with minor modifications
checkedlistctrl.cpp
$<IF:$<BOOL:${APPLE}>,dpi-support-mac.mm,dpi-support.cpp>
group-check-box.cpp
keep-on-top-styler.cpp
option-validator.cpp
@ -46,6 +47,7 @@ if(BUILD_TESTING)
group-check-box-test.cpp
keep-on-top-styler-test.cpp
option-validator-test.cpp
user-input-ctrl-test.cpp
)
target_link_libraries(vbam-wx-widgets-tests

View File

@ -14,7 +14,7 @@ WidgetsTest::~WidgetsTest() = default;
void WidgetsTest::SetUp() {
// Give the wxFrame a unique name and initialize it.
const char* test_name(testing::UnitTest::GetInstance()->current_test_info()->name());
frame_ = std::make_unique<wxFrame>(nullptr, wxXmlResource ::DoGetXRCID(test_name), test_name);
frame_ = std::make_unique<wxFrame>(nullptr, wxXmlResource::DoGetXRCID(test_name), test_name);
}
void WidgetsTest::TearDown() {

View File

@ -0,0 +1,78 @@
#include "wx/widgets/user-input-ctrl.h"
#include <unordered_set>
#include "wx/config/user-input.h"
#include "wx/widgets/test/widgets-test.h"
namespace widgets {
TEST_F(WidgetsTest, UserInputCtrlTest) {
// Add a UserInputCtrl to the frame, `frame()` takes ownership here.
UserInputCtrl* user_input_ctrl = new UserInputCtrl(frame(), XRCID("UserInputCtrl"));
// Check the UserInputCtrl is empty.
EXPECT_TRUE(user_input_ctrl->IsEmpty());
// Send a EVT_CHAR event to the UserInputCtrl.
wxKeyEvent key_event(wxEVT_CHAR);
key_event.m_keyCode = 'a';
user_input_ctrl->GetEventHandler()->ProcessEvent(key_event);
// Check the UserInputCtrl is empty.
EXPECT_TRUE(user_input_ctrl->IsEmpty());
// Send a EVT_KEY_DOWN event to the UserInputCtrl.
wxKeyEvent key_down_event(wxEVT_KEY_DOWN);
key_down_event.m_keyCode = 'a';
user_input_ctrl->GetEventHandler()->ProcessEvent(key_down_event);
// Check the UserInputCtrl is empty.
EXPECT_TRUE(user_input_ctrl->IsEmpty());
// Send a EVT_USER_INPUT event to the UserInputCtrl.
UserInputEvent user_input_event1({UserInputEvent::Data(config::KeyboardInput('A'), false)});
user_input_ctrl->GetEventHandler()->ProcessEvent(user_input_event1);
// Check the UserInputCtrl is not empty.
EXPECT_FALSE(user_input_ctrl->IsEmpty());
EXPECT_EQ(user_input_ctrl->SingleInput(), config::KeyboardInput('A'));
// Send another EVT_USER_INPUT event to the UserInputCtrl.
UserInputEvent user_input_event2({UserInputEvent::Data(config::KeyboardInput('B'), false)});
user_input_ctrl->GetEventHandler()->ProcessEvent(user_input_event2);
// Check the UserInputCtrl is not empty and contains a single input.
EXPECT_FALSE(user_input_ctrl->IsEmpty());
EXPECT_EQ(user_input_ctrl->SingleInput(), config::KeyboardInput('B'));
EXPECT_EQ(user_input_ctrl->inputs().size(), 1);
}
TEST_F(WidgetsTest, UserInputCtrlMultiKeyTest) {
// Add a UserInputCtrl to the frame, `frame()` takes ownership here.
UserInputCtrl* user_input_ctrl = new UserInputCtrl(frame(), XRCID("UserInputCtrl"));
user_input_ctrl->SetMultiKey(true);
// Check the UserInputCtrl is empty.
EXPECT_TRUE(user_input_ctrl->IsEmpty());
// Send a EVT_USER_INPUT event to the UserInputCtrl.
UserInputEvent user_input_event1({UserInputEvent::Data(config::KeyboardInput('A'), false)});
user_input_ctrl->GetEventHandler()->ProcessEvent(user_input_event1);
// Check the UserInputCtrl is not empty.
EXPECT_FALSE(user_input_ctrl->IsEmpty());
EXPECT_EQ(user_input_ctrl->inputs(),
std::unordered_set<config::UserInput>({config::KeyboardInput('A')}));
// Send another EVT_USER_INPUT event to the UserInputCtrl.
UserInputEvent user_input_event2({UserInputEvent::Data(config::KeyboardInput('B'), false)});
user_input_ctrl->GetEventHandler()->ProcessEvent(user_input_event2);
// Check the UserInputCtrl is not empty and contains two inputs.
EXPECT_FALSE(user_input_ctrl->IsEmpty());
EXPECT_EQ(user_input_ctrl->inputs(),
std::unordered_set<config::UserInput>(
{config::KeyboardInput('A'), config::KeyboardInput('B')}));
}
} // namespace widgets

View File

@ -8,25 +8,33 @@
namespace widgets {
namespace {
// Helper callback to disable an event. This is bound dynmically to prevent
// the event from being processed by the base class.
void DisableEvent(wxEvent& event) {
event.Skip(false);
}
} // 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);
Create(parent, id, 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,
@ -36,7 +44,12 @@ bool UserInputCtrl::Create(wxWindow* parent,
last_focus_time_ = wxGetUTCTimeMillis();
event.Skip();
});
return wxTextCtrl::Create(parent, id, value, pos, size, style, wxValidator(), name);
// Diable key events.
this->Bind(wxEVT_CHAR, &DisableEvent);
this->Bind(wxEVT_KEY_DOWN, &DisableEvent);
return wxTextCtrl::Create(parent, id, wxEmptyString, pos, size, style, wxValidator(), name);
}
void UserInputCtrl::SetMultiKey(bool multikey) {
@ -115,7 +128,7 @@ UserInputCtrlXmlHandler::UserInputCtrlXmlHandler() : wxXmlResourceHandler() {
wxObject* UserInputCtrlXmlHandler::DoCreateResource() {
XRC_MAKE_INSTANCE(control, UserInputCtrl)
control->Create(m_parentAsWindow, GetID(), GetText("value"), GetPosition(), GetSize(),
control->Create(m_parentAsWindow, GetID(), GetPosition(), GetSize(),
GetStyle() | wxTE_PROCESS_ENTER | wxTE_PROCESS_TAB, GetName());
SetupWindow(control);

View File

@ -25,7 +25,6 @@ public:
UserInputCtrl();
UserInputCtrl(wxWindow* parent,
wxWindowID id,
const wxString& value = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0,
@ -34,7 +33,6 @@ public:
bool Create(wxWindow* parent,
wxWindowID id,
const wxString& value = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0,

View File

@ -1333,9 +1333,9 @@ int wxvbamApp::FilterEvent(wxEvent& event)
}
if (event.GetEventType() == wxEVT_KEY_DOWN || event.GetEventType() == wxEVT_KEY_UP) {
// Handle keyboard input events here. No control will receive them.
// Handle keyboard input events here to generate user input events.
keyboard_input_sender_.ProcessKeyEvent(static_cast<wxKeyEvent&>(event));
return wxEventFilter::Event_Processed;
return wxEventFilter::Event_Skip;
}
if (!frame->CanProcessShortcuts()) {