1 // Copyright (c) 2013 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_imm32.h"
6
7 #include "base/basictypes.h"
8 #include "ui/base/ime/composition_text.h"
9 #include "ui/base/ime/text_input_client.h"
10 #include "ui/base/ime/win/tsf_input_scope.h"
11
12
13 namespace ui {
14
InputMethodIMM32(internal::InputMethodDelegate * delegate,HWND toplevel_window_handle)15 InputMethodIMM32::InputMethodIMM32(internal::InputMethodDelegate* delegate,
16 HWND toplevel_window_handle)
17 : InputMethodWin(delegate, toplevel_window_handle),
18 enabled_(false), is_candidate_popup_open_(false),
19 composing_window_handle_(NULL) {
20 // In non-Aura environment, appropriate callbacks to OnFocus() and OnBlur()
21 // are not implemented yet. To work around this limitation, here we use
22 // "always focused" model.
23 // TODO(ime): Fix the caller of OnFocus() and OnBlur() so that appropriate
24 // focus event will be passed.
25 InputMethodWin::OnFocus();
26 }
27
OnFocus()28 void InputMethodIMM32::OnFocus() {
29 // Ignore OnFocus event for "always focused" model. See the comment in the
30 // constructor.
31 // TODO(ime): Implement OnFocus once the callers are fixed.
32 }
33
OnBlur()34 void InputMethodIMM32::OnBlur() {
35 // Ignore OnBlur event for "always focused" model. See the comment in the
36 // constructor.
37 // TODO(ime): Implement OnFocus once the callers are fixed.
38 }
39
OnUntranslatedIMEMessage(const base::NativeEvent & event,InputMethod::NativeEventResult * result)40 bool InputMethodIMM32::OnUntranslatedIMEMessage(
41 const base::NativeEvent& event, InputMethod::NativeEventResult* result) {
42 LRESULT original_result = 0;
43 BOOL handled = FALSE;
44 switch (event.message) {
45 case WM_IME_SETCONTEXT:
46 original_result = OnImeSetContext(
47 event.hwnd, event.message, event.wParam, event.lParam, &handled);
48 break;
49 case WM_IME_STARTCOMPOSITION:
50 original_result = OnImeStartComposition(
51 event.hwnd, event.message, event.wParam, event.lParam, &handled);
52 break;
53 case WM_IME_COMPOSITION:
54 original_result = OnImeComposition(
55 event.hwnd, event.message, event.wParam, event.lParam, &handled);
56 break;
57 case WM_IME_ENDCOMPOSITION:
58 original_result = OnImeEndComposition(
59 event.hwnd, event.message, event.wParam, event.lParam, &handled);
60 break;
61 case WM_IME_REQUEST:
62 original_result = OnImeRequest(
63 event.message, event.wParam, event.lParam, &handled);
64 break;
65 case WM_CHAR:
66 case WM_SYSCHAR:
67 original_result = OnChar(
68 event.hwnd, event.message, event.wParam, event.lParam, &handled);
69 break;
70 case WM_DEADCHAR:
71 case WM_SYSDEADCHAR:
72 original_result = OnDeadChar(
73 event.message, event.wParam, event.lParam, &handled);
74 break;
75 case WM_IME_NOTIFY:
76 original_result = OnImeNotify(
77 event.message, event.wParam, event.lParam, &handled);
78 break;
79 default:
80 NOTREACHED() << "Unknown IME message:" << event.message;
81 break;
82 }
83 if (result)
84 *result = original_result;
85 return !!handled;
86 }
87
OnTextInputTypeChanged(const TextInputClient * client)88 void InputMethodIMM32::OnTextInputTypeChanged(const TextInputClient* client) {
89 if (IsTextInputClientFocused(client) && IsWindowFocused(client)) {
90 imm32_manager_.CancelIME(GetAttachedWindowHandle(client));
91 UpdateIMEState();
92 }
93 InputMethodWin::OnTextInputTypeChanged(client);
94 }
95
OnCaretBoundsChanged(const TextInputClient * client)96 void InputMethodIMM32::OnCaretBoundsChanged(const TextInputClient* client) {
97 if (!enabled_ || !IsTextInputClientFocused(client) ||
98 !IsWindowFocused(client)) {
99 return;
100 }
101 // The current text input type should not be NONE if |client| is focused.
102 DCHECK(!IsTextInputTypeNone());
103 gfx::Rect screen_bounds(GetTextInputClient()->GetCaretBounds());
104
105 HWND attached_window = GetAttachedWindowHandle(client);
106 // TODO(ime): see comment in TextInputClient::GetCaretBounds(), this
107 // conversion shouldn't be necessary.
108 RECT r = {};
109 GetClientRect(attached_window, &r);
110 POINT window_point = { screen_bounds.x(), screen_bounds.y() };
111 ScreenToClient(attached_window, &window_point);
112 gfx::Rect caret_rect(gfx::Point(window_point.x, window_point.y),
113 screen_bounds.size());
114 imm32_manager_.UpdateCaretRect(attached_window, caret_rect);
115 }
116
CancelComposition(const TextInputClient * client)117 void InputMethodIMM32::CancelComposition(const TextInputClient* client) {
118 if (enabled_ && IsTextInputClientFocused(client))
119 imm32_manager_.CancelIME(GetAttachedWindowHandle(client));
120 }
121
IsCandidatePopupOpen() const122 bool InputMethodIMM32::IsCandidatePopupOpen() const {
123 return is_candidate_popup_open_;
124 }
125
OnWillChangeFocusedClient(TextInputClient * focused_before,TextInputClient * focused)126 void InputMethodIMM32::OnWillChangeFocusedClient(
127 TextInputClient* focused_before,
128 TextInputClient* focused) {
129 if (IsWindowFocused(focused_before)) {
130 ConfirmCompositionText();
131 }
132 }
133
OnDidChangeFocusedClient(TextInputClient * focused_before,TextInputClient * focused)134 void InputMethodIMM32::OnDidChangeFocusedClient(TextInputClient* focused_before,
135 TextInputClient* focused) {
136 if (IsWindowFocused(focused)) {
137 // Force to update the input type since client's TextInputStateChanged()
138 // function might not be called if text input types before the client loses
139 // focus and after it acquires focus again are the same.
140 OnTextInputTypeChanged(focused);
141
142 UpdateIMEState();
143
144 // Force to update caret bounds, in case the client thinks that the caret
145 // bounds has not changed.
146 OnCaretBoundsChanged(focused);
147 }
148 InputMethodWin::OnDidChangeFocusedClient(focused_before, focused);
149 }
150
OnImeSetContext(HWND window_handle,UINT message,WPARAM wparam,LPARAM lparam,BOOL * handled)151 LRESULT InputMethodIMM32::OnImeSetContext(HWND window_handle,
152 UINT message,
153 WPARAM wparam,
154 LPARAM lparam,
155 BOOL* handled) {
156 if (!!wparam)
157 imm32_manager_.CreateImeWindow(window_handle);
158
159 OnInputMethodChanged();
160 return imm32_manager_.SetImeWindowStyle(
161 window_handle, message, wparam, lparam, handled);
162 }
163
OnImeStartComposition(HWND window_handle,UINT message,WPARAM wparam,LPARAM lparam,BOOL * handled)164 LRESULT InputMethodIMM32::OnImeStartComposition(HWND window_handle,
165 UINT message,
166 WPARAM wparam,
167 LPARAM lparam,
168 BOOL* handled) {
169 // We have to prevent WTL from calling ::DefWindowProc() because the function
170 // calls ::ImmSetCompositionWindow() and ::ImmSetCandidateWindow() to
171 // over-write the position of IME windows.
172 *handled = TRUE;
173
174 // Reset the composition status and create IME windows.
175 composing_window_handle_ = window_handle;
176 imm32_manager_.CreateImeWindow(window_handle);
177 imm32_manager_.ResetComposition(window_handle);
178 return 0;
179 }
180
OnImeComposition(HWND window_handle,UINT message,WPARAM wparam,LPARAM lparam,BOOL * handled)181 LRESULT InputMethodIMM32::OnImeComposition(HWND window_handle,
182 UINT message,
183 WPARAM wparam,
184 LPARAM lparam,
185 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 // At first, update the position of the IME window.
191 imm32_manager_.UpdateImeWindow(window_handle);
192
193 // Retrieve the result string and its attributes of the ongoing composition
194 // and send it to a renderer process.
195 ui::CompositionText composition;
196 if (imm32_manager_.GetResult(window_handle, lparam, &composition.text)) {
197 if (!IsTextInputTypeNone())
198 GetTextInputClient()->InsertText(composition.text);
199 imm32_manager_.ResetComposition(window_handle);
200 // Fall though and try reading the composition string.
201 // Japanese IMEs send a message containing both GCS_RESULTSTR and
202 // GCS_COMPSTR, which means an ongoing composition has been finished
203 // by the start of another composition.
204 }
205 // Retrieve the composition string and its attributes of the ongoing
206 // composition and send it to a renderer process.
207 if (imm32_manager_.GetComposition(window_handle, lparam, &composition) &&
208 !IsTextInputTypeNone())
209 GetTextInputClient()->SetCompositionText(composition);
210
211 return 0;
212 }
213
OnImeEndComposition(HWND window_handle,UINT message,WPARAM wparam,LPARAM lparam,BOOL * handled)214 LRESULT InputMethodIMM32::OnImeEndComposition(HWND window_handle,
215 UINT message,
216 WPARAM wparam,
217 LPARAM lparam,
218 BOOL* handled) {
219 // Let WTL call ::DefWindowProc() and release its resources.
220 *handled = FALSE;
221
222 composing_window_handle_ = NULL;
223
224 if (!IsTextInputTypeNone() && GetTextInputClient()->HasCompositionText())
225 GetTextInputClient()->ClearCompositionText();
226
227 imm32_manager_.ResetComposition(window_handle);
228 imm32_manager_.DestroyImeWindow(window_handle);
229 return 0;
230 }
231
OnImeNotify(UINT message,WPARAM wparam,LPARAM lparam,BOOL * handled)232 LRESULT InputMethodIMM32::OnImeNotify(UINT message,
233 WPARAM wparam,
234 LPARAM lparam,
235 BOOL* handled) {
236 *handled = FALSE;
237
238 bool previous_state = is_candidate_popup_open_;
239
240 // Update |is_candidate_popup_open_|, whether a candidate window is open.
241 switch (wparam) {
242 case IMN_OPENCANDIDATE:
243 is_candidate_popup_open_ = true;
244 if (!previous_state)
245 OnCandidateWindowShown();
246 break;
247 case IMN_CLOSECANDIDATE:
248 is_candidate_popup_open_ = false;
249 if (previous_state)
250 OnCandidateWindowHidden();
251 break;
252 case IMN_CHANGECANDIDATE:
253 // TODO(kochi): The IME API expects this event to notify window size change,
254 // while this may fire more often without window resize. There is no generic
255 // way to get bounds of candidate window.
256 OnCandidateWindowUpdated();
257 break;
258 }
259
260 return 0;
261 }
262
ConfirmCompositionText()263 void InputMethodIMM32::ConfirmCompositionText() {
264 if (composing_window_handle_)
265 imm32_manager_.CleanupComposition(composing_window_handle_);
266
267 if (!IsTextInputTypeNone()) {
268 // Though above line should confirm the client's composition text by sending
269 // a result text to us, in case the input method and the client are in
270 // inconsistent states, we check the client's composition state again.
271 if (GetTextInputClient()->HasCompositionText())
272 GetTextInputClient()->ConfirmCompositionText();
273 }
274 }
275
UpdateIMEState()276 void InputMethodIMM32::UpdateIMEState() {
277 // Use switch here in case we are going to add more text input types.
278 // We disable input method in password field.
279 const HWND window_handle = GetAttachedWindowHandle(GetTextInputClient());
280 const TextInputType text_input_type = GetTextInputType();
281 const TextInputMode text_input_mode = GetTextInputMode();
282 switch (text_input_type) {
283 case ui::TEXT_INPUT_TYPE_NONE:
284 case ui::TEXT_INPUT_TYPE_PASSWORD:
285 imm32_manager_.DisableIME(window_handle);
286 enabled_ = false;
287 break;
288 default:
289 imm32_manager_.EnableIME(window_handle);
290 enabled_ = true;
291 break;
292 }
293
294 imm32_manager_.SetTextInputMode(window_handle, text_input_mode);
295 tsf_inputscope::SetInputScopeForTsfUnawareWindow(
296 window_handle, text_input_type, text_input_mode);
297 }
298
299 } // namespace ui
300