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.
5 #include "ui/base/ime/input_method_win.h"
7 #include "base/basictypes.h"
8 #include "base/profiler/scoped_tracker.h"
9 #include "ui/base/ime/text_input_client.h"
10 #include "ui/base/ime/win/tsf_input_scope.h"
11 #include "ui/events/event.h"
12 #include "ui/events/event_constants.h"
13 #include "ui/events/event_utils.h"
14 #include "ui/events/keycodes/keyboard_codes.h"
15 #include "ui/gfx/win/dpi.h"
16 #include "ui/gfx/win/hwnd_util.h"
21 // Extra number of chars before and after selection (or composition) range which
22 // is returned to IME for improving conversion accuracy.
23 static const size_t kExtraNumberOfChars
= 20;
27 InputMethodWin::InputMethodWin(internal::InputMethodDelegate
* delegate
,
28 HWND toplevel_window_handle
)
29 : toplevel_window_handle_(toplevel_window_handle
),
30 pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION
),
31 accept_carriage_return_(false),
33 is_candidate_popup_open_(false),
34 composing_window_handle_(NULL
),
35 suppress_next_char_(false) {
36 SetDelegate(delegate
);
37 OnInputLocaleChanged();
40 void InputMethodWin::OnFocus() {
41 InputMethodBase::OnFocus();
42 if (GetTextInputClient())
46 void InputMethodWin::OnBlur() {
47 ConfirmCompositionText();
48 // Gets the focused text input client before calling parent's OnBlur() because
49 // it will cause GetTextInputClient() returns NULL.
50 ui::TextInputClient
* client
= GetTextInputClient();
51 InputMethodBase::OnBlur();
56 bool InputMethodWin::OnUntranslatedIMEMessage(
57 const base::NativeEvent
& event
,
58 InputMethod::NativeEventResult
* result
) {
59 LRESULT original_result
= 0;
62 switch (event
.message
) {
63 case WM_IME_SETCONTEXT
:
64 original_result
= OnImeSetContext(
65 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
67 case WM_IME_STARTCOMPOSITION
:
68 original_result
= OnImeStartComposition(
69 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
71 case WM_IME_COMPOSITION
:
72 original_result
= OnImeComposition(
73 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
75 case WM_IME_ENDCOMPOSITION
:
76 original_result
= OnImeEndComposition(
77 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
80 original_result
= OnImeRequest(
81 event
.message
, event
.wParam
, event
.lParam
, &handled
);
85 original_result
= OnChar(
86 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
89 original_result
= OnImeNotify(
90 event
.message
, event
.wParam
, event
.lParam
, &handled
);
93 NOTREACHED() << "Unknown IME message:" << event
.message
;
97 *result
= original_result
;
101 bool InputMethodWin::DispatchKeyEvent(const ui::KeyEvent
& event
) {
102 if (!event
.HasNativeEvent())
103 return DispatchFabricatedKeyEvent(event
);
105 const base::NativeEvent
& native_key_event
= event
.native_event();
106 if (native_key_event
.message
== WM_CHAR
) {
108 OnChar(native_key_event
.hwnd
, native_key_event
.message
,
109 native_key_event
.wParam
, native_key_event
.lParam
, &handled
);
110 return !!handled
; // Don't send WM_CHAR for post event processing.
112 // Handles ctrl-shift key to change text direction and layout alignment.
113 if (ui::IMM32Manager::IsRTLKeyboardLayoutInstalled() &&
114 !IsTextInputTypeNone()) {
115 // TODO: shouldn't need to generate a KeyEvent here.
116 const ui::KeyEvent
key(native_key_event
);
117 ui::KeyboardCode code
= key
.key_code();
118 if (key
.type() == ui::ET_KEY_PRESSED
) {
119 if (code
== ui::VKEY_SHIFT
) {
120 base::i18n::TextDirection dir
;
121 if (ui::IMM32Manager::IsCtrlShiftPressed(&dir
))
122 pending_requested_direction_
= dir
;
123 } else if (code
!= ui::VKEY_CONTROL
) {
124 pending_requested_direction_
= base::i18n::UNKNOWN_DIRECTION
;
126 } else if (key
.type() == ui::ET_KEY_RELEASED
&&
127 (code
== ui::VKEY_SHIFT
|| code
== ui::VKEY_CONTROL
) &&
128 pending_requested_direction_
!= base::i18n::UNKNOWN_DIRECTION
) {
129 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment(
130 pending_requested_direction_
);
131 pending_requested_direction_
= base::i18n::UNKNOWN_DIRECTION
;
135 suppress_next_char_
= DispatchKeyEventPostIME(event
);
136 return suppress_next_char_
;
139 void InputMethodWin::OnTextInputTypeChanged(const TextInputClient
* client
) {
140 if (!IsTextInputClientFocused(client
) || !IsWindowFocused(client
))
142 imm32_manager_
.CancelIME(toplevel_window_handle_
);
146 void InputMethodWin::OnCaretBoundsChanged(const TextInputClient
* client
) {
147 if (!enabled_
|| !IsTextInputClientFocused(client
) ||
148 !IsWindowFocused(client
)) {
151 // The current text input type should not be NONE if |client| is focused.
152 DCHECK(!IsTextInputTypeNone());
153 // Tentatively assume that the returned value is DIP (Density Independent
154 // Pixel). See the comment in text_input_client.h and http://crbug.com/360334.
155 const gfx::Rect
dip_screen_bounds(GetTextInputClient()->GetCaretBounds());
156 const gfx::Rect screen_bounds
= gfx::win::DIPToScreenRect(dip_screen_bounds
);
158 HWND attached_window
= toplevel_window_handle_
;
159 // TODO(ime): see comment in TextInputClient::GetCaretBounds(), this
160 // conversion shouldn't be necessary.
162 GetClientRect(attached_window
, &r
);
163 POINT window_point
= { screen_bounds
.x(), screen_bounds
.y() };
164 ScreenToClient(attached_window
, &window_point
);
165 gfx::Rect
caret_rect(gfx::Point(window_point
.x
, window_point
.y
),
166 screen_bounds
.size());
167 imm32_manager_
.UpdateCaretRect(attached_window
, caret_rect
);
170 void InputMethodWin::CancelComposition(const TextInputClient
* client
) {
171 if (enabled_
&& IsTextInputClientFocused(client
))
172 imm32_manager_
.CancelIME(toplevel_window_handle_
);
175 void InputMethodWin::OnInputLocaleChanged() {
176 locale_
= imm32_manager_
.GetInputLanguageName();
177 OnInputMethodChanged();
180 std::string
InputMethodWin::GetInputLocale() {
184 bool InputMethodWin::IsCandidatePopupOpen() const {
185 return is_candidate_popup_open_
;
188 void InputMethodWin::OnWillChangeFocusedClient(TextInputClient
* focused_before
,
189 TextInputClient
* focused
) {
190 if (IsWindowFocused(focused_before
))
191 ConfirmCompositionText();
194 void InputMethodWin::OnDidChangeFocusedClient(
195 TextInputClient
* focused_before
,
196 TextInputClient
* focused
) {
197 if (IsWindowFocused(focused
)) {
198 // Force to update the input type since client's TextInputStateChanged()
199 // function might not be called if text input types before the client loses
200 // focus and after it acquires focus again are the same.
201 OnTextInputTypeChanged(focused
);
203 // Force to update caret bounds, in case the client thinks that the caret
204 // bounds has not changed.
205 OnCaretBoundsChanged(focused
);
207 if (focused_before
!= focused
)
208 accept_carriage_return_
= false;
211 LRESULT
InputMethodWin::OnChar(HWND window_handle
,
216 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
217 tracked_objects::ScopedTracker
tracking_profile(
218 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 InputMethodWin::OnChar"));
222 if (suppress_next_char_
) {
223 suppress_next_char_
= false;
227 // We need to send character events to the focused text input client event if
228 // its text input type is ui::TEXT_INPUT_TYPE_NONE.
229 if (GetTextInputClient()) {
230 const base::char16 kCarriageReturn
= L
'\r';
231 const base::char16 ch
= static_cast<base::char16
>(wparam
);
232 // A mask to determine the previous key state from |lparam|. The value is 1
233 // if the key is down before the message is sent, or it is 0 if the key is
235 const uint32 kPrevKeyDownBit
= 0x40000000;
236 if (ch
== kCarriageReturn
&& !(lparam
& kPrevKeyDownBit
))
237 accept_carriage_return_
= true;
238 // Conditionally ignore '\r' events to work around crbug.com/319100.
239 // TODO(yukawa, IME): Figure out long-term solution.
240 if (ch
!= kCarriageReturn
|| accept_carriage_return_
)
241 GetTextInputClient()->InsertChar(ch
, ui::GetModifiersFromKeyState());
244 // Explicitly show the system menu at a good location on [Alt]+[Space].
245 // Note: Setting |handled| to FALSE for DefWindowProc triggering of the system
246 // menu causes undesirable titlebar artifacts in the classic theme.
247 if (message
== WM_SYSCHAR
&& wparam
== VK_SPACE
)
248 gfx::ShowSystemMenu(window_handle
);
253 LRESULT
InputMethodWin::OnImeSetContext(HWND window_handle
,
258 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
259 tracked_objects::ScopedTracker
tracking_profile(
260 FROM_HERE_WITH_EXPLICIT_FUNCTION(
261 "440919 InputMethodWin::OnImeSetContext"));
264 imm32_manager_
.CreateImeWindow(window_handle
);
266 OnInputMethodChanged();
267 return imm32_manager_
.SetImeWindowStyle(
268 window_handle
, message
, wparam
, lparam
, handled
);
271 LRESULT
InputMethodWin::OnImeStartComposition(HWND window_handle
,
276 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
277 tracked_objects::ScopedTracker
tracking_profile(
278 FROM_HERE_WITH_EXPLICIT_FUNCTION(
279 "440919 InputMethodWin::OnImeStartComposition"));
281 // We have to prevent WTL from calling ::DefWindowProc() because the function
282 // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to
283 // over-write the position of IME windows.
286 // Reset the composition status and create IME windows.
287 composing_window_handle_
= window_handle
;
288 imm32_manager_
.CreateImeWindow(window_handle
);
289 imm32_manager_
.ResetComposition(window_handle
);
293 LRESULT
InputMethodWin::OnImeComposition(HWND window_handle
,
298 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
299 tracked_objects::ScopedTracker
tracking_profile(
300 FROM_HERE_WITH_EXPLICIT_FUNCTION(
301 "440919 InputMethodWin::OnImeComposition"));
303 // We have to prevent WTL from calling ::DefWindowProc() because we do not
304 // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages.
307 // At first, update the position of the IME window.
308 imm32_manager_
.UpdateImeWindow(window_handle
);
310 // Retrieve the result string and its attributes of the ongoing composition
311 // and send it to a renderer process.
312 ui::CompositionText composition
;
313 if (imm32_manager_
.GetResult(window_handle
, lparam
, &composition
.text
)) {
314 if (!IsTextInputTypeNone())
315 GetTextInputClient()->InsertText(composition
.text
);
316 imm32_manager_
.ResetComposition(window_handle
);
317 // Fall though and try reading the composition string.
318 // Japanese IMEs send a message containing both GCS_RESULTSTR and
319 // GCS_COMPSTR, which means an ongoing composition has been finished
320 // by the start of another composition.
322 // Retrieve the composition string and its attributes of the ongoing
323 // composition and send it to a renderer process.
324 if (imm32_manager_
.GetComposition(window_handle
, lparam
, &composition
) &&
325 !IsTextInputTypeNone())
326 GetTextInputClient()->SetCompositionText(composition
);
331 LRESULT
InputMethodWin::OnImeEndComposition(HWND window_handle
,
336 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
337 tracked_objects::ScopedTracker
tracking_profile(
338 FROM_HERE_WITH_EXPLICIT_FUNCTION(
339 "440919 InputMethodWin::OnImeEndComposition"));
341 // Let WTL call ::DefWindowProc() and release its resources.
344 composing_window_handle_
= NULL
;
346 if (!IsTextInputTypeNone() && GetTextInputClient()->HasCompositionText())
347 GetTextInputClient()->ClearCompositionText();
349 imm32_manager_
.ResetComposition(window_handle
);
350 imm32_manager_
.DestroyImeWindow(window_handle
);
354 LRESULT
InputMethodWin::OnImeNotify(UINT message
,
358 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
359 tracked_objects::ScopedTracker
tracking_profile(
360 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 InputMethodWin::OnImeNotify"));
364 // Update |is_candidate_popup_open_|, whether a candidate window is open.
366 case IMN_OPENCANDIDATE
:
367 is_candidate_popup_open_
= true;
369 case IMN_CLOSECANDIDATE
:
370 is_candidate_popup_open_
= false;
377 LRESULT
InputMethodWin::OnImeRequest(UINT message
,
381 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
382 tracked_objects::ScopedTracker
tracking_profile(
383 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 InputMethodWin::OnImeRequest"));
387 // Should not receive WM_IME_REQUEST message, if IME is disabled.
388 const ui::TextInputType type
= GetTextInputType();
389 if (type
== ui::TEXT_INPUT_TYPE_NONE
||
390 type
== ui::TEXT_INPUT_TYPE_PASSWORD
) {
395 case IMR_RECONVERTSTRING
:
397 return OnReconvertString(reinterpret_cast<RECONVERTSTRING
*>(lparam
));
398 case IMR_DOCUMENTFEED
:
400 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING
*>(lparam
));
401 case IMR_QUERYCHARPOSITION
:
403 return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION
*>(lparam
));
409 LRESULT
InputMethodWin::OnDocumentFeed(RECONVERTSTRING
* reconv
) {
410 ui::TextInputClient
* client
= GetTextInputClient();
414 gfx::Range text_range
;
415 if (!client
->GetTextRange(&text_range
) || text_range
.is_empty())
419 gfx::Range target_range
;
420 if (client
->HasCompositionText())
421 result
= client
->GetCompositionTextRange(&target_range
);
423 if (!result
|| target_range
.is_empty()) {
424 if (!client
->GetSelectionRange(&target_range
) ||
425 !target_range
.IsValid()) {
430 if (!text_range
.Contains(target_range
))
433 if (target_range
.GetMin() - text_range
.start() > kExtraNumberOfChars
)
434 text_range
.set_start(target_range
.GetMin() - kExtraNumberOfChars
);
436 if (text_range
.end() - target_range
.GetMax() > kExtraNumberOfChars
)
437 text_range
.set_end(target_range
.GetMax() + kExtraNumberOfChars
);
439 size_t len
= text_range
.length();
440 size_t need_size
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
445 if (reconv
->dwSize
< need_size
)
449 if (!GetTextInputClient()->GetTextFromRange(text_range
, &text
))
451 DCHECK_EQ(text_range
.length(), text
.length());
453 reconv
->dwVersion
= 0;
454 reconv
->dwStrLen
= len
;
455 reconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
456 reconv
->dwCompStrLen
=
457 client
->HasCompositionText() ? target_range
.length() : 0;
458 reconv
->dwCompStrOffset
=
459 (target_range
.GetMin() - text_range
.start()) * sizeof(WCHAR
);
460 reconv
->dwTargetStrLen
= target_range
.length();
461 reconv
->dwTargetStrOffset
= reconv
->dwCompStrOffset
;
463 memcpy((char*)reconv
+ sizeof(RECONVERTSTRING
),
464 text
.c_str(), len
* sizeof(WCHAR
));
466 // According to Microsoft API document, IMR_RECONVERTSTRING and
467 // IMR_DOCUMENTFEED should return reconv, but some applications return
469 return reinterpret_cast<LRESULT
>(reconv
);
472 LRESULT
InputMethodWin::OnReconvertString(RECONVERTSTRING
* reconv
) {
473 ui::TextInputClient
* client
= GetTextInputClient();
477 // If there is a composition string already, we don't allow reconversion.
478 if (client
->HasCompositionText())
481 gfx::Range text_range
;
482 if (!client
->GetTextRange(&text_range
) || text_range
.is_empty())
485 gfx::Range selection_range
;
486 if (!client
->GetSelectionRange(&selection_range
) ||
487 selection_range
.is_empty()) {
491 DCHECK(text_range
.Contains(selection_range
));
493 size_t len
= selection_range
.length();
494 size_t need_size
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
499 if (reconv
->dwSize
< need_size
)
502 // TODO(penghuang): Return some extra context to help improve IME's
503 // reconversion accuracy.
505 if (!GetTextInputClient()->GetTextFromRange(selection_range
, &text
))
507 DCHECK_EQ(selection_range
.length(), text
.length());
509 reconv
->dwVersion
= 0;
510 reconv
->dwStrLen
= len
;
511 reconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
512 reconv
->dwCompStrLen
= len
;
513 reconv
->dwCompStrOffset
= 0;
514 reconv
->dwTargetStrLen
= len
;
515 reconv
->dwTargetStrOffset
= 0;
517 memcpy(reinterpret_cast<char*>(reconv
) + sizeof(RECONVERTSTRING
),
518 text
.c_str(), len
* sizeof(WCHAR
));
520 // According to Microsoft API document, IMR_RECONVERTSTRING and
521 // IMR_DOCUMENTFEED should return reconv, but some applications return
523 return reinterpret_cast<LRESULT
>(reconv
);
526 LRESULT
InputMethodWin::OnQueryCharPosition(IMECHARPOSITION
* char_positon
) {
530 if (char_positon
->dwSize
< sizeof(IMECHARPOSITION
))
533 ui::TextInputClient
* client
= GetTextInputClient();
537 // Tentatively assume that the returned value from |client| is DIP (Density
538 // Independent Pixel). See the comment in text_input_client.h and
539 // http://crbug.com/360334.
541 if (client
->HasCompositionText()) {
542 if (!client
->GetCompositionCharacterBounds(char_positon
->dwCharPos
,
547 // If there is no composition and the first character is queried, returns
548 // the caret bounds. This behavior is the same to that of RichEdit control.
549 if (char_positon
->dwCharPos
!= 0)
551 dip_rect
= client
->GetCaretBounds();
553 const gfx::Rect rect
= gfx::win::DIPToScreenRect(dip_rect
);
555 char_positon
->pt
.x
= rect
.x();
556 char_positon
->pt
.y
= rect
.y();
557 char_positon
->cLineHeight
= rect
.height();
558 return 1; // returns non-zero value when succeeded.
561 bool InputMethodWin::IsWindowFocused(const TextInputClient
* client
) const {
564 // When Aura is enabled, |attached_window_handle| should always be a top-level
565 // window. So we can safely assume that |attached_window_handle| is ready for
566 // receiving keyboard input as long as it is an active window. This works well
567 // even when the |attached_window_handle| becomes active but has not received
569 return toplevel_window_handle_
&&
570 GetActiveWindow() == toplevel_window_handle_
;
573 bool InputMethodWin::DispatchFabricatedKeyEvent(const ui::KeyEvent
& event
) {
574 if (event
.is_char()) {
575 if (suppress_next_char_
) {
576 suppress_next_char_
= false;
579 if (GetTextInputClient()) {
580 GetTextInputClient()->InsertChar(
581 static_cast<base::char16
>(event
.key_code()),
582 ui::GetModifiersFromKeyState());
586 return DispatchKeyEventPostIME(event
);
589 void InputMethodWin::ConfirmCompositionText() {
590 if (composing_window_handle_
)
591 imm32_manager_
.CleanupComposition(composing_window_handle_
);
593 if (!IsTextInputTypeNone()) {
594 // Though above line should confirm the client's composition text by sending
595 // a result text to us, in case the input method and the client are in
596 // inconsistent states, we check the client's composition state again.
597 if (GetTextInputClient()->HasCompositionText())
598 GetTextInputClient()->ConfirmCompositionText();
602 void InputMethodWin::UpdateIMEState() {
603 // Use switch here in case we are going to add more text input types.
604 // We disable input method in password field.
605 const HWND window_handle
= toplevel_window_handle_
;
606 const TextInputType text_input_type
= GetTextInputType();
607 const TextInputMode text_input_mode
= GetTextInputMode();
608 switch (text_input_type
) {
609 case ui::TEXT_INPUT_TYPE_NONE
:
610 case ui::TEXT_INPUT_TYPE_PASSWORD
:
611 imm32_manager_
.DisableIME(window_handle
);
615 imm32_manager_
.EnableIME(window_handle
);
620 imm32_manager_
.SetTextInputMode(window_handle
, text_input_mode
);
621 tsf_inputscope::SetInputScopeForTsfUnawareWindow(
622 window_handle
, text_input_type
, text_input_mode
);