1// Copyright (c) 2012 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 "ui/base/ime/input_method_win.h" 6 7#include "base/basictypes.h" 8#include "ui/base/ime/text_input_client.h" 9#include "ui/events/event.h" 10#include "ui/events/event_constants.h" 11#include "ui/events/event_utils.h" 12#include "ui/events/keycodes/keyboard_codes.h" 13#include "ui/gfx/win/hwnd_util.h" 14 15namespace ui { 16namespace { 17 18// Extra number of chars before and after selection (or composition) range which 19// is returned to IME for improving conversion accuracy. 20static const size_t kExtraNumberOfChars = 20; 21 22} // namespace 23 24InputMethodWin::InputMethodWin(internal::InputMethodDelegate* delegate, 25 HWND toplevel_window_handle) 26 : active_(false), 27 toplevel_window_handle_(toplevel_window_handle), 28 direction_(base::i18n::UNKNOWN_DIRECTION), 29 pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION), 30 accept_carriage_return_(false) { 31 SetDelegate(delegate); 32} 33 34void InputMethodWin::Init(bool focused) { 35 // Gets the initial input locale and text direction information. 36 OnInputLocaleChanged(); 37 38 InputMethodBase::Init(focused); 39} 40 41bool InputMethodWin::DispatchKeyEvent(const ui::KeyEvent& event) { 42 if (!event.HasNativeEvent()) 43 return DispatchFabricatedKeyEvent(event); 44 45 const base::NativeEvent& native_key_event = event.native_event(); 46 if (native_key_event.message == WM_CHAR) { 47 BOOL handled; 48 OnChar(native_key_event.hwnd, native_key_event.message, 49 native_key_event.wParam, native_key_event.lParam, &handled); 50 return !!handled; // Don't send WM_CHAR for post event processing. 51 } 52 // Handles ctrl-shift key to change text direction and layout alignment. 53 if (ui::IMM32Manager::IsRTLKeyboardLayoutInstalled() && 54 !IsTextInputTypeNone()) { 55 // TODO: shouldn't need to generate a KeyEvent here. 56 const ui::KeyEvent key(native_key_event, 57 native_key_event.message == WM_CHAR); 58 ui::KeyboardCode code = key.key_code(); 59 if (key.type() == ui::ET_KEY_PRESSED) { 60 if (code == ui::VKEY_SHIFT) { 61 base::i18n::TextDirection dir; 62 if (ui::IMM32Manager::IsCtrlShiftPressed(&dir)) 63 pending_requested_direction_ = dir; 64 } else if (code != ui::VKEY_CONTROL) { 65 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION; 66 } 67 } else if (key.type() == ui::ET_KEY_RELEASED && 68 (code == ui::VKEY_SHIFT || code == ui::VKEY_CONTROL) && 69 pending_requested_direction_ != base::i18n::UNKNOWN_DIRECTION) { 70 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment( 71 pending_requested_direction_); 72 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION; 73 } 74 } 75 76 return DispatchKeyEventPostIME(event); 77} 78 79void InputMethodWin::OnInputLocaleChanged() { 80 active_ = imm32_manager_.SetInputLanguage(); 81 locale_ = imm32_manager_.GetInputLanguageName(); 82 direction_ = imm32_manager_.GetTextDirection(); 83 OnInputMethodChanged(); 84} 85 86std::string InputMethodWin::GetInputLocale() { 87 return locale_; 88} 89 90base::i18n::TextDirection InputMethodWin::GetInputTextDirection() { 91 return direction_; 92} 93 94bool InputMethodWin::IsActive() { 95 return active_; 96} 97 98void InputMethodWin::OnDidChangeFocusedClient( 99 TextInputClient* focused_before, 100 TextInputClient* focused) { 101 if (focused_before != focused) 102 accept_carriage_return_ = false; 103} 104 105LRESULT InputMethodWin::OnImeRequest(UINT message, 106 WPARAM wparam, 107 LPARAM lparam, 108 BOOL* handled) { 109 *handled = FALSE; 110 111 // Should not receive WM_IME_REQUEST message, if IME is disabled. 112 const ui::TextInputType type = GetTextInputType(); 113 if (type == ui::TEXT_INPUT_TYPE_NONE || 114 type == ui::TEXT_INPUT_TYPE_PASSWORD) { 115 return 0; 116 } 117 118 switch (wparam) { 119 case IMR_RECONVERTSTRING: 120 *handled = TRUE; 121 return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam)); 122 case IMR_DOCUMENTFEED: 123 *handled = TRUE; 124 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam)); 125 case IMR_QUERYCHARPOSITION: 126 *handled = TRUE; 127 return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION*>(lparam)); 128 default: 129 return 0; 130 } 131} 132 133LRESULT InputMethodWin::OnChar(HWND window_handle, 134 UINT message, 135 WPARAM wparam, 136 LPARAM lparam, 137 BOOL* handled) { 138 *handled = TRUE; 139 140 // We need to send character events to the focused text input client event if 141 // its text input type is ui::TEXT_INPUT_TYPE_NONE. 142 if (GetTextInputClient()) { 143 const char16 kCarriageReturn = L'\r'; 144 const char16 ch = static_cast<char16>(wparam); 145 // A mask to determine the previous key state from |lparam|. The value is 1 146 // if the key is down before the message is sent, or it is 0 if the key is 147 // up. 148 const uint32 kPrevKeyDownBit = 0x40000000; 149 if (ch == kCarriageReturn && !(lparam & kPrevKeyDownBit)) 150 accept_carriage_return_ = true; 151 // Conditionally ignore '\r' events to work around crbug.com/319100. 152 // TODO(yukawa, IME): Figure out long-term solution. 153 if (ch != kCarriageReturn || accept_carriage_return_) 154 GetTextInputClient()->InsertChar(ch, ui::GetModifiersFromKeyState()); 155 } 156 157 // Explicitly show the system menu at a good location on [Alt]+[Space]. 158 // Note: Setting |handled| to FALSE for DefWindowProc triggering of the system 159 // menu causes undesirable titlebar artifacts in the classic theme. 160 if (message == WM_SYSCHAR && wparam == VK_SPACE) 161 gfx::ShowSystemMenu(window_handle); 162 163 return 0; 164} 165 166LRESULT InputMethodWin::OnDeadChar(UINT message, 167 WPARAM wparam, 168 LPARAM lparam, 169 BOOL* handled) { 170 *handled = TRUE; 171 return 0; 172} 173 174LRESULT InputMethodWin::OnDocumentFeed(RECONVERTSTRING* reconv) { 175 ui::TextInputClient* client = GetTextInputClient(); 176 if (!client) 177 return 0; 178 179 gfx::Range text_range; 180 if (!client->GetTextRange(&text_range) || text_range.is_empty()) 181 return 0; 182 183 bool result = false; 184 gfx::Range target_range; 185 if (client->HasCompositionText()) 186 result = client->GetCompositionTextRange(&target_range); 187 188 if (!result || target_range.is_empty()) { 189 if (!client->GetSelectionRange(&target_range) || 190 !target_range.IsValid()) { 191 return 0; 192 } 193 } 194 195 if (!text_range.Contains(target_range)) 196 return 0; 197 198 if (target_range.GetMin() - text_range.start() > kExtraNumberOfChars) 199 text_range.set_start(target_range.GetMin() - kExtraNumberOfChars); 200 201 if (text_range.end() - target_range.GetMax() > kExtraNumberOfChars) 202 text_range.set_end(target_range.GetMax() + kExtraNumberOfChars); 203 204 size_t len = text_range.length(); 205 size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); 206 207 if (!reconv) 208 return need_size; 209 210 if (reconv->dwSize < need_size) 211 return 0; 212 213 string16 text; 214 if (!GetTextInputClient()->GetTextFromRange(text_range, &text)) 215 return 0; 216 DCHECK_EQ(text_range.length(), text.length()); 217 218 reconv->dwVersion = 0; 219 reconv->dwStrLen = len; 220 reconv->dwStrOffset = sizeof(RECONVERTSTRING); 221 reconv->dwCompStrLen = 222 client->HasCompositionText() ? target_range.length() : 0; 223 reconv->dwCompStrOffset = 224 (target_range.GetMin() - text_range.start()) * sizeof(WCHAR); 225 reconv->dwTargetStrLen = target_range.length(); 226 reconv->dwTargetStrOffset = reconv->dwCompStrOffset; 227 228 memcpy((char*)reconv + sizeof(RECONVERTSTRING), 229 text.c_str(), len * sizeof(WCHAR)); 230 231 // According to Microsoft API document, IMR_RECONVERTSTRING and 232 // IMR_DOCUMENTFEED should return reconv, but some applications return 233 // need_size. 234 return reinterpret_cast<LRESULT>(reconv); 235} 236 237LRESULT InputMethodWin::OnReconvertString(RECONVERTSTRING* reconv) { 238 ui::TextInputClient* client = GetTextInputClient(); 239 if (!client) 240 return 0; 241 242 // If there is a composition string already, we don't allow reconversion. 243 if (client->HasCompositionText()) 244 return 0; 245 246 gfx::Range text_range; 247 if (!client->GetTextRange(&text_range) || text_range.is_empty()) 248 return 0; 249 250 gfx::Range selection_range; 251 if (!client->GetSelectionRange(&selection_range) || 252 selection_range.is_empty()) { 253 return 0; 254 } 255 256 DCHECK(text_range.Contains(selection_range)); 257 258 size_t len = selection_range.length(); 259 size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); 260 261 if (!reconv) 262 return need_size; 263 264 if (reconv->dwSize < need_size) 265 return 0; 266 267 // TODO(penghuang): Return some extra context to help improve IME's 268 // reconversion accuracy. 269 string16 text; 270 if (!GetTextInputClient()->GetTextFromRange(selection_range, &text)) 271 return 0; 272 DCHECK_EQ(selection_range.length(), text.length()); 273 274 reconv->dwVersion = 0; 275 reconv->dwStrLen = len; 276 reconv->dwStrOffset = sizeof(RECONVERTSTRING); 277 reconv->dwCompStrLen = len; 278 reconv->dwCompStrOffset = 0; 279 reconv->dwTargetStrLen = len; 280 reconv->dwTargetStrOffset = 0; 281 282 memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING), 283 text.c_str(), len * sizeof(WCHAR)); 284 285 // According to Microsoft API document, IMR_RECONVERTSTRING and 286 // IMR_DOCUMENTFEED should return reconv, but some applications return 287 // need_size. 288 return reinterpret_cast<LRESULT>(reconv); 289} 290 291LRESULT InputMethodWin::OnQueryCharPosition(IMECHARPOSITION* char_positon) { 292 if (!char_positon) 293 return 0; 294 295 if (char_positon->dwSize < sizeof(IMECHARPOSITION)) 296 return 0; 297 298 ui::TextInputClient* client = GetTextInputClient(); 299 if (!client) 300 return 0; 301 302 gfx::Rect rect; 303 if (client->HasCompositionText()) { 304 if (!client->GetCompositionCharacterBounds(char_positon->dwCharPos, 305 &rect)) { 306 return 0; 307 } 308 } else { 309 // If there is no composition and the first character is queried, returns 310 // the caret bounds. This behavior is the same to that of RichEdit control. 311 if (char_positon->dwCharPos != 0) 312 return 0; 313 rect = client->GetCaretBounds(); 314 } 315 316 char_positon->pt.x = rect.x(); 317 char_positon->pt.y = rect.y(); 318 char_positon->cLineHeight = rect.height(); 319 return 1; // returns non-zero value when succeeded. 320} 321 322HWND InputMethodWin::GetAttachedWindowHandle( 323 const TextInputClient* text_input_client) const { 324 // On Aura environment, we can assume that |toplevel_window_handle_| always 325 // represents the valid top-level window handle because each top-level window 326 // is responsible for lifecycle management of corresponding InputMethod 327 // instance. 328#if defined(USE_AURA) 329 return toplevel_window_handle_; 330#else 331 // On Non-Aura environment, TextInputClient::GetAttachedWindow() returns 332 // window handle to which each input method is bound. 333 if (!text_input_client) 334 return NULL; 335 return text_input_client->GetAttachedWindow(); 336#endif 337} 338 339bool InputMethodWin::IsWindowFocused(const TextInputClient* client) const { 340 if (!client) 341 return false; 342 HWND attached_window_handle = GetAttachedWindowHandle(client); 343#if defined(USE_AURA) 344 // When Aura is enabled, |attached_window_handle| should always be a top-level 345 // window. So we can safely assume that |attached_window_handle| is ready for 346 // receiving keyboard input as long as it is an active window. This works well 347 // even when the |attached_window_handle| becomes active but has not received 348 // WM_FOCUS yet. 349 return attached_window_handle && GetActiveWindow() == attached_window_handle; 350#else 351 return attached_window_handle && GetFocus() == attached_window_handle; 352#endif 353} 354 355bool InputMethodWin::DispatchFabricatedKeyEvent(const ui::KeyEvent& event) { 356 // TODO(ananta) 357 // Support IMEs and RTL layout in Windows 8 metro Ash. The code below won't 358 // work with IMEs. 359 // Bug: https://code.google.com/p/chromium/issues/detail?id=164964 360 if (event.is_char()) { 361 if (GetTextInputClient()) { 362 GetTextInputClient()->InsertChar(event.key_code(), 363 ui::GetModifiersFromKeyState()); 364 return true; 365 } 366 } 367 return DispatchKeyEventPostIME(event); 368} 369 370} // namespace ui 371