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.
4
5 #include "ui/base/ime/input_method_win.h"
6
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"
14
15 namespace ui {
16 namespace {
17
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;
21
22 } // namespace
23
InputMethodWin(internal::InputMethodDelegate * delegate,HWND toplevel_window_handle)24 InputMethodWin::InputMethodWin(internal::InputMethodDelegate* delegate,
25 HWND toplevel_window_handle)
26 : active_(false),
27 toplevel_window_handle_(toplevel_window_handle),
28 direction_(base::i18n::UNKNOWN_DIRECTION),
29 pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION),
30 accept_carriage_return_(false) {
31 SetDelegate(delegate);
32 }
33
Init(bool focused)34 void InputMethodWin::Init(bool focused) {
35 // Gets the initial input locale and text direction information.
36 OnInputLocaleChanged();
37
38 InputMethodBase::Init(focused);
39 }
40
DispatchKeyEvent(const ui::KeyEvent & event)41 bool InputMethodWin::DispatchKeyEvent(const ui::KeyEvent& event) {
42 if (!event.HasNativeEvent())
43 return DispatchFabricatedKeyEvent(event);
44
45 const base::NativeEvent& native_key_event = event.native_event();
46 if (native_key_event.message == WM_CHAR) {
47 BOOL handled;
48 OnChar(native_key_event.hwnd, native_key_event.message,
49 native_key_event.wParam, native_key_event.lParam, &handled);
50 return !!handled; // Don't send WM_CHAR for post event processing.
51 }
52 // Handles ctrl-shift key to change text direction and layout alignment.
53 if (ui::IMM32Manager::IsRTLKeyboardLayoutInstalled() &&
54 !IsTextInputTypeNone()) {
55 // TODO: shouldn't need to generate a KeyEvent here.
56 const ui::KeyEvent key(native_key_event,
57 native_key_event.message == WM_CHAR);
58 ui::KeyboardCode code = key.key_code();
59 if (key.type() == ui::ET_KEY_PRESSED) {
60 if (code == ui::VKEY_SHIFT) {
61 base::i18n::TextDirection dir;
62 if (ui::IMM32Manager::IsCtrlShiftPressed(&dir))
63 pending_requested_direction_ = dir;
64 } else if (code != ui::VKEY_CONTROL) {
65 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION;
66 }
67 } else if (key.type() == ui::ET_KEY_RELEASED &&
68 (code == ui::VKEY_SHIFT || code == ui::VKEY_CONTROL) &&
69 pending_requested_direction_ != base::i18n::UNKNOWN_DIRECTION) {
70 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment(
71 pending_requested_direction_);
72 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION;
73 }
74 }
75
76 return DispatchKeyEventPostIME(event);
77 }
78
OnInputLocaleChanged()79 void InputMethodWin::OnInputLocaleChanged() {
80 active_ = imm32_manager_.SetInputLanguage();
81 locale_ = imm32_manager_.GetInputLanguageName();
82 direction_ = imm32_manager_.GetTextDirection();
83 OnInputMethodChanged();
84 }
85
GetInputLocale()86 std::string InputMethodWin::GetInputLocale() {
87 return locale_;
88 }
89
GetInputTextDirection()90 base::i18n::TextDirection InputMethodWin::GetInputTextDirection() {
91 return direction_;
92 }
93
IsActive()94 bool InputMethodWin::IsActive() {
95 return active_;
96 }
97
OnDidChangeFocusedClient(TextInputClient * focused_before,TextInputClient * focused)98 void InputMethodWin::OnDidChangeFocusedClient(
99 TextInputClient* focused_before,
100 TextInputClient* focused) {
101 if (focused_before != focused)
102 accept_carriage_return_ = false;
103 }
104
OnImeRequest(UINT message,WPARAM wparam,LPARAM lparam,BOOL * handled)105 LRESULT InputMethodWin::OnImeRequest(UINT message,
106 WPARAM wparam,
107 LPARAM lparam,
108 BOOL* handled) {
109 *handled = FALSE;
110
111 // Should not receive WM_IME_REQUEST message, if IME is disabled.
112 const ui::TextInputType type = GetTextInputType();
113 if (type == ui::TEXT_INPUT_TYPE_NONE ||
114 type == ui::TEXT_INPUT_TYPE_PASSWORD) {
115 return 0;
116 }
117
118 switch (wparam) {
119 case IMR_RECONVERTSTRING:
120 *handled = TRUE;
121 return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam));
122 case IMR_DOCUMENTFEED:
123 *handled = TRUE;
124 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam));
125 case IMR_QUERYCHARPOSITION:
126 *handled = TRUE;
127 return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION*>(lparam));
128 default:
129 return 0;
130 }
131 }
132
OnChar(HWND window_handle,UINT message,WPARAM wparam,LPARAM lparam,BOOL * handled)133 LRESULT InputMethodWin::OnChar(HWND window_handle,
134 UINT message,
135 WPARAM wparam,
136 LPARAM lparam,
137 BOOL* handled) {
138 *handled = TRUE;
139
140 // We need to send character events to the focused text input client event if
141 // its text input type is ui::TEXT_INPUT_TYPE_NONE.
142 if (GetTextInputClient()) {
143 const char16 kCarriageReturn = L'\r';
144 const char16 ch = static_cast<char16>(wparam);
145 // A mask to determine the previous key state from |lparam|. The value is 1
146 // if the key is down before the message is sent, or it is 0 if the key is
147 // up.
148 const uint32 kPrevKeyDownBit = 0x40000000;
149 if (ch == kCarriageReturn && !(lparam & kPrevKeyDownBit))
150 accept_carriage_return_ = true;
151 // Conditionally ignore '\r' events to work around crbug.com/319100.
152 // TODO(yukawa, IME): Figure out long-term solution.
153 if (ch != kCarriageReturn || accept_carriage_return_)
154 GetTextInputClient()->InsertChar(ch, ui::GetModifiersFromKeyState());
155 }
156
157 // Explicitly show the system menu at a good location on [Alt]+[Space].
158 // Note: Setting |handled| to FALSE for DefWindowProc triggering of the system
159 // menu causes undesirable titlebar artifacts in the classic theme.
160 if (message == WM_SYSCHAR && wparam == VK_SPACE)
161 gfx::ShowSystemMenu(window_handle);
162
163 return 0;
164 }
165
OnDeadChar(UINT message,WPARAM wparam,LPARAM lparam,BOOL * handled)166 LRESULT InputMethodWin::OnDeadChar(UINT message,
167 WPARAM wparam,
168 LPARAM lparam,
169 BOOL* handled) {
170 *handled = TRUE;
171 return 0;
172 }
173
OnDocumentFeed(RECONVERTSTRING * reconv)174 LRESULT InputMethodWin::OnDocumentFeed(RECONVERTSTRING* reconv) {
175 ui::TextInputClient* client = GetTextInputClient();
176 if (!client)
177 return 0;
178
179 gfx::Range text_range;
180 if (!client->GetTextRange(&text_range) || text_range.is_empty())
181 return 0;
182
183 bool result = false;
184 gfx::Range target_range;
185 if (client->HasCompositionText())
186 result = client->GetCompositionTextRange(&target_range);
187
188 if (!result || target_range.is_empty()) {
189 if (!client->GetSelectionRange(&target_range) ||
190 !target_range.IsValid()) {
191 return 0;
192 }
193 }
194
195 if (!text_range.Contains(target_range))
196 return 0;
197
198 if (target_range.GetMin() - text_range.start() > kExtraNumberOfChars)
199 text_range.set_start(target_range.GetMin() - kExtraNumberOfChars);
200
201 if (text_range.end() - target_range.GetMax() > kExtraNumberOfChars)
202 text_range.set_end(target_range.GetMax() + kExtraNumberOfChars);
203
204 size_t len = text_range.length();
205 size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
206
207 if (!reconv)
208 return need_size;
209
210 if (reconv->dwSize < need_size)
211 return 0;
212
213 string16 text;
214 if (!GetTextInputClient()->GetTextFromRange(text_range, &text))
215 return 0;
216 DCHECK_EQ(text_range.length(), text.length());
217
218 reconv->dwVersion = 0;
219 reconv->dwStrLen = len;
220 reconv->dwStrOffset = sizeof(RECONVERTSTRING);
221 reconv->dwCompStrLen =
222 client->HasCompositionText() ? target_range.length() : 0;
223 reconv->dwCompStrOffset =
224 (target_range.GetMin() - text_range.start()) * sizeof(WCHAR);
225 reconv->dwTargetStrLen = target_range.length();
226 reconv->dwTargetStrOffset = reconv->dwCompStrOffset;
227
228 memcpy((char*)reconv + sizeof(RECONVERTSTRING),
229 text.c_str(), len * sizeof(WCHAR));
230
231 // According to Microsoft API document, IMR_RECONVERTSTRING and
232 // IMR_DOCUMENTFEED should return reconv, but some applications return
233 // need_size.
234 return reinterpret_cast<LRESULT>(reconv);
235 }
236
OnReconvertString(RECONVERTSTRING * reconv)237 LRESULT InputMethodWin::OnReconvertString(RECONVERTSTRING* reconv) {
238 ui::TextInputClient* client = GetTextInputClient();
239 if (!client)
240 return 0;
241
242 // If there is a composition string already, we don't allow reconversion.
243 if (client->HasCompositionText())
244 return 0;
245
246 gfx::Range text_range;
247 if (!client->GetTextRange(&text_range) || text_range.is_empty())
248 return 0;
249
250 gfx::Range selection_range;
251 if (!client->GetSelectionRange(&selection_range) ||
252 selection_range.is_empty()) {
253 return 0;
254 }
255
256 DCHECK(text_range.Contains(selection_range));
257
258 size_t len = selection_range.length();
259 size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR);
260
261 if (!reconv)
262 return need_size;
263
264 if (reconv->dwSize < need_size)
265 return 0;
266
267 // TODO(penghuang): Return some extra context to help improve IME's
268 // reconversion accuracy.
269 string16 text;
270 if (!GetTextInputClient()->GetTextFromRange(selection_range, &text))
271 return 0;
272 DCHECK_EQ(selection_range.length(), text.length());
273
274 reconv->dwVersion = 0;
275 reconv->dwStrLen = len;
276 reconv->dwStrOffset = sizeof(RECONVERTSTRING);
277 reconv->dwCompStrLen = len;
278 reconv->dwCompStrOffset = 0;
279 reconv->dwTargetStrLen = len;
280 reconv->dwTargetStrOffset = 0;
281
282 memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING),
283 text.c_str(), len * sizeof(WCHAR));
284
285 // According to Microsoft API document, IMR_RECONVERTSTRING and
286 // IMR_DOCUMENTFEED should return reconv, but some applications return
287 // need_size.
288 return reinterpret_cast<LRESULT>(reconv);
289 }
290
OnQueryCharPosition(IMECHARPOSITION * char_positon)291 LRESULT InputMethodWin::OnQueryCharPosition(IMECHARPOSITION* char_positon) {
292 if (!char_positon)
293 return 0;
294
295 if (char_positon->dwSize < sizeof(IMECHARPOSITION))
296 return 0;
297
298 ui::TextInputClient* client = GetTextInputClient();
299 if (!client)
300 return 0;
301
302 gfx::Rect rect;
303 if (client->HasCompositionText()) {
304 if (!client->GetCompositionCharacterBounds(char_positon->dwCharPos,
305 &rect)) {
306 return 0;
307 }
308 } else {
309 // If there is no composition and the first character is queried, returns
310 // the caret bounds. This behavior is the same to that of RichEdit control.
311 if (char_positon->dwCharPos != 0)
312 return 0;
313 rect = client->GetCaretBounds();
314 }
315
316 char_positon->pt.x = rect.x();
317 char_positon->pt.y = rect.y();
318 char_positon->cLineHeight = rect.height();
319 return 1; // returns non-zero value when succeeded.
320 }
321
GetAttachedWindowHandle(const TextInputClient * text_input_client) const322 HWND InputMethodWin::GetAttachedWindowHandle(
323 const TextInputClient* text_input_client) const {
324 // On Aura environment, we can assume that |toplevel_window_handle_| always
325 // represents the valid top-level window handle because each top-level window
326 // is responsible for lifecycle management of corresponding InputMethod
327 // instance.
328 #if defined(USE_AURA)
329 return toplevel_window_handle_;
330 #else
331 // On Non-Aura environment, TextInputClient::GetAttachedWindow() returns
332 // window handle to which each input method is bound.
333 if (!text_input_client)
334 return NULL;
335 return text_input_client->GetAttachedWindow();
336 #endif
337 }
338
IsWindowFocused(const TextInputClient * client) const339 bool InputMethodWin::IsWindowFocused(const TextInputClient* client) const {
340 if (!client)
341 return false;
342 HWND attached_window_handle = GetAttachedWindowHandle(client);
343 #if defined(USE_AURA)
344 // When Aura is enabled, |attached_window_handle| should always be a top-level
345 // window. So we can safely assume that |attached_window_handle| is ready for
346 // receiving keyboard input as long as it is an active window. This works well
347 // even when the |attached_window_handle| becomes active but has not received
348 // WM_FOCUS yet.
349 return attached_window_handle && GetActiveWindow() == attached_window_handle;
350 #else
351 return attached_window_handle && GetFocus() == attached_window_handle;
352 #endif
353 }
354
DispatchFabricatedKeyEvent(const ui::KeyEvent & event)355 bool InputMethodWin::DispatchFabricatedKeyEvent(const ui::KeyEvent& event) {
356 // TODO(ananta)
357 // Support IMEs and RTL layout in Windows 8 metro Ash. The code below won't
358 // work with IMEs.
359 // Bug: https://code.google.com/p/chromium/issues/detail?id=164964
360 if (event.is_char()) {
361 if (GetTextInputClient()) {
362 GetTextInputClient()->InsertChar(event.key_code(),
363 ui::GetModifiersFromKeyState());
364 return true;
365 }
366 }
367 return DispatchKeyEventPostIME(event);
368 }
369
370 } // namespace ui
371