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 ui::KeyboardCode code
= key
.key_code();
124 if (key
.type() == ui::ET_KEY_PRESSED
) {
125 if (code
== ui::VKEY_SHIFT
) {
126 base::i18n::TextDirection dir
;
127 if (ui::IMM32Manager::IsCtrlShiftPressed(&dir
))
128 pending_requested_direction_
= dir
;
129 } else if (code
!= ui::VKEY_CONTROL
) {
130 pending_requested_direction_
= base::i18n::UNKNOWN_DIRECTION
;
132 } else if (key
.type() == ui::ET_KEY_RELEASED
&&
133 (code
== ui::VKEY_SHIFT
|| code
== ui::VKEY_CONTROL
) &&
134 pending_requested_direction_
!= base::i18n::UNKNOWN_DIRECTION
) {
135 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment(
136 pending_requested_direction_
);
137 pending_requested_direction_
= base::i18n::UNKNOWN_DIRECTION
;
141 return DispatchKeyEventPostIME(event
);
144 void InputMethodWin::OnTextInputTypeChanged(const TextInputClient
* client
) {
145 if (!IsTextInputClientFocused(client
) || !IsWindowFocused(client
))
147 imm32_manager_
.CancelIME(GetAttachedWindowHandle(client
));
151 void InputMethodWin::OnCaretBoundsChanged(const TextInputClient
* client
) {
152 if (!enabled_
|| !IsTextInputClientFocused(client
) ||
153 !IsWindowFocused(client
)) {
156 // The current text input type should not be NONE if |client| is focused.
157 DCHECK(!IsTextInputTypeNone());
158 // Tentatively assume that the returned value is DIP (Density Independent
159 // Pixel). See the comment in text_input_client.h and http://crbug.com/360334.
160 const gfx::Rect
dip_screen_bounds(GetTextInputClient()->GetCaretBounds());
161 const gfx::Rect screen_bounds
= gfx::win::DIPToScreenRect(dip_screen_bounds
);
163 HWND attached_window
= GetAttachedWindowHandle(client
);
164 // TODO(ime): see comment in TextInputClient::GetCaretBounds(), this
165 // conversion shouldn't be necessary.
167 GetClientRect(attached_window
, &r
);
168 POINT window_point
= { screen_bounds
.x(), screen_bounds
.y() };
169 ScreenToClient(attached_window
, &window_point
);
170 gfx::Rect
caret_rect(gfx::Point(window_point
.x
, window_point
.y
),
171 screen_bounds
.size());
172 imm32_manager_
.UpdateCaretRect(attached_window
, caret_rect
);
175 void InputMethodWin::CancelComposition(const TextInputClient
* client
) {
176 if (enabled_
&& IsTextInputClientFocused(client
))
177 imm32_manager_
.CancelIME(GetAttachedWindowHandle(client
));
180 void InputMethodWin::OnInputLocaleChanged() {
181 active_
= imm32_manager_
.SetInputLanguage();
182 locale_
= imm32_manager_
.GetInputLanguageName();
183 OnInputMethodChanged();
186 std::string
InputMethodWin::GetInputLocale() {
190 bool InputMethodWin::IsActive() {
194 bool InputMethodWin::IsCandidatePopupOpen() const {
195 return is_candidate_popup_open_
;
198 void InputMethodWin::OnWillChangeFocusedClient(TextInputClient
* focused_before
,
199 TextInputClient
* focused
) {
200 if (IsWindowFocused(focused_before
))
201 ConfirmCompositionText();
204 void InputMethodWin::OnDidChangeFocusedClient(
205 TextInputClient
* focused_before
,
206 TextInputClient
* focused
) {
207 if (IsWindowFocused(focused
)) {
208 // Force to update the input type since client's TextInputStateChanged()
209 // function might not be called if text input types before the client loses
210 // focus and after it acquires focus again are the same.
211 OnTextInputTypeChanged(focused
);
215 // Force to update caret bounds, in case the client thinks that the caret
216 // bounds has not changed.
217 OnCaretBoundsChanged(focused
);
219 if (focused_before
!= focused
)
220 accept_carriage_return_
= false;
223 LRESULT
InputMethodWin::OnChar(HWND window_handle
,
230 // We need to send character events to the focused text input client event if
231 // its text input type is ui::TEXT_INPUT_TYPE_NONE.
232 if (GetTextInputClient()) {
233 const base::char16 kCarriageReturn
= L
'\r';
234 const base::char16 ch
= static_cast<base::char16
>(wparam
);
235 // A mask to determine the previous key state from |lparam|. The value is 1
236 // if the key is down before the message is sent, or it is 0 if the key is
238 const uint32 kPrevKeyDownBit
= 0x40000000;
239 if (ch
== kCarriageReturn
&& !(lparam
& kPrevKeyDownBit
))
240 accept_carriage_return_
= true;
241 // Conditionally ignore '\r' events to work around crbug.com/319100.
242 // TODO(yukawa, IME): Figure out long-term solution.
243 if (ch
!= kCarriageReturn
|| accept_carriage_return_
)
244 GetTextInputClient()->InsertChar(ch
, ui::GetModifiersFromKeyState());
247 // Explicitly show the system menu at a good location on [Alt]+[Space].
248 // Note: Setting |handled| to FALSE for DefWindowProc triggering of the system
249 // menu causes undesirable titlebar artifacts in the classic theme.
250 if (message
== WM_SYSCHAR
&& wparam
== VK_SPACE
)
251 gfx::ShowSystemMenu(window_handle
);
256 LRESULT
InputMethodWin::OnImeSetContext(HWND window_handle
,
262 imm32_manager_
.CreateImeWindow(window_handle
);
264 OnInputMethodChanged();
265 return imm32_manager_
.SetImeWindowStyle(
266 window_handle
, message
, wparam
, lparam
, handled
);
269 LRESULT
InputMethodWin::OnImeStartComposition(HWND window_handle
,
274 // We have to prevent WTL from calling ::DefWindowProc() because the function
275 // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to
276 // over-write the position of IME windows.
279 // Reset the composition status and create IME windows.
280 composing_window_handle_
= window_handle
;
281 imm32_manager_
.CreateImeWindow(window_handle
);
282 imm32_manager_
.ResetComposition(window_handle
);
286 LRESULT
InputMethodWin::OnImeComposition(HWND window_handle
,
291 // We have to prevent WTL from calling ::DefWindowProc() because we do not
292 // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages.
295 // At first, update the position of the IME window.
296 imm32_manager_
.UpdateImeWindow(window_handle
);
298 // Retrieve the result string and its attributes of the ongoing composition
299 // and send it to a renderer process.
300 ui::CompositionText composition
;
301 if (imm32_manager_
.GetResult(window_handle
, lparam
, &composition
.text
)) {
302 if (!IsTextInputTypeNone())
303 GetTextInputClient()->InsertText(composition
.text
);
304 imm32_manager_
.ResetComposition(window_handle
);
305 // Fall though and try reading the composition string.
306 // Japanese IMEs send a message containing both GCS_RESULTSTR and
307 // GCS_COMPSTR, which means an ongoing composition has been finished
308 // by the start of another composition.
310 // Retrieve the composition string and its attributes of the ongoing
311 // composition and send it to a renderer process.
312 if (imm32_manager_
.GetComposition(window_handle
, lparam
, &composition
) &&
313 !IsTextInputTypeNone())
314 GetTextInputClient()->SetCompositionText(composition
);
319 LRESULT
InputMethodWin::OnImeEndComposition(HWND window_handle
,
324 // Let WTL call ::DefWindowProc() and release its resources.
327 composing_window_handle_
= NULL
;
329 if (!IsTextInputTypeNone() && GetTextInputClient()->HasCompositionText())
330 GetTextInputClient()->ClearCompositionText();
332 imm32_manager_
.ResetComposition(window_handle
);
333 imm32_manager_
.DestroyImeWindow(window_handle
);
337 LRESULT
InputMethodWin::OnImeNotify(UINT message
,
343 bool previous_state
= is_candidate_popup_open_
;
345 // Update |is_candidate_popup_open_|, whether a candidate window is open.
347 case IMN_OPENCANDIDATE
:
348 is_candidate_popup_open_
= true;
350 OnCandidateWindowShown();
352 case IMN_CLOSECANDIDATE
:
353 is_candidate_popup_open_
= false;
355 OnCandidateWindowHidden();
357 case IMN_CHANGECANDIDATE
:
358 // TODO(kochi): The IME API expects this event to notify window size change,
359 // while this may fire more often without window resize. There is no generic
360 // way to get bounds of candidate window.
361 OnCandidateWindowUpdated();
368 LRESULT
InputMethodWin::OnImeRequest(UINT message
,
374 // Should not receive WM_IME_REQUEST message, if IME is disabled.
375 const ui::TextInputType type
= GetTextInputType();
376 if (type
== ui::TEXT_INPUT_TYPE_NONE
||
377 type
== ui::TEXT_INPUT_TYPE_PASSWORD
) {
382 case IMR_RECONVERTSTRING
:
384 return OnReconvertString(reinterpret_cast<RECONVERTSTRING
*>(lparam
));
385 case IMR_DOCUMENTFEED
:
387 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING
*>(lparam
));
388 case IMR_QUERYCHARPOSITION
:
390 return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION
*>(lparam
));
396 LRESULT
InputMethodWin::OnDocumentFeed(RECONVERTSTRING
* reconv
) {
397 ui::TextInputClient
* client
= GetTextInputClient();
401 gfx::Range text_range
;
402 if (!client
->GetTextRange(&text_range
) || text_range
.is_empty())
406 gfx::Range target_range
;
407 if (client
->HasCompositionText())
408 result
= client
->GetCompositionTextRange(&target_range
);
410 if (!result
|| target_range
.is_empty()) {
411 if (!client
->GetSelectionRange(&target_range
) ||
412 !target_range
.IsValid()) {
417 if (!text_range
.Contains(target_range
))
420 if (target_range
.GetMin() - text_range
.start() > kExtraNumberOfChars
)
421 text_range
.set_start(target_range
.GetMin() - kExtraNumberOfChars
);
423 if (text_range
.end() - target_range
.GetMax() > kExtraNumberOfChars
)
424 text_range
.set_end(target_range
.GetMax() + kExtraNumberOfChars
);
426 size_t len
= text_range
.length();
427 size_t need_size
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
432 if (reconv
->dwSize
< need_size
)
436 if (!GetTextInputClient()->GetTextFromRange(text_range
, &text
))
438 DCHECK_EQ(text_range
.length(), text
.length());
440 reconv
->dwVersion
= 0;
441 reconv
->dwStrLen
= len
;
442 reconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
443 reconv
->dwCompStrLen
=
444 client
->HasCompositionText() ? target_range
.length() : 0;
445 reconv
->dwCompStrOffset
=
446 (target_range
.GetMin() - text_range
.start()) * sizeof(WCHAR
);
447 reconv
->dwTargetStrLen
= target_range
.length();
448 reconv
->dwTargetStrOffset
= reconv
->dwCompStrOffset
;
450 memcpy((char*)reconv
+ sizeof(RECONVERTSTRING
),
451 text
.c_str(), len
* sizeof(WCHAR
));
453 // According to Microsoft API document, IMR_RECONVERTSTRING and
454 // IMR_DOCUMENTFEED should return reconv, but some applications return
456 return reinterpret_cast<LRESULT
>(reconv
);
459 LRESULT
InputMethodWin::OnReconvertString(RECONVERTSTRING
* reconv
) {
460 ui::TextInputClient
* client
= GetTextInputClient();
464 // If there is a composition string already, we don't allow reconversion.
465 if (client
->HasCompositionText())
468 gfx::Range text_range
;
469 if (!client
->GetTextRange(&text_range
) || text_range
.is_empty())
472 gfx::Range selection_range
;
473 if (!client
->GetSelectionRange(&selection_range
) ||
474 selection_range
.is_empty()) {
478 DCHECK(text_range
.Contains(selection_range
));
480 size_t len
= selection_range
.length();
481 size_t need_size
= sizeof(RECONVERTSTRING
) + len
* sizeof(WCHAR
);
486 if (reconv
->dwSize
< need_size
)
489 // TODO(penghuang): Return some extra context to help improve IME's
490 // reconversion accuracy.
492 if (!GetTextInputClient()->GetTextFromRange(selection_range
, &text
))
494 DCHECK_EQ(selection_range
.length(), text
.length());
496 reconv
->dwVersion
= 0;
497 reconv
->dwStrLen
= len
;
498 reconv
->dwStrOffset
= sizeof(RECONVERTSTRING
);
499 reconv
->dwCompStrLen
= len
;
500 reconv
->dwCompStrOffset
= 0;
501 reconv
->dwTargetStrLen
= len
;
502 reconv
->dwTargetStrOffset
= 0;
504 memcpy(reinterpret_cast<char*>(reconv
) + sizeof(RECONVERTSTRING
),
505 text
.c_str(), len
* sizeof(WCHAR
));
507 // According to Microsoft API document, IMR_RECONVERTSTRING and
508 // IMR_DOCUMENTFEED should return reconv, but some applications return
510 return reinterpret_cast<LRESULT
>(reconv
);
513 LRESULT
InputMethodWin::OnQueryCharPosition(IMECHARPOSITION
* char_positon
) {
517 if (char_positon
->dwSize
< sizeof(IMECHARPOSITION
))
520 ui::TextInputClient
* client
= GetTextInputClient();
524 // Tentatively assume that the returned value from |client| is DIP (Density
525 // Independent Pixel). See the comment in text_input_client.h and
526 // http://crbug.com/360334.
528 if (client
->HasCompositionText()) {
529 if (!client
->GetCompositionCharacterBounds(char_positon
->dwCharPos
,
534 // If there is no composition and the first character is queried, returns
535 // the caret bounds. This behavior is the same to that of RichEdit control.
536 if (char_positon
->dwCharPos
!= 0)
538 dip_rect
= client
->GetCaretBounds();
540 const gfx::Rect rect
= gfx::win::DIPToScreenRect(dip_rect
);
542 char_positon
->pt
.x
= rect
.x();
543 char_positon
->pt
.y
= rect
.y();
544 char_positon
->cLineHeight
= rect
.height();
545 return 1; // returns non-zero value when succeeded.
548 HWND
InputMethodWin::GetAttachedWindowHandle(
549 const TextInputClient
* text_input_client
) const {
550 // On Aura environment, we can assume that |toplevel_window_handle_| always
551 // represents the valid top-level window handle because each top-level window
552 // is responsible for lifecycle management of corresponding InputMethod
554 return toplevel_window_handle_
;
557 bool InputMethodWin::IsWindowFocused(const TextInputClient
* client
) const {
560 HWND attached_window_handle
= GetAttachedWindowHandle(client
);
561 // When Aura is enabled, |attached_window_handle| should always be a top-level
562 // window. So we can safely assume that |attached_window_handle| is ready for
563 // receiving keyboard input as long as it is an active window. This works well
564 // even when the |attached_window_handle| becomes active but has not received
566 return attached_window_handle
&& GetActiveWindow() == attached_window_handle
;
569 bool InputMethodWin::DispatchFabricatedKeyEvent(const ui::KeyEvent
& event
) {
570 if (event
.is_char()) {
571 if (GetTextInputClient()) {
572 GetTextInputClient()->InsertChar(event
.key_code(),
573 ui::GetModifiersFromKeyState());
577 return DispatchKeyEventPostIME(event
);
580 void InputMethodWin::ConfirmCompositionText() {
581 if (composing_window_handle_
)
582 imm32_manager_
.CleanupComposition(composing_window_handle_
);
584 if (!IsTextInputTypeNone()) {
585 // Though above line should confirm the client's composition text by sending
586 // a result text to us, in case the input method and the client are in
587 // inconsistent states, we check the client's composition state again.
588 if (GetTextInputClient()->HasCompositionText())
589 GetTextInputClient()->ConfirmCompositionText();
593 void InputMethodWin::UpdateIMEState() {
594 // Use switch here in case we are going to add more text input types.
595 // We disable input method in password field.
596 const HWND window_handle
= GetAttachedWindowHandle(GetTextInputClient());
597 const TextInputType text_input_type
= GetTextInputType();
598 const TextInputMode text_input_mode
= GetTextInputMode();
599 switch (text_input_type
) {
600 case ui::TEXT_INPUT_TYPE_NONE
:
601 case ui::TEXT_INPUT_TYPE_PASSWORD
:
602 imm32_manager_
.DisableIME(window_handle
);
606 imm32_manager_
.EnableIME(window_handle
);
611 imm32_manager_
.SetTextInputMode(window_handle
, text_input_mode
);
612 tsf_inputscope::SetInputScopeForTsfUnawareWindow(
613 window_handle
, text_input_type
, text_input_mode
);