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