diff --git a/include/wx/gtk/combobox.h b/include/wx/gtk/combobox.h index 880ec67c8e..cb64c7aaf6 100644 --- a/include/wx/gtk/combobox.h +++ b/include/wx/gtk/combobox.h @@ -145,13 +145,16 @@ protected: virtual GtkEntry *GetEntry() const wxOVERRIDE { return m_entry; } + virtual int GTKIMFilterKeypress(GdkEventKey* event) const wxOVERRIDE + { return GTKEntryIMFilterKeypress(event); } + + GtkEntry* m_entry; 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 d1e4e5ee71..b343fc9b1a 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; @@ -182,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(); @@ -196,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 aaf777a515..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,6 +63,16 @@ public: bool GTKEntryOnInsertText(const char* text); bool GTKIsUpperCase() const { return m_isUpperCase; } + // 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. @@ -70,6 +81,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; @@ -81,11 +96,24 @@ 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; + + // 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. + // + // 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 @@ -94,6 +122,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; @@ -101,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/samples/widgets/textctrl.cpp b/samples/widgets/textctrl.cpp index 8618261215..ff6ea02226 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,19 +987,21 @@ 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 - // an annoying message box - static bool s_firstTime = true; - if ( s_firstTime ) - { - s_firstTime = false; + if ( !IsUsingLogWindow() ) 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) 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 diff --git a/src/gtk/combobox.cpp b/src/gtk/combobox.cpp index 578088c0fd..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)); } @@ -219,7 +209,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 ) @@ -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 54af9c1def..190c6840eb 100644 --- a/src/gtk/textctrl.cpp +++ b/src/gtk/textctrl.cpp @@ -567,24 +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 ) -{ - if ( win->IgnoreTextUpdate() ) - return; - - if ( win->MarkDirtyOnChange() ) - win->MarkDirty(); - - win->SendTextUpdatedEvent(); -} -} - //----------------------------------------------------------------------------- // "mark_set" //----------------------------------------------------------------------------- @@ -774,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", @@ -885,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) @@ -1371,24 +1348,21 @@ void wxTextCtrl::DiscardEdits() m_modified = false; } +void wxTextCtrl::GTKOnTextChanged() +{ + if ( IgnoreTextUpdate() ) + return; + + if ( MarkDirtyOnChange() ) + MarkDirty(); + + SendTextUpdatedEvent(); +} + // ---------------------------------------------------------------------------- // 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..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 //----------------------------------------------------------------------------- @@ -57,8 +116,45 @@ static int GetEntryTextLength(GtkEntry* entry) // signal handlers implementation // ============================================================================ -// "insert_text" handler for GtkEntry 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(); +} + +// "insert_text" handler for GtkEntry static void wx_gtk_insert_text_callback(GtkEditable *editable, const gchar * new_text, @@ -510,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; } @@ -854,8 +952,38 @@ void wxTextEntry::ForceUpper() // IM handling // ---------------------------------------------------------------------------- -int wxTextEntry::GTKIMFilterKeypress(GdkEventKey* event) const +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)) @@ -869,6 +997,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",