Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 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 "chrome/browser/input_method/input_method_engine_base.h" | |
| 6 | |
| 7 #undef FocusIn | |
| 8 #undef FocusOut | |
| 9 #undef RootWindow | |
| 10 #include <algorithm> | |
| 11 #include <map> | |
| 12 | |
| 13 #include "base/logging.h" | |
| 14 #include "base/memory/scoped_ptr.h" | |
| 15 #include "base/metrics/histogram.h" | |
| 16 #include "base/strings/string_number_conversions.h" | |
| 17 #include "base/strings/string_util.h" | |
| 18 #include "base/strings/stringprintf.h" | |
| 19 #include "base/strings/utf_string_conversions.h" | |
| 20 #include "chrome/browser/profiles/profile_manager.h" | |
| 21 #include "ui/aura/window.h" | |
| 22 #include "ui/aura/window_tree_host.h" | |
| 23 #include "ui/base/ime/composition_text.h" | |
| 24 #include "ui/base/ime/ime_bridge.h" | |
| 25 #include "ui/base/ime/text_input_flags.h" | |
| 26 #include "ui/events/event.h" | |
| 27 #include "ui/events/event_processor.h" | |
| 28 #include "ui/events/event_utils.h" | |
| 29 #include "ui/events/keycodes/dom/dom_code.h" | |
| 30 #include "ui/keyboard/keyboard_controller.h" | |
| 31 #include "ui/keyboard/keyboard_util.h" | |
| 32 | |
| 33 #if defined(OS_CHROMEOS) | |
| 34 #include "ui/base/ime/chromeos/ime_keymap.h" | |
| 35 #elif defined(OS_WIN) | |
| 36 #include "ui/events/keycodes/dom/keycode_converter.h" | |
| 37 #include "ui/events/keycodes/keyboard_codes_win.h" | |
| 38 #elif defined(OS_LINUX) | |
| 39 #include "ui/events/keycodes/dom/keycode_converter.h" | |
| 40 #include "ui/events/keycodes/keyboard_codes_posix.h" | |
| 41 #endif | |
| 42 | |
| 43 namespace input_method { | |
| 44 | |
| 45 namespace { | |
| 46 | |
| 47 const char kErrorNotActive[] = "IME is not active"; | |
| 48 const char kErrorWrongContext[] = "Context is not active"; | |
| 49 | |
| 50 // Notifies InputContextHandler that the composition is changed. | |
| 51 void UpdateComposition(const ui::CompositionText& composition_text, | |
| 52 uint32_t cursor_pos, | |
| 53 bool is_visible) { | |
| 54 ui::IMEInputContextHandlerInterface* input_context = | |
| 55 ui::IMEBridge::Get()->GetInputContextHandler(); | |
| 56 if (input_context) | |
| 57 input_context->UpdateCompositionText(composition_text, cursor_pos, | |
| 58 is_visible); | |
| 59 } | |
| 60 | |
| 61 // Returns the length of characters of a UTF-8 string with unknown string | |
| 62 // length. Cannot apply faster algorithm to count characters in an utf-8 | |
| 63 // string without knowing the string length, so just does a full scan. | |
| 64 size_t GetUtf8StringLength(const char* s) { | |
| 65 size_t ret = 0; | |
| 66 while (*s) { | |
| 67 if ((*s & 0xC0) != 0x80) | |
| 68 ret++; | |
| 69 ++s; | |
| 70 } | |
| 71 return ret; | |
| 72 } | |
| 73 | |
| 74 #if defined(OS_CHROMEOS) | |
| 75 std::string GetKeyFromEvent(const ui::KeyEvent& event) { | |
| 76 const std::string code = event.GetCodeString(); | |
| 77 if (base::StartsWith(code, "Control", base::CompareCase::SENSITIVE)) | |
| 78 return "Ctrl"; | |
| 79 if (base::StartsWith(code, "Shift", base::CompareCase::SENSITIVE)) | |
| 80 return "Shift"; | |
| 81 if (base::StartsWith(code, "Alt", base::CompareCase::SENSITIVE)) | |
| 82 return "Alt"; | |
| 83 if (base::StartsWith(code, "Arrow", base::CompareCase::SENSITIVE)) | |
| 84 return code.substr(5); | |
| 85 if (code == "Escape") | |
| 86 return "Esc"; | |
| 87 if (code == "Backspace" || code == "Tab" || code == "Enter" || | |
| 88 code == "CapsLock" || code == "Power") | |
| 89 return code; | |
| 90 // Cases for media keys. | |
| 91 switch (event.key_code()) { | |
| 92 case ui::VKEY_BROWSER_BACK: | |
| 93 case ui::VKEY_F1: | |
| 94 return "HistoryBack"; | |
| 95 case ui::VKEY_BROWSER_FORWARD: | |
| 96 case ui::VKEY_F2: | |
| 97 return "HistoryForward"; | |
| 98 case ui::VKEY_BROWSER_REFRESH: | |
| 99 case ui::VKEY_F3: | |
| 100 return "BrowserRefresh"; | |
| 101 case ui::VKEY_MEDIA_LAUNCH_APP2: | |
| 102 case ui::VKEY_F4: | |
| 103 return "ChromeOSFullscreen"; | |
| 104 case ui::VKEY_MEDIA_LAUNCH_APP1: | |
| 105 case ui::VKEY_F5: | |
| 106 return "ChromeOSSwitchWindow"; | |
| 107 case ui::VKEY_BRIGHTNESS_DOWN: | |
| 108 case ui::VKEY_F6: | |
| 109 return "BrightnessDown"; | |
| 110 case ui::VKEY_BRIGHTNESS_UP: | |
| 111 case ui::VKEY_F7: | |
| 112 return "BrightnessUp"; | |
| 113 case ui::VKEY_VOLUME_MUTE: | |
| 114 case ui::VKEY_F8: | |
| 115 return "AudioVolumeMute"; | |
| 116 case ui::VKEY_VOLUME_DOWN: | |
| 117 case ui::VKEY_F9: | |
| 118 return "AudioVolumeDown"; | |
| 119 case ui::VKEY_VOLUME_UP: | |
| 120 case ui::VKEY_F10: | |
| 121 return "AudioVolumeUp"; | |
| 122 default: | |
| 123 break; | |
| 124 } | |
| 125 uint16_t ch = 0; | |
| 126 // Ctrl+? cases, gets key value for Ctrl is not down. | |
| 127 if (event.flags() & ui::EF_CONTROL_DOWN) { | |
| 128 ui::KeyEvent event_no_ctrl(event.type(), event.key_code(), | |
| 129 event.flags() ^ ui::EF_CONTROL_DOWN); | |
| 130 ch = event_no_ctrl.GetCharacter(); | |
| 131 } else { | |
| 132 ch = event.GetCharacter(); | |
| 133 } | |
| 134 return base::UTF16ToUTF8(base::string16(1, ch)); | |
| 135 } | |
| 136 #endif // defined(OS_CHROMEOS) | |
| 137 | |
| 138 void GetExtensionKeyboardEventFromKeyEvent( | |
| 139 const ui::KeyEvent& event, | |
| 140 InputMethodEngineBase::KeyboardEvent* ext_event) { | |
| 141 DCHECK(event.type() == ui::ET_KEY_RELEASED || | |
| 142 event.type() == ui::ET_KEY_PRESSED); | |
| 143 DCHECK(ext_event); | |
| 144 ext_event->type = (event.type() == ui::ET_KEY_RELEASED) ? "keyup" : "keydown"; | |
| 145 | |
| 146 if (event.code() == ui::DomCode::NONE) { | |
| 147 // TODO(azurewei): Use KeycodeConverter::DomCodeToCodeString on all platforms | |
| 148 #if defined(OS_CHROMEOS) | |
| 149 ext_event->code = ui::KeyboardCodeToDomKeycode(event.key_code()); | |
| 150 #else | |
| 151 ext_event->code = | |
| 152 std::string(ui::KeycodeConverter::DomCodeToCodeString(event.code())); | |
| 153 #endif | |
| 154 } else { | |
| 155 ext_event->code = event.GetCodeString(); | |
| 156 } | |
| 157 ext_event->key_code = static_cast<int>(event.key_code()); | |
| 158 ext_event->alt_key = event.IsAltDown(); | |
| 159 ext_event->ctrl_key = event.IsControlDown(); | |
| 160 ext_event->shift_key = event.IsShiftDown(); | |
| 161 ext_event->caps_lock = event.IsCapsLockDown(); | |
| 162 #if defined(OS_CHROMEOS) | |
| 163 ext_event->key = GetKeyFromEvent(event); | |
| 164 #else | |
| 165 ext_event->key = ui::KeycodeConverter::DomKeyToKeyString(event.GetDomKey()); | |
| 166 #endif // defined(OS_CHROMEOS) | |
| 167 } | |
| 168 | |
| 169 } // namespace | |
| 170 | |
| 171 InputMethodEngineBase::InputMethodEngineBase() | |
| 172 : current_input_type_(ui::TEXT_INPUT_TYPE_NONE), | |
| 173 context_id_(0), | |
| 174 next_context_id_(1), | |
| 175 composition_text_(new ui::CompositionText()), | |
| 176 composition_cursor_(0), | |
| 177 sent_key_event_(NULL), | |
| 178 profile_(NULL) {} | |
| 179 | |
| 180 InputMethodEngineBase::~InputMethodEngineBase() {} | |
| 181 | |
| 182 void InputMethodEngineBase::Initialize( | |
| 183 scoped_ptr<ui::IMEEngineObserver> observer, | |
| 184 const char* extension_id, | |
| 185 Profile* profile) { | |
| 186 DCHECK(observer) << "Observer must not be null."; | |
| 187 | |
| 188 // TODO(komatsu): It is probably better to set observer out of Initialize. | |
| 189 observer_ = std::move(observer); | |
| 190 extension_id_ = extension_id; | |
| 191 profile_ = profile; | |
| 192 } | |
| 193 | |
| 194 const std::string& InputMethodEngineBase::GetActiveComponentId() const { | |
| 195 return active_component_id_; | |
| 196 } | |
| 197 | |
| 198 bool InputMethodEngineBase::SetComposition( | |
| 199 int context_id, | |
| 200 const char* text, | |
| 201 int selection_start, | |
| 202 int selection_end, | |
| 203 int cursor, | |
| 204 const std::vector<SegmentInfo>& segments, | |
| 205 std::string* error) { | |
| 206 if (!IsActive()) { | |
| 207 *error = kErrorNotActive; | |
| 208 return false; | |
| 209 } | |
| 210 if (context_id != context_id_ || context_id_ == -1) { | |
| 211 *error = kErrorWrongContext; | |
| 212 return false; | |
| 213 } | |
| 214 | |
| 215 composition_cursor_ = cursor; | |
| 216 composition_text_.reset(new ui::CompositionText()); | |
| 217 composition_text_->text = base::UTF8ToUTF16(text); | |
| 218 | |
| 219 composition_text_->selection.set_start(selection_start); | |
| 220 composition_text_->selection.set_end(selection_end); | |
| 221 | |
| 222 // TODO: Add support for displaying selected text in the composition string. | |
| 223 for (std::vector<SegmentInfo>::const_iterator segment = segments.begin(); | |
| 224 segment != segments.end(); ++segment) { | |
| 225 ui::CompositionUnderline underline; | |
| 226 | |
| 227 switch (segment->style) { | |
| 228 case SEGMENT_STYLE_UNDERLINE: | |
| 229 underline.color = SK_ColorBLACK; | |
| 230 break; | |
| 231 case SEGMENT_STYLE_DOUBLE_UNDERLINE: | |
| 232 underline.color = SK_ColorBLACK; | |
| 233 underline.thick = true; | |
| 234 break; | |
| 235 case SEGMENT_STYLE_NO_UNDERLINE: | |
| 236 underline.color = SK_ColorTRANSPARENT; | |
| 237 break; | |
| 238 default: | |
| 239 continue; | |
| 240 } | |
| 241 | |
| 242 underline.start_offset = segment->start; | |
| 243 underline.end_offset = segment->end; | |
| 244 composition_text_->underlines.push_back(underline); | |
| 245 } | |
| 246 | |
| 247 // TODO(nona): Makes focus out mode configuable, if necessary. | |
| 248 UpdateComposition(*composition_text_, composition_cursor_, true); | |
| 249 return true; | |
| 250 } | |
| 251 | |
| 252 bool InputMethodEngineBase::ClearComposition(int context_id, | |
| 253 std::string* error) { | |
| 254 if (!IsActive()) { | |
| 255 *error = kErrorNotActive; | |
| 256 return false; | |
| 257 } | |
| 258 if (context_id != context_id_ || context_id_ == -1) { | |
| 259 *error = kErrorWrongContext; | |
| 260 return false; | |
| 261 } | |
| 262 | |
| 263 composition_cursor_ = 0; | |
| 264 composition_text_.reset(new ui::CompositionText()); | |
| 265 UpdateComposition(*composition_text_, composition_cursor_, false); | |
| 266 return true; | |
| 267 } | |
| 268 | |
| 269 bool InputMethodEngineBase::CommitText(int context_id, | |
| 270 const char* text, | |
| 271 std::string* error) { | |
| 272 if (!IsActive()) { | |
| 273 // TODO: Commit the text anyways. | |
| 274 *error = kErrorNotActive; | |
| 275 return false; | |
| 276 } | |
| 277 if (context_id != context_id_ || context_id_ == -1) { | |
| 278 *error = kErrorWrongContext; | |
| 279 return false; | |
| 280 } | |
| 281 | |
| 282 ui::IMEBridge::Get()->GetInputContextHandler()->CommitText(text); | |
| 283 | |
| 284 // Records histograms for committed characters. | |
| 285 if (!composition_text_->text.empty()) { | |
| 286 size_t len = GetUtf8StringLength(text); | |
| 287 UMA_HISTOGRAM_CUSTOM_COUNTS("InputMethod.CommitLength", len, 1, 25, 25); | |
| 288 composition_text_.reset(new ui::CompositionText()); | |
| 289 } | |
| 290 return true; | |
| 291 } | |
| 292 | |
| 293 bool InputMethodEngineBase::DeleteSurroundingText(int context_id, | |
| 294 int offset, | |
| 295 size_t number_of_chars, | |
| 296 std::string* error) { | |
| 297 if (!IsActive()) { | |
| 298 *error = kErrorNotActive; | |
| 299 return false; | |
| 300 } | |
| 301 if (context_id != context_id_ || context_id_ == -1) { | |
| 302 *error = kErrorWrongContext; | |
| 303 return false; | |
| 304 } | |
| 305 | |
| 306 // TODO(nona): Return false if there is ongoing composition. | |
| 307 | |
| 308 ui::IMEInputContextHandlerInterface* input_context = | |
| 309 ui::IMEBridge::Get()->GetInputContextHandler(); | |
| 310 if (input_context) | |
| 311 input_context->DeleteSurroundingText(offset, number_of_chars); | |
| 312 | |
| 313 return true; | |
| 314 } | |
| 315 | |
| 316 void InputMethodEngineBase::SetCompositionBounds( | |
| 317 const std::vector<gfx::Rect>& bounds) { | |
| 318 if (!CheckProfile()) | |
|
sky
2016/01/08 16:35:27
Why do we need all these CheckProfile functions? I
Azure Wei
2016/01/10 08:19:30
I think CheckProfile() was mainly used for Disable
sky
2016/01/11 16:30:14
Not sure I follow. Shouldn't there only be one inp
| |
| 319 return; | |
| 320 observer_->OnCompositionBoundsChanged(bounds); | |
| 321 } | |
| 322 | |
| 323 void InputMethodEngineBase::FocusIn( | |
| 324 const ui::IMEEngineHandlerInterface::InputContext& input_context) { | |
| 325 if (!CheckProfile()) | |
| 326 return; | |
| 327 current_input_type_ = input_context.type; | |
| 328 | |
| 329 if (!IsActive() || current_input_type_ == ui::TEXT_INPUT_TYPE_NONE) | |
| 330 return; | |
| 331 | |
| 332 context_id_ = next_context_id_; | |
| 333 ++next_context_id_; | |
| 334 | |
| 335 observer_->OnFocus(ui::IMEEngineHandlerInterface::InputContext( | |
| 336 context_id_, input_context.type, input_context.mode, | |
| 337 input_context.flags)); | |
| 338 } | |
| 339 | |
| 340 void InputMethodEngineBase::FocusOut() { | |
| 341 if (!CheckProfile()) | |
| 342 return; | |
| 343 if (!IsActive() || current_input_type_ == ui::TEXT_INPUT_TYPE_NONE) | |
| 344 return; | |
| 345 | |
| 346 current_input_type_ = ui::TEXT_INPUT_TYPE_NONE; | |
| 347 | |
| 348 int context_id = context_id_; | |
| 349 context_id_ = -1; | |
| 350 observer_->OnBlur(context_id); | |
| 351 } | |
| 352 | |
| 353 void InputMethodEngineBase::Enable(const std::string& component_id) { | |
| 354 if (!CheckProfile()) | |
| 355 return; | |
| 356 DCHECK(!component_id.empty()); | |
| 357 active_component_id_ = component_id; | |
| 358 observer_->OnActivate(component_id); | |
| 359 const ui::IMEEngineHandlerInterface::InputContext& input_context = | |
| 360 ui::IMEBridge::Get()->GetCurrentInputContext(); | |
| 361 current_input_type_ = input_context.type; | |
| 362 FocusIn(input_context); | |
| 363 } | |
| 364 | |
| 365 void InputMethodEngineBase::Disable() { | |
| 366 if (!CheckProfile()) | |
| 367 return; | |
| 368 active_component_id_.clear(); | |
| 369 if (ui::IMEBridge::Get()->GetInputContextHandler()) | |
| 370 ui::IMEBridge::Get()->GetInputContextHandler()->CommitText( | |
| 371 base::UTF16ToUTF8(composition_text_->text)); | |
| 372 composition_text_.reset(new ui::CompositionText()); | |
| 373 observer_->OnDeactivated(active_component_id_); | |
| 374 } | |
| 375 | |
| 376 void InputMethodEngineBase::Reset() { | |
| 377 if (!CheckProfile()) | |
| 378 return; | |
| 379 composition_text_.reset(new ui::CompositionText()); | |
| 380 observer_->OnReset(active_component_id_); | |
| 381 } | |
| 382 | |
| 383 bool InputMethodEngineBase::IsInterestedInKeyEvent() const { | |
| 384 return observer_->IsInterestedInKeyEvent(); | |
| 385 } | |
| 386 | |
| 387 void InputMethodEngineBase::ProcessKeyEvent(const ui::KeyEvent& key_event, | |
| 388 KeyEventDoneCallback& callback) { | |
| 389 if (!CheckProfile()) | |
| 390 return; | |
| 391 | |
| 392 KeyboardEvent ext_event; | |
| 393 GetExtensionKeyboardEventFromKeyEvent(key_event, &ext_event); | |
| 394 | |
| 395 // If the given key event is equal to the key event sent by | |
| 396 // SendKeyEvents, this engine ID is propagated to the extension IME. | |
| 397 // Note, this check relies on that ui::KeyEvent is propagated as | |
| 398 // reference without copying. | |
| 399 if (&key_event == sent_key_event_) | |
| 400 ext_event.extension_id = extension_id_; | |
| 401 | |
| 402 observer_->OnKeyEvent(active_component_id_, ext_event, callback); | |
| 403 } | |
| 404 | |
| 405 void InputMethodEngineBase::SetSurroundingText(const std::string& text, | |
| 406 uint32_t cursor_pos, | |
| 407 uint32_t anchor_pos, | |
| 408 uint32_t offset_pos) { | |
| 409 if (!CheckProfile()) | |
| 410 return; | |
| 411 observer_->OnSurroundingTextChanged( | |
| 412 active_component_id_, text, static_cast<int>(cursor_pos), | |
| 413 static_cast<int>(anchor_pos), static_cast<int>(offset_pos)); | |
| 414 } | |
| 415 | |
| 416 } // namespace input_method | |
| OLD | NEW |