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/events/event.h"
10 #include "ui/events/event_constants.h"
11 #include "ui/events/event_utils.h"
12 #include "ui/events/keycodes/keyboard_codes.h"
13 #include "ui/gfx/win/hwnd_util.h"
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;
24 InputMethodWin::InputMethodWin(internal::InputMethodDelegate* delegate,
25 HWND toplevel_window_handle)
27 toplevel_window_handle_(toplevel_window_handle),
28 pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION),
29 accept_carriage_return_(false) {
30 SetDelegate(delegate);
33 void InputMethodWin::Init(bool focused) {
34 // Gets the initial input locale.
35 OnInputLocaleChanged();
37 InputMethodBase::Init(focused);
40 bool InputMethodWin::DispatchKeyEvent(const ui::KeyEvent& event) {
41 if (!event.HasNativeEvent())
42 return DispatchFabricatedKeyEvent(event);
44 const base::NativeEvent& native_key_event = event.native_event();
45 if (native_key_event.message == WM_CHAR) {
47 OnChar(native_key_event.hwnd, native_key_event.message,
48 native_key_event.wParam, native_key_event.lParam, &handled);
49 return !!handled; // Don't send WM_CHAR for post event processing.
51 // Handles ctrl-shift key to change text direction and layout alignment.
52 if (ui::IMM32Manager::IsRTLKeyboardLayoutInstalled() &&
53 !IsTextInputTypeNone()) {
54 // TODO: shouldn't need to generate a KeyEvent here.
55 const ui::KeyEvent key(native_key_event,
56 native_key_event.message == WM_CHAR);
57 ui::KeyboardCode code = key.key_code();
58 if (key.type() == ui::ET_KEY_PRESSED) {
59 if (code == ui::VKEY_SHIFT) {
60 base::i18n::TextDirection dir;
61 if (ui::IMM32Manager::IsCtrlShiftPressed(&dir))
62 pending_requested_direction_ = dir;
63 } else if (code != ui::VKEY_CONTROL) {
64 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION;
66 } else if (key.type() == ui::ET_KEY_RELEASED &&
67 (code == ui::VKEY_SHIFT || code == ui::VKEY_CONTROL) &&
68 pending_requested_direction_ != base::i18n::UNKNOWN_DIRECTION) {
69 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment(
70 pending_requested_direction_);
71 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION;
75 return DispatchKeyEventPostIME(event);
78 void InputMethodWin::OnInputLocaleChanged() {
79 active_ = imm32_manager_.SetInputLanguage();
80 locale_ = imm32_manager_.GetInputLanguageName();
81 OnInputMethodChanged();
84 std::string InputMethodWin::GetInputLocale() {
88 bool InputMethodWin::IsActive() {
92 void InputMethodWin::OnDidChangeFocusedClient(
93 TextInputClient* focused_before,
94 TextInputClient* focused) {
95 if (focused_before != focused)
96 accept_carriage_return_ = false;
99 LRESULT InputMethodWin::OnImeRequest(UINT message,
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) {
113 case IMR_RECONVERTSTRING:
115 return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam));
116 case IMR_DOCUMENTFEED:
118 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam));
119 case IMR_QUERYCHARPOSITION:
121 return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION*>(lparam));
127 LRESULT InputMethodWin::OnChar(HWND window_handle,
134 // 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.
136 if (GetTextInputClient()) {
137 const base::char16 kCarriageReturn = L'\r';
138 const base::char16 ch = static_cast<base::char16>(wparam);
139 // A mask to determine the previous key state from |lparam|. The value is 1
140 // if the key is down before the message is sent, or it is 0 if the key is
142 const uint32 kPrevKeyDownBit = 0x40000000;
143 if (ch == kCarriageReturn && !(lparam & kPrevKeyDownBit))
144 accept_carriage_return_ = true;
145 // Conditionally ignore '\r' events to work around crbug.com/319100.
146 // TODO(yukawa, IME): Figure out long-term solution.
147 if (ch != kCarriageReturn || accept_carriage_return_)
148 GetTextInputClient()->InsertChar(ch, ui::GetModifiersFromKeyState());
151 // Explicitly show the system menu at a good location on [Alt]+[Space].
152 // Note: Setting |handled| to FALSE for DefWindowProc triggering of the system
153 // menu causes undesirable titlebar artifacts in the classic theme.
154 if (message == WM_SYSCHAR && wparam == VK_SPACE)
155 gfx::ShowSystemMenu(window_handle);
160 LRESULT InputMethodWin::OnDocumentFeed(RECONVERTSTRING* reconv) {
161 ui::TextInputClient* client = GetTextInputClient();
165 gfx::Range text_range;
166 if (!client->GetTextRange(&text_range) || text_range.is_empty())
170 gfx::Range target_range;
171 if (client->HasCompositionText())
172 result = client->GetCompositionTextRange(&target_range);
174 if (!result || target_range.is_empty()) {
175 if (!client->GetSelectionRange(&target_range) ||
176 !target_range.IsValid()) {
181 if (!text_range.Contains(target_range))
184 if (target_range.GetMin() - text_range.start() > kExtraNumberOfChars)
185 text_range.set_start(target_range.GetMin() - kExtraNumberOfChars);
187 if (text_range.end() - target_range.GetMax() > kExtraNumberOfChars)
188 text_range.set_end(target_range.GetMax() + kExtraNumberOfChars);
190 size_t len = text_range.length();
191 size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
196 if (reconv->dwSize < need_size)
200 if (!GetTextInputClient()->GetTextFromRange(text_range, &text))
202 DCHECK_EQ(text_range.length(), text.length());
204 reconv->dwVersion = 0;
205 reconv->dwStrLen = len;
206 reconv->dwStrOffset = sizeof(RECONVERTSTRING);
207 reconv->dwCompStrLen =
208 client->HasCompositionText() ? target_range.length() : 0;
209 reconv->dwCompStrOffset =
210 (target_range.GetMin() - text_range.start()) * sizeof(WCHAR);
211 reconv->dwTargetStrLen = target_range.length();
212 reconv->dwTargetStrOffset = reconv->dwCompStrOffset;
214 memcpy((char*)reconv + sizeof(RECONVERTSTRING),
215 text.c_str(), len * sizeof(WCHAR));
217 // According to Microsoft API document, IMR_RECONVERTSTRING and
218 // IMR_DOCUMENTFEED should return reconv, but some applications return
220 return reinterpret_cast<LRESULT>(reconv);
223 LRESULT InputMethodWin::OnReconvertString(RECONVERTSTRING* reconv) {
224 ui::TextInputClient* client = GetTextInputClient();
228 // If there is a composition string already, we don't allow reconversion.
229 if (client->HasCompositionText())
232 gfx::Range text_range;
233 if (!client->GetTextRange(&text_range) || text_range.is_empty())
236 gfx::Range selection_range;
237 if (!client->GetSelectionRange(&selection_range) ||
238 selection_range.is_empty()) {
242 DCHECK(text_range.Contains(selection_range));
244 size_t len = selection_range.length();
245 size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
250 if (reconv->dwSize < need_size)
253 // TODO(penghuang): Return some extra context to help improve IME's
254 // reconversion accuracy.
256 if (!GetTextInputClient()->GetTextFromRange(selection_range, &text))
258 DCHECK_EQ(selection_range.length(), text.length());
260 reconv->dwVersion = 0;
261 reconv->dwStrLen = len;
262 reconv->dwStrOffset = sizeof(RECONVERTSTRING);
263 reconv->dwCompStrLen = len;
264 reconv->dwCompStrOffset = 0;
265 reconv->dwTargetStrLen = len;
266 reconv->dwTargetStrOffset = 0;
268 memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING),
269 text.c_str(), len * sizeof(WCHAR));
271 // According to Microsoft API document, IMR_RECONVERTSTRING and
272 // IMR_DOCUMENTFEED should return reconv, but some applications return
274 return reinterpret_cast<LRESULT>(reconv);
277 LRESULT InputMethodWin::OnQueryCharPosition(IMECHARPOSITION* char_positon) {
281 if (char_positon->dwSize < sizeof(IMECHARPOSITION))
284 ui::TextInputClient* client = GetTextInputClient();
289 if (client->HasCompositionText()) {
290 if (!client->GetCompositionCharacterBounds(char_positon->dwCharPos,
295 // If there is no composition and the first character is queried, returns
296 // the caret bounds. This behavior is the same to that of RichEdit control.
297 if (char_positon->dwCharPos != 0)
299 rect = client->GetCaretBounds();
302 char_positon->pt.x = rect.x();
303 char_positon->pt.y = rect.y();
304 char_positon->cLineHeight = rect.height();
305 return 1; // returns non-zero value when succeeded.
308 HWND InputMethodWin::GetAttachedWindowHandle(
309 const TextInputClient* text_input_client) const {
310 // On Aura environment, we can assume that |toplevel_window_handle_| always
311 // represents the valid top-level window handle because each top-level window
312 // is responsible for lifecycle management of corresponding InputMethod
314 return toplevel_window_handle_;
317 bool InputMethodWin::IsWindowFocused(const TextInputClient* client) const {
320 HWND attached_window_handle = GetAttachedWindowHandle(client);
321 // When Aura is enabled, |attached_window_handle| should always be a top-level
322 // window. So we can safely assume that |attached_window_handle| is ready for
323 // receiving keyboard input as long as it is an active window. This works well
324 // even when the |attached_window_handle| becomes active but has not received
326 return attached_window_handle && GetActiveWindow() == attached_window_handle;
329 bool InputMethodWin::DispatchFabricatedKeyEvent(const ui::KeyEvent& event) {
330 if (event.is_char()) {
331 if (GetTextInputClient()) {
332 GetTextInputClient()->InsertChar(event.key_code(),
333 ui::GetModifiersFromKeyState());
337 return DispatchKeyEventPostIME(event);