| 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 "views/ime/input_method_win.h" | |
| 6 | |
| 7 #include "base/basictypes.h" | |
| 8 #include "base/logging.h" | |
| 9 #include "base/string_util.h" | |
| 10 #include "ui/base/ime/composition_text.h" | |
| 11 #include "ui/base/ime/text_input_client.h" | |
| 12 #include "ui/base/keycodes/keyboard_codes.h" | |
| 13 #include "views/events/event.h" | |
| 14 | |
| 15 // Extra number of chars before and after selection (or composition) range which | |
| 16 // is returned to IME for improving conversion accuracy. | |
| 17 static const size_t kExtraNumberOfChars = 20; | |
| 18 | |
| 19 namespace views { | |
| 20 | |
| 21 InputMethodWin::InputMethodWin(internal::InputMethodDelegate* delegate) | |
| 22 : active_(false), | |
| 23 direction_(base::i18n::UNKNOWN_DIRECTION), | |
| 24 pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION) { | |
| 25 set_delegate(delegate); | |
| 26 } | |
| 27 | |
| 28 InputMethodWin::~InputMethodWin() { | |
| 29 if (widget()) | |
| 30 ime_input_.DisableIME(hwnd()); | |
| 31 } | |
| 32 | |
| 33 void InputMethodWin::Init(Widget* widget) { | |
| 34 // Gets the initial input locale and text direction information. | |
| 35 OnInputLangChange(0, 0); | |
| 36 | |
| 37 InputMethodBase::Init(widget); | |
| 38 } | |
| 39 | |
| 40 void InputMethodWin::OnFocus() { | |
| 41 DCHECK(!widget_focused()); | |
| 42 InputMethodBase::OnFocus(); | |
| 43 UpdateIMEState(); | |
| 44 } | |
| 45 | |
| 46 void InputMethodWin::OnBlur() { | |
| 47 DCHECK(widget_focused()); | |
| 48 ConfirmCompositionText(); | |
| 49 InputMethodBase::OnBlur(); | |
| 50 } | |
| 51 | |
| 52 void InputMethodWin::DispatchKeyEvent(const KeyEvent& key) { | |
| 53 // Handles ctrl-shift key to change text direction and layout alignment. | |
| 54 if (ui::ImeInput::IsRTLKeyboardLayoutInstalled() && !IsTextInputTypeNone()) { | |
| 55 ui::KeyboardCode code = key.key_code(); | |
| 56 if (key.type() == ui::ET_KEY_PRESSED) { | |
| 57 if (code == ui::VKEY_SHIFT) { | |
| 58 base::i18n::TextDirection dir; | |
| 59 if (ui::ImeInput::IsCtrlShiftPressed(&dir)) | |
| 60 pending_requested_direction_ = dir; | |
| 61 } else if (code != ui::VKEY_CONTROL) { | |
| 62 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION; | |
| 63 } | |
| 64 } else if (key.type() == ui::ET_KEY_RELEASED && | |
| 65 (code == ui::VKEY_SHIFT || code == ui::VKEY_CONTROL) && | |
| 66 pending_requested_direction_ != base::i18n::UNKNOWN_DIRECTION) { | |
| 67 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment( | |
| 68 pending_requested_direction_); | |
| 69 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION; | |
| 70 } | |
| 71 } | |
| 72 | |
| 73 DispatchKeyEventPostIME(key); | |
| 74 } | |
| 75 | |
| 76 void InputMethodWin::OnTextInputTypeChanged(View* view) { | |
| 77 if (IsViewFocused(view)) { | |
| 78 ime_input_.CancelIME(hwnd()); | |
| 79 UpdateIMEState(); | |
| 80 } | |
| 81 InputMethodBase::OnTextInputTypeChanged(view); | |
| 82 } | |
| 83 | |
| 84 void InputMethodWin::OnCaretBoundsChanged(View* view) { | |
| 85 gfx::Rect rect; | |
| 86 if (!IsViewFocused(view) || !GetCaretBoundsInWidget(&rect)) | |
| 87 return; | |
| 88 ime_input_.UpdateCaretRect(hwnd(), rect); | |
| 89 } | |
| 90 | |
| 91 void InputMethodWin::CancelComposition(View* view) { | |
| 92 if (IsViewFocused(view)) | |
| 93 ime_input_.CancelIME(hwnd()); | |
| 94 } | |
| 95 | |
| 96 std::string InputMethodWin::GetInputLocale() { | |
| 97 return locale_; | |
| 98 } | |
| 99 | |
| 100 base::i18n::TextDirection InputMethodWin::GetInputTextDirection() { | |
| 101 return direction_; | |
| 102 } | |
| 103 | |
| 104 bool InputMethodWin::IsActive() { | |
| 105 return active_; | |
| 106 } | |
| 107 | |
| 108 LRESULT InputMethodWin::OnImeMessages( | |
| 109 UINT message, WPARAM w_param, LPARAM l_param, BOOL* handled) { | |
| 110 LRESULT result = 0; | |
| 111 switch (message) { | |
| 112 case WM_IME_SETCONTEXT: | |
| 113 result = OnImeSetContext(message, w_param, l_param, handled); | |
| 114 break; | |
| 115 case WM_IME_STARTCOMPOSITION: | |
| 116 result = OnImeStartComposition(message, w_param, l_param, handled); | |
| 117 break; | |
| 118 case WM_IME_COMPOSITION: | |
| 119 result = OnImeComposition(message, w_param, l_param, handled); | |
| 120 break; | |
| 121 case WM_IME_ENDCOMPOSITION: | |
| 122 result = OnImeEndComposition(message, w_param, l_param, handled); | |
| 123 break; | |
| 124 case WM_IME_REQUEST: | |
| 125 result = OnImeRequest(message, w_param, l_param, handled); | |
| 126 break; | |
| 127 case WM_CHAR: | |
| 128 case WM_SYSCHAR: | |
| 129 result = OnChar(message, w_param, l_param, handled); | |
| 130 break; | |
| 131 case WM_DEADCHAR: | |
| 132 case WM_SYSDEADCHAR: | |
| 133 result = OnDeadChar(message, w_param, l_param, handled); | |
| 134 break; | |
| 135 default: | |
| 136 NOTREACHED() << "Unknown IME message:" << message; | |
| 137 break; | |
| 138 } | |
| 139 return result; | |
| 140 } | |
| 141 | |
| 142 void InputMethodWin::OnWillChangeFocus(View* focused_before, View* focused) { | |
| 143 ConfirmCompositionText(); | |
| 144 } | |
| 145 | |
| 146 void InputMethodWin::OnDidChangeFocus(View* focused_before, View* focused) { | |
| 147 UpdateIMEState(); | |
| 148 } | |
| 149 | |
| 150 void InputMethodWin::OnInputLangChange(DWORD character_set, | |
| 151 HKL input_language_id) { | |
| 152 active_ = ime_input_.SetInputLanguage(); | |
| 153 locale_ = ime_input_.GetInputLanguageName(); | |
| 154 direction_ = ime_input_.GetTextDirection(); | |
| 155 OnInputMethodChanged(); | |
| 156 } | |
| 157 | |
| 158 LRESULT InputMethodWin::OnImeSetContext( | |
| 159 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
| 160 active_ = (wparam == TRUE); | |
| 161 if (active_) | |
| 162 ime_input_.CreateImeWindow(hwnd()); | |
| 163 | |
| 164 OnInputMethodChanged(); | |
| 165 return ime_input_.SetImeWindowStyle(hwnd(), message, wparam, lparam, handled); | |
| 166 } | |
| 167 | |
| 168 LRESULT InputMethodWin::OnImeStartComposition( | |
| 169 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
| 170 // We have to prevent WTL from calling ::DefWindowProc() because the function | |
| 171 // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to | |
| 172 // over-write the position of IME windows. | |
| 173 *handled = TRUE; | |
| 174 | |
| 175 if (IsTextInputTypeNone()) | |
| 176 return 0; | |
| 177 | |
| 178 // Reset the composition status and create IME windows. | |
| 179 ime_input_.CreateImeWindow(hwnd()); | |
| 180 ime_input_.ResetComposition(hwnd()); | |
| 181 return 0; | |
| 182 } | |
| 183 | |
| 184 LRESULT InputMethodWin::OnImeComposition( | |
| 185 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
| 186 // We have to prevent WTL from calling ::DefWindowProc() because we do not | |
| 187 // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages. | |
| 188 *handled = TRUE; | |
| 189 | |
| 190 if (IsTextInputTypeNone()) | |
| 191 return 0; | |
| 192 | |
| 193 // At first, update the position of the IME window. | |
| 194 ime_input_.UpdateImeWindow(hwnd()); | |
| 195 | |
| 196 // Retrieve the result string and its attributes of the ongoing composition | |
| 197 // and send it to a renderer process. | |
| 198 ui::CompositionText composition; | |
| 199 if (ime_input_.GetResult(hwnd(), lparam, &composition.text)) { | |
| 200 GetTextInputClient()->InsertText(composition.text); | |
| 201 ime_input_.ResetComposition(hwnd()); | |
| 202 // Fall though and try reading the composition string. | |
| 203 // Japanese IMEs send a message containing both GCS_RESULTSTR and | |
| 204 // GCS_COMPSTR, which means an ongoing composition has been finished | |
| 205 // by the start of another composition. | |
| 206 } | |
| 207 // Retrieve the composition string and its attributes of the ongoing | |
| 208 // composition and send it to a renderer process. | |
| 209 if (ime_input_.GetComposition(hwnd(), lparam, &composition)) | |
| 210 GetTextInputClient()->SetCompositionText(composition); | |
| 211 | |
| 212 return 0; | |
| 213 } | |
| 214 | |
| 215 LRESULT InputMethodWin::OnImeEndComposition( | |
| 216 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
| 217 // Let WTL call ::DefWindowProc() and release its resources. | |
| 218 *handled = FALSE; | |
| 219 | |
| 220 if (IsTextInputTypeNone()) | |
| 221 return 0; | |
| 222 | |
| 223 if (GetTextInputClient()->HasCompositionText()) | |
| 224 GetTextInputClient()->ClearCompositionText(); | |
| 225 | |
| 226 ime_input_.ResetComposition(hwnd()); | |
| 227 ime_input_.DestroyImeWindow(hwnd()); | |
| 228 return 0; | |
| 229 } | |
| 230 | |
| 231 LRESULT InputMethodWin::OnImeRequest( | |
| 232 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
| 233 *handled = FALSE; | |
| 234 | |
| 235 // Should not receive WM_IME_REQUEST message, if IME is disabled. | |
| 236 const ui::TextInputType type = GetTextInputType(); | |
| 237 if (type == ui::TEXT_INPUT_TYPE_NONE || | |
| 238 type == ui::TEXT_INPUT_TYPE_PASSWORD) { | |
| 239 return 0; | |
| 240 } | |
| 241 | |
| 242 switch (wparam) { | |
| 243 case IMR_RECONVERTSTRING: | |
| 244 *handled = TRUE; | |
| 245 return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam)); | |
| 246 case IMR_DOCUMENTFEED: | |
| 247 *handled = TRUE; | |
| 248 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam)); | |
| 249 default: | |
| 250 return 0; | |
| 251 } | |
| 252 } | |
| 253 | |
| 254 LRESULT InputMethodWin::OnChar( | |
| 255 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
| 256 *handled = TRUE; | |
| 257 | |
| 258 // We need to send character events to the focused text input client event if | |
| 259 // its text input type is ui::TEXT_INPUT_TYPE_NONE. | |
| 260 if (!GetTextInputClient()) | |
| 261 return 0; | |
| 262 | |
| 263 int flags = 0; | |
| 264 flags |= (::GetKeyState(VK_MENU) & 0x80)? ui::EF_ALT_DOWN : 0; | |
| 265 flags |= (::GetKeyState(VK_SHIFT) & 0x80)? ui::EF_SHIFT_DOWN : 0; | |
| 266 flags |= (::GetKeyState(VK_CONTROL) & 0x80)? ui::EF_CONTROL_DOWN : 0; | |
| 267 GetTextInputClient()->InsertChar(static_cast<char16>(wparam), flags); | |
| 268 return 0; | |
| 269 } | |
| 270 | |
| 271 LRESULT InputMethodWin::OnDeadChar( | |
| 272 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
| 273 *handled = TRUE; | |
| 274 | |
| 275 if (IsTextInputTypeNone()) | |
| 276 return 0; | |
| 277 | |
| 278 // Shows the dead character as a composition text, so that the user can know | |
| 279 // what dead key was pressed. | |
| 280 ui::CompositionText composition; | |
| 281 composition.text.assign(1, static_cast<char16>(wparam)); | |
| 282 composition.selection = ui::Range(0, 1); | |
| 283 composition.underlines.push_back( | |
| 284 ui::CompositionUnderline(0, 1, SK_ColorBLACK, false)); | |
| 285 GetTextInputClient()->SetCompositionText(composition); | |
| 286 return 0; | |
| 287 } | |
| 288 | |
| 289 LRESULT InputMethodWin::OnDocumentFeed(RECONVERTSTRING* reconv) { | |
| 290 ui::TextInputClient* client = GetTextInputClient(); | |
| 291 if (!client) | |
| 292 return 0; | |
| 293 | |
| 294 ui::Range text_range; | |
| 295 if (!client->GetTextRange(&text_range) || text_range.is_empty()) | |
| 296 return 0; | |
| 297 | |
| 298 bool result = false; | |
| 299 ui::Range target_range; | |
| 300 if (client->HasCompositionText()) | |
| 301 result = client->GetCompositionTextRange(&target_range); | |
| 302 | |
| 303 if (!result || target_range.is_empty()) { | |
| 304 if (!client->GetSelectionRange(&target_range) || | |
| 305 !target_range.IsValid()) { | |
| 306 return 0; | |
| 307 } | |
| 308 } | |
| 309 | |
| 310 if (!text_range.Contains(target_range)) | |
| 311 return 0; | |
| 312 | |
| 313 if (target_range.GetMin() - text_range.start() > kExtraNumberOfChars) | |
| 314 text_range.set_start(target_range.GetMin() - kExtraNumberOfChars); | |
| 315 | |
| 316 if (text_range.end() - target_range.GetMax() > kExtraNumberOfChars) | |
| 317 text_range.set_end(target_range.GetMax() + kExtraNumberOfChars); | |
| 318 | |
| 319 size_t len = text_range.length(); | |
| 320 size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); | |
| 321 | |
| 322 if (!reconv) | |
| 323 return need_size; | |
| 324 | |
| 325 if (reconv->dwSize < need_size) | |
| 326 return 0; | |
| 327 | |
| 328 string16 text; | |
| 329 if (!GetTextInputClient()->GetTextFromRange(text_range, &text)) | |
| 330 return 0; | |
| 331 DCHECK_EQ(text_range.length(), text.length()); | |
| 332 | |
| 333 reconv->dwVersion = 0; | |
| 334 reconv->dwStrLen = len; | |
| 335 reconv->dwStrOffset = sizeof(RECONVERTSTRING); | |
| 336 reconv->dwCompStrLen = | |
| 337 client->HasCompositionText() ? target_range.length() : 0; | |
| 338 reconv->dwCompStrOffset = | |
| 339 (target_range.GetMin() - text_range.start()) * sizeof(WCHAR); | |
| 340 reconv->dwTargetStrLen = target_range.length(); | |
| 341 reconv->dwTargetStrOffset = reconv->dwCompStrOffset; | |
| 342 | |
| 343 memcpy((char*)reconv + sizeof(RECONVERTSTRING), | |
| 344 text.c_str(), len * sizeof(WCHAR)); | |
| 345 | |
| 346 // According to Microsft API document, IMR_RECONVERTSTRING and | |
| 347 // IMR_DOCUMENTFEED should return reconv, but some applications return | |
| 348 // need_size. | |
| 349 return reinterpret_cast<LRESULT>(reconv); | |
| 350 } | |
| 351 | |
| 352 LRESULT InputMethodWin::OnReconvertString(RECONVERTSTRING* reconv) { | |
| 353 ui::TextInputClient* client = GetTextInputClient(); | |
| 354 if (!client) | |
| 355 return 0; | |
| 356 | |
| 357 // If there is a composition string already, we don't allow reconversion. | |
| 358 if (client->HasCompositionText()) | |
| 359 return 0; | |
| 360 | |
| 361 ui::Range text_range; | |
| 362 if (!client->GetTextRange(&text_range) || text_range.is_empty()) | |
| 363 return 0; | |
| 364 | |
| 365 ui::Range selection_range; | |
| 366 if (!client->GetSelectionRange(&selection_range) || | |
| 367 selection_range.is_empty()) { | |
| 368 return 0; | |
| 369 } | |
| 370 | |
| 371 DCHECK(text_range.Contains(selection_range)); | |
| 372 | |
| 373 size_t len = selection_range.length(); | |
| 374 size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); | |
| 375 | |
| 376 if (!reconv) | |
| 377 return need_size; | |
| 378 | |
| 379 if (reconv->dwSize < need_size) | |
| 380 return 0; | |
| 381 | |
| 382 // TODO(penghuang): Return some extra context to help improve IME's | |
| 383 // reconversion accuracy. | |
| 384 string16 text; | |
| 385 if (!GetTextInputClient()->GetTextFromRange(selection_range, &text)) | |
| 386 return 0; | |
| 387 DCHECK_EQ(selection_range.length(), text.length()); | |
| 388 | |
| 389 reconv->dwVersion = 0; | |
| 390 reconv->dwStrLen = len; | |
| 391 reconv->dwStrOffset = sizeof(RECONVERTSTRING); | |
| 392 reconv->dwCompStrLen = len; | |
| 393 reconv->dwCompStrOffset = 0; | |
| 394 reconv->dwTargetStrLen = len; | |
| 395 reconv->dwTargetStrOffset = 0; | |
| 396 | |
| 397 memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING), | |
| 398 text.c_str(), len * sizeof(WCHAR)); | |
| 399 | |
| 400 // According to Microsft API document, IMR_RECONVERTSTRING and | |
| 401 // IMR_DOCUMENTFEED should return reconv, but some applications return | |
| 402 // need_size. | |
| 403 return reinterpret_cast<LRESULT>(reconv); | |
| 404 } | |
| 405 | |
| 406 void InputMethodWin::ConfirmCompositionText() { | |
| 407 if (!IsTextInputTypeNone()) { | |
| 408 ime_input_.CleanupComposition(hwnd()); | |
| 409 // Though above line should confirm the client's composition text by sending | |
| 410 // a result text to us, in case the input method and the client are in | |
| 411 // inconsistent states, we check the client's composition state again. | |
| 412 if (GetTextInputClient()->HasCompositionText()) | |
| 413 GetTextInputClient()->ConfirmCompositionText(); | |
| 414 } | |
| 415 } | |
| 416 | |
| 417 void InputMethodWin::UpdateIMEState() { | |
| 418 // Use switch here in case we are going to add more text input types. | |
| 419 // We disable input method in password field. | |
| 420 switch (GetTextInputType()) { | |
| 421 case ui::TEXT_INPUT_TYPE_NONE: | |
| 422 case ui::TEXT_INPUT_TYPE_PASSWORD: | |
| 423 ime_input_.DisableIME(hwnd()); | |
| 424 break; | |
| 425 default: | |
| 426 ime_input_.EnableIME(hwnd()); | |
| 427 break; | |
| 428 } | |
| 429 } | |
| 430 | |
| 431 } // namespace views | |
| OLD | NEW |