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