From b2c357747dd5a64b3302befcb7463586f624223b Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Thu, 21 Mar 2013 22:37:09 +0000 Subject: [PATCH] Add IM and full wxEVT_CHAR support to wxTextCtrl and wxComboBox in wxGTK. Generate wxEVT_CHAR events for non-ASCII characters entered in these controls by intercepting their insert-text signal. Also try to use GtkEntry/GtkTextView internal IM objects but unsuccessfully so far. Closes #3158. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@73695 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/gtk/textctrl.h | 4 ++ include/wx/gtk/textentry.h | 8 ++++ include/wx/gtk/window.h | 12 +++++ src/gtk/combobox.cpp | 1 + src/gtk/textctrl.cpp | 50 ++++++++++++++++++++ src/gtk/textentry.cpp | 96 +++++++++++++++++++++----------------- src/gtk/window.cpp | 31 ++++++++++-- 7 files changed, 153 insertions(+), 49 deletions(-) diff --git a/include/wx/gtk/textctrl.h b/include/wx/gtk/textctrl.h index 60e07203a7..15e44703da 100644 --- a/include/wx/gtk/textctrl.h +++ b/include/wx/gtk/textctrl.h @@ -166,6 +166,10 @@ protected: virtual void DoSetValue(const wxString &value, int flags = 0); + // Override this to use either GtkEntry or GtkTextView IME depending on the + // kind of control we are. + virtual int GTKIMFilterKeypress(GdkEventKey* event) const; + virtual wxPoint DoPositionToCoords(long pos) const; // wrappers hiding the differences between functions doing the same thing diff --git a/include/wx/gtk/textentry.h b/include/wx/gtk/textentry.h index a2ec262c84..5ba028d3cd 100644 --- a/include/wx/gtk/textentry.h +++ b/include/wx/gtk/textentry.h @@ -50,12 +50,17 @@ public: // implementation only from now on void SendMaxLenEvent(); + bool GTKEntryOnInsertText(const char* text); protected: // This method must be called from the derived class Create() to connect // the handlers for the clipboard (cut/copy/paste) events. void GTKConnectClipboardSignals(GtkWidget* entry); + // And this one to connect "insert-text" signal. + void GTKConnectInsertTextSignal(GtkEntry* entry); + + virtual void DoSetValue(const wxString& value, int flags); virtual wxString DoGetValue() const; @@ -65,6 +70,9 @@ protected: virtual bool DoAutoCompleteStrings(const wxArrayString& choices); + // Override the base class method to use GtkEntry IM context. + virtual int GTKIMFilterKeypress(GdkEventKey* event) const; + private: // implement this to return the associated GtkEntry or another widget // implementing GtkEditable diff --git a/include/wx/gtk/window.h b/include/wx/gtk/window.h index d107239fd1..fa3ae5c104 100644 --- a/include/wx/gtk/window.h +++ b/include/wx/gtk/window.h @@ -297,6 +297,18 @@ public: // methods for accessing it such gtk_entry_im_context_filter_keypress(). virtual int GTKIMFilterKeypress(GdkEventKey* event) const; + // This method must be called from the derived classes "insert-text" signal + // handlers to check if the text is not being inserted by the IM and, if + // this is the case, generate appropriate wxEVT_CHAR events for it. + // + // Returns true if we did generate and process events corresponding to this + // text or false if we didn't handle it. + bool GTKOnInsertText(const char* text); + + // This is just a helper of GTKOnInsertText() which is also used by GTK+ + // "commit" signal handler. + bool GTKDoInsertTextFromIM(const char* text); + // indices for the arrays below enum ScrollDir { ScrollDir_Horz, ScrollDir_Vert, ScrollDir_Max }; diff --git a/src/gtk/combobox.cpp b/src/gtk/combobox.cpp index 40c437fa23..6f84ab0a8a 100644 --- a/src/gtk/combobox.cpp +++ b/src/gtk/combobox.cpp @@ -173,6 +173,7 @@ bool wxComboBox::Create( wxWindow *parent, wxWindowID id, const wxString& value, g_signal_connect_after (entry, "changed", G_CALLBACK (gtkcombobox_text_changed_callback), this); + GTKConnectInsertTextSignal(entry); GTKConnectClipboardSignals(GTK_WIDGET(entry)); } diff --git a/src/gtk/textctrl.cpp b/src/gtk/textctrl.cpp index f0597d724b..cd1e766045 100644 --- a/src/gtk/textctrl.cpp +++ b/src/gtk/textctrl.cpp @@ -461,6 +461,25 @@ au_check_range(GtkTextIter *s, //----------------------------------------------------------------------------- extern "C" { + +// Normal version used for detecting IME input and generating appropriate +// events for it. +void +wx_insert_text_callback(GtkTextBuffer* buffer, + GtkTextIter* WXUNUSED(end), + gchar *text, + gint WXUNUSED(len), + wxTextCtrl *win) +{ + if ( win->GTKOnInsertText(text) ) + { + // If we already handled the new text insertion, don't do it again. + g_signal_stop_emission_by_name (buffer, "insert_text"); + } +} + + +// And an "after" version used for detecting URLs in the text. static void au_insert_text_callback(GtkTextBuffer * WXUNUSED(buffer), GtkTextIter *end, @@ -787,12 +806,19 @@ bool wxTextCtrl::Create( wxWindow *parent, gtk_text_buffer_get_end_iter(m_buffer, &end); au_check_range(&start, &end); } + + // Also connect a normal (not "after") signal handler for checking for + // the IME-generated input. + g_signal_connect(m_buffer, "insert_text", + G_CALLBACK(wx_insert_text_callback), this); } else // single line { // do the right thing with Enter presses depending on whether we have // wxTE_PROCESS_ENTER or not GTKSetActivatesDefault(); + + GTKConnectInsertTextSignal(GTK_ENTRY(m_text)); } @@ -815,6 +841,30 @@ GtkEntry *wxTextCtrl::GetEntry() const return GTK_ENTRY(m_text); } +int wxTextCtrl::GTKIMFilterKeypress(GdkEventKey* event) const +{ +#if GTK_CHECK_VERSION(2, 22, 0) + if ( gtk_check_version(2, 12, 0) == 0 ) + { + if ( IsSingleLine() ) + { + return wxTextEntry::GTKIMFilterKeypress(event); + } + else + { + return gtk_text_view_im_context_filter_keypress( + GTK_TEXT_VIEW(m_text), + event + ); + } + } +#else // GTK+ < 2.22 + wxUnusedVar(event); +#endif // GTK+ 2.22+ + + return FALSE; +} + // ---------------------------------------------------------------------------- // flags handling // ---------------------------------------------------------------------------- diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index 79e690c0f5..1f38fa69c7 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -56,27 +56,37 @@ wx_gtk_insert_text_callback(GtkEditable *editable, const int text_max_length = entry->text_max_length; #endif - // we should only be called if we have a max len limit at all - wxCHECK_RET(text_max_length, "shouldn't be called"); + bool handled = false; - // check that we don't overflow the max length limit + // check that we don't overflow the max length limit if we have it + if ( text_max_length ) + { + const int text_length = gtk_entry_get_text_length(entry); - const int text_length = gtk_entry_get_text_length(entry); + // We can't use new_text_length as it is in bytes while we want to count + // characters (in first approximation, anyhow...). + if ( text_length + g_utf8_strlen(new_text, -1) > text_max_length ) + { + // Prevent the new text from being inserted. + handled = true; - // We can't use new_text_length as it is in bytes while we want to count - // characters (in first approximation, anyhow...). - if ( text_length + g_utf8_strlen(new_text, -1) > text_max_length ) - { - // Prevent the new text from being inserted. - g_signal_stop_emission_by_name (editable, "insert_text"); + // Currently we don't insert anything at all, but it would be better to + // insert as many characters as would fit into the text control and + // only discard the rest. - // Currently we don't insert anything at all, but it would be better to - // insert as many characters as would fit into the text control and - // only discard the rest. + // Notify the user code about overflow. + text->SendMaxLenEvent(); + } + } - // Notify the user code about overflow. - text->SendMaxLenEvent(); + if ( !handled && text->GTKEntryOnInsertText(new_text) ) + { + // If we already handled the new text insertion, don't do it again. + handled = true; } + + if ( handled ) + g_signal_stop_emission_by_name (editable, "insert_text"); } //----------------------------------------------------------------------------- @@ -387,35 +397,6 @@ void wxTextEntry::SetMaxLength(unsigned long len) return; gtk_entry_set_max_length(entry, len); - - // there is a bug in GTK+ 1.2.x: "changed" signal is emitted even if we had - // tried to enter more text than allowed by max text length and the text - // wasn't really changed - // - // to detect this and generate TEXT_MAXLEN event instead of TEXT_CHANGED - // one in this case we also catch "insert_text" signal - // - // when max len is set to 0 we disconnect our handler as it means that we - // shouldn't check anything any more - if ( len ) - { - g_signal_connect - ( - entry, - "insert_text", - G_CALLBACK(wx_gtk_insert_text_callback), - this - ); - } - else // no max length - { - g_signal_handlers_disconnect_by_func - ( - entry, - (gpointer)wx_gtk_insert_text_callback, - this - ); - } } void wxTextEntry::SendMaxLenEvent() @@ -431,6 +412,33 @@ void wxTextEntry::SendMaxLenEvent() win->HandleWindowEvent(event); } +// ---------------------------------------------------------------------------- +// IM handling +// ---------------------------------------------------------------------------- + +int wxTextEntry::GTKIMFilterKeypress(GdkEventKey* event) const +{ +#if GTK_CHECK_VERSION(2, 22, 0) + if ( gtk_check_version(2, 12, 0) == 0 ) + return gtk_entry_im_context_filter_keypress(GetEntry(), event); +#else // GTK+ < 2.22 + wxUnusedVar(event); +#endif // GTK+ 2.22+ + + return FALSE; +} + +void wxTextEntry::GTKConnectInsertTextSignal(GtkEntry* entry) +{ + g_signal_connect(entry, "insert_text", + G_CALLBACK(wx_gtk_insert_text_callback), this); +} + +bool wxTextEntry::GTKEntryOnInsertText(const char* text) +{ + return GetEditableWindow()->GTKOnInsertText(text); +} + // ---------------------------------------------------------------------------- // margins support // ---------------------------------------------------------------------------- diff --git a/src/gtk/window.cpp b/src/gtk/window.cpp index a264041af7..7eb9314093 100644 --- a/src/gtk/window.cpp +++ b/src/gtk/window.cpp @@ -1069,24 +1069,32 @@ static void gtk_wxwindow_commit_cb (GtkIMContext * WXUNUSED(context), const gchar *str, wxWindow *window) +{ + // Ignore the return value here, it doesn't matter for the "commit" signal. + window->GTKDoInsertTextFromIM(str); +} +} + +bool wxWindowGTK::GTKDoInsertTextFromIM(const char* str) { wxKeyEvent event( wxEVT_CHAR ); // take modifiers, cursor position, timestamp etc. from the last // key_press_event that was fed into Input Method: - if (window->m_imKeyEvent) + if ( m_imKeyEvent ) { - wxFillOtherKeyEventFields(event, window, window->m_imKeyEvent); + wxFillOtherKeyEventFields(event, this, m_imKeyEvent); } else { - event.SetEventObject( window ); + event.SetEventObject(this); } const wxString data(wxGTK_CONV_BACK_SYS(str)); if( data.empty() ) - return; + return false; + bool processed = false; for( wxString::const_iterator pstr = data.begin(); pstr != data.end(); ++pstr ) { #if wxUSE_UNICODE @@ -1100,9 +1108,22 @@ gtk_wxwindow_commit_cb (GtkIMContext * WXUNUSED(context), AdjustCharEventKeyCodes(event); - window->HandleWindowEvent(event); + if ( HandleWindowEvent(event) ) + processed = true; } + + return processed; } + +bool wxWindowGTK::GTKOnInsertText(const char* text) +{ + if ( !m_imKeyEvent ) + { + // We're not inside IM key handling at all. + return false; + } + + return GTKDoInsertTextFromIM(text); } -- 2.45.2