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
This commit is contained in:
Vadim Zeitlin
2013-03-21 22:37:09 +00:00
parent c77ef57e6e
commit b2c357747d
7 changed files with 154 additions and 50 deletions

View File

@@ -166,6 +166,10 @@ protected:
virtual void DoSetValue(const wxString &value, int flags = 0); 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; virtual wxPoint DoPositionToCoords(long pos) const;
// wrappers hiding the differences between functions doing the same thing // wrappers hiding the differences between functions doing the same thing

View File

@@ -50,12 +50,17 @@ public:
// implementation only from now on // implementation only from now on
void SendMaxLenEvent(); void SendMaxLenEvent();
bool GTKEntryOnInsertText(const char* text);
protected: protected:
// This method must be called from the derived class Create() to connect // This method must be called from the derived class Create() to connect
// the handlers for the clipboard (cut/copy/paste) events. // the handlers for the clipboard (cut/copy/paste) events.
void GTKConnectClipboardSignals(GtkWidget* entry); 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 void DoSetValue(const wxString& value, int flags);
virtual wxString DoGetValue() const; virtual wxString DoGetValue() const;
@@ -65,6 +70,9 @@ protected:
virtual bool DoAutoCompleteStrings(const wxArrayString& choices); virtual bool DoAutoCompleteStrings(const wxArrayString& choices);
// Override the base class method to use GtkEntry IM context.
virtual int GTKIMFilterKeypress(GdkEventKey* event) const;
private: private:
// implement this to return the associated GtkEntry or another widget // implement this to return the associated GtkEntry or another widget
// implementing GtkEditable // implementing GtkEditable

View File

@@ -297,6 +297,18 @@ public:
// methods for accessing it such gtk_entry_im_context_filter_keypress(). // methods for accessing it such gtk_entry_im_context_filter_keypress().
virtual int GTKIMFilterKeypress(GdkEventKey* event) const; 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 // indices for the arrays below
enum ScrollDir { ScrollDir_Horz, ScrollDir_Vert, ScrollDir_Max }; enum ScrollDir { ScrollDir_Horz, ScrollDir_Vert, ScrollDir_Max };

View File

@@ -173,6 +173,7 @@ bool wxComboBox::Create( wxWindow *parent, wxWindowID id, const wxString& value,
g_signal_connect_after (entry, "changed", g_signal_connect_after (entry, "changed",
G_CALLBACK (gtkcombobox_text_changed_callback), this); G_CALLBACK (gtkcombobox_text_changed_callback), this);
GTKConnectInsertTextSignal(entry);
GTKConnectClipboardSignals(GTK_WIDGET(entry)); GTKConnectClipboardSignals(GTK_WIDGET(entry));
} }

View File

@@ -461,6 +461,25 @@ au_check_range(GtkTextIter *s,
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
extern "C" { 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 static void
au_insert_text_callback(GtkTextBuffer * WXUNUSED(buffer), au_insert_text_callback(GtkTextBuffer * WXUNUSED(buffer),
GtkTextIter *end, GtkTextIter *end,
@@ -787,12 +806,19 @@ bool wxTextCtrl::Create( wxWindow *parent,
gtk_text_buffer_get_end_iter(m_buffer, &end); gtk_text_buffer_get_end_iter(m_buffer, &end);
au_check_range(&start, &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 else // single line
{ {
// do the right thing with Enter presses depending on whether we have // do the right thing with Enter presses depending on whether we have
// wxTE_PROCESS_ENTER or not // wxTE_PROCESS_ENTER or not
GTKSetActivatesDefault(); GTKSetActivatesDefault();
GTKConnectInsertTextSignal(GTK_ENTRY(m_text));
} }
@@ -815,6 +841,30 @@ GtkEntry *wxTextCtrl::GetEntry() const
return GTK_ENTRY(m_text); 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 // flags handling
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@@ -56,27 +56,37 @@ wx_gtk_insert_text_callback(GtkEditable *editable,
const int text_max_length = entry->text_max_length; const int text_max_length = entry->text_max_length;
#endif #endif
// we should only be called if we have a max len limit at all bool handled = false;
wxCHECK_RET(text_max_length, "shouldn't be called");
// 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);
// 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. const int text_length = gtk_entry_get_text_length(entry);
g_signal_stop_emission_by_name (editable, "insert_text");
// Currently we don't insert anything at all, but it would be better to // We can't use new_text_length as it is in bytes while we want to count
// insert as many characters as would fit into the text control and // characters (in first approximation, anyhow...).
// only discard the rest. if ( text_length + g_utf8_strlen(new_text, -1) > text_max_length )
{
// Prevent the new text from being inserted.
handled = true;
// Notify the user code about overflow. // Currently we don't insert anything at all, but it would be better to
text->SendMaxLenEvent(); // insert as many characters as would fit into the text control and
// only discard the rest.
// 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; return;
gtk_entry_set_max_length(entry, len); 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() void wxTextEntry::SendMaxLenEvent()
@@ -431,6 +412,33 @@ void wxTextEntry::SendMaxLenEvent()
win->HandleWindowEvent(event); 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 // margins support
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@@ -1069,24 +1069,32 @@ static void
gtk_wxwindow_commit_cb (GtkIMContext * WXUNUSED(context), gtk_wxwindow_commit_cb (GtkIMContext * WXUNUSED(context),
const gchar *str, const gchar *str,
wxWindow *window) 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 ); wxKeyEvent event( wxEVT_CHAR );
// take modifiers, cursor position, timestamp etc. from the last // take modifiers, cursor position, timestamp etc. from the last
// key_press_event that was fed into Input Method: // 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 else
{ {
event.SetEventObject( window ); event.SetEventObject(this);
} }
const wxString data(wxGTK_CONV_BACK_SYS(str)); const wxString data(wxGTK_CONV_BACK_SYS(str));
if( data.empty() ) if( data.empty() )
return; return false;
bool processed = false;
for( wxString::const_iterator pstr = data.begin(); pstr != data.end(); ++pstr ) for( wxString::const_iterator pstr = data.begin(); pstr != data.end(); ++pstr )
{ {
#if wxUSE_UNICODE #if wxUSE_UNICODE
@@ -1100,9 +1108,22 @@ gtk_wxwindow_commit_cb (GtkIMContext * WXUNUSED(context),
AdjustCharEventKeyCodes(event); 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);
} }