OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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 "ui/base/ime/input_method_win.h" | 5 #include "ui/base/ime/input_method_win.h" |
6 | 6 |
7 #include "base/basictypes.h" | 7 #include "base/basictypes.h" |
8 #include "ui/base/ime/text_input_client.h" | 8 #include "ui/base/ime/text_input_client.h" |
| 9 #include "ui/base/ime/win/tsf_input_scope.h" |
9 #include "ui/events/event.h" | 10 #include "ui/events/event.h" |
10 #include "ui/events/event_constants.h" | 11 #include "ui/events/event_constants.h" |
11 #include "ui/events/event_utils.h" | 12 #include "ui/events/event_utils.h" |
12 #include "ui/events/keycodes/keyboard_codes.h" | 13 #include "ui/events/keycodes/keyboard_codes.h" |
13 #include "ui/gfx/win/hwnd_util.h" | 14 #include "ui/gfx/win/hwnd_util.h" |
14 | 15 |
15 namespace ui { | 16 namespace ui { |
16 namespace { | 17 namespace { |
17 | 18 |
18 // Extra number of chars before and after selection (or composition) range which | 19 // Extra number of chars before and after selection (or composition) range which |
19 // is returned to IME for improving conversion accuracy. | 20 // is returned to IME for improving conversion accuracy. |
20 static const size_t kExtraNumberOfChars = 20; | 21 static const size_t kExtraNumberOfChars = 20; |
21 | 22 |
22 } // namespace | 23 } // namespace |
23 | 24 |
24 InputMethodWin::InputMethodWin(internal::InputMethodDelegate* delegate, | 25 InputMethodWin::InputMethodWin(internal::InputMethodDelegate* delegate, |
25 HWND toplevel_window_handle) | 26 HWND toplevel_window_handle) |
26 : active_(false), | 27 : toplevel_window_handle_(toplevel_window_handle), |
27 toplevel_window_handle_(toplevel_window_handle), | |
28 pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION), | 28 pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION), |
29 accept_carriage_return_(false) { | 29 accept_carriage_return_(false), |
| 30 active_(false), |
| 31 enabled_(false), |
| 32 is_candidate_popup_open_(false), |
| 33 composing_window_handle_(NULL) { |
30 SetDelegate(delegate); | 34 SetDelegate(delegate); |
| 35 // In non-Aura environment, appropriate callbacks to OnFocus() and OnBlur() |
| 36 // are not implemented yet. To work around this limitation, here we use |
| 37 // "always focused" model. |
| 38 // TODO(ime): Fix the caller of OnFocus() and OnBlur() so that appropriate |
| 39 // focus event will be passed. |
| 40 InputMethodBase::OnFocus(); |
31 } | 41 } |
32 | 42 |
33 void InputMethodWin::Init(bool focused) { | 43 void InputMethodWin::Init(bool focused) { |
34 // Gets the initial input locale. | 44 // Gets the initial input locale. |
35 OnInputLocaleChanged(); | 45 OnInputLocaleChanged(); |
36 | 46 |
37 InputMethodBase::Init(focused); | 47 InputMethodBase::Init(focused); |
38 } | 48 } |
39 | 49 |
| 50 void InputMethodWin::OnFocus() { |
| 51 // Ignore OnFocus event for "always focused" model. See the comment in the |
| 52 // constructor. |
| 53 // TODO(ime): Implement OnFocus once the callers are fixed. |
| 54 } |
| 55 |
| 56 void InputMethodWin::OnBlur() { |
| 57 // Ignore OnBlur event for "always focused" model. See the comment in the |
| 58 // constructor. |
| 59 // TODO(ime): Implement OnFocus once the callers are fixed. |
| 60 } |
| 61 |
| 62 bool InputMethodWin::OnUntranslatedIMEMessage( |
| 63 const base::NativeEvent& event, |
| 64 InputMethod::NativeEventResult* result) { |
| 65 LRESULT original_result = 0; |
| 66 BOOL handled = FALSE; |
| 67 switch (event.message) { |
| 68 case WM_IME_SETCONTEXT: |
| 69 original_result = OnImeSetContext( |
| 70 event.hwnd, event.message, event.wParam, event.lParam, &handled); |
| 71 break; |
| 72 case WM_IME_STARTCOMPOSITION: |
| 73 original_result = OnImeStartComposition( |
| 74 event.hwnd, event.message, event.wParam, event.lParam, &handled); |
| 75 break; |
| 76 case WM_IME_COMPOSITION: |
| 77 original_result = OnImeComposition( |
| 78 event.hwnd, event.message, event.wParam, event.lParam, &handled); |
| 79 break; |
| 80 case WM_IME_ENDCOMPOSITION: |
| 81 original_result = OnImeEndComposition( |
| 82 event.hwnd, event.message, event.wParam, event.lParam, &handled); |
| 83 break; |
| 84 case WM_IME_REQUEST: |
| 85 original_result = OnImeRequest( |
| 86 event.message, event.wParam, event.lParam, &handled); |
| 87 break; |
| 88 case WM_CHAR: |
| 89 case WM_SYSCHAR: |
| 90 original_result = OnChar( |
| 91 event.hwnd, event.message, event.wParam, event.lParam, &handled); |
| 92 break; |
| 93 case WM_IME_NOTIFY: |
| 94 original_result = OnImeNotify( |
| 95 event.message, event.wParam, event.lParam, &handled); |
| 96 break; |
| 97 default: |
| 98 NOTREACHED() << "Unknown IME message:" << event.message; |
| 99 break; |
| 100 } |
| 101 if (result) |
| 102 *result = original_result; |
| 103 return !!handled; |
| 104 } |
| 105 |
40 bool InputMethodWin::DispatchKeyEvent(const ui::KeyEvent& event) { | 106 bool InputMethodWin::DispatchKeyEvent(const ui::KeyEvent& event) { |
41 if (!event.HasNativeEvent()) | 107 if (!event.HasNativeEvent()) |
42 return DispatchFabricatedKeyEvent(event); | 108 return DispatchFabricatedKeyEvent(event); |
43 | 109 |
44 const base::NativeEvent& native_key_event = event.native_event(); | 110 const base::NativeEvent& native_key_event = event.native_event(); |
45 if (native_key_event.message == WM_CHAR) { | 111 if (native_key_event.message == WM_CHAR) { |
46 BOOL handled; | 112 BOOL handled; |
47 OnChar(native_key_event.hwnd, native_key_event.message, | 113 OnChar(native_key_event.hwnd, native_key_event.message, |
48 native_key_event.wParam, native_key_event.lParam, &handled); | 114 native_key_event.wParam, native_key_event.lParam, &handled); |
49 return !!handled; // Don't send WM_CHAR for post event processing. | 115 return !!handled; // Don't send WM_CHAR for post event processing. |
(...skipping 18 matching lines...) Expand all Loading... |
68 pending_requested_direction_ != base::i18n::UNKNOWN_DIRECTION) { | 134 pending_requested_direction_ != base::i18n::UNKNOWN_DIRECTION) { |
69 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment( | 135 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment( |
70 pending_requested_direction_); | 136 pending_requested_direction_); |
71 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION; | 137 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION; |
72 } | 138 } |
73 } | 139 } |
74 | 140 |
75 return DispatchKeyEventPostIME(event); | 141 return DispatchKeyEventPostIME(event); |
76 } | 142 } |
77 | 143 |
| 144 void InputMethodWin::OnTextInputTypeChanged(const TextInputClient* client) { |
| 145 if (!IsTextInputClientFocused(client) || !IsWindowFocused(client)) |
| 146 return; |
| 147 imm32_manager_.CancelIME(GetAttachedWindowHandle(client)); |
| 148 UpdateIMEState(); |
| 149 } |
| 150 |
| 151 void InputMethodWin::OnCaretBoundsChanged(const TextInputClient* client) { |
| 152 if (!enabled_ || !IsTextInputClientFocused(client) || |
| 153 !IsWindowFocused(client)) { |
| 154 return; |
| 155 } |
| 156 // The current text input type should not be NONE if |client| is focused. |
| 157 DCHECK(!IsTextInputTypeNone()); |
| 158 gfx::Rect screen_bounds(GetTextInputClient()->GetCaretBounds()); |
| 159 |
| 160 HWND attached_window = GetAttachedWindowHandle(client); |
| 161 // TODO(ime): see comment in TextInputClient::GetCaretBounds(), this |
| 162 // conversion shouldn't be necessary. |
| 163 RECT r = {}; |
| 164 GetClientRect(attached_window, &r); |
| 165 POINT window_point = { screen_bounds.x(), screen_bounds.y() }; |
| 166 ScreenToClient(attached_window, &window_point); |
| 167 gfx::Rect caret_rect(gfx::Point(window_point.x, window_point.y), |
| 168 screen_bounds.size()); |
| 169 imm32_manager_.UpdateCaretRect(attached_window, caret_rect); |
| 170 } |
| 171 |
| 172 void InputMethodWin::CancelComposition(const TextInputClient* client) { |
| 173 if (enabled_ && IsTextInputClientFocused(client)) |
| 174 imm32_manager_.CancelIME(GetAttachedWindowHandle(client)); |
| 175 } |
| 176 |
78 void InputMethodWin::OnInputLocaleChanged() { | 177 void InputMethodWin::OnInputLocaleChanged() { |
79 active_ = imm32_manager_.SetInputLanguage(); | 178 active_ = imm32_manager_.SetInputLanguage(); |
80 locale_ = imm32_manager_.GetInputLanguageName(); | 179 locale_ = imm32_manager_.GetInputLanguageName(); |
81 OnInputMethodChanged(); | 180 OnInputMethodChanged(); |
82 } | 181 } |
83 | 182 |
84 std::string InputMethodWin::GetInputLocale() { | 183 std::string InputMethodWin::GetInputLocale() { |
85 return locale_; | 184 return locale_; |
86 } | 185 } |
87 | 186 |
88 bool InputMethodWin::IsActive() { | 187 bool InputMethodWin::IsActive() { |
89 return active_; | 188 return active_; |
90 } | 189 } |
91 | 190 |
| 191 bool InputMethodWin::IsCandidatePopupOpen() const { |
| 192 return is_candidate_popup_open_; |
| 193 } |
| 194 |
| 195 void InputMethodWin::OnWillChangeFocusedClient(TextInputClient* focused_before, |
| 196 TextInputClient* focused) { |
| 197 if (IsWindowFocused(focused_before)) |
| 198 ConfirmCompositionText(); |
| 199 } |
| 200 |
92 void InputMethodWin::OnDidChangeFocusedClient( | 201 void InputMethodWin::OnDidChangeFocusedClient( |
93 TextInputClient* focused_before, | 202 TextInputClient* focused_before, |
94 TextInputClient* focused) { | 203 TextInputClient* focused) { |
| 204 if (IsWindowFocused(focused)) { |
| 205 // Force to update the input type since client's TextInputStateChanged() |
| 206 // function might not be called if text input types before the client loses |
| 207 // focus and after it acquires focus again are the same. |
| 208 OnTextInputTypeChanged(focused); |
| 209 |
| 210 UpdateIMEState(); |
| 211 |
| 212 // Force to update caret bounds, in case the client thinks that the caret |
| 213 // bounds has not changed. |
| 214 OnCaretBoundsChanged(focused); |
| 215 } |
95 if (focused_before != focused) | 216 if (focused_before != focused) |
96 accept_carriage_return_ = false; | 217 accept_carriage_return_ = false; |
97 } | 218 } |
98 | 219 |
99 LRESULT InputMethodWin::OnImeRequest(UINT message, | |
100 WPARAM wparam, | |
101 LPARAM lparam, | |
102 BOOL* handled) { | |
103 *handled = FALSE; | |
104 | |
105 // Should not receive WM_IME_REQUEST message, if IME is disabled. | |
106 const ui::TextInputType type = GetTextInputType(); | |
107 if (type == ui::TEXT_INPUT_TYPE_NONE || | |
108 type == ui::TEXT_INPUT_TYPE_PASSWORD) { | |
109 return 0; | |
110 } | |
111 | |
112 switch (wparam) { | |
113 case IMR_RECONVERTSTRING: | |
114 *handled = TRUE; | |
115 return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam)); | |
116 case IMR_DOCUMENTFEED: | |
117 *handled = TRUE; | |
118 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam)); | |
119 case IMR_QUERYCHARPOSITION: | |
120 *handled = TRUE; | |
121 return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION*>(lparam)); | |
122 default: | |
123 return 0; | |
124 } | |
125 } | |
126 | |
127 LRESULT InputMethodWin::OnChar(HWND window_handle, | 220 LRESULT InputMethodWin::OnChar(HWND window_handle, |
128 UINT message, | 221 UINT message, |
129 WPARAM wparam, | 222 WPARAM wparam, |
130 LPARAM lparam, | 223 LPARAM lparam, |
131 BOOL* handled) { | 224 BOOL* handled) { |
132 *handled = TRUE; | 225 *handled = TRUE; |
133 | 226 |
134 // We need to send character events to the focused text input client event if | 227 // We need to send character events to the focused text input client event if |
135 // its text input type is ui::TEXT_INPUT_TYPE_NONE. | 228 // its text input type is ui::TEXT_INPUT_TYPE_NONE. |
136 if (GetTextInputClient()) { | 229 if (GetTextInputClient()) { |
(...skipping 13 matching lines...) Expand all Loading... |
150 | 243 |
151 // Explicitly show the system menu at a good location on [Alt]+[Space]. | 244 // Explicitly show the system menu at a good location on [Alt]+[Space]. |
152 // Note: Setting |handled| to FALSE for DefWindowProc triggering of the system | 245 // Note: Setting |handled| to FALSE for DefWindowProc triggering of the system |
153 // menu causes undesirable titlebar artifacts in the classic theme. | 246 // menu causes undesirable titlebar artifacts in the classic theme. |
154 if (message == WM_SYSCHAR && wparam == VK_SPACE) | 247 if (message == WM_SYSCHAR && wparam == VK_SPACE) |
155 gfx::ShowSystemMenu(window_handle); | 248 gfx::ShowSystemMenu(window_handle); |
156 | 249 |
157 return 0; | 250 return 0; |
158 } | 251 } |
159 | 252 |
| 253 LRESULT InputMethodWin::OnImeSetContext(HWND window_handle, |
| 254 UINT message, |
| 255 WPARAM wparam, |
| 256 LPARAM lparam, |
| 257 BOOL* handled) { |
| 258 if (!!wparam) |
| 259 imm32_manager_.CreateImeWindow(window_handle); |
| 260 |
| 261 OnInputMethodChanged(); |
| 262 return imm32_manager_.SetImeWindowStyle( |
| 263 window_handle, message, wparam, lparam, handled); |
| 264 } |
| 265 |
| 266 LRESULT InputMethodWin::OnImeStartComposition(HWND window_handle, |
| 267 UINT message, |
| 268 WPARAM wparam, |
| 269 LPARAM lparam, |
| 270 BOOL* handled) { |
| 271 // We have to prevent WTL from calling ::DefWindowProc() because the function |
| 272 // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to |
| 273 // over-write the position of IME windows. |
| 274 *handled = TRUE; |
| 275 |
| 276 // Reset the composition status and create IME windows. |
| 277 composing_window_handle_ = window_handle; |
| 278 imm32_manager_.CreateImeWindow(window_handle); |
| 279 imm32_manager_.ResetComposition(window_handle); |
| 280 return 0; |
| 281 } |
| 282 |
| 283 LRESULT InputMethodWin::OnImeComposition(HWND window_handle, |
| 284 UINT message, |
| 285 WPARAM wparam, |
| 286 LPARAM lparam, |
| 287 BOOL* handled) { |
| 288 // We have to prevent WTL from calling ::DefWindowProc() because we do not |
| 289 // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages. |
| 290 *handled = TRUE; |
| 291 |
| 292 // At first, update the position of the IME window. |
| 293 imm32_manager_.UpdateImeWindow(window_handle); |
| 294 |
| 295 // Retrieve the result string and its attributes of the ongoing composition |
| 296 // and send it to a renderer process. |
| 297 ui::CompositionText composition; |
| 298 if (imm32_manager_.GetResult(window_handle, lparam, &composition.text)) { |
| 299 if (!IsTextInputTypeNone()) |
| 300 GetTextInputClient()->InsertText(composition.text); |
| 301 imm32_manager_.ResetComposition(window_handle); |
| 302 // Fall though and try reading the composition string. |
| 303 // Japanese IMEs send a message containing both GCS_RESULTSTR and |
| 304 // GCS_COMPSTR, which means an ongoing composition has been finished |
| 305 // by the start of another composition. |
| 306 } |
| 307 // Retrieve the composition string and its attributes of the ongoing |
| 308 // composition and send it to a renderer process. |
| 309 if (imm32_manager_.GetComposition(window_handle, lparam, &composition) && |
| 310 !IsTextInputTypeNone()) |
| 311 GetTextInputClient()->SetCompositionText(composition); |
| 312 |
| 313 return 0; |
| 314 } |
| 315 |
| 316 LRESULT InputMethodWin::OnImeEndComposition(HWND window_handle, |
| 317 UINT message, |
| 318 WPARAM wparam, |
| 319 LPARAM lparam, |
| 320 BOOL* handled) { |
| 321 // Let WTL call ::DefWindowProc() and release its resources. |
| 322 *handled = FALSE; |
| 323 |
| 324 composing_window_handle_ = NULL; |
| 325 |
| 326 if (!IsTextInputTypeNone() && GetTextInputClient()->HasCompositionText()) |
| 327 GetTextInputClient()->ClearCompositionText(); |
| 328 |
| 329 imm32_manager_.ResetComposition(window_handle); |
| 330 imm32_manager_.DestroyImeWindow(window_handle); |
| 331 return 0; |
| 332 } |
| 333 |
| 334 LRESULT InputMethodWin::OnImeNotify(UINT message, |
| 335 WPARAM wparam, |
| 336 LPARAM lparam, |
| 337 BOOL* handled) { |
| 338 *handled = FALSE; |
| 339 |
| 340 bool previous_state = is_candidate_popup_open_; |
| 341 |
| 342 // Update |is_candidate_popup_open_|, whether a candidate window is open. |
| 343 switch (wparam) { |
| 344 case IMN_OPENCANDIDATE: |
| 345 is_candidate_popup_open_ = true; |
| 346 if (!previous_state) |
| 347 OnCandidateWindowShown(); |
| 348 break; |
| 349 case IMN_CLOSECANDIDATE: |
| 350 is_candidate_popup_open_ = false; |
| 351 if (previous_state) |
| 352 OnCandidateWindowHidden(); |
| 353 break; |
| 354 case IMN_CHANGECANDIDATE: |
| 355 // TODO(kochi): The IME API expects this event to notify window size change, |
| 356 // while this may fire more often without window resize. There is no generic |
| 357 // way to get bounds of candidate window. |
| 358 OnCandidateWindowUpdated(); |
| 359 break; |
| 360 } |
| 361 |
| 362 return 0; |
| 363 } |
| 364 |
| 365 LRESULT InputMethodWin::OnImeRequest(UINT message, |
| 366 WPARAM wparam, |
| 367 LPARAM lparam, |
| 368 BOOL* handled) { |
| 369 *handled = FALSE; |
| 370 |
| 371 // Should not receive WM_IME_REQUEST message, if IME is disabled. |
| 372 const ui::TextInputType type = GetTextInputType(); |
| 373 if (type == ui::TEXT_INPUT_TYPE_NONE || |
| 374 type == ui::TEXT_INPUT_TYPE_PASSWORD) { |
| 375 return 0; |
| 376 } |
| 377 |
| 378 switch (wparam) { |
| 379 case IMR_RECONVERTSTRING: |
| 380 *handled = TRUE; |
| 381 return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam)); |
| 382 case IMR_DOCUMENTFEED: |
| 383 *handled = TRUE; |
| 384 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam)); |
| 385 case IMR_QUERYCHARPOSITION: |
| 386 *handled = TRUE; |
| 387 return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION*>(lparam)); |
| 388 default: |
| 389 return 0; |
| 390 } |
| 391 } |
| 392 |
160 LRESULT InputMethodWin::OnDocumentFeed(RECONVERTSTRING* reconv) { | 393 LRESULT InputMethodWin::OnDocumentFeed(RECONVERTSTRING* reconv) { |
161 ui::TextInputClient* client = GetTextInputClient(); | 394 ui::TextInputClient* client = GetTextInputClient(); |
162 if (!client) | 395 if (!client) |
163 return 0; | 396 return 0; |
164 | 397 |
165 gfx::Range text_range; | 398 gfx::Range text_range; |
166 if (!client->GetTextRange(&text_range) || text_range.is_empty()) | 399 if (!client->GetTextRange(&text_range) || text_range.is_empty()) |
167 return 0; | 400 return 0; |
168 | 401 |
169 bool result = false; | 402 bool result = false; |
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
330 if (event.is_char()) { | 563 if (event.is_char()) { |
331 if (GetTextInputClient()) { | 564 if (GetTextInputClient()) { |
332 GetTextInputClient()->InsertChar(event.key_code(), | 565 GetTextInputClient()->InsertChar(event.key_code(), |
333 ui::GetModifiersFromKeyState()); | 566 ui::GetModifiersFromKeyState()); |
334 return true; | 567 return true; |
335 } | 568 } |
336 } | 569 } |
337 return DispatchKeyEventPostIME(event); | 570 return DispatchKeyEventPostIME(event); |
338 } | 571 } |
339 | 572 |
| 573 void InputMethodWin::ConfirmCompositionText() { |
| 574 if (composing_window_handle_) |
| 575 imm32_manager_.CleanupComposition(composing_window_handle_); |
| 576 |
| 577 if (!IsTextInputTypeNone()) { |
| 578 // Though above line should confirm the client's composition text by sending |
| 579 // a result text to us, in case the input method and the client are in |
| 580 // inconsistent states, we check the client's composition state again. |
| 581 if (GetTextInputClient()->HasCompositionText()) |
| 582 GetTextInputClient()->ConfirmCompositionText(); |
| 583 } |
| 584 } |
| 585 |
| 586 void InputMethodWin::UpdateIMEState() { |
| 587 // Use switch here in case we are going to add more text input types. |
| 588 // We disable input method in password field. |
| 589 const HWND window_handle = GetAttachedWindowHandle(GetTextInputClient()); |
| 590 const TextInputType text_input_type = GetTextInputType(); |
| 591 const TextInputMode text_input_mode = GetTextInputMode(); |
| 592 switch (text_input_type) { |
| 593 case ui::TEXT_INPUT_TYPE_NONE: |
| 594 case ui::TEXT_INPUT_TYPE_PASSWORD: |
| 595 imm32_manager_.DisableIME(window_handle); |
| 596 enabled_ = false; |
| 597 break; |
| 598 default: |
| 599 imm32_manager_.EnableIME(window_handle); |
| 600 enabled_ = true; |
| 601 break; |
| 602 } |
| 603 |
| 604 imm32_manager_.SetTextInputMode(window_handle, text_input_mode); |
| 605 tsf_inputscope::SetInputScopeForTsfUnawareWindow( |
| 606 window_handle, text_input_type, text_input_mode); |
| 607 } |
| 608 |
340 } // namespace ui | 609 } // namespace ui |
OLD | NEW |