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:
parent
958df5fb74
commit
2c6dcc2e51
@ -15,6 +15,7 @@ typedef struct _GtkEditable GtkEditable;
|
||||
typedef struct _GtkEntry GtkEntry;
|
||||
|
||||
class wxTextAutoCompleteData; // private class used only by wxTextEntry itself
|
||||
class wxTextCoalesceData; // another private class
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxTextEntry: roughly corresponds to GtkEditable
|
||||
@ -62,12 +63,16 @@ public:
|
||||
bool GTKEntryOnInsertText(const char* text);
|
||||
bool GTKIsUpperCase() const { return m_isUpperCase; }
|
||||
|
||||
// Called from "changed" signal handler for GtkEntry.
|
||||
// Called from "changed" signal handler (or, possibly, slightly later, when
|
||||
// coalescing several "changed" signals into a single event) for GtkEntry.
|
||||
//
|
||||
// By default just generates a wxEVT_TEXT, but overridden to do more things
|
||||
// in wxTextCtrl.
|
||||
virtual void GTKOnTextChanged() { SendTextUpdatedEvent(); }
|
||||
|
||||
// Helper functions only used internally.
|
||||
wxTextCoalesceData* GTKGetCoalesceData() const { return m_coalesceData; }
|
||||
|
||||
protected:
|
||||
// This method must be called from the derived class Create() to connect
|
||||
// the handlers for the clipboard (cut/copy/paste) events.
|
||||
@ -95,6 +100,12 @@ protected:
|
||||
// GtkEntry IM context.
|
||||
int GTKEntryIMFilterKeypress(GdkEventKey* event) const;
|
||||
|
||||
// If GTKEntryIMFilterKeypress() is not called (as multiline wxTextCtrl
|
||||
// uses its own IM), call this method instead to still notify wxTextEntry
|
||||
// about the key press events in the given widget.
|
||||
void GTKEntryOnKeypress(GtkWidget* widget) const;
|
||||
|
||||
|
||||
static int GTKGetEntryTextLength(GtkEntry* entry);
|
||||
|
||||
// Block/unblock the corresponding GTK signal.
|
||||
@ -124,6 +135,10 @@ private:
|
||||
// It needs to call our GetEntry() method.
|
||||
friend class wxTextAutoCompleteData;
|
||||
|
||||
// Data used for coalescing "changed" events resulting from a single user
|
||||
// action.
|
||||
mutable wxTextCoalesceData* m_coalesceData;
|
||||
|
||||
bool m_isUpperCase;
|
||||
};
|
||||
|
||||
|
@ -858,7 +858,11 @@ GtkEntry *wxTextCtrl::GetEntry() const
|
||||
int wxTextCtrl::GTKIMFilterKeypress(GdkEventKey* event) const
|
||||
{
|
||||
if (IsSingleLine())
|
||||
return wxTextEntry::GTKIMFilterKeypress(event);
|
||||
return GTKEntryIMFilterKeypress(event);
|
||||
|
||||
// When not calling GTKEntryIMFilterKeypress(), we need to notify the code
|
||||
// in wxTextEntry about the key presses explicitly.
|
||||
GTKEntryOnKeypress(m_text);
|
||||
|
||||
int result = false;
|
||||
#if GTK_CHECK_VERSION(2, 22, 0)
|
||||
|
@ -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))
|
||||
|
Loading…
Reference in New Issue
Block a user