• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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