| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/renderer_host/gtk_im_context_wrapper.h" | 5 #include "chrome/browser/renderer_host/gtk_im_context_wrapper.h" |
| 6 | 6 |
| 7 #include <gdk/gdk.h> | 7 #include <gdk/gdk.h> |
| 8 #include <gdk/gdkkeysyms.h> | 8 #include <gdk/gdkkeysyms.h> |
| 9 #include <gtk/gtk.h> | 9 #include <gtk/gtk.h> |
| 10 #include <algorithm> | 10 #include <algorithm> |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 48 : host_view_(host_view), | 48 : host_view_(host_view), |
| 49 context_(gtk_im_multicontext_new()), | 49 context_(gtk_im_multicontext_new()), |
| 50 context_simple_(gtk_im_context_simple_new()), | 50 context_simple_(gtk_im_context_simple_new()), |
| 51 is_focused_(false), | 51 is_focused_(false), |
| 52 is_composing_text_(false), | 52 is_composing_text_(false), |
| 53 is_enabled_(false), | 53 is_enabled_(false), |
| 54 is_in_key_event_handler_(false), | 54 is_in_key_event_handler_(false), |
| 55 preedit_selection_start_(0), | 55 preedit_selection_start_(0), |
| 56 preedit_selection_end_(0), | 56 preedit_selection_end_(0), |
| 57 is_preedit_changed_(false), | 57 is_preedit_changed_(false), |
| 58 suppress_next_commit_(false) { | 58 suppress_next_commit_(false), |
| 59 last_key_code_(0), |
| 60 last_key_was_up_(false), |
| 61 last_key_filtered_no_result_(false) { |
| 59 DCHECK(context_); | 62 DCHECK(context_); |
| 60 DCHECK(context_simple_); | 63 DCHECK(context_simple_); |
| 61 | 64 |
| 62 // context_ and context_simple_ share the same callback handlers. | 65 // context_ and context_simple_ share the same callback handlers. |
| 63 // All data come from them are treated equally. | 66 // All data come from them are treated equally. |
| 64 // context_ is for full input method support. | 67 // context_ is for full input method support. |
| 65 // context_simple_ is for supporting dead/compose keys when input method is | 68 // context_simple_ is for supporting dead/compose keys when input method is |
| 66 // disabled by webkit, eg. in password input box. | 69 // disabled by webkit, eg. in password input box. |
| 67 g_signal_connect(context_, "preedit_start", | 70 g_signal_connect(context_, "preedit_start", |
| 68 G_CALLBACK(HandlePreeditStartThunk), this); | 71 G_CALLBACK(HandlePreeditStartThunk), this); |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 151 | 154 |
| 152 // Reset this flag here, as it's only used in input method callbacks. | 155 // Reset this flag here, as it's only used in input method callbacks. |
| 153 is_in_key_event_handler_ = false; | 156 is_in_key_event_handler_ = false; |
| 154 | 157 |
| 155 NativeWebKeyboardEvent wke(event); | 158 NativeWebKeyboardEvent wke(event); |
| 156 | 159 |
| 157 // If the key event was handled by the input method, then we need to prevent | 160 // If the key event was handled by the input method, then we need to prevent |
| 158 // RenderView::UnhandledKeyboardEvent() from processing it. | 161 // RenderView::UnhandledKeyboardEvent() from processing it. |
| 159 // Otherwise unexpected result may occur. For example if it's a | 162 // Otherwise unexpected result may occur. For example if it's a |
| 160 // Backspace key event, the browser may go back to previous page. | 163 // Backspace key event, the browser may go back to previous page. |
| 161 if (filtered) | 164 // We just send all keyup events to the browser to avoid breaking the |
| 165 // browser's MENU key function, which is actually the only keyup event |
| 166 // handled in the browser. |
| 167 if (filtered && event->type == GDK_KEY_PRESS) |
| 162 wke.skip_in_browser = true; | 168 wke.skip_in_browser = true; |
| 163 | 169 |
| 170 const int key_code = wke.windowsKeyCode; |
| 171 const bool has_result = HasInputMethodResult(); |
| 172 |
| 164 // Send filtered keydown event before sending IME result. | 173 // Send filtered keydown event before sending IME result. |
| 165 if (event->type == GDK_KEY_PRESS && filtered) | 174 // In order to workaround http://crosbug.com/6582, we only send a filtered |
| 175 // keydown event if it generated any input method result. |
| 176 if (event->type == GDK_KEY_PRESS && filtered && has_result) |
| 166 ProcessFilteredKeyPressEvent(&wke); | 177 ProcessFilteredKeyPressEvent(&wke); |
| 167 | 178 |
| 168 // Send IME results. In most cases, it's only available if the key event | 179 // Send IME results. In most cases, it's only available if the key event |
| 169 // is filtered by IME. But in rare cases, an unfiltered key event may also | 180 // is filtered by IME. But in rare cases, an unfiltered key event may also |
| 170 // generate IME results. | 181 // generate IME results. |
| 171 // Any IME results generated by a unfiltered key down event must be sent | 182 // Any IME results generated by a unfiltered key down event must be sent |
| 172 // before the key down event, to avoid some tricky issues. For example, | 183 // before the key down event, to avoid some tricky issues. For example, |
| 173 // when using latin-post input method, pressing 'a' then Backspace, may | 184 // when using latin-post input method, pressing 'a' then Backspace, may |
| 174 // generate following events in sequence: | 185 // generate following events in sequence: |
| 175 // 1. keydown 'a' (filtered) | 186 // 1. keydown 'a' (filtered) |
| 176 // 2. preedit changed to "a" | 187 // 2. preedit changed to "a" |
| 177 // 3. keyup 'a' (unfiltered) | 188 // 3. keyup 'a' (unfiltered) |
| 178 // 4. keydown Backspace (unfiltered) | 189 // 4. keydown Backspace (unfiltered) |
| 179 // 5. commit "a" | 190 // 5. commit "a" |
| 180 // 6. preedit end | 191 // 6. preedit end |
| 181 // 7. keyup Backspace (unfiltered) | 192 // 7. keyup Backspace (unfiltered) |
| 182 // | 193 // |
| 183 // In this case, the input box will be in a strange state if keydown | 194 // In this case, the input box will be in a strange state if keydown |
| 184 // Backspace is sent to webkit before commit "a" and preedit end. | 195 // Backspace is sent to webkit before commit "a" and preedit end. |
| 185 ProcessInputMethodResult(event, filtered); | 196 if (has_result) |
| 197 ProcessInputMethodResult(event, filtered); |
| 186 | 198 |
| 187 // Send unfiltered keydown and keyup events after sending IME result. | 199 // Send unfiltered keydown and keyup events after sending IME result. |
| 188 if (event->type == GDK_KEY_PRESS && !filtered) | 200 if (event->type == GDK_KEY_PRESS && !filtered) { |
| 189 ProcessUnfilteredKeyPressEvent(&wke); | 201 ProcessUnfilteredKeyPressEvent(&wke); |
| 190 else if (event->type == GDK_KEY_RELEASE) | 202 } else if (event->type == GDK_KEY_RELEASE) { |
| 191 host_view_->ForwardKeyboardEvent(wke); | 203 // In order to workaround http://crosbug.com/6582, we need to suppress |
| 204 // the keyup event if corresponding keydown event was suppressed, or |
| 205 // the last key event was a keyup event with the same keycode. |
| 206 const bool suppress = (last_key_code_ == key_code) && |
| 207 (last_key_was_up_ || last_key_filtered_no_result_); |
| 208 |
| 209 if (!suppress) |
| 210 host_view_->ForwardKeyboardEvent(wke); |
| 211 } |
| 212 |
| 213 last_key_code_ = key_code; |
| 214 last_key_was_up_ = (event->type == GDK_KEY_RELEASE); |
| 215 last_key_filtered_no_result_ = (filtered && !has_result); |
| 192 } | 216 } |
| 193 | 217 |
| 194 void GtkIMContextWrapper::UpdateInputMethodState(WebKit::WebTextInputType type, | 218 void GtkIMContextWrapper::UpdateInputMethodState(WebKit::WebTextInputType type, |
| 195 const gfx::Rect& caret_rect) { | 219 const gfx::Rect& caret_rect) { |
| 196 suppress_next_commit_ = false; | 220 suppress_next_commit_ = false; |
| 197 | 221 |
| 198 // The renderer has updated its IME status. | 222 // The renderer has updated its IME status. |
| 199 // Control the GtkIMContext object according to this status. | 223 // Control the GtkIMContext object according to this status. |
| 200 if (!context_ || !is_focused_) | 224 if (!context_ || !is_focused_) |
| 201 return; | 225 return; |
| (...skipping 20 matching lines...) Expand all Loading... |
| 222 } | 246 } |
| 223 | 247 |
| 224 void GtkIMContextWrapper::OnFocusIn() { | 248 void GtkIMContextWrapper::OnFocusIn() { |
| 225 if (is_focused_) | 249 if (is_focused_) |
| 226 return; | 250 return; |
| 227 | 251 |
| 228 // Tracks the focused state so that we can give focus to the | 252 // Tracks the focused state so that we can give focus to the |
| 229 // GtkIMContext object correctly later when IME is enabled by WebKit. | 253 // GtkIMContext object correctly later when IME is enabled by WebKit. |
| 230 is_focused_ = true; | 254 is_focused_ = true; |
| 231 | 255 |
| 256 last_key_code_ = 0; |
| 257 last_key_was_up_ = false; |
| 258 last_key_filtered_no_result_ = false; |
| 259 |
| 232 // Notify the GtkIMContext object of this focus-in event only if IME is | 260 // Notify the GtkIMContext object of this focus-in event only if IME is |
| 233 // enabled by WebKit. | 261 // enabled by WebKit. |
| 234 if (is_enabled_) | 262 if (is_enabled_) |
| 235 gtk_im_context_focus_in(context_); | 263 gtk_im_context_focus_in(context_); |
| 236 | 264 |
| 237 // context_simple_ is always enabled. | 265 // context_simple_ is always enabled. |
| 238 // Actually it doesn't care focus state at all. | 266 // Actually it doesn't care focus state at all. |
| 239 gtk_im_context_focus_in(context_simple_); | 267 gtk_im_context_focus_in(context_simple_); |
| 240 | 268 |
| 241 // Enables RenderWidget's IME related events, so that we can be notified | 269 // Enables RenderWidget's IME related events, so that we can be notified |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 315 } | 343 } |
| 316 | 344 |
| 317 is_composing_text_ = false; | 345 is_composing_text_ = false; |
| 318 preedit_text_.clear(); | 346 preedit_text_.clear(); |
| 319 preedit_underlines_.clear(); | 347 preedit_underlines_.clear(); |
| 320 commit_text_.clear(); | 348 commit_text_.clear(); |
| 321 | 349 |
| 322 is_in_key_event_handler_ = false; | 350 is_in_key_event_handler_ = false; |
| 323 } | 351 } |
| 324 | 352 |
| 325 bool GtkIMContextWrapper::NeedCommitByForwardingCharEvent() { | 353 bool GtkIMContextWrapper::NeedCommitByForwardingCharEvent() const { |
| 326 // If there is no composition text and has only one character to be | 354 // If there is no composition text and has only one character to be |
| 327 // committed, then the character will be send to webkit as a Char event | 355 // committed, then the character will be send to webkit as a Char event |
| 328 // instead of a confirmed composition text. | 356 // instead of a confirmed composition text. |
| 329 // It should be fine to handle BMP character only, as non-BMP characters | 357 // It should be fine to handle BMP character only, as non-BMP characters |
| 330 // can always be committed as confirmed composition text. | 358 // can always be committed as confirmed composition text. |
| 331 return !is_composing_text_ && commit_text_.length() == 1; | 359 return !is_composing_text_ && commit_text_.length() == 1; |
| 332 } | 360 } |
| 333 | 361 |
| 362 bool GtkIMContextWrapper::HasInputMethodResult() const { |
| 363 return commit_text_.length() || is_preedit_changed_; |
| 364 } |
| 365 |
| 334 void GtkIMContextWrapper::ProcessFilteredKeyPressEvent( | 366 void GtkIMContextWrapper::ProcessFilteredKeyPressEvent( |
| 335 NativeWebKeyboardEvent* wke) { | 367 NativeWebKeyboardEvent* wke) { |
| 336 // If IME has filtered this event, then replace virtual key code with | 368 // If IME has filtered this event, then replace virtual key code with |
| 337 // VK_PROCESSKEY. See comment in ProcessKeyEvent() for details. | 369 // VK_PROCESSKEY. See comment in ProcessKeyEvent() for details. |
| 338 // It's only required for keydown events. | 370 // It's only required for keydown events. |
| 339 // To emulate windows behavior, when input method is enabled, if the commit | 371 // To emulate windows behavior, when input method is enabled, if the commit |
| 340 // text can be emulated by a Char event, then don't do this replacement. | 372 // text can be emulated by a Char event, then don't do this replacement. |
| 341 if (!NeedCommitByForwardingCharEvent()) { | 373 if (!NeedCommitByForwardingCharEvent()) { |
| 342 wke->windowsKeyCode = kCompositionEventKeyCode; | 374 wke->windowsKeyCode = kCompositionEventKeyCode; |
| 343 // keyidentifier must be updated accordingly, otherwise this key event may | 375 // keyidentifier must be updated accordingly, otherwise this key event may |
| (...skipping 325 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 669 } while (pango_attr_iterator_next(iter)); | 701 } while (pango_attr_iterator_next(iter)); |
| 670 pango_attr_iterator_destroy(iter); | 702 pango_attr_iterator_destroy(iter); |
| 671 } | 703 } |
| 672 | 704 |
| 673 // Use a black thin underline by default. | 705 // Use a black thin underline by default. |
| 674 if (underlines->empty()) { | 706 if (underlines->empty()) { |
| 675 underlines->push_back( | 707 underlines->push_back( |
| 676 WebKit::WebCompositionUnderline(0, length, SK_ColorBLACK, false)); | 708 WebKit::WebCompositionUnderline(0, length, SK_ColorBLACK, false)); |
| 677 } | 709 } |
| 678 } | 710 } |
| OLD | NEW |