Merge remote-tracking branch 'sunset/process-enter'

Fix wxTE_PROCESS_ENTER logic for wxMSW and wxGTK too.

See https://github.com/wxWidgets/wxWidgets/pull/1415

Closes #18273.
This commit is contained in:
Vadim Zeitlin
2019-07-26 18:02:08 +02:00
14 changed files with 260 additions and 27 deletions

View File

@@ -114,6 +114,10 @@ protected:
// currently.
virtual void EnableTextChangedEvents(bool enable) wxOVERRIDE;
// Helper for wxTE_PROCESS_ENTER handling: activates the default button in
// the dialog containing this control if any.
bool ClickDefaultButtonIfPossible();
private:
// implement this to return the associated GtkEntry or another widget
// implementing GtkEditable

View File

@@ -81,6 +81,10 @@ protected:
virtual bool DoAutoCompleteCustom(wxTextCompleter *completer) wxOVERRIDE;
#endif // wxUSE_OLE
// Helper for wxTE_PROCESS_ENTER handling: activates the default button in
// the dialog containing this control if any.
bool ClickDefaultButtonIfPossible();
private:
// implement this to return the HWND of the EDIT control
virtual WXHWND GetEditHWND() const = 0;

View File

@@ -41,9 +41,13 @@
Sorts the entries in the list alphabetically. Notice that this style
is not currently implemented in wxOSX.
@style{wxTE_PROCESS_ENTER}
The control will generate the event @c wxEVT_TEXT_ENTER
(otherwise pressing Enter key is either processed internally by the
control or used for navigation between dialog controls).
The control will generate the event @c wxEVT_TEXT_ENTER that can be
handled by the program. Otherwise, i.e. either if this style not
specified at all, or it is used, but there is no event handler for
this event or the event handler called wxEvent::Skip() to avoid
overriding the default handling, pressing Enter key is either
processed internally by the control or used to activate the default
button of the dialog, if any.
@endStyleTable
@beginEventEmissionTable{wxCommandEvent}

View File

@@ -992,9 +992,13 @@ public:
@beginStyleTable
@style{wxTE_PROCESS_ENTER}
The control will generate the event @c wxEVT_TEXT_ENTER
(otherwise pressing Enter key is either processed internally by the
control or used to activate the default button of the dialog, if any).
The control will generate the event @c wxEVT_TEXT_ENTER that can be
handled by the program. Otherwise, i.e. either if this style not
specified at all, or it is used, but there is no event handler for
this event or the event handler called wxEvent::Skip() to avoid
overriding the default handling, pressing Enter key is either
processed internally by the control or used to activate the default
button of the dialog, if any.
@style{wxTE_PROCESS_TAB}
Normally, TAB key is used for keyboard navigation and pressing it in
a control switches focus to the next one. With this style, this

View File

@@ -231,6 +231,12 @@ void wxComboBox::OnChar( wxKeyEvent &event )
// down list upon RETURN.
return;
}
// We disable built-in default button activation when
// wxTE_PROCESS_ENTER is used, but we still should activate it
// if the event wasn't handled, so do it from here.
if ( ClickDefaultButtonIfPossible() )
return;
}
break;
}

View File

@@ -1728,6 +1728,12 @@ void wxTextCtrl::OnChar( wxKeyEvent &key_event )
event.SetString(GetValue());
if ( HandleWindowEvent(event) )
return;
// We disable built-in default button activation when
// wxTE_PROCESS_ENTER is used, but we still should activate it
// if the event wasn't handled, so do it from here.
if ( ClickDefaultButtonIfPossible() )
return;
}
}

View File

@@ -1139,4 +1139,35 @@ wxString wxTextEntry::GetHint() const
}
#endif // __WXGTK3__
bool wxTextEntry::ClickDefaultButtonIfPossible()
{
GtkWidget* const widget = GTK_WIDGET(GetEntry());
// This does the same thing as gtk_entry_real_activate() in GTK itself.
//
// Note: in GTK 4 we should probably just use gtk_widget_activate_default().
GtkWidget* const toplevel = gtk_widget_get_toplevel(widget);
if ( GTK_IS_WINDOW (toplevel) )
{
GtkWindow* const window = GTK_WINDOW(toplevel);
if ( window )
{
GtkWidget* const default_widget = gtk_window_get_default_widget(window);
GtkWidget* const focus_widget = gtk_window_get_focus(window);
if ( widget != default_widget &&
!(widget == focus_widget &&
(!default_widget ||
!gtk_widget_get_sensitive(default_widget))) )
{
if ( gtk_window_activate_default(window) )
return true;
}
}
}
return false;
}
#endif // wxUSE_TEXTCTRL || wxUSE_COMBOBOX

View File

@@ -243,6 +243,9 @@ bool wxComboBox::MSWProcessEditSpecialKey(WXWPARAM vkey)
// beep if it gets it
return true;
}
if ( ClickDefaultButtonIfPossible() )
return true;
}
break;

View File

@@ -2199,6 +2199,9 @@ wxTextCtrl::MSWHandleMessage(WXLRESULT *rc,
{
bool processed = wxTextCtrlBase::MSWHandleMessage(rc, nMsg, wParam, lParam);
switch ( nMsg )
{
case WM_CHAR:
// Handle the special case of "Enter" key: the user code needs to specify
// wxTE_PROCESS_ENTER style to get it in the first place, but if this flag
// is used, then even if the wxEVT_TEXT_ENTER handler skips the event, the
@@ -2209,19 +2212,13 @@ wxTextCtrl::MSWHandleMessage(WXLRESULT *rc,
// Fix these problems by explicitly performing the default function of this
// key (which would be done by MSWProcessMessage() if we didn't have
// wxTE_PROCESS_ENTER) and preventing the default WndProc from getting it.
if ( nMsg == WM_CHAR &&
!processed &&
HasFlag(wxTE_PROCESS_ENTER) &&
wParam == VK_RETURN &&
!wxIsAnyModifierDown() )
if ( !processed && wParam == VK_RETURN )
{
MSWClickButtonIfPossible(MSWGetDefaultButtonFor(this));
if ( ClickDefaultButtonIfPossible() )
processed = true;
}
break;
switch ( nMsg )
{
case WM_GETDLGCODE:
{
// Ensure that the result value is initialized even if the base

View File

@@ -1036,4 +1036,15 @@ wxPoint wxTextEntry::DoGetMargins() const
return wxPoint(left, top);
}
// ----------------------------------------------------------------------------
// input handling
// ----------------------------------------------------------------------------
bool wxTextEntry::ClickDefaultButtonIfPossible()
{
return !wxIsAnyModifierDown() &&
wxWindow::MSWClickButtonIfPossible(
wxWindow::MSWGetDefaultButtonFor(GetEditableWindow()));
}
#endif // wxUSE_TEXTCTRL || wxUSE_COMBOBOX

View File

@@ -231,4 +231,22 @@ void ComboBoxTestCase::IsEmpty()
#endif
}
TEST_CASE("wxComboBox::ProcessEnter", "[wxComboBox][enter]")
{
struct ComboBoxCreator
{
static wxControl* Do(wxWindow* parent, int style)
{
const wxString choices[] = { "foo", "bar", "baz" };
return new wxComboBox(parent, wxID_ANY, wxString(),
wxDefaultPosition, wxDefaultSize,
WXSIZEOF(choices), choices,
style);
}
};
TestProcessEnter(&ComboBoxCreator::Do);
}
#endif //wxUSE_COMBOBOX

View File

@@ -1269,4 +1269,19 @@ void TextCtrlTestCase::XYToPositionSingleLine()
}
}
TEST_CASE("wxTextCtrl::ProcessEnter", "[wxTextCtrl][enter]")
{
struct TextCtrlCreator
{
static wxControl* Do(wxWindow* parent, int style)
{
return new wxTextCtrl(parent, wxID_ANY, wxString(),
wxDefaultPosition, wxDefaultSize,
style);
}
};
TestProcessEnter(&TextCtrlCreator::Do);
}
#endif //wxUSE_TEXTCTRL

View File

@@ -10,8 +10,12 @@
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/dialog.h"
#include "wx/event.h"
#include "wx/sizer.h"
#include "wx/textctrl.h"
#include "wx/textentry.h"
#include "wx/timer.h"
#include "wx/window.h"
#endif // WX_PRECOMP
@@ -364,3 +368,122 @@ void TextEntryTestCase::UndoRedo()
}
}
}
#if wxUSE_UIACTIONSIMULATOR
namespace
{
enum ProcessEnter
{
ProcessEnter_No,
ProcessEnter_ButSkip,
ProcessEnter_WithoutSkipping
};
class TestDialog : public wxDialog
{
public:
explicit TestDialog(TextLikeControlCreator controlCreator,
ProcessEnter processEnter)
: wxDialog(wxTheApp->GetTopWindow(), wxID_ANY, "Test dialog"),
m_control((*controlCreator)(this,
processEnter == ProcessEnter_No
? 0
: wxTE_PROCESS_ENTER)),
m_processEnter(processEnter),
m_gotEnter(false)
{
// We can't always bind this handler because wx will helpfully
// complain if we bind it without using wxTE_PROCESS_ENTER.
if ( processEnter != ProcessEnter_No )
m_control->Bind(wxEVT_TEXT_ENTER, &TestDialog::OnTextEnter, this);
wxSizer* const sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(m_control, wxSizerFlags().Expand());
sizer->Add(CreateStdDialogButtonSizer(wxOK));
SetSizerAndFit(sizer);
CallAfter(&TestDialog::SimulateEnter);
m_timer.Bind(wxEVT_TIMER, &TestDialog::OnTimeOut, this);
m_timer.StartOnce(2000);
}
bool GotEnter() const { return m_gotEnter; }
private:
void OnTextEnter(wxCommandEvent& e)
{
m_gotEnter = true;
switch ( m_processEnter )
{
case ProcessEnter_No:
FAIL("Shouldn't be getting wxEVT_TEXT_ENTER at all");
break;
case ProcessEnter_ButSkip:
e.Skip();
break;
case ProcessEnter_WithoutSkipping:
// Close the dialog with a different exit code than what
// pressing the OK button would have generated.
EndModal(wxID_APPLY);
break;
}
}
void OnTimeOut(wxTimerEvent&)
{
EndModal(wxID_CANCEL);
}
void SimulateEnter()
{
wxUIActionSimulator sim;
m_control->SetFocus();
sim.Char(WXK_RETURN);
}
wxControl* const m_control;
const ProcessEnter m_processEnter;
wxTimer m_timer;
bool m_gotEnter;
};
} // anonymous namespace
void TestProcessEnter(TextLikeControlCreator controlCreator)
{
SECTION("Without wxTE_PROCESS_ENTER")
{
TestDialog dlg(controlCreator, ProcessEnter_No);
REQUIRE( dlg.ShowModal() == wxID_OK );
CHECK( !dlg.GotEnter() );
}
SECTION("With wxTE_PROCESS_ENTER but skipping")
{
TestDialog dlgProcessEnter(controlCreator, ProcessEnter_ButSkip);
REQUIRE( dlgProcessEnter.ShowModal() == wxID_OK );
CHECK( dlgProcessEnter.GotEnter() );
}
SECTION("With wxTE_PROCESS_ENTER without skipping")
{
TestDialog dlgProcessEnter(controlCreator, ProcessEnter_WithoutSkipping);
REQUIRE( dlgProcessEnter.ShowModal() == wxID_APPLY );
CHECK( dlgProcessEnter.GotEnter() );
}
}
#else // !wxUSE_UIACTIONSIMULATOR
void TestProcessEnter(TextLikeControlCreator WXUNUSED(controlCreator))
{
WARN("Skipping wxTE_PROCESS_ENTER tests: wxUIActionSimulator not available");
}
#endif // wxUSE_UIACTIONSIMULATOR/!wxUSE_UIACTIONSIMULATOR

View File

@@ -75,4 +75,11 @@ private:
wxDECLARE_NO_COPY_CLASS(TextEntryTestCase);
};
// Function to call to test that wxTE_PROCESS_ENTER is handled correctly for
// the controls of the type created by the given creator function when they're
// placed in a dialog with a default button.
typedef wxControl* (*TextLikeControlCreator)(wxWindow* parent, int style);
void TestProcessEnter(TextLikeControlCreator controlCreator);
#endif // _WX_TESTS_CONTROLS_TEXTENTRYTEST_H_