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
15namespace ui {
16namespace {
17
18// Extra number of chars before and after selection (or composition) range which
19// is returned to IME for improving conversion accuracy.
20static const size_t kExtraNumberOfChars = 20;
21
22}  // namespace
23
24InputMethodWin::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
34void InputMethodWin::Init(bool focused) {
35  // Gets the initial input locale and text direction information.
36  OnInputLocaleChanged();
37
38  InputMethodBase::Init(focused);
39}
40
41bool 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
79void InputMethodWin::OnInputLocaleChanged() {
80  active_ = imm32_manager_.SetInputLanguage();
81  locale_ = imm32_manager_.GetInputLanguageName();
82  direction_ = imm32_manager_.GetTextDirection();
83  OnInputMethodChanged();
84}
85
86std::string InputMethodWin::GetInputLocale() {
87  return locale_;
88}
89
90base::i18n::TextDirection InputMethodWin::GetInputTextDirection() {
91  return direction_;
92}
93
94bool InputMethodWin::IsActive() {
95  return active_;
96}
97
98void InputMethodWin::OnDidChangeFocusedClient(
99    TextInputClient* focused_before,
100    TextInputClient* focused) {
101  if (focused_before != focused)
102    accept_carriage_return_ = false;
103}
104
105LRESULT 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
133LRESULT 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
166LRESULT InputMethodWin::OnDeadChar(UINT message,
167                                   WPARAM wparam,
168                                   LPARAM lparam,
169                                   BOOL* handled) {
170  *handled = TRUE;
171  return 0;
172}
173
174LRESULT 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
237LRESULT 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
291LRESULT 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
322HWND 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
339bool 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
355bool 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