OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/browser/renderer_host/gtk_im_context_wrapper.h" | |
6 | |
7 #include <gdk/gdk.h> | |
8 #include <gdk/gdkkeysyms.h> | |
9 #include <gtk/gtk.h> | |
10 | |
11 #include <algorithm> | |
12 | |
13 #include "base/logging.h" | |
14 #include "base/string_util.h" | |
15 #include "base/third_party/icu/icu_utf.h" | |
16 #include "base/utf_string_conversions.h" | |
17 #include "chrome/app/chrome_command_ids.h" | |
18 #include "chrome/browser/ui/gtk/gtk_util.h" | |
19 #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h" | |
20 #include "chrome/common/render_messages.h" | |
21 #include "content/browser/renderer_host/render_widget_host.h" | |
22 #include "content/common/native_web_keyboard_event.h" | |
23 #include "grit/generated_resources.h" | |
24 #include "third_party/skia/include/core/SkColor.h" | |
25 #include "third_party/WebKit/Source/WebKit/chromium/public/WebCompositionUnderli
ne.h" | |
26 #include "ui/base/gtk/gtk_im_context_util.h" | |
27 #include "ui/base/l10n/l10n_util.h" | |
28 #include "ui/gfx/gtk_util.h" | |
29 #include "ui/gfx/rect.h" | |
30 | |
31 #if !defined(TOOLKIT_VIEWS) | |
32 #include "chrome/browser/ui/gtk/menu_gtk.h" | |
33 #endif | |
34 | |
35 namespace { | |
36 // Copied from third_party/WebKit/Source/WebCore/page/EventHandler.cpp | |
37 // | |
38 // Match key code of composition keydown event on windows. | |
39 // IE sends VK_PROCESSKEY which has value 229; | |
40 // | |
41 // Please refer to following documents for detals: | |
42 // - Virtual-Key Codes | |
43 // http://msdn.microsoft.com/en-us/library/ms645540(VS.85).aspx | |
44 // - How the IME System Works | |
45 // http://msdn.microsoft.com/en-us/library/cc194848.aspx | |
46 // - ImmGetVirtualKey Function | |
47 // http://msdn.microsoft.com/en-us/library/dd318570(VS.85).aspx | |
48 const int kCompositionEventKeyCode = 229; | |
49 } // namespace | |
50 | |
51 // ui::CompositionUnderline should be identical to | |
52 // WebKit::WebCompositionUnderline, so that we can do reinterpret_cast safely. | |
53 // TODO(suzhe): remove it after migrating all code in chrome to use | |
54 // ui::CompositionUnderline. | |
55 COMPILE_ASSERT(sizeof(ui::CompositionUnderline) == | |
56 sizeof(WebKit::WebCompositionUnderline), | |
57 ui_CompositionUnderline__WebKit_WebCompositionUnderline_diff); | |
58 | |
59 GtkIMContextWrapper::GtkIMContextWrapper(RenderWidgetHostViewGtk* host_view) | |
60 : host_view_(host_view), | |
61 context_(gtk_im_multicontext_new()), | |
62 context_simple_(gtk_im_context_simple_new()), | |
63 is_focused_(false), | |
64 is_composing_text_(false), | |
65 is_enabled_(false), | |
66 is_in_key_event_handler_(false), | |
67 is_composition_changed_(false), | |
68 suppress_next_commit_(false), | |
69 last_key_code_(0), | |
70 last_key_was_up_(false), | |
71 last_key_filtered_no_result_(false) { | |
72 DCHECK(context_); | |
73 DCHECK(context_simple_); | |
74 | |
75 // context_ and context_simple_ share the same callback handlers. | |
76 // All data come from them are treated equally. | |
77 // context_ is for full input method support. | |
78 // context_simple_ is for supporting dead/compose keys when input method is | |
79 // disabled by webkit, eg. in password input box. | |
80 g_signal_connect(context_, "preedit_start", | |
81 G_CALLBACK(HandlePreeditStartThunk), this); | |
82 g_signal_connect(context_, "preedit_end", | |
83 G_CALLBACK(HandlePreeditEndThunk), this); | |
84 g_signal_connect(context_, "preedit_changed", | |
85 G_CALLBACK(HandlePreeditChangedThunk), this); | |
86 g_signal_connect(context_, "commit", | |
87 G_CALLBACK(HandleCommitThunk), this); | |
88 | |
89 g_signal_connect(context_simple_, "preedit_start", | |
90 G_CALLBACK(HandlePreeditStartThunk), this); | |
91 g_signal_connect(context_simple_, "preedit_end", | |
92 G_CALLBACK(HandlePreeditEndThunk), this); | |
93 g_signal_connect(context_simple_, "preedit_changed", | |
94 G_CALLBACK(HandlePreeditChangedThunk), this); | |
95 g_signal_connect(context_simple_, "commit", | |
96 G_CALLBACK(HandleCommitThunk), this); | |
97 | |
98 GtkWidget* widget = host_view->native_view(); | |
99 DCHECK(widget); | |
100 | |
101 g_signal_connect(widget, "realize", | |
102 G_CALLBACK(HandleHostViewRealizeThunk), this); | |
103 g_signal_connect(widget, "unrealize", | |
104 G_CALLBACK(HandleHostViewUnrealizeThunk), this); | |
105 | |
106 // Set client window if the widget is already realized. | |
107 HandleHostViewRealize(widget); | |
108 } | |
109 | |
110 GtkIMContextWrapper::~GtkIMContextWrapper() { | |
111 if (context_) | |
112 g_object_unref(context_); | |
113 if (context_simple_) | |
114 g_object_unref(context_simple_); | |
115 } | |
116 | |
117 void GtkIMContextWrapper::ProcessKeyEvent(GdkEventKey* event) { | |
118 suppress_next_commit_ = false; | |
119 | |
120 // Indicates preedit-changed and commit signal handlers that we are | |
121 // processing a key event. | |
122 is_in_key_event_handler_ = true; | |
123 // Reset this flag so that we can know if preedit is changed after | |
124 // processing this key event. | |
125 is_composition_changed_ = false; | |
126 // Clear it so that we can know if something needs committing after | |
127 // processing this key event. | |
128 commit_text_.clear(); | |
129 | |
130 // According to Document Object Model (DOM) Level 3 Events Specification | |
131 // Appendix A: Keyboard events and key identifiers | |
132 // http://www.w3.org/TR/DOM-Level-3-Events/keyset.html: | |
133 // The event sequence would be: | |
134 // 1. keydown | |
135 // 2. textInput | |
136 // 3. keyup | |
137 // | |
138 // So keydown must be sent to webkit before sending input method result, | |
139 // while keyup must be sent afterwards. | |
140 // However on Windows, if a keydown event has been processed by IME, its | |
141 // virtual keycode will be changed to VK_PROCESSKEY(0xE5) before being sent | |
142 // to application. | |
143 // To emulate the windows behavior as much as possible, we need to send the | |
144 // key event to the GtkIMContext object first, and decide whether or not to | |
145 // send the original key event to webkit according to the result from IME. | |
146 // | |
147 // If IME is enabled by WebKit, this event will be dispatched to context_ | |
148 // to get full IME support. Otherwise it'll be dispatched to | |
149 // context_simple_, so that dead/compose keys can still work. | |
150 // | |
151 // It sends a "commit" signal when it has a character to be inserted | |
152 // even when we use a US keyboard so that we can send a Char event | |
153 // (or an IME event) to the renderer in our "commit"-signal handler. | |
154 // We should send a KeyDown (or a KeyUp) event before dispatching this | |
155 // event to the GtkIMContext object (and send a Char event) so that WebKit | |
156 // can dispatch the JavaScript events in the following order: onkeydown(), | |
157 // onkeypress(), and onkeyup(). (Many JavaScript pages assume this.) | |
158 gboolean filtered = false; | |
159 if (is_enabled_) { | |
160 filtered = gtk_im_context_filter_keypress(context_, event); | |
161 } else { | |
162 filtered = gtk_im_context_filter_keypress(context_simple_, event); | |
163 } | |
164 | |
165 // Reset this flag here, as it's only used in input method callbacks. | |
166 is_in_key_event_handler_ = false; | |
167 | |
168 NativeWebKeyboardEvent wke(event); | |
169 | |
170 // If the key event was handled by the input method, then we need to prevent | |
171 // RenderView::UnhandledKeyboardEvent() from processing it. | |
172 // Otherwise unexpected result may occur. For example if it's a | |
173 // Backspace key event, the browser may go back to previous page. | |
174 // We just send all keyup events to the browser to avoid breaking the | |
175 // browser's MENU key function, which is actually the only keyup event | |
176 // handled in the browser. | |
177 if (filtered && event->type == GDK_KEY_PRESS) | |
178 wke.skip_in_browser = true; | |
179 | |
180 const int key_code = wke.windowsKeyCode; | |
181 const bool has_result = HasInputMethodResult(); | |
182 | |
183 // Send filtered keydown event before sending IME result. | |
184 // In order to workaround http://crosbug.com/6582, we only send a filtered | |
185 // keydown event if it generated any input method result. | |
186 if (event->type == GDK_KEY_PRESS && filtered && has_result) | |
187 ProcessFilteredKeyPressEvent(&wke); | |
188 | |
189 // Send IME results. In most cases, it's only available if the key event | |
190 // is filtered by IME. But in rare cases, an unfiltered key event may also | |
191 // generate IME results. | |
192 // Any IME results generated by a unfiltered key down event must be sent | |
193 // before the key down event, to avoid some tricky issues. For example, | |
194 // when using latin-post input method, pressing 'a' then Backspace, may | |
195 // generate following events in sequence: | |
196 // 1. keydown 'a' (filtered) | |
197 // 2. preedit changed to "a" | |
198 // 3. keyup 'a' (unfiltered) | |
199 // 4. keydown Backspace (unfiltered) | |
200 // 5. commit "a" | |
201 // 6. preedit end | |
202 // 7. keyup Backspace (unfiltered) | |
203 // | |
204 // In this case, the input box will be in a strange state if keydown | |
205 // Backspace is sent to webkit before commit "a" and preedit end. | |
206 if (has_result) | |
207 ProcessInputMethodResult(event, filtered); | |
208 | |
209 // Send unfiltered keydown and keyup events after sending IME result. | |
210 if (event->type == GDK_KEY_PRESS && !filtered) { | |
211 ProcessUnfilteredKeyPressEvent(&wke); | |
212 } else if (event->type == GDK_KEY_RELEASE) { | |
213 // In order to workaround http://crosbug.com/6582, we need to suppress | |
214 // the keyup event if corresponding keydown event was suppressed, or | |
215 // the last key event was a keyup event with the same keycode. | |
216 const bool suppress = (last_key_code_ == key_code) && | |
217 (last_key_was_up_ || last_key_filtered_no_result_); | |
218 | |
219 if (!suppress) | |
220 host_view_->ForwardKeyboardEvent(wke); | |
221 } | |
222 | |
223 last_key_code_ = key_code; | |
224 last_key_was_up_ = (event->type == GDK_KEY_RELEASE); | |
225 last_key_filtered_no_result_ = (filtered && !has_result); | |
226 } | |
227 | |
228 void GtkIMContextWrapper::UpdateInputMethodState( | |
229 ui::TextInputType type, | |
230 bool can_compose_inline, | |
231 const gfx::Rect& caret_rect) { | |
232 suppress_next_commit_ = false; | |
233 | |
234 // The renderer has updated its IME status. | |
235 // Control the GtkIMContext object according to this status. | |
236 if (!context_ || !is_focused_) | |
237 return; | |
238 | |
239 DCHECK(!is_in_key_event_handler_); | |
240 | |
241 bool is_enabled = (type != ui::TEXT_INPUT_TYPE_NONE && | |
242 type != ui::TEXT_INPUT_TYPE_PASSWORD); | |
243 if (is_enabled_ != is_enabled) { | |
244 is_enabled_ = is_enabled; | |
245 if (is_enabled) | |
246 gtk_im_context_focus_in(context_); | |
247 else | |
248 gtk_im_context_focus_out(context_); | |
249 } | |
250 | |
251 if (is_enabled) { | |
252 // If the focused element supports inline rendering of composition text, | |
253 // we receive and send related events to it. Otherwise, the events related | |
254 // to the updates of composition text are directed to the candidate window. | |
255 gtk_im_context_set_use_preedit(context_, can_compose_inline); | |
256 // Updates the position of the IME candidate window. | |
257 // The position sent from the renderer is a relative one, so we need to | |
258 // attach the GtkIMContext object to this window before changing the | |
259 // position. | |
260 GdkRectangle cursor_rect(caret_rect.ToGdkRectangle()); | |
261 gtk_im_context_set_cursor_location(context_, &cursor_rect); | |
262 } | |
263 } | |
264 | |
265 void GtkIMContextWrapper::OnFocusIn() { | |
266 if (is_focused_) | |
267 return; | |
268 | |
269 // Tracks the focused state so that we can give focus to the | |
270 // GtkIMContext object correctly later when IME is enabled by WebKit. | |
271 is_focused_ = true; | |
272 | |
273 last_key_code_ = 0; | |
274 last_key_was_up_ = false; | |
275 last_key_filtered_no_result_ = false; | |
276 | |
277 // Notify the GtkIMContext object of this focus-in event only if IME is | |
278 // enabled by WebKit. | |
279 if (is_enabled_) | |
280 gtk_im_context_focus_in(context_); | |
281 | |
282 // context_simple_ is always enabled. | |
283 // Actually it doesn't care focus state at all. | |
284 gtk_im_context_focus_in(context_simple_); | |
285 | |
286 // Enables RenderWidget's IME related events, so that we can be notified | |
287 // when WebKit wants to enable or disable IME. | |
288 if (host_view_->GetRenderWidgetHost()) | |
289 host_view_->GetRenderWidgetHost()->SetInputMethodActive(true); | |
290 } | |
291 | |
292 void GtkIMContextWrapper::OnFocusOut() { | |
293 if (!is_focused_) | |
294 return; | |
295 | |
296 // Tracks the focused state so that we won't give focus to the | |
297 // GtkIMContext object unexpectly. | |
298 is_focused_ = false; | |
299 | |
300 // Notify the GtkIMContext object of this focus-out event only if IME is | |
301 // enabled by WebKit. | |
302 if (is_enabled_) { | |
303 // To reset the GtkIMContext object and prevent data loss. | |
304 ConfirmComposition(); | |
305 gtk_im_context_focus_out(context_); | |
306 } | |
307 | |
308 // To make sure it'll be in correct state when focused in again. | |
309 gtk_im_context_reset(context_simple_); | |
310 gtk_im_context_focus_out(context_simple_); | |
311 | |
312 is_composing_text_ = false; | |
313 | |
314 // Disable RenderWidget's IME related events to save bandwidth. | |
315 if (host_view_->GetRenderWidgetHost()) | |
316 host_view_->GetRenderWidgetHost()->SetInputMethodActive(false); | |
317 } | |
318 | |
319 #if !defined(TOOLKIT_VIEWS) | |
320 // Not defined for views because the views context menu doesn't | |
321 // implement input methods yet. | |
322 void GtkIMContextWrapper::AppendInputMethodsContextMenu(MenuGtk* menu) { | |
323 gboolean show_input_method_menu = TRUE; | |
324 | |
325 g_object_get(gtk_widget_get_settings(GTK_WIDGET(host_view_->native_view())), | |
326 "gtk-show-input-method-menu", &show_input_method_menu, NULL); | |
327 if (!show_input_method_menu) | |
328 return; | |
329 | |
330 std::string label = gfx::ConvertAcceleratorsFromWindowsStyle( | |
331 l10n_util::GetStringUTF8(IDS_CONTENT_CONTEXT_INPUT_METHODS_MENU)); | |
332 GtkWidget* menuitem = gtk_menu_item_new_with_mnemonic(label.c_str()); | |
333 GtkWidget* submenu = gtk_menu_new(); | |
334 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu); | |
335 gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(context_), | |
336 GTK_MENU_SHELL(submenu)); | |
337 menu->AppendSeparator(); | |
338 menu->AppendMenuItem(IDC_INPUT_METHODS_MENU, menuitem); | |
339 } | |
340 #endif | |
341 | |
342 void GtkIMContextWrapper::CancelComposition() { | |
343 if (!is_enabled_) | |
344 return; | |
345 | |
346 DCHECK(!is_in_key_event_handler_); | |
347 | |
348 // To prevent any text from being committed when resetting the |context_|; | |
349 is_in_key_event_handler_ = true; | |
350 suppress_next_commit_ = true; | |
351 | |
352 gtk_im_context_reset(context_); | |
353 gtk_im_context_reset(context_simple_); | |
354 | |
355 if (is_focused_) { | |
356 // Some input methods may not honour the reset call. Focusing out/in the | |
357 // |context_| to make sure it gets reset correctly. | |
358 gtk_im_context_focus_out(context_); | |
359 gtk_im_context_focus_in(context_); | |
360 } | |
361 | |
362 is_composing_text_ = false; | |
363 composition_.Clear(); | |
364 commit_text_.clear(); | |
365 | |
366 is_in_key_event_handler_ = false; | |
367 } | |
368 | |
369 bool GtkIMContextWrapper::NeedCommitByForwardingCharEvent() const { | |
370 // If there is no composition text and has only one character to be | |
371 // committed, then the character will be send to webkit as a Char event | |
372 // instead of a confirmed composition text. | |
373 // It should be fine to handle BMP character only, as non-BMP characters | |
374 // can always be committed as confirmed composition text. | |
375 return !is_composing_text_ && commit_text_.length() == 1; | |
376 } | |
377 | |
378 bool GtkIMContextWrapper::HasInputMethodResult() const { | |
379 return commit_text_.length() || is_composition_changed_; | |
380 } | |
381 | |
382 void GtkIMContextWrapper::ProcessFilteredKeyPressEvent( | |
383 NativeWebKeyboardEvent* wke) { | |
384 // If IME has filtered this event, then replace virtual key code with | |
385 // VK_PROCESSKEY. See comment in ProcessKeyEvent() for details. | |
386 // It's only required for keydown events. | |
387 // To emulate windows behavior, when input method is enabled, if the commit | |
388 // text can be emulated by a Char event, then don't do this replacement. | |
389 if (!NeedCommitByForwardingCharEvent()) { | |
390 wke->windowsKeyCode = kCompositionEventKeyCode; | |
391 // keyidentifier must be updated accordingly, otherwise this key event may | |
392 // still be processed by webkit. | |
393 wke->setKeyIdentifierFromWindowsKeyCode(); | |
394 } | |
395 host_view_->ForwardKeyboardEvent(*wke); | |
396 } | |
397 | |
398 void GtkIMContextWrapper::ProcessUnfilteredKeyPressEvent( | |
399 NativeWebKeyboardEvent* wke) { | |
400 // Send keydown event as it, because it's not filtered by IME. | |
401 host_view_->ForwardKeyboardEvent(*wke); | |
402 | |
403 // IME is disabled by WebKit or the GtkIMContext object cannot handle | |
404 // this key event. | |
405 // This case is caused by two reasons: | |
406 // 1. The given key event is a control-key event, (e.g. return, page up, | |
407 // page down, tab, arrows, etc.) or; | |
408 // 2. The given key event is not a control-key event but printable | |
409 // characters aren't assigned to the event, (e.g. alt+d, etc.) | |
410 // Create a Char event manually from this key event and send it to the | |
411 // renderer when this Char event contains a printable character which | |
412 // should be processed by WebKit. | |
413 // isSystemKey will be set to true if this key event has Alt modifier, | |
414 // see WebInputEventFactory::keyboardEvent() for details. | |
415 if (wke->text[0]) { | |
416 wke->type = WebKit::WebInputEvent::Char; | |
417 wke->skip_in_browser = true; | |
418 host_view_->ForwardKeyboardEvent(*wke); | |
419 } | |
420 } | |
421 | |
422 void GtkIMContextWrapper::ProcessInputMethodResult(const GdkEventKey* event, | |
423 bool filtered) { | |
424 RenderWidgetHost* host = host_view_->GetRenderWidgetHost(); | |
425 if (!host) | |
426 return; | |
427 | |
428 bool committed = false; | |
429 // We do commit before preedit change, so that we can optimize some | |
430 // unnecessary preedit changes. | |
431 if (commit_text_.length()) { | |
432 if (filtered && NeedCommitByForwardingCharEvent()) { | |
433 // Send a Char event when we input a composed character without IMEs | |
434 // so that this event is to be dispatched to onkeypress() handlers, | |
435 // autofill, etc. | |
436 // Only commit text generated by a filtered key down event can be sent | |
437 // as a Char event, because a unfiltered key down event will probably | |
438 // generate another Char event. | |
439 // TODO(james.su@gmail.com): Is it necessary to support non BMP chars | |
440 // here? | |
441 NativeWebKeyboardEvent char_event(commit_text_[0], | |
442 event->state, | |
443 base::Time::Now().ToDoubleT()); | |
444 char_event.skip_in_browser = true; | |
445 host_view_->ForwardKeyboardEvent(char_event); | |
446 } else { | |
447 committed = true; | |
448 // Send an IME event. | |
449 // Unlike a Char event, an IME event is NOT dispatched to onkeypress() | |
450 // handlers or autofill. | |
451 host->ImeConfirmComposition(commit_text_); | |
452 // Set this flag to false, as this composition session has been | |
453 // finished. | |
454 is_composing_text_ = false; | |
455 } | |
456 } | |
457 | |
458 // Send preedit text only if it's changed. | |
459 // If a text has been committed, then we don't need to send the empty | |
460 // preedit text again. | |
461 if (is_composition_changed_) { | |
462 if (composition_.text.length()) { | |
463 // Another composition session has been started. | |
464 is_composing_text_ = true; | |
465 // TODO(suzhe): convert both renderer_host and renderer to use | |
466 // ui::CompositionText. | |
467 const std::vector<WebKit::WebCompositionUnderline>& underlines = | |
468 reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>( | |
469 composition_.underlines); | |
470 host->ImeSetComposition(composition_.text, underlines, | |
471 composition_.selection.start(), | |
472 composition_.selection.end()); | |
473 } else if (!committed) { | |
474 host->ImeCancelComposition(); | |
475 } | |
476 } | |
477 } | |
478 | |
479 void GtkIMContextWrapper::ConfirmComposition() { | |
480 if (!is_enabled_) | |
481 return; | |
482 | |
483 DCHECK(!is_in_key_event_handler_); | |
484 | |
485 if (is_composing_text_) { | |
486 if (host_view_->GetRenderWidgetHost()) | |
487 host_view_->GetRenderWidgetHost()->ImeConfirmComposition(); | |
488 | |
489 // Reset the input method. | |
490 CancelComposition(); | |
491 } | |
492 } | |
493 | |
494 void GtkIMContextWrapper::HandleCommit(const string16& text) { | |
495 if (suppress_next_commit_) { | |
496 suppress_next_commit_ = false; | |
497 return; | |
498 } | |
499 | |
500 // Append the text to the buffer, because commit signal might be fired | |
501 // multiple times when processing a key event. | |
502 commit_text_.append(text); | |
503 // Nothing needs to do, if it's currently in ProcessKeyEvent() | |
504 // handler, which will send commit text to webkit later. Otherwise, | |
505 // we need send it here. | |
506 // It's possible that commit signal is fired without a key event, for | |
507 // example when user input via a voice or handwriting recognition software. | |
508 // In this case, the text must be committed directly. | |
509 if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) { | |
510 // Workaround http://crbug.com/45478 by sending fake key down/up events. | |
511 SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown); | |
512 host_view_->GetRenderWidgetHost()->ImeConfirmComposition(text); | |
513 SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp); | |
514 } | |
515 } | |
516 | |
517 void GtkIMContextWrapper::HandlePreeditStart() { | |
518 // Ignore preedit related signals triggered by CancelComposition() method. | |
519 if (suppress_next_commit_) | |
520 return; | |
521 is_composing_text_ = true; | |
522 } | |
523 | |
524 void GtkIMContextWrapper::HandlePreeditChanged(const gchar* text, | |
525 PangoAttrList* attrs, | |
526 int cursor_position) { | |
527 // Ignore preedit related signals triggered by CancelComposition() method. | |
528 if (suppress_next_commit_) | |
529 return; | |
530 | |
531 // Don't set is_composition_changed_ to false if there is no change, because | |
532 // this handler might be called multiple times with the same data. | |
533 is_composition_changed_ = true; | |
534 composition_.Clear(); | |
535 | |
536 ui::ExtractCompositionTextFromGtkPreedit(text, attrs, cursor_position, | |
537 &composition_); | |
538 | |
539 // TODO(suzhe): due to a bug of webkit, we currently can't use selection range | |
540 // with composition string. See: https://bugs.webkit.org/show_bug.cgi?id=40805 | |
541 composition_.selection = ui::Range(cursor_position); | |
542 | |
543 // In case we are using a buggy input method which doesn't fire | |
544 // "preedit_start" signal. | |
545 if (composition_.text.length()) | |
546 is_composing_text_ = true; | |
547 | |
548 // Nothing needs to do, if it's currently in ProcessKeyEvent() | |
549 // handler, which will send preedit text to webkit later. | |
550 // Otherwise, we need send it here if it's been changed. | |
551 if (!is_in_key_event_handler_ && is_composing_text_ && | |
552 host_view_->GetRenderWidgetHost()) { | |
553 // Workaround http://crbug.com/45478 by sending fake key down/up events. | |
554 SendFakeCompositionKeyEvent(WebKit::WebInputEvent::RawKeyDown); | |
555 // TODO(suzhe): convert both renderer_host and renderer to use | |
556 // ui::CompositionText. | |
557 const std::vector<WebKit::WebCompositionUnderline>& underlines = | |
558 reinterpret_cast<const std::vector<WebKit::WebCompositionUnderline>&>( | |
559 composition_.underlines); | |
560 host_view_->GetRenderWidgetHost()->ImeSetComposition( | |
561 composition_.text, underlines, composition_.selection.start(), | |
562 composition_.selection.end()); | |
563 SendFakeCompositionKeyEvent(WebKit::WebInputEvent::KeyUp); | |
564 } | |
565 } | |
566 | |
567 void GtkIMContextWrapper::HandlePreeditEnd() { | |
568 if (composition_.text.length()) { | |
569 // The composition session has been finished. | |
570 composition_.Clear(); | |
571 is_composition_changed_ = true; | |
572 | |
573 // If there is still a preedit text when firing "preedit-end" signal, | |
574 // we need inform webkit to clear it. | |
575 // It's only necessary when it's not in ProcessKeyEvent (). | |
576 if (!is_in_key_event_handler_ && host_view_->GetRenderWidgetHost()) | |
577 host_view_->GetRenderWidgetHost()->ImeCancelComposition(); | |
578 } | |
579 | |
580 // Don't set is_composing_text_ to false here, because "preedit_end" | |
581 // signal may be fired before "commit" signal. | |
582 } | |
583 | |
584 void GtkIMContextWrapper::HandleHostViewRealize(GtkWidget* widget) { | |
585 // We should only set im context's client window once, because when setting | |
586 // client window.im context may destroy and recreate its internal states and | |
587 // objects. | |
588 if (widget->window) { | |
589 gtk_im_context_set_client_window(context_, widget->window); | |
590 gtk_im_context_set_client_window(context_simple_, widget->window); | |
591 } | |
592 } | |
593 | |
594 void GtkIMContextWrapper::HandleHostViewUnrealize() { | |
595 gtk_im_context_set_client_window(context_, NULL); | |
596 gtk_im_context_set_client_window(context_simple_, NULL); | |
597 } | |
598 | |
599 void GtkIMContextWrapper::SendFakeCompositionKeyEvent( | |
600 WebKit::WebInputEvent::Type type) { | |
601 NativeWebKeyboardEvent fake_event; | |
602 fake_event.windowsKeyCode = kCompositionEventKeyCode; | |
603 fake_event.skip_in_browser = true; | |
604 fake_event.type = type; | |
605 host_view_->ForwardKeyboardEvent(fake_event); | |
606 } | |
607 | |
608 void GtkIMContextWrapper::HandleCommitThunk( | |
609 GtkIMContext* context, gchar* text, GtkIMContextWrapper* self) { | |
610 self->HandleCommit(UTF8ToUTF16(text)); | |
611 } | |
612 | |
613 void GtkIMContextWrapper::HandlePreeditStartThunk( | |
614 GtkIMContext* context, GtkIMContextWrapper* self) { | |
615 self->HandlePreeditStart(); | |
616 } | |
617 | |
618 void GtkIMContextWrapper::HandlePreeditChangedThunk( | |
619 GtkIMContext* context, GtkIMContextWrapper* self) { | |
620 gchar* text = NULL; | |
621 PangoAttrList* attrs = NULL; | |
622 gint cursor_position = 0; | |
623 gtk_im_context_get_preedit_string(context, &text, &attrs, &cursor_position); | |
624 self->HandlePreeditChanged(text, attrs, cursor_position); | |
625 g_free(text); | |
626 pango_attr_list_unref(attrs); | |
627 } | |
628 | |
629 void GtkIMContextWrapper::HandlePreeditEndThunk( | |
630 GtkIMContext* context, GtkIMContextWrapper* self) { | |
631 self->HandlePreeditEnd(); | |
632 } | |
633 | |
634 void GtkIMContextWrapper::HandleHostViewRealizeThunk( | |
635 GtkWidget* widget, GtkIMContextWrapper* self) { | |
636 self->HandleHostViewRealize(widget); | |
637 } | |
638 | |
639 void GtkIMContextWrapper::HandleHostViewUnrealizeThunk( | |
640 GtkWidget* widget, GtkIMContextWrapper* self) { | |
641 self->HandleHostViewUnrealize(); | |
642 } | |
OLD | NEW |