From 7206194d087a8ff27ab81237131806508c85b6ac Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 8 Jul 2019 11:08:53 +0200 Subject: [PATCH 1/7] Show new wxTextCtrl value in the widgets sample This is useful for quickly checking that we're getting expected events when modifying the control and/or not getting any unexpected ones and was already done on wxComboBox page, but not for wxTextCtrl. --- samples/widgets/textctrl.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/samples/widgets/textctrl.cpp b/samples/widgets/textctrl.cpp index 8618261215..cda0bc9362 100644 --- a/samples/widgets/textctrl.cpp +++ b/samples/widgets/textctrl.cpp @@ -31,6 +31,7 @@ #include "wx/bitmap.h" #include "wx/button.h" #include "wx/checkbox.h" + #include "wx/dcclient.h" #include "wx/radiobox.h" #include "wx/statbox.h" #include "wx/stattext.h" @@ -986,7 +987,7 @@ void TextWidgetsPage::OnUpdateUIResetButton(wxUpdateUIEvent& event) (m_radioWrap->GetSelection() != DEFAULTS.wrapStyle) ); } -void TextWidgetsPage::OnText(wxCommandEvent& WXUNUSED(event)) +void TextWidgetsPage::OnText(wxCommandEvent& event) { // small hack to suppress the very first message: by then the logging is // not yet redirected and so initial setting of the text value results in @@ -998,7 +999,16 @@ void TextWidgetsPage::OnText(wxCommandEvent& WXUNUSED(event)) return; } - wxLogMessage("Text ctrl value changed"); + // Replace middle of long text with ellipsis just to avoid filling up the + // log control with too much unnecessary stuff. + wxLogMessage("Text control value changed (now '%s')", + wxControl::Ellipsize + ( + event.GetString(), + wxClientDC(this), + wxELLIPSIZE_MIDDLE, + GetTextExtent('W').x*100 + )); } void TextWidgetsPage::OnTextEnter(wxCommandEvent& event) From 3d9656395a0d00bece6befdf641b4fb5508fdfa5 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 8 Jul 2019 12:53:02 +0200 Subject: [PATCH 2/7] Fix unwanted message boxes in widgets sample once and for all Add IsUsingLogWindow() that can be used to check if the log messages go into the listbox instead of being shown in a message box, which was annoying when starting or quitting the sample. This is a bit more complicated than the hack previously used in TextWidgetsPage::OnText(), but more general and can be easily reused for the focus loss messages, for example. --- samples/widgets/textctrl.cpp | 9 +------ samples/widgets/widgets.cpp | 47 ++++++++++++++++++++++++++++++++++-- samples/widgets/widgets.h | 4 +++ 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/samples/widgets/textctrl.cpp b/samples/widgets/textctrl.cpp index cda0bc9362..ff6ea02226 100644 --- a/samples/widgets/textctrl.cpp +++ b/samples/widgets/textctrl.cpp @@ -989,15 +989,8 @@ void TextWidgetsPage::OnUpdateUIResetButton(wxUpdateUIEvent& event) void TextWidgetsPage::OnText(wxCommandEvent& event) { - // small hack to suppress the very first message: by then the logging is - // not yet redirected and so initial setting of the text value results in - // an annoying message box - static bool s_firstTime = true; - if ( s_firstTime ) - { - s_firstTime = false; + if ( !IsUsingLogWindow() ) return; - } // Replace middle of long text with ellipsis just to avoid filling up the // log control with too much unnecessary stuff. diff --git a/samples/widgets/widgets.cpp b/samples/widgets/widgets.cpp index 0d26fc05e8..fbdc36e528 100644 --- a/samples/widgets/widgets.cpp +++ b/samples/widgets/widgets.cpp @@ -139,6 +139,13 @@ const wxChar *WidgetsCategories[MAX_PAGES] = { class WidgetsApp : public wxApp { public: + WidgetsApp() + { +#if USE_LOG + m_logTarget = NULL; +#endif // USE_LOG + } + // override base class virtuals // ---------------------------- @@ -146,8 +153,20 @@ public: // initialization (doing it here and not in the ctor allows to have an error // return: if OnInit() returns false, the application terminates) virtual bool OnInit() wxOVERRIDE; + + // real implementation of WidgetsPage method with the same name + bool IsUsingLogWindow() const; + +private: +#if USE_LOG + wxLog* m_logTarget; +#endif // USE_LOG + + wxDECLARE_NO_COPY_CLASS(WidgetsApp); }; +wxDECLARE_APP(WidgetsApp); // This provides a convenient wxGetApp() accessor. + // Define a new frame type: this is going to be our main frame class WidgetsFrame : public wxFrame { @@ -375,9 +394,22 @@ bool WidgetsApp::OnInit() wxFrame *frame = new WidgetsFrame(title + " widgets demo"); frame->Show(); +#if USE_LOG + m_logTarget = wxLog::GetActiveTarget(); +#endif // USE_LOG + return true; } +bool WidgetsApp::IsUsingLogWindow() const +{ +#if USE_LOG + return wxLog::GetActiveTarget() == m_logTarget; +#else // !USE_LOG + return false; +#endif // USE_LOG +} + // ---------------------------------------------------------------------------- // WidgetsFrame construction // ---------------------------------------------------------------------------- @@ -1200,8 +1232,13 @@ void WidgetsFrame::OnSetHint(wxCommandEvent& WXUNUSED(event)) void WidgetsFrame::OnWidgetFocus(wxFocusEvent& event) { - wxLogMessage("Widgets %s focus", - event.GetEventType() == wxEVT_SET_FOCUS ? "got" : "lost"); + // Don't show annoying message boxes when starting or closing the sample, + // only log these events in our own logger. + if ( wxGetApp().IsUsingLogWindow() ) + { + wxLogMessage("Widgets %s focus", + event.GetEventType() == wxEVT_SET_FOCUS ? "got" : "lost"); + } event.Skip(); } @@ -1384,3 +1421,9 @@ wxCheckBox *WidgetsPage::CreateCheckBoxAndAddToSizer(wxSizer *sizer, return checkbox; } + +/* static */ +bool WidgetsPage::IsUsingLogWindow() +{ + return wxGetApp().IsUsingLogWindow(); +} diff --git a/samples/widgets/widgets.h b/samples/widgets/widgets.h index c632fffe6c..77d4ffe642 100644 --- a/samples/widgets/widgets.h +++ b/samples/widgets/widgets.h @@ -155,6 +155,10 @@ public: // the default attributes for the widget static WidgetAttributes& GetAttrs(); + // return true if we're showing logs in the log window (always the case + // except during startup and shutdown) + static bool IsUsingLogWindow(); + protected: // several helper functions for page creation From c75067f0b4f1a928a3faa08c34ee9744128dfa6b Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 8 Jul 2019 16:10:29 +0200 Subject: [PATCH 3/7] Simplify wxComboBox::GetEditable() Use m_entry that we already store instead of retrieving it from GTK. No real changes. --- src/gtk/combobox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gtk/combobox.cpp b/src/gtk/combobox.cpp index 578088c0fd..d0d6ad23c8 100644 --- a/src/gtk/combobox.cpp +++ b/src/gtk/combobox.cpp @@ -219,7 +219,7 @@ void wxComboBox::GTKCreateComboBoxWidget() GtkEditable *wxComboBox::GetEditable() const { - return GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(m_widget))); + return GTK_EDITABLE(m_entry); } void wxComboBox::OnChar( wxKeyEvent &event ) From c024944d78e0818b85d45c98b9d5c6f2476c9a57 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 8 Jul 2019 15:58:12 +0200 Subject: [PATCH 4/7] Move logic from GTK callback to GTKOnTextChanged() virtual method Check whether we should ignore the event and mark the control as being dirty if necessary in a virtual method, which can be defined in wxTextEntry and overridden by wxTextCtrl, instead of doing it in GTK callback itself. This will allow to reuse wxTextEntry callback for wxTextCtrl too in the upcoming commits. No real changes so far. --- include/wx/gtk/textctrl.h | 2 ++ include/wx/gtk/textentry.h | 6 ++++++ src/gtk/textctrl.cpp | 19 ++++++++++++------- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/include/wx/gtk/textctrl.h b/include/wx/gtk/textctrl.h index d1e4e5ee71..bf7f2e42a1 100644 --- a/include/wx/gtk/textctrl.h +++ b/include/wx/gtk/textctrl.h @@ -142,6 +142,8 @@ public: static wxVisualAttributes GetClassDefaultAttributes(wxWindowVariant variant = wxWINDOW_VARIANT_NORMAL); + void GTKOnTextChanged() wxOVERRIDE; + protected: // overridden wxWindow virtual methods virtual wxSize DoGetBestSize() const wxOVERRIDE; diff --git a/include/wx/gtk/textentry.h b/include/wx/gtk/textentry.h index aaf777a515..810a4659e0 100644 --- a/include/wx/gtk/textentry.h +++ b/include/wx/gtk/textentry.h @@ -62,6 +62,12 @@ public: bool GTKEntryOnInsertText(const char* text); bool GTKIsUpperCase() const { return m_isUpperCase; } + // Called from "changed" signal handler for GtkEntry. + // + // By default just generates a wxEVT_TEXT, but overridden to do more things + // in wxTextCtrl. + virtual void GTKOnTextChanged() { SendTextUpdatedEvent(); } + protected: // This method must be called from the derived class Create() to connect // the handlers for the clipboard (cut/copy/paste) events. diff --git a/src/gtk/textctrl.cpp b/src/gtk/textctrl.cpp index 54af9c1def..1406919174 100644 --- a/src/gtk/textctrl.cpp +++ b/src/gtk/textctrl.cpp @@ -575,13 +575,7 @@ extern "C" { static void gtk_text_changed_callback( GtkWidget *WXUNUSED(widget), wxTextCtrl *win ) { - if ( win->IgnoreTextUpdate() ) - return; - - if ( win->MarkDirtyOnChange() ) - win->MarkDirty(); - - win->SendTextUpdatedEvent(); + win->GTKOnTextChanged(); } } @@ -1371,6 +1365,17 @@ void wxTextCtrl::DiscardEdits() m_modified = false; } +void wxTextCtrl::GTKOnTextChanged() +{ + if ( IgnoreTextUpdate() ) + return; + + if ( MarkDirtyOnChange() ) + MarkDirty(); + + SendTextUpdatedEvent(); +} + // ---------------------------------------------------------------------------- // event handling // ---------------------------------------------------------------------------- From 5c766c0b8b4f5dabd7ff75e229eee58d46c41cb8 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 8 Jul 2019 16:04:24 +0200 Subject: [PATCH 5/7] Use the same "changed" GTK callback for wxComboBox and wxTextCtrl And reuse EnableTextChangedEvents() between these classes as well. No real changes so far, this is just a refactoring to centralize the code in a single place before modifying it. --- include/wx/gtk/combobox.h | 1 - include/wx/gtk/textctrl.h | 3 +-- include/wx/gtk/textentry.h | 16 ++++++++++++++++ src/gtk/combobox.cpp | 29 +---------------------------- src/gtk/textctrl.cpp | 37 +------------------------------------ src/gtk/textentry.cpp | 35 ++++++++++++++++++++++++++++++++++- 6 files changed, 53 insertions(+), 68 deletions(-) diff --git a/include/wx/gtk/combobox.h b/include/wx/gtk/combobox.h index 880ec67c8e..06da278b1b 100644 --- a/include/wx/gtk/combobox.h +++ b/include/wx/gtk/combobox.h @@ -151,7 +151,6 @@ private: // From wxTextEntry: virtual wxWindow *GetEditableWindow() wxOVERRIDE { return this; } virtual GtkEditable *GetEditable() const wxOVERRIDE; - virtual void EnableTextChangedEvents(bool enable) wxOVERRIDE; void Init(); diff --git a/include/wx/gtk/textctrl.h b/include/wx/gtk/textctrl.h index bf7f2e42a1..b343fc9b1a 100644 --- a/include/wx/gtk/textctrl.h +++ b/include/wx/gtk/textctrl.h @@ -184,7 +184,6 @@ private: // overridden wxTextEntry virtual methods virtual GtkEditable *GetEditable() const wxOVERRIDE; virtual GtkEntry *GetEntry() const wxOVERRIDE; - virtual void EnableTextChangedEvents(bool enable) wxOVERRIDE; // change the font for everything in this control void ChangeFontGlobally(); @@ -198,7 +197,7 @@ private: // returns either m_text or m_buffer depending on whether the control is // single- or multi-line; convenient for the GTK+ functions which work with // both - void *GetTextObject() const + void *GetTextObject() const wxOVERRIDE { return IsMultiLine() ? static_cast(m_buffer) : static_cast(m_text); diff --git a/include/wx/gtk/textentry.h b/include/wx/gtk/textentry.h index 810a4659e0..aad2982d80 100644 --- a/include/wx/gtk/textentry.h +++ b/include/wx/gtk/textentry.h @@ -76,6 +76,10 @@ protected: // And this one to connect "insert-text" signal. void GTKConnectInsertTextSignal(GtkEntry* entry); + // Finally this one connects to the "changed" signal on the object returned + // by GetTextObject(). + void GTKConnectChangedSignal(); + virtual void DoSetValue(const wxString& value, int flags) wxOVERRIDE; virtual wxString DoGetValue() const wxOVERRIDE; @@ -92,6 +96,12 @@ protected: static int GTKGetEntryTextLength(GtkEntry* entry); + // Block/unblock the corresponding GTK signal. + // + // Note that we make it protected in wxGTK as it is called from wxComboBox + // currently. + virtual void EnableTextChangedEvents(bool enable) wxOVERRIDE; + private: // implement this to return the associated GtkEntry or another widget // implementing GtkEditable @@ -100,6 +110,12 @@ private: // implement this to return the associated GtkEntry virtual GtkEntry *GetEntry() const = 0; + // This one exists in order to be overridden by wxTextCtrl which uses + // either GtkEditable or GtkTextBuffer depending on whether it is single- + // or multi-line. + virtual void *GetTextObject() const { return GetEntry(); } + + // Various auto-completion-related stuff, only used if any of AutoComplete() // methods are called. wxTextAutoCompleteData *m_autoCompleteData; diff --git a/src/gtk/combobox.cpp b/src/gtk/combobox.cpp index d0d6ad23c8..47352b8626 100644 --- a/src/gtk/combobox.cpp +++ b/src/gtk/combobox.cpp @@ -27,14 +27,6 @@ // ---------------------------------------------------------------------------- extern "C" { -static void -gtkcombobox_text_changed_callback( GtkWidget *WXUNUSED(widget), wxComboBox *combo ) -{ - wxCommandEvent event( wxEVT_TEXT, combo->GetId() ); - event.SetString( combo->GetValue() ); - event.SetEventObject( combo ); - combo->HandleWindowEvent( event ); -} static void gtkcombobox_changed_callback( GtkWidget *WXUNUSED(widget), wxComboBox *combo ) @@ -185,9 +177,7 @@ bool wxComboBox::Create( wxWindow *parent, wxWindowID id, const wxString& value, gtk_entry_set_text( entry, wxGTK_CONV(value) ); } - g_signal_connect_after (entry, "changed", - G_CALLBACK (gtkcombobox_text_changed_callback), this); - + GTKConnectChangedSignal(); GTKConnectInsertTextSignal(entry); GTKConnectClipboardSignals(GTK_WIDGET(entry)); } @@ -248,23 +238,6 @@ void wxComboBox::OnChar( wxKeyEvent &event ) event.Skip(); } -void wxComboBox::EnableTextChangedEvents(bool enable) -{ - if ( !GetEntry() ) - return; - - if ( enable ) - { - g_signal_handlers_unblock_by_func(gtk_bin_get_child(GTK_BIN(m_widget)), - (gpointer)gtkcombobox_text_changed_callback, this); - } - else // disable - { - g_signal_handlers_block_by_func(gtk_bin_get_child(GTK_BIN(m_widget)), - (gpointer)gtkcombobox_text_changed_callback, this); - } -} - void wxComboBox::GTKDisableEvents() { EnableTextChangedEvents(false); diff --git a/src/gtk/textctrl.cpp b/src/gtk/textctrl.cpp index 1406919174..c30726362b 100644 --- a/src/gtk/textctrl.cpp +++ b/src/gtk/textctrl.cpp @@ -567,18 +567,6 @@ gtk_textctrl_populate_popup( GtkEntry *WXUNUSED(entry), GtkMenu *menu, wxTextCtr } } -//----------------------------------------------------------------------------- -// "changed" -//----------------------------------------------------------------------------- - -extern "C" { -static void -gtk_text_changed_callback( GtkWidget *WXUNUSED(widget), wxTextCtrl *win ) -{ - win->GTKOnTextChanged(); -} -} - //----------------------------------------------------------------------------- // "mark_set" //----------------------------------------------------------------------------- @@ -768,16 +756,7 @@ bool wxTextCtrl::Create( wxWindow *parent, } // We want to be notified about text changes. - if (multi_line) - { - g_signal_connect (m_buffer, "changed", - G_CALLBACK (gtk_text_changed_callback), this); - } - else - { - g_signal_connect (m_text, "changed", - G_CALLBACK (gtk_text_changed_callback), this); - } + GTKConnectChangedSignal(); // Catch to disable focus out handling g_signal_connect (m_text, "populate_popup", @@ -1380,20 +1359,6 @@ void wxTextCtrl::GTKOnTextChanged() // event handling // ---------------------------------------------------------------------------- -void wxTextCtrl::EnableTextChangedEvents(bool enable) -{ - if ( enable ) - { - g_signal_handlers_unblock_by_func(GetTextObject(), - (gpointer)gtk_text_changed_callback, this); - } - else // disable events - { - g_signal_handlers_block_by_func(GetTextObject(), - (gpointer)gtk_text_changed_callback, this); - } -} - bool wxTextCtrl::IgnoreTextUpdate() { if ( m_countUpdatesToIgnore > 0 ) diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index 598d7593a0..850082ec41 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -57,8 +57,16 @@ static int GetEntryTextLength(GtkEntry* entry) // signal handlers implementation // ============================================================================ -// "insert_text" handler for GtkEntry extern "C" { + +// "changed" handler for GtkEntry +static void +wx_gtk_text_changed_callback(GtkWidget* WXUNUSED(widget), wxTextEntry* entry) +{ + entry->GTKOnTextChanged(); +} + +// "insert_text" handler for GtkEntry static void wx_gtk_insert_text_callback(GtkEditable *editable, const gchar * new_text, @@ -869,6 +877,31 @@ int wxTextEntry::GTKIMFilterKeypress(GdkEventKey* event) const return result; } +// ---------------------------------------------------------------------------- +// signals and events +// ---------------------------------------------------------------------------- + +void wxTextEntry::EnableTextChangedEvents(bool enable) +{ + if ( enable ) + { + g_signal_handlers_unblock_by_func(GetTextObject(), + (gpointer)wx_gtk_text_changed_callback, this); + } + else // disable events + { + g_signal_handlers_block_by_func(GetTextObject(), + (gpointer)wx_gtk_text_changed_callback, this); + } +} + +void wxTextEntry::GTKConnectChangedSignal() +{ + g_signal_connect(GetTextObject(), "changed", + G_CALLBACK(wx_gtk_text_changed_callback), this); + +} + void wxTextEntry::GTKConnectInsertTextSignal(GtkEntry* entry) { g_signal_connect(entry, "insert_text", From 958df5fb7450f9a4d9ae1d170c722a9248cd7f3a Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 9 Jul 2019 19:37:21 +0200 Subject: [PATCH 6/7] Use IME in wxGTK wxComboBox too There was confusing with the method GTKIMFilterKeypress() in wxGTK wxTextEntry: it was called the same as wxWindow virtual method of the same name, but didn't override it, of course, as wxTextEntry doesn't derive from wxWindow. It also wasn't called for wxComboBox which inherited from wxTextEntry but didn't override wxWindow::GTKIMFilterKeypress() to actually call its method. Fix this and rename the wxTextEntry to use a different name for clarity. --- include/wx/gtk/combobox.h | 4 ++++ include/wx/gtk/textentry.h | 5 +++-- src/gtk/textentry.cpp | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/include/wx/gtk/combobox.h b/include/wx/gtk/combobox.h index 06da278b1b..cb64c7aaf6 100644 --- a/include/wx/gtk/combobox.h +++ b/include/wx/gtk/combobox.h @@ -145,6 +145,10 @@ protected: virtual GtkEntry *GetEntry() const wxOVERRIDE { return m_entry; } + virtual int GTKIMFilterKeypress(GdkEventKey* event) const wxOVERRIDE + { return GTKEntryIMFilterKeypress(event); } + + GtkEntry* m_entry; private: diff --git a/include/wx/gtk/textentry.h b/include/wx/gtk/textentry.h index aad2982d80..a4f92fc7b7 100644 --- a/include/wx/gtk/textentry.h +++ b/include/wx/gtk/textentry.h @@ -91,8 +91,9 @@ protected: virtual bool DoAutoCompleteStrings(const wxArrayString& choices) wxOVERRIDE; virtual bool DoAutoCompleteCustom(wxTextCompleter *completer) wxOVERRIDE; - // Override the base class method to use GtkEntry IM context. - virtual int GTKIMFilterKeypress(GdkEventKey* event) const; + // Call this from the overridden wxWindow::GTKIMFilterKeypress() to use + // GtkEntry IM context. + int GTKEntryIMFilterKeypress(GdkEventKey* event) const; static int GTKGetEntryTextLength(GtkEntry* entry); diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index 850082ec41..6d5d04edbe 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -862,7 +862,7 @@ void wxTextEntry::ForceUpper() // IM handling // ---------------------------------------------------------------------------- -int wxTextEntry::GTKIMFilterKeypress(GdkEventKey* event) const +int wxTextEntry::GTKEntryIMFilterKeypress(GdkEventKey* event) const { int result = false; #if GTK_CHECK_VERSION(2, 22, 0) From 2c6dcc2e510dcf0f6cbd16a43eb542f25982ec5d Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 10 Jul 2019 02:14:21 +0200 Subject: [PATCH 7/7] 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. --- include/wx/gtk/textentry.h | 17 +++++- src/gtk/textctrl.cpp | 6 +- src/gtk/textentry.cpp | 120 +++++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 2 deletions(-) diff --git a/include/wx/gtk/textentry.h b/include/wx/gtk/textentry.h index a4f92fc7b7..6198348bc7 100644 --- a/include/wx/gtk/textentry.h +++ b/include/wx/gtk/textentry.h @@ -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; }; diff --git a/src/gtk/textctrl.cpp b/src/gtk/textctrl.cpp index c30726362b..190c6840eb 100644 --- a/src/gtk/textctrl.cpp +++ b/src/gtk/textctrl.cpp @@ -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) diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index 6d5d04edbe..61819c6df3 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -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(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))