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 "ui/base/ime/text_input_client.h"
9 #include "ui/base/ime/win/tsf_input_scope.h"
10 #include "ui/events/event.h"
11 #include "ui/events/event_constants.h"
12 #include "ui/events/event_utils.h"
13 #include "ui/events/keycodes/keyboard_codes.h"
14 #include "ui/gfx/win/dpi.h"
15 #include "ui/gfx/win/hwnd_util.h"
20 // Extra number of chars before and after selection (or composition) range which
21 // is returned to IME for improving conversion accuracy.
22 static const size_t kExtraNumberOfChars
= 20;
26 InputMethodWin::InputMethodWin(internal::InputMethodDelegate
* delegate
,
27 HWND toplevel_window_handle
)
28 : toplevel_window_handle_(toplevel_window_handle
),
29 pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION
),
30 accept_carriage_return_(false),
33 is_candidate_popup_open_(false),
34 composing_window_handle_(NULL
) {
35 SetDelegate(delegate
);
36 // In non-Aura environment, appropriate callbacks to OnFocus() and OnBlur()
37 // are not implemented yet. To work around this limitation, here we use
38 // "always focused" model.
39 // TODO(ime): Fix the caller of OnFocus() and OnBlur() so that appropriate
40 // focus event will be passed.
41 InputMethodBase::OnFocus();
44 void InputMethodWin::Init(bool focused
) {
45 // Gets the initial input locale.
46 OnInputLocaleChanged();
48 InputMethodBase::Init(focused
);
51 void InputMethodWin::OnFocus() {
52 // Ignore OnFocus event for "always focused" model. See the comment in the
54 // TODO(ime): Implement OnFocus once the callers are fixed.
57 void InputMethodWin::OnBlur() {
58 // Ignore OnBlur event for "always focused" model. See the comment in the
60 // TODO(ime): Implement OnFocus once the callers are fixed.
63 bool InputMethodWin::OnUntranslatedIMEMessage(
64 const base::NativeEvent
& event
,
65 InputMethod::NativeEventResult
* result
) {
66 LRESULT original_result
= 0;
68 switch (event
.message
) {
69 case WM_IME_SETCONTEXT
:
70 original_result
= OnImeSetContext(
71 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
73 case WM_IME_STARTCOMPOSITION
:
74 original_result
= OnImeStartComposition(
75 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
77 case WM_IME_COMPOSITION
:
78 original_result
= OnImeComposition(
79 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
81 case WM_IME_ENDCOMPOSITION
:
82 original_result
= OnImeEndComposition(
83 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
86 original_result
= OnImeRequest(
87 event
.message
, event
.wParam
, event
.lParam
, &handled
);
91 original_result
= OnChar(
92 event
.hwnd
, event
.message
, event
.wParam
, event
.lParam
, &handled
);
95 original_result
= OnImeNotify(
96 event
.message
, event
.wParam
, event
.lParam
, &handled
);
99 NOTREACHED() << "Unknown IME message:" << event
.message
;
103 *result
= original_result
;
107 bool InputMethodWin::DispatchKeyEvent(const ui::KeyEvent
& event
) {
108 if (!event
.HasNativeEvent())
109 return DispatchFabricatedKeyEvent(event
);
111 const base::NativeEvent
& native_key_event
= event
.native_event();
112 if (native_key_event
.message
== WM_CHAR
) {
114 OnChar(native_key_event
.hwnd
, native_key_event
.message
,
115 native_key_event
.wParam
, native_key_event
.lParam
, &handled
);
116 return !!handled
; // Don't send WM_CHAR for post event processing.
118 // Handles ctrl-shift key to change text direction and layout alignment.
119 if (ui::IMM32Manager::IsRTLKeyboardLayoutInstalled() &&
120 !IsTextInputTypeNone()) {
121 // TODO: shouldn't need to generate a KeyEvent here.
122 const ui::KeyEvent
key(native_key_event
,
123 native_key_event
.message
== WM_CHAR
);
124 ui::KeyboardCode code
= key
.key_code();
125 if (key
.type() == ui::ET_KEY_PRESSED
) {
126 if (code
== ui::VKEY_SHIFT
) {
127 base::i18n::TextDirection dir
;
128 if (ui::IMM32Manager::IsCtrlShiftPressed(&dir
))
129 pending_requested_direction_
= dir
;
130 } else if (code
!= ui::VKEY_CONTROL
) {
131 pending_requested_direction_
= base::i18n::UNKNOWN_DIRECTION
;
133 } else if (key
.type() == ui::ET_KEY_RELEASED
&&
134 (code
== ui::VKEY_SHIFT
|| code
== ui::VKEY_CONTROL
) &&
135 pending_requested_direction_
!= base::i18n::UNKNOWN_DIRECTION
) {
136 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment(
137 pending_requested_direction_
);
138 pending_requested_direction_
= base::i18n::UNKNOWN_DIRECTION
;
142 return DispatchKeyEventPostIME(event
);
145 void InputMethodWin::OnTextInputTypeChanged(const TextInputClient
* client
) {
146 if (!IsTextInputClientFocused(client
) || !IsWindowFocused(client
))
148 imm32_manager_
.CancelIME(GetAttachedWindowHandle(client
));
152 void InputMethodWin::OnCaretBoundsChanged(const TextInputClient
* client
) {
153 if (!enabled_
|| !IsTextInputClientFocused(client
) ||
154 !IsWindowFocused(client
)) {
157 // The current text input type should not be NONE if |client| is focused.
158 DCHECK(!IsTextInputTypeNone());
159 // Tentatively assume that the returned value is DIP (Density Independent
160 // Pixel). See the comment in text_input_client.h and http://crbug.com/360334.
161 const gfx::Rect
dip_screen_bounds(GetTextInputClient()->GetCaretBounds());
162 const gfx::Rect screen_bounds
= gfx::win::DIPToScreenRect(dip_screen_bounds
);
164 HWND attached_window
= GetAttachedWindowHandle(client
);
165 // TODO(ime): see comment in TextInputClient::GetCaretBounds(), this
166 // conversion shouldn't be necessary.
168 GetClientRect(attached_window
, &r
);
169 POINT window_point
= { screen_bounds
.x(), screen_bounds
.y() };
170 ScreenToClient(attached_window
, &window_point
);
171 gfx::Rect
caret_rect(gfx::Point(window_point
.x
, window_point
.y
),
172 screen_bounds
.size());
173 imm32_manager_
.UpdateCaretRect(attached_window
, caret_rect
);
176 void InputMethodWin::CancelComposition(const TextInputClient
* client
) {
177 if (enabled_
&& IsTextInputClientFocused(client
))
178 imm32_manager_
.CancelIME(GetAttachedWindowHandle(client
));
181 void InputMethodWin::OnInputLocaleChanged() {
182 active_
= imm32_manager_
.SetInputLanguage();
183 locale_
= imm32_manager_
.GetInputLanguageName();
184 OnInputMethodChanged();
187 std::string
InputMethodWin::GetInputLocale() {
191 bool InputMethodWin::IsActive() {
195 bool InputMethodWin::IsCandidatePopupOpen() const {
196 return is_candidate_popup_open_
;
199 void InputMethodWin::OnWillChangeFocusedClient(TextInputClient
* focused_before
,
200 TextInputClient
* focused
) {
201 if (IsWindowFocused(focused_before
))
202 ConfirmCompositionText();
205 void InputMethodWin::OnDidChangeFocusedClient(
206 TextInputClient
* focused_before
,
207 TextInputClient
* focused
) {
208 if (IsWindowFocused(focused
)) {
209 // Force to update the input type since client's TextInputStateChanged()
210 // function might not be called if text input types before the client loses
211 // focus and after it acquires focus again are the same.
212 OnTextInputTypeChanged(focused
);
216 // Force to update caret bounds, in case the client thinks that the caret
217 // bounds has not changed.
218 OnCaretBoundsChanged(focused
);
220 if (focused_before
!= focused
)
221 accept_carriage_return_
= false;
224 LRESULT
InputMethodWin::OnChar(HWND window_handle
,
231 // We need to send character events to the focused text input client event if
232 // its text input type is ui::TEXT_INPUT_TYPE_NONE.
233 if (GetTextInputClient()) {
234 const base::char16 kCarriageReturn
= L
'\r';
235 const base::char16 ch
= static_cast<base::char16
>(wparam
);
236 // A mask to determine the previous key state from |lparam|. The value is 1
237 // if the key is down before the message is sent, or it is 0 if the key is
239 const uint32 kPrevKeyDownBit
= 0x40000000;
240 if (ch
== kCarriageReturn
&& !(lparam
& kPrevKeyDownBit
))
241 accept_carriage_return_
= true;
242 // Conditionally ignore '\r' events to work around crbug.com/319100.
243 // TODO(yukawa, IME): Figure out long-term solution.
244 if (ch
!= kCarriageReturn
|| accept_carriage_return_
)
245 GetTextInputClient()->InsertChar(ch
, ui::GetModifiersFromKeyState());
248 // Explicitly show the system menu at a good location on [Alt]+[Space].
249 // Note: Setting |handled| to FALSE for DefWindowProc triggering of the system
250 // menu causes undesirable titlebar artifacts in the classic theme.
251 if (message
== WM_SYSCHAR
&& wparam
== VK_SPACE
)
252 gfx::ShowSystemMenu(window_handle
);
257 LRESULT
InputMethodWin::OnImeSetContext(HWND window_handle
,
263 imm32_manager_
.CreateImeWindow(window_handle
);
265 OnInputMethodChanged();
266 return imm32_manager_
.SetImeWindowStyle(
267 window_handle
, message
, wparam
, lparam
, handled
);
270 LRESULT
InputMethodWin::OnImeStartComposition(HWND window_handle
,
275 // We have to prevent WTL from calling ::DefWindowProc() because the function
276 // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to
277 // over-write the position of IME windows.
280 // Reset the composition status and create IME windows.
281 composing_window_handle_
= window_handle
;
282 imm32_manager_
.CreateImeWindow(window_handle
);
283 imm32_manager_
.ResetComposition(window_handle
);
287 LRESULT
InputMethodWin::OnImeComposition(HWND window_handle
,
292 // We have to prevent WTL from calling ::DefWindowProc() because we do not
293 // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages.
296 // At first, update the position of the IME window.
297 imm32_manager_
.UpdateImeWindow(window_handle
);
299 // Retrieve the result string and its attributes of the ongoing composition
300 // and send it to a renderer process.
301 ui::CompositionText composition
;
302 if (imm32_manager_
.GetResult(window_handle
, lparam
, &composition
.text
)) {
303 if (!IsTextInputTypeNone())
304 GetTextInputClient()->InsertText(composition
.text
);
305 imm32_manager_
.ResetComposition(window_handle
);
306 // Fall though and try reading the composition string.
307 // Japanese IMEs send a message containing both GCS_RESULTSTR and
308 // GCS_COMPSTR, which means an ongoing composition has been finished
309 // by the start of another composition.
311 // Retrieve the composition string and its attributes of the ongoing
312 // composition and send it to a renderer process.
313 if (imm32_manager_
.GetComposition(window_handle
, lparam
, &composition
) &&
314 !IsTextInputTypeNone())
315 GetTextInputClient()->SetCompositionText(composition
);
320 LRESULT
InputMethodWin::OnImeEndComposition(HWND window_handle
,
325 // Let WTL call ::DefWindowProc() and release its resources.
328 composing_window_handle_
= NULL
;
330 if (!IsTextInputTypeNone() && GetTextInputClient()->HasCompositionText())
331 GetTextInputClient()->ClearCompositionText();
333 imm32_manager_
.ResetComposition(window_handle
);
334 imm32_manager_
.DestroyImeWindow(window_handle
);
338 LRESULT
InputMethodWin::OnImeNotify(UINT message
,
344 bool previous_state
= is_candidate_popup_open_
;
346 // Update |is_candidate_popup_open_|, whether a candidate window is open.
348 case IMN_OPENCANDIDATE
:
349 is_candidate_popup_open_
= true;
351 OnCandidateWindowShown();
353 case IMN_CLOSECANDIDATE
:
354 is_candidate_popup_open_
= false;
356 OnCandidateWindowHidden();
358 case IMN_CHANGECANDIDATE
:
359 // TODO(kochi): The IME API expects this event to notify window size change,
360 // while this may fire more often without window resize. There is no generic
361 // way to get bounds of candidate window.
362 OnCandidateWindowUpdated();
369 LRESULT
InputMethodWin::OnImeRequest(UINT message
,
375 // Should not receive WM_IME_REQUEST message, if IME is disabled.
376 const ui::TextInputType type
= GetTextInputType();
377 if (type
== ui::TEXT_INPUT_TYPE_NONE
||
378 type
== ui::TEXT_INPUT_TYPE_PASSWORD
) {
383 case IMR_RECONVERTSTRING
:
385 return OnReconvertString(reinterpret_cast<RECONVERTSTRING
*>(lparam
));
386 case IMR_DOCUMENTFEED
:
388 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING
*>(lparam
));
389 case IMR_QUERYCHARPOSITION
:
391 return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION
*>(lparam
));
397 LRESULT
InputMethodWin::OnDocumentFeed(RECONVERTSTRING
* reconv
) {
398 ui::TextInputClient
* client
= GetTextInputClient();
402 gfx::Range text_range
;
403 if (!client
->GetTextRange(&text_range
) || text_range
.is_empty())
407 gfx::Range target_range
;
408 if (client
->HasCompositionText())
409 result
= client
->GetCompositionTextRange(&target_range
);
411 if (!result
|| target_range
.is_empty()) {
412 if (!client
->GetSelectionRange(&target_range
) ||
413 !target_range
.IsValid()) {
418 if (!text_range
.Contains(target_range
))
421 if (target_range
.GetMin() - text_range
.start() > kExtraNumberOfChars
)
422 text_range
.set_start(target_range
.GetMin() - kExtraNumberOfChars
);
424 if (text_range
.end() - target_range
.GetMax() > kExtraNumberOfChars
)
425 text_range
.set_end(target_range
.GetMax() + kExtraNumberOfChars
);
427 size_t len
= text_range
.length();
428 size_t need_size
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
433 if (reconv
->dwSize
< need_size
)
437 if (!GetTextInputClient()->GetTextFromRange(text_range
, &text
))
439 DCHECK_EQ(text_range
.length(), text
.length());
441 reconv
->dwVersion
= 0;
442 reconv
->dwStrLen
= len
;
443 reconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
444 reconv
->dwCompStrLen
=
445 client
->HasCompositionText() ? target_range
.length() : 0;
446 reconv
->dwCompStrOffset
=
447 (target_range
.GetMin() - text_range
.start()) * sizeof(WCHAR
);
448 reconv
->dwTargetStrLen
= target_range
.length();
449 reconv
->dwTargetStrOffset
= reconv
->dwCompStrOffset
;
451 memcpy((char*)reconv
+ sizeof(RECONVERTSTRING
),
452 text
.c_str(), len
* sizeof(WCHAR
));
454 // According to Microsoft API document, IMR_RECONVERTSTRING and
455 // IMR_DOCUMENTFEED should return reconv, but some applications return
457 return reinterpret_cast<LRESULT
>(reconv
);
460 LRESULT
InputMethodWin::OnReconvertString(RECONVERTSTRING
* reconv
) {
461 ui::TextInputClient
* client
= GetTextInputClient();
465 // If there is a composition string already, we don't allow reconversion.
466 if (client
->HasCompositionText())
469 gfx::Range text_range
;
470 if (!client
->GetTextRange(&text_range
) || text_range
.is_empty())
473 gfx::Range selection_range
;
474 if (!client
->GetSelectionRange(&selection_range
) ||
475 selection_range
.is_empty()) {
479 DCHECK(text_range
.Contains(selection_range
));
481 size_t len
= selection_range
.length();
482 size_t need_size
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
487 if (reconv
->dwSize
< need_size
)
490 // TODO(penghuang): Return some extra context to help improve IME's
491 // reconversion accuracy.
493 if (!GetTextInputClient()->GetTextFromRange(selection_range
, &text
))
495 DCHECK_EQ(selection_range
.length(), text
.length());
497 reconv
->dwVersion
= 0;
498 reconv
->dwStrLen
= len
;
499 reconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
500 reconv
->dwCompStrLen
= len
;
501 reconv
->dwCompStrOffset
= 0;
502 reconv
->dwTargetStrLen
= len
;
503 reconv
->dwTargetStrOffset
= 0;
505 memcpy(reinterpret_cast<char*>(reconv
) + sizeof(RECONVERTSTRING
),
506 text
.c_str(), len
* sizeof(WCHAR
));
508 // According to Microsoft API document, IMR_RECONVERTSTRING and
509 // IMR_DOCUMENTFEED should return reconv, but some applications return
511 return reinterpret_cast<LRESULT
>(reconv
);
514 LRESULT
InputMethodWin::OnQueryCharPosition(IMECHARPOSITION
* char_positon
) {
518 if (char_positon
->dwSize
< sizeof(IMECHARPOSITION
))
521 ui::TextInputClient
* client
= GetTextInputClient();
525 // Tentatively assume that the returned value from |client| is DIP (Density
526 // Independent Pixel). See the comment in text_input_client.h and
527 // http://crbug.com/360334.
529 if (client
->HasCompositionText()) {
530 if (!client
->GetCompositionCharacterBounds(char_positon
->dwCharPos
,
535 // If there is no composition and the first character is queried, returns
536 // the caret bounds. This behavior is the same to that of RichEdit control.
537 if (char_positon
->dwCharPos
!= 0)
539 dip_rect
= client
->GetCaretBounds();
541 const gfx::Rect rect
= gfx::win::DIPToScreenRect(dip_rect
);
543 char_positon
->pt
.x
= rect
.x();
544 char_positon
->pt
.y
= rect
.y();
545 char_positon
->cLineHeight
= rect
.height();
546 return 1; // returns non-zero value when succeeded.
549 HWND
InputMethodWin::GetAttachedWindowHandle(
550 const TextInputClient
* text_input_client
) const {
551 // On Aura environment, we can assume that |toplevel_window_handle_| always
552 // represents the valid top-level window handle because each top-level window
553 // is responsible for lifecycle management of corresponding InputMethod
555 return toplevel_window_handle_
;
558 bool InputMethodWin::IsWindowFocused(const TextInputClient
* client
) const {
561 HWND attached_window_handle
= GetAttachedWindowHandle(client
);
562 // When Aura is enabled, |attached_window_handle| should always be a top-level
563 // window. So we can safely assume that |attached_window_handle| is ready for
564 // receiving keyboard input as long as it is an active window. This works well
565 // even when the |attached_window_handle| becomes active but has not received
567 return attached_window_handle
&& GetActiveWindow() == attached_window_handle
;
570 bool InputMethodWin::DispatchFabricatedKeyEvent(const ui::KeyEvent
& event
) {
571 if (event
.is_char()) {
572 if (GetTextInputClient()) {
573 GetTextInputClient()->InsertChar(event
.key_code(),
574 ui::GetModifiersFromKeyState());
578 return DispatchKeyEventPostIME(event
);
581 void InputMethodWin::ConfirmCompositionText() {
582 if (composing_window_handle_
)
583 imm32_manager_
.CleanupComposition(composing_window_handle_
);
585 if (!IsTextInputTypeNone()) {
586 // Though above line should confirm the client's composition text by sending
587 // a result text to us, in case the input method and the client are in
588 // inconsistent states, we check the client's composition state again.
589 if (GetTextInputClient()->HasCompositionText())
590 GetTextInputClient()->ConfirmCompositionText();
594 void InputMethodWin::UpdateIMEState() {
595 // Use switch here in case we are going to add more text input types.
596 // We disable input method in password field.
597 const HWND window_handle
= GetAttachedWindowHandle(GetTextInputClient());
598 const TextInputType text_input_type
= GetTextInputType();
599 const TextInputMode text_input_mode
= GetTextInputMode();
600 switch (text_input_type
) {
601 case ui::TEXT_INPUT_TYPE_NONE
:
602 case ui::TEXT_INPUT_TYPE_PASSWORD
:
603 imm32_manager_
.DisableIME(window_handle
);
607 imm32_manager_
.EnableIME(window_handle
);
612 imm32_manager_
.SetTextInputMode(window_handle
, text_input_mode
);
613 tsf_inputscope::SetInputScopeForTsfUnawareWindow(
614 window_handle
, text_input_type
, text_input_mode
);