Coalesce wxEVT_TEXT events in wxGTK wxTextCtrl and wxComboBox

For consistency with the other platforms, coalesce multiple wxEVT_TEXT
events resulting from a single user action into a single one in wxGTK
too. For example, when pressing a key in a control with some text
selected, wxGTK previously generated 2 wxEVT_TEXT events: one
corresponding to the removal of the selection and another one to the
addition of the new text. Now only a single event with the new text is
generated, as in the other ports.

Doing this requires delaying sending wxEVT_TEXT until GTK itself ends
handling the key press, however we delay it as little as possible, so
hopefully this shouldn't have any visible effects at wx API level.

Closes #10050.
This commit is contained in:
Vadim Zeitlin
2019-07-10 02:14:21 +02:00
parent 958df5fb74
commit 2c6dcc2e51
3 changed files with 141 additions and 2 deletions

View File

@@ -37,6 +37,65 @@
#include "wx/gtk/private/object.h"
#include "wx/gtk/private/string.h"
// ----------------------------------------------------------------------------
// wxTextCoalesceData
// ----------------------------------------------------------------------------
class wxTextCoalesceData
{
public:
wxTextCoalesceData(GtkWidget* widget, gulong handlerAfterKeyPress)
: m_handlerAfterKeyPress(handlerAfterKeyPress)
{
m_inKeyPress = false;
m_pendingTextChanged = false;
// This signal handler is unblocked in StartHandlingKeyPress(), so
// we need to block it initially to compensate for this.
g_signal_handler_block(widget, m_handlerAfterKeyPress);
}
void StartHandlingKeyPress(GtkWidget* widget)
{
m_inKeyPress = true;
m_pendingTextChanged = false;
g_signal_handler_unblock(widget, m_handlerAfterKeyPress);
}
bool SetPendingIfInKeyPress()
{
if ( !m_inKeyPress )
return false;
m_pendingTextChanged = true;
return true;
}
bool EndHandlingKeyPressAndCheckIfPending(GtkWidget* widget)
{
g_signal_handler_block(widget, m_handlerAfterKeyPress);
wxASSERT( m_inKeyPress );
m_inKeyPress = false;
if ( !m_pendingTextChanged )
return false;
m_pendingTextChanged = false;
return true;
}
private:
bool m_inKeyPress;
bool m_pendingTextChanged;
const gulong m_handlerAfterKeyPress;
wxDECLARE_NO_COPY_CLASS(wxTextCoalesceData);
};
//-----------------------------------------------------------------------------
// helper function to get the length of the text
//-----------------------------------------------------------------------------
@@ -59,10 +118,39 @@ static int GetEntryTextLength(GtkEntry* entry)
extern "C" {
// "event-after" handler is only connected when we get a "key-press-event", so
// it's effectively called after the end of processing of this event and used
// to send a single wxEVT_TEXT even if we received several (typically two, when
// the selected text in the control is replaced by new text) "changed" signals.
static gboolean
wx_gtk_text_after_key_press(GtkWidget* widget,
GdkEventKey* WXUNUSED(gdk_event),
wxTextEntry* entry)
{
wxTextCoalesceData* const data = entry->GTKGetCoalesceData();
wxCHECK_MSG( data, FALSE, "must be non-null if this handler is called" );
if ( data->EndHandlingKeyPressAndCheckIfPending(widget) )
{
entry->GTKOnTextChanged();
}
return FALSE;
}
// "changed" handler for GtkEntry
static void
wx_gtk_text_changed_callback(GtkWidget* WXUNUSED(widget), wxTextEntry* entry)
{
if ( wxTextCoalesceData* const data = entry->GTKGetCoalesceData() )
{
if ( data->SetPendingIfInKeyPress() )
{
// Don't send the event right now as more might be coming.
return;
}
}
entry->GTKOnTextChanged();
}
@@ -518,11 +606,13 @@ wx_gtk_entry_parent_grab_notify (GtkWidget *widget,
wxTextEntry::wxTextEntry()
{
m_autoCompleteData = NULL;
m_coalesceData = NULL;
m_isUpperCase = false;
}
wxTextEntry::~wxTextEntry()
{
delete m_coalesceData;
delete m_autoCompleteData;
}
@@ -862,8 +952,38 @@ void wxTextEntry::ForceUpper()
// IM handling
// ----------------------------------------------------------------------------
void wxTextEntry::GTKEntryOnKeypress(GtkWidget* widget) const
{
// We coalesce possibly multiple events resulting from a single key press
// (this always happens when there is a selection, as we always get a
// "changed" event when the selection is removed and another one when the
// new text is inserted) into a single wxEVT_TEXT and to do this we need
// this extra handler.
if ( !m_coalesceData )
{
// We can't use g_signal_connect_after("key-press-event") because the
// emission of this signal is stopped by GtkEntry own key-press-event
// handler, so we have to use the generic "event-after" instead to be
// notified about the end of handling of this key press and to send any
// pending events a.s.a.p.
const gulong handler = g_signal_connect
(
widget,
"event-after",
G_CALLBACK(wx_gtk_text_after_key_press),
const_cast<wxTextEntry*>(this)
);
m_coalesceData = new wxTextCoalesceData(widget, handler);
}
m_coalesceData->StartHandlingKeyPress(widget);
}
int wxTextEntry::GTKEntryIMFilterKeypress(GdkEventKey* event) const
{
GTKEntryOnKeypress(GTK_WIDGET(GetEntry()));
int result = false;
#if GTK_CHECK_VERSION(2, 22, 0)
if (wx_is_at_least_gtk2(22))