OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 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 "views/ime/input_method_win.h" | |
6 | |
7 #include "base/basictypes.h" | |
8 #include "base/logging.h" | |
9 #include "base/string_util.h" | |
10 #include "ui/base/ime/composition_text.h" | |
11 #include "ui/base/ime/text_input_client.h" | |
12 #include "ui/base/keycodes/keyboard_codes.h" | |
13 #include "views/events/event.h" | |
14 | |
15 // Extra number of chars before and after selection (or composition) range which | |
16 // is returned to IME for improving conversion accuracy. | |
17 static const size_t kExtraNumberOfChars = 20; | |
18 | |
19 namespace views { | |
20 | |
21 InputMethodWin::InputMethodWin(internal::InputMethodDelegate* delegate) | |
22 : active_(false), | |
23 direction_(base::i18n::UNKNOWN_DIRECTION), | |
24 pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION) { | |
25 set_delegate(delegate); | |
26 } | |
27 | |
28 InputMethodWin::~InputMethodWin() { | |
29 if (widget()) | |
30 ime_input_.DisableIME(hwnd()); | |
31 } | |
32 | |
33 void InputMethodWin::Init(Widget* widget) { | |
34 // Gets the initial input locale and text direction information. | |
35 OnInputLangChange(0, 0); | |
36 | |
37 InputMethodBase::Init(widget); | |
38 } | |
39 | |
40 void InputMethodWin::OnFocus() { | |
41 DCHECK(!widget_focused()); | |
42 InputMethodBase::OnFocus(); | |
43 UpdateIMEState(); | |
44 } | |
45 | |
46 void InputMethodWin::OnBlur() { | |
47 DCHECK(widget_focused()); | |
48 ConfirmCompositionText(); | |
49 InputMethodBase::OnBlur(); | |
50 } | |
51 | |
52 void InputMethodWin::DispatchKeyEvent(const KeyEvent& key) { | |
53 // Handles ctrl-shift key to change text direction and layout alignment. | |
54 if (ui::ImeInput::IsRTLKeyboardLayoutInstalled() && !IsTextInputTypeNone()) { | |
55 ui::KeyboardCode code = key.key_code(); | |
56 if (key.type() == ui::ET_KEY_PRESSED) { | |
57 if (code == ui::VKEY_SHIFT) { | |
58 base::i18n::TextDirection dir; | |
59 if (ui::ImeInput::IsCtrlShiftPressed(&dir)) | |
60 pending_requested_direction_ = dir; | |
61 } else if (code != ui::VKEY_CONTROL) { | |
62 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION; | |
63 } | |
64 } else if (key.type() == ui::ET_KEY_RELEASED && | |
65 (code == ui::VKEY_SHIFT || code == ui::VKEY_CONTROL) && | |
66 pending_requested_direction_ != base::i18n::UNKNOWN_DIRECTION) { | |
67 GetTextInputClient()->ChangeTextDirectionAndLayoutAlignment( | |
68 pending_requested_direction_); | |
69 pending_requested_direction_ = base::i18n::UNKNOWN_DIRECTION; | |
70 } | |
71 } | |
72 | |
73 DispatchKeyEventPostIME(key); | |
74 } | |
75 | |
76 void InputMethodWin::OnTextInputTypeChanged(View* view) { | |
77 if (IsViewFocused(view)) { | |
78 ime_input_.CancelIME(hwnd()); | |
79 UpdateIMEState(); | |
80 } | |
81 InputMethodBase::OnTextInputTypeChanged(view); | |
82 } | |
83 | |
84 void InputMethodWin::OnCaretBoundsChanged(View* view) { | |
85 gfx::Rect rect; | |
86 if (!IsViewFocused(view) || !GetCaretBoundsInWidget(&rect)) | |
87 return; | |
88 ime_input_.UpdateCaretRect(hwnd(), rect); | |
89 } | |
90 | |
91 void InputMethodWin::CancelComposition(View* view) { | |
92 if (IsViewFocused(view)) | |
93 ime_input_.CancelIME(hwnd()); | |
94 } | |
95 | |
96 std::string InputMethodWin::GetInputLocale() { | |
97 return locale_; | |
98 } | |
99 | |
100 base::i18n::TextDirection InputMethodWin::GetInputTextDirection() { | |
101 return direction_; | |
102 } | |
103 | |
104 bool InputMethodWin::IsActive() { | |
105 return active_; | |
106 } | |
107 | |
108 LRESULT InputMethodWin::OnImeMessages( | |
109 UINT message, WPARAM w_param, LPARAM l_param, BOOL* handled) { | |
110 LRESULT result = 0; | |
111 switch (message) { | |
112 case WM_IME_SETCONTEXT: | |
113 result = OnImeSetContext(message, w_param, l_param, handled); | |
114 break; | |
115 case WM_IME_STARTCOMPOSITION: | |
116 result = OnImeStartComposition(message, w_param, l_param, handled); | |
117 break; | |
118 case WM_IME_COMPOSITION: | |
119 result = OnImeComposition(message, w_param, l_param, handled); | |
120 break; | |
121 case WM_IME_ENDCOMPOSITION: | |
122 result = OnImeEndComposition(message, w_param, l_param, handled); | |
123 break; | |
124 case WM_IME_REQUEST: | |
125 result = OnImeRequest(message, w_param, l_param, handled); | |
126 break; | |
127 case WM_CHAR: | |
128 case WM_SYSCHAR: | |
129 result = OnChar(message, w_param, l_param, handled); | |
130 break; | |
131 case WM_DEADCHAR: | |
132 case WM_SYSDEADCHAR: | |
133 result = OnDeadChar(message, w_param, l_param, handled); | |
134 break; | |
135 default: | |
136 NOTREACHED() << "Unknown IME message:" << message; | |
137 break; | |
138 } | |
139 return result; | |
140 } | |
141 | |
142 void InputMethodWin::OnWillChangeFocus(View* focused_before, View* focused) { | |
143 ConfirmCompositionText(); | |
144 } | |
145 | |
146 void InputMethodWin::OnDidChangeFocus(View* focused_before, View* focused) { | |
147 UpdateIMEState(); | |
148 } | |
149 | |
150 void InputMethodWin::OnInputLangChange(DWORD character_set, | |
151 HKL input_language_id) { | |
152 active_ = ime_input_.SetInputLanguage(); | |
153 locale_ = ime_input_.GetInputLanguageName(); | |
154 direction_ = ime_input_.GetTextDirection(); | |
155 OnInputMethodChanged(); | |
156 } | |
157 | |
158 LRESULT InputMethodWin::OnImeSetContext( | |
159 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
160 active_ = (wparam == TRUE); | |
161 if (active_) | |
162 ime_input_.CreateImeWindow(hwnd()); | |
163 | |
164 OnInputMethodChanged(); | |
165 return ime_input_.SetImeWindowStyle(hwnd(), message, wparam, lparam, handled); | |
166 } | |
167 | |
168 LRESULT InputMethodWin::OnImeStartComposition( | |
169 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
170 // We have to prevent WTL from calling ::DefWindowProc() because the function | |
171 // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to | |
172 // over-write the position of IME windows. | |
173 *handled = TRUE; | |
174 | |
175 if (IsTextInputTypeNone()) | |
176 return 0; | |
177 | |
178 // Reset the composition status and create IME windows. | |
179 ime_input_.CreateImeWindow(hwnd()); | |
180 ime_input_.ResetComposition(hwnd()); | |
181 return 0; | |
182 } | |
183 | |
184 LRESULT InputMethodWin::OnImeComposition( | |
185 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
186 // We have to prevent WTL from calling ::DefWindowProc() because we do not | |
187 // want for the IMM (Input Method Manager) to send WM_IME_CHAR messages. | |
188 *handled = TRUE; | |
189 | |
190 if (IsTextInputTypeNone()) | |
191 return 0; | |
192 | |
193 // At first, update the position of the IME window. | |
194 ime_input_.UpdateImeWindow(hwnd()); | |
195 | |
196 // Retrieve the result string and its attributes of the ongoing composition | |
197 // and send it to a renderer process. | |
198 ui::CompositionText composition; | |
199 if (ime_input_.GetResult(hwnd(), lparam, &composition.text)) { | |
200 GetTextInputClient()->InsertText(composition.text); | |
201 ime_input_.ResetComposition(hwnd()); | |
202 // Fall though and try reading the composition string. | |
203 // Japanese IMEs send a message containing both GCS_RESULTSTR and | |
204 // GCS_COMPSTR, which means an ongoing composition has been finished | |
205 // by the start of another composition. | |
206 } | |
207 // Retrieve the composition string and its attributes of the ongoing | |
208 // composition and send it to a renderer process. | |
209 if (ime_input_.GetComposition(hwnd(), lparam, &composition)) | |
210 GetTextInputClient()->SetCompositionText(composition); | |
211 | |
212 return 0; | |
213 } | |
214 | |
215 LRESULT InputMethodWin::OnImeEndComposition( | |
216 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
217 // Let WTL call ::DefWindowProc() and release its resources. | |
218 *handled = FALSE; | |
219 | |
220 if (IsTextInputTypeNone()) | |
221 return 0; | |
222 | |
223 if (GetTextInputClient()->HasCompositionText()) | |
224 GetTextInputClient()->ClearCompositionText(); | |
225 | |
226 ime_input_.ResetComposition(hwnd()); | |
227 ime_input_.DestroyImeWindow(hwnd()); | |
228 return 0; | |
229 } | |
230 | |
231 LRESULT InputMethodWin::OnImeRequest( | |
232 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
233 *handled = FALSE; | |
234 | |
235 // Should not receive WM_IME_REQUEST message, if IME is disabled. | |
236 const ui::TextInputType type = GetTextInputType(); | |
237 if (type == ui::TEXT_INPUT_TYPE_NONE || | |
238 type == ui::TEXT_INPUT_TYPE_PASSWORD) { | |
239 return 0; | |
240 } | |
241 | |
242 switch (wparam) { | |
243 case IMR_RECONVERTSTRING: | |
244 *handled = TRUE; | |
245 return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam)); | |
246 case IMR_DOCUMENTFEED: | |
247 *handled = TRUE; | |
248 return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam)); | |
249 default: | |
250 return 0; | |
251 } | |
252 } | |
253 | |
254 LRESULT InputMethodWin::OnChar( | |
255 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
256 *handled = TRUE; | |
257 | |
258 // We need to send character events to the focused text input client event if | |
259 // its text input type is ui::TEXT_INPUT_TYPE_NONE. | |
260 if (!GetTextInputClient()) | |
261 return 0; | |
262 | |
263 int flags = 0; | |
264 flags |= (::GetKeyState(VK_MENU) & 0x80)? ui::EF_ALT_DOWN : 0; | |
265 flags |= (::GetKeyState(VK_SHIFT) & 0x80)? ui::EF_SHIFT_DOWN : 0; | |
266 flags |= (::GetKeyState(VK_CONTROL) & 0x80)? ui::EF_CONTROL_DOWN : 0; | |
267 GetTextInputClient()->InsertChar(static_cast<char16>(wparam), flags); | |
268 return 0; | |
269 } | |
270 | |
271 LRESULT InputMethodWin::OnDeadChar( | |
272 UINT message, WPARAM wparam, LPARAM lparam, BOOL* handled) { | |
273 *handled = TRUE; | |
274 | |
275 if (IsTextInputTypeNone()) | |
276 return 0; | |
277 | |
278 // Shows the dead character as a composition text, so that the user can know | |
279 // what dead key was pressed. | |
280 ui::CompositionText composition; | |
281 composition.text.assign(1, static_cast<char16>(wparam)); | |
282 composition.selection = ui::Range(0, 1); | |
283 composition.underlines.push_back( | |
284 ui::CompositionUnderline(0, 1, SK_ColorBLACK, false)); | |
285 GetTextInputClient()->SetCompositionText(composition); | |
286 return 0; | |
287 } | |
288 | |
289 LRESULT InputMethodWin::OnDocumentFeed(RECONVERTSTRING* reconv) { | |
290 ui::TextInputClient* client = GetTextInputClient(); | |
291 if (!client) | |
292 return 0; | |
293 | |
294 ui::Range text_range; | |
295 if (!client->GetTextRange(&text_range) || text_range.is_empty()) | |
296 return 0; | |
297 | |
298 bool result = false; | |
299 ui::Range target_range; | |
300 if (client->HasCompositionText()) | |
301 result = client->GetCompositionTextRange(&target_range); | |
302 | |
303 if (!result || target_range.is_empty()) { | |
304 if (!client->GetSelectionRange(&target_range) || | |
305 !target_range.IsValid()) { | |
306 return 0; | |
307 } | |
308 } | |
309 | |
310 if (!text_range.Contains(target_range)) | |
311 return 0; | |
312 | |
313 if (target_range.GetMin() - text_range.start() > kExtraNumberOfChars) | |
314 text_range.set_start(target_range.GetMin() - kExtraNumberOfChars); | |
315 | |
316 if (text_range.end() - target_range.GetMax() > kExtraNumberOfChars) | |
317 text_range.set_end(target_range.GetMax() + kExtraNumberOfChars); | |
318 | |
319 size_t len = text_range.length(); | |
320 size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); | |
321 | |
322 if (!reconv) | |
323 return need_size; | |
324 | |
325 if (reconv->dwSize < need_size) | |
326 return 0; | |
327 | |
328 string16 text; | |
329 if (!GetTextInputClient()->GetTextFromRange(text_range, &text)) | |
330 return 0; | |
331 DCHECK_EQ(text_range.length(), text.length()); | |
332 | |
333 reconv->dwVersion = 0; | |
334 reconv->dwStrLen = len; | |
335 reconv->dwStrOffset = sizeof(RECONVERTSTRING); | |
336 reconv->dwCompStrLen = | |
337 client->HasCompositionText() ? target_range.length() : 0; | |
338 reconv->dwCompStrOffset = | |
339 (target_range.GetMin() - text_range.start()) * sizeof(WCHAR); | |
340 reconv->dwTargetStrLen = target_range.length(); | |
341 reconv->dwTargetStrOffset = reconv->dwCompStrOffset; | |
342 | |
343 memcpy((char*)reconv + sizeof(RECONVERTSTRING), | |
344 text.c_str(), len * sizeof(WCHAR)); | |
345 | |
346 // According to Microsft API document, IMR_RECONVERTSTRING and | |
347 // IMR_DOCUMENTFEED should return reconv, but some applications return | |
348 // need_size. | |
349 return reinterpret_cast<LRESULT>(reconv); | |
350 } | |
351 | |
352 LRESULT InputMethodWin::OnReconvertString(RECONVERTSTRING* reconv) { | |
353 ui::TextInputClient* client = GetTextInputClient(); | |
354 if (!client) | |
355 return 0; | |
356 | |
357 // If there is a composition string already, we don't allow reconversion. | |
358 if (client->HasCompositionText()) | |
359 return 0; | |
360 | |
361 ui::Range text_range; | |
362 if (!client->GetTextRange(&text_range) || text_range.is_empty()) | |
363 return 0; | |
364 | |
365 ui::Range selection_range; | |
366 if (!client->GetSelectionRange(&selection_range) || | |
367 selection_range.is_empty()) { | |
368 return 0; | |
369 } | |
370 | |
371 DCHECK(text_range.Contains(selection_range)); | |
372 | |
373 size_t len = selection_range.length(); | |
374 size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); | |
375 | |
376 if (!reconv) | |
377 return need_size; | |
378 | |
379 if (reconv->dwSize < need_size) | |
380 return 0; | |
381 | |
382 // TODO(penghuang): Return some extra context to help improve IME's | |
383 // reconversion accuracy. | |
384 string16 text; | |
385 if (!GetTextInputClient()->GetTextFromRange(selection_range, &text)) | |
386 return 0; | |
387 DCHECK_EQ(selection_range.length(), text.length()); | |
388 | |
389 reconv->dwVersion = 0; | |
390 reconv->dwStrLen = len; | |
391 reconv->dwStrOffset = sizeof(RECONVERTSTRING); | |
392 reconv->dwCompStrLen = len; | |
393 reconv->dwCompStrOffset = 0; | |
394 reconv->dwTargetStrLen = len; | |
395 reconv->dwTargetStrOffset = 0; | |
396 | |
397 memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING), | |
398 text.c_str(), len * sizeof(WCHAR)); | |
399 | |
400 // According to Microsft API document, IMR_RECONVERTSTRING and | |
401 // IMR_DOCUMENTFEED should return reconv, but some applications return | |
402 // need_size. | |
403 return reinterpret_cast<LRESULT>(reconv); | |
404 } | |
405 | |
406 void InputMethodWin::ConfirmCompositionText() { | |
407 if (!IsTextInputTypeNone()) { | |
408 ime_input_.CleanupComposition(hwnd()); | |
409 // Though above line should confirm the client's composition text by sending | |
410 // a result text to us, in case the input method and the client are in | |
411 // inconsistent states, we check the client's composition state again. | |
412 if (GetTextInputClient()->HasCompositionText()) | |
413 GetTextInputClient()->ConfirmCompositionText(); | |
414 } | |
415 } | |
416 | |
417 void InputMethodWin::UpdateIMEState() { | |
418 // Use switch here in case we are going to add more text input types. | |
419 // We disable input method in password field. | |
420 switch (GetTextInputType()) { | |
421 case ui::TEXT_INPUT_TYPE_NONE: | |
422 case ui::TEXT_INPUT_TYPE_PASSWORD: | |
423 ime_input_.DisableIME(hwnd()); | |
424 break; | |
425 default: | |
426 ime_input_.EnableIME(hwnd()); | |
427 break; | |
428 } | |
429 } | |
430 | |
431 } // namespace views | |
OLD | NEW |