diff options
author | hbono@chromium.org <hbono@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-23 05:35:02 +0000 |
---|---|---|
committer | hbono@chromium.org <hbono@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-23 05:35:02 +0000 |
commit | f338a0ed8c0490fad58fa47799c98909e8750978 (patch) | |
tree | bb27f6d22afaeca65f41fd6b226fe32812193e7d /chrome/browser/renderer_host | |
parent | abe7a89488d132d650aff0846ccd9a0b83d4a1f1 (diff) | |
download | chromium_src-f338a0ed8c0490fad58fa47799c98909e8750978.zip chromium_src-f338a0ed8c0490fad58fa47799c98909e8750978.tar.gz chromium_src-f338a0ed8c0490fad58fa47799c98909e8750978.tar.bz2 |
Integrating GtkIMContext into the RenderWidgetHostViewGtk class.This change implements signal handers of the GtkIMContext object to support IMEs and dead-keys. Also, to improve compatibility with Windows Chrome, this change emulates IPC messages sent on Windows when we input characters and fixes Issue 13604 as well as Issue 10953 and 11226. Even though I notice we need more work for fixing edge cases (e.g. disabling IMEs on a password input) on Linux, I think this is the good starting point. (Supporting edge-cases requires complicated code and it makes hard to review.)
BUG=10953 "IME support"
BUG=11226 "Dead keys and accents input not working"
BUG=13604 "Hotkeys not working in non-us keyboard layout"
TEST=Open a web page which contains an <input> form (e.g. <http://www.google.com/>), type a '[{' key and an 'A' key on a Canadian-French keyboard, and see a Latin character "U+00E2" is displayed in the <input> form.
TEST=Open a web page which contains an <input> form (e.g. <http://www.google.com/>), enable an Chinese Pinyin IME, type a 'W' key, type an 'O' key, and see a Chinese character is displayed in the <input> form.
TEST=Change the keyboard layout to Hebrew (or Russian), open a web page which contains an <input> form, input some characters in the <input> form, type control+a, and see the text in the <input> form is selected.
Review URL: http://codereview.chromium.org/126118
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19009 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/renderer_host')
4 files changed, 201 insertions, 2 deletions
diff --git a/chrome/browser/renderer_host/render_widget_host.cc b/chrome/browser/renderer_host/render_widget_host.cc index 51d2b3b..fd734bb 100644 --- a/chrome/browser/renderer_host/render_widget_host.cc +++ b/chrome/browser/renderer_host/render_widget_host.cc @@ -424,6 +424,28 @@ void RenderWidgetHost::NotifyTextDirection() { } } +void RenderWidgetHost::ImeSetInputMode(bool activate) { + Send(new ViewMsg_ImeSetInputMode(routing_id(), activate)); +} + +void RenderWidgetHost::ImeSetComposition(const std::wstring& ime_string, + int cursor_position, + int target_start, + int target_end) { + Send(new ViewMsg_ImeSetComposition(routing_id(), 0, cursor_position, + target_start, target_end, ime_string)); +} + +void RenderWidgetHost::ImeConfirmComposition(const std::wstring& ime_string) { + Send(new ViewMsg_ImeSetComposition(routing_id(), 1, -1, -1, -1, ime_string)); +} + +void RenderWidgetHost::ImeCancelComposition() { + std::wstring empty_string; + Send(new ViewMsg_ImeSetComposition(routing_id(), -1, -1, -1, -1, + empty_string)); +} + gfx::Rect RenderWidgetHost::GetRootWindowResizerRect() const { return gfx::Rect(); } diff --git a/chrome/browser/renderer_host/render_widget_host.h b/chrome/browser/renderer_host/render_widget_host.h index c5913ef..aab4645 100644 --- a/chrome/browser/renderer_host/render_widget_host.h +++ b/chrome/browser/renderer_host/render_widget_host.h @@ -279,6 +279,45 @@ class RenderWidgetHost : public IPC::Channel::Listener { void CancelUpdateTextDirection(); void NotifyTextDirection(); + // Notifies the renderer whether or not the IME attached to this process is + // activated. + // When the IME is activated, a renderer process sends IPC messages to notify + // the status of its composition node. (This message is mainly used for + // notifying the position of the input cursor so that the browser can + // display IME windows under the cursor.) + void ImeSetInputMode(bool activate); + + // Update the composition node of the renderer (or WebKit). + // WebKit has a special node (a composition node) for IMEs to change its text + // without affecting any other DOM nodes. When the IME (attached to the + // browser) updates its text, the browser sends IPC messages to update the + // composition node of the renderer. + // (Read the comments of each function for its detail.) + + // Sets the text of the composition node. + // This function can also update the cursor position and mark the specified + // range in the composition node. + // A browser should call this function: + // * when it receives a WM_IME_COMPOSITION message with a GCS_COMPSTR flag + // (on Windows); + // * when it receives a "preedit_changed" signal of GtkIMContext (on Linux); + // * when markedText of NSTextInput is called (on Mac). + void ImeSetComposition(const std::wstring& ime_string, + int cursor_position, + int target_start, + int target_end); + + // Finishes an ongoing composition with the specified text. + // A browser should call this function: + // * when it receives a WM_IME_COMPOSITION message with a GCS_RESULTSTR flag + // (on Windows); + // * when it receives a "commit" signal of GtkIMContext (on Linux); + // * when insertText of NSTextInput is called (on Mac). + void ImeConfirmComposition(const std::wstring& ime_string); + + // Cancels an ongoing composition. + void ImeCancelComposition(); + // This is for derived classes to give us access to the resizer rect. // And to also expose it to the RenderWidgetHostView. virtual gfx::Rect GetRootWindowResizerRect() const; diff --git a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc index 73b60c9..5d42ff8 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc +++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc @@ -71,6 +71,17 @@ class RenderWidgetHostViewGtkWidget { g_signal_connect(widget, "scroll-event", G_CALLBACK(MouseScrollEvent), host_view); + // Create a GtkIMContext instance and attach its signal handlers. + host_view->im_context_ = gtk_im_multicontext_new(); + g_signal_connect(host_view->im_context_, "preedit_start", + G_CALLBACK(InputMethodPreeditStart), host_view); + g_signal_connect(host_view->im_context_, "preedit_end", + G_CALLBACK(InputMethodPreeditEnd), host_view); + g_signal_connect(host_view->im_context_, "preedit_changed", + G_CALLBACK(InputMethodPreeditChanged), host_view); + g_signal_connect(host_view->im_context_, "commit", + G_CALLBACK(InputMethodCommit), host_view); + GtkTargetList* target_list = gtk_target_list_new(NULL, 0); gtk_target_list_add_text_targets(target_list, 0); gint num_targets = 0; @@ -109,6 +120,19 @@ class RenderWidgetHostViewGtkWidget { NativeWebKeyboardEvent wke(event); host_view->GetRenderWidgetHost()->ForwardKeyboardEvent(wke); } + + // Dispatch this event to the GtkIMContext object. + // It sends a "commit" signal when it has a character to be inserted + // even when we use a US keyboard so that we can send a Char event + // (or an IME event) to the renderer in our "commit"-signal handler. + // We should send a KeyDown (or a KeyUp) event before dispatching this + // event to the GtkIMContext object (and send a Char event) so that WebKit + // can dispatch the JavaScript events in the following order: onkeydown(), + // onkeypress(), and onkeyup(). (Many JavaScript pages assume this.) + // TODO(hbono): we should not dispatch a key event when the input focus + // is in a password input? + gtk_im_context_filter_keypress(host_view->im_context_, event); + // We return TRUE because we did handle the event. If it turns out webkit // can't handle the event, we'll deal with it in // RenderView::UnhandledKeyboardEvent(). @@ -215,6 +239,68 @@ class RenderWidgetHostViewGtkWidget { return FALSE; } + static void InputMethodCommit(GtkIMContext* im_context, + gchar* text, + RenderWidgetHostViewGtk* host_view) { + std::wstring im_text = UTF8ToWide(text); + if (!host_view->im_is_composing_cjk_text_ && im_text.length() == 1) { + // Send a Char event when we input a composed character without IMEs so + // that this event is to be dispatched to onkeypress() handlers, + // autofill, etc. + ForwardCharEvent(host_view, im_text[0]); + } else { + // Send an IME event. + // Unlike a Char event, an IME event is NOT dispatched to onkeypress() + // handlers or autofill. + host_view->GetRenderWidgetHost()->ImeConfirmComposition(im_text); + } + } + + static void InputMethodPreeditStart(GtkIMContext* im_context, + RenderWidgetHostViewGtk* host_view) { + // Start monitoring IME events of the renderer. + // TODO(hbono): a renderer sends these IME events not only for sending the + // caret position, but also for enabling/disabling IMEs. If we need to + // enable/disable IMEs, we should move this code to a better place. + // (This signal handler is called only when an IME is enabled. So, once + // we disable an IME, we cannot receive any IME events from the renderer, + // i.e. we cannot re-enable the IME any longer.) + host_view->GetRenderWidgetHost()->ImeSetInputMode(true); + host_view->im_is_composing_cjk_text_ = true; + } + + static void InputMethodPreeditEnd(GtkIMContext* im_context, + RenderWidgetHostViewGtk* host_view) { + // End monitoring IME events. + host_view->GetRenderWidgetHost()->ImeSetInputMode(false); + host_view->im_is_composing_cjk_text_ = false; + } + + static void InputMethodPreeditChanged(GtkIMContext* im_context, + RenderWidgetHostViewGtk* host_view) { + // Send an IME event to update the composition node of the renderer. + // TODO(hbono): an IME intercepts all key events while composing a text, + // i.e. we cannot receive any GDK_KEY_PRESS (or GDK_KEY_UP) events. + // Should we send pseudo KeyDown (and KeyUp) events to emulate Windows? + gchar* preedit_text = NULL; + gint cursor_position = 0; + gtk_im_context_get_preedit_string(im_context, &preedit_text, NULL, + &cursor_position); + host_view->GetRenderWidgetHost()->ImeSetComposition( + UTF8ToWide(preedit_text), cursor_position, -1, -1); + g_free(preedit_text); + } + + static void ForwardCharEvent(RenderWidgetHostViewGtk* host_view, + wchar_t im_character) { + if (!im_character) + return; + + NativeWebKeyboardEvent char_event(im_character, + base::Time::Now().ToDoubleT()); + host_view->GetRenderWidgetHost()->ForwardKeyboardEvent(char_event); + } + DISALLOW_IMPLICIT_CONSTRUCTORS(RenderWidgetHostViewGtkWidget); }; @@ -232,11 +318,15 @@ RenderWidgetHostViewGtk::RenderWidgetHostViewGtk(RenderWidgetHost* widget_host) is_showing_context_menu_(false), parent_host_view_(NULL), parent_(NULL), - is_popup_first_mouse_release_(true) { + is_popup_first_mouse_release_(true), + im_context_(NULL), + im_is_composing_cjk_text_(false) { host_->set_view(this); } RenderWidgetHostViewGtk::~RenderWidgetHostViewGtk() { + if (im_context_) + g_object_unref(im_context_); view_.Destroy(); } @@ -396,7 +486,32 @@ void RenderWidgetHostViewGtk::SetIsLoading(bool is_loading) { void RenderWidgetHostViewGtk::IMEUpdateStatus(int control, const gfx::Rect& caret_rect) { - NOTIMPLEMENTED(); + // The renderer has updated its IME status. + // Control the GtkIMContext object according to this status. + if (!im_context_) + return; + + if (control == IME_DISABLE) { + // TODO(hbono): this code just resets the GtkIMContext object and + // detaches it from this window. Should we prevent sending key events to + // the GtkIMContext object (or unref it) when we disable IMEs? + gtk_im_context_reset(im_context_); + gtk_im_context_set_client_window(im_context_, NULL); + gtk_im_context_set_cursor_location(im_context_, NULL); + } else { + // TODO(hbono): we should finish (not reset) an ongoing composition + // when |control| is IME_COMPLETE_COMPOSITION. + + // Attach the GtkIMContext object to this window. + gtk_im_context_set_client_window(im_context_, view_.get()->window); + + // Updates the position of the IME candidate window. + // The position sent from the renderer is a relative one, so we need to + // attach the GtkIMContext object to this window before changing the + // position. + GdkRectangle cursor_rect(caret_rect.ToGdkRectangle()); + gtk_im_context_set_cursor_location(im_context_, &cursor_rect); + } } void RenderWidgetHostViewGtk::DidPaintRect(const gfx::Rect& rect) { diff --git a/chrome/browser/renderer_host/render_widget_host_view_gtk.h b/chrome/browser/renderer_host/render_widget_host_view_gtk.h index af0ac47..e727e08 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_gtk.h +++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.h @@ -18,6 +18,7 @@ class RenderWidgetHost; typedef struct _GtkClipboard GtkClipboard; typedef struct _GtkSelectionData GtkSelectionData; +typedef struct _GtkIMContext GtkIMContext; // ----------------------------------------------------------------------------- // See comments in render_widget_host_view.h about this class and its members. @@ -109,6 +110,28 @@ class RenderWidgetHostViewGtk : public RenderWidgetHostView { // We ignore the first mouse release on popups. This allows the popup to // stay open. bool is_popup_first_mouse_release_; + + // The GtkIMContext object. + // In terms of the DOM event specification Appendix A + // <http://www.w3.org/TR/DOM-Level-3-Events/keyset.html>, + // GTK uses a GtkIMContext object for the following two purposes: + // 1. Composing Latin characters (A.1.2), and; + // 2. Composing CJK characters with an IME (A.1.3). + // Many JavaScript pages assume composed Latin characters are dispatched to + // their onkeypress() handlers but not dispatched CJK characters composed + // with an IME. To emulate this behavior, we should monitor the status of + // this GtkIMContext object and prevent sending Char events when a + // GtkIMContext object sends a "commit" signal with the CJK characters + // composed by an IME. + GtkIMContext* im_context_; + + // Whether or not the above GtkIMContext is composing a CJK text with an IME. + // The GtkIMContext object sends a "preedit_start" before it starts composing + // a CJK text and a "preedit_end" signal after it finishes composing it. + // On the other hand, the GtkIMContext object doesn't send them when + // composing Latin texts. So, we monitor the above signals to check whether + // or not the GtkIMContext object is composing a CJK text. + bool im_is_composing_cjk_text_; }; #endif // CHROME_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_VIEW_GTK_H_ |