1 // Copyright 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 "chrome/browser/chromeos/input_method/input_method_engine.h" 6 7 #undef FocusIn 8 #undef FocusOut 9 #undef RootWindow 10 #include <map> 11 12 #include "ash/ime/input_method_menu_item.h" 13 #include "ash/ime/input_method_menu_manager.h" 14 #include "ash/shell.h" 15 #include "base/logging.h" 16 #include "base/memory/scoped_ptr.h" 17 #include "base/metrics/histogram.h" 18 #include "base/strings/string_number_conversions.h" 19 #include "base/strings/string_util.h" 20 #include "base/strings/stringprintf.h" 21 #include "base/strings/utf_string_conversions.h" 22 #include "chromeos/ime/component_extension_ime_manager.h" 23 #include "chromeos/ime/composition_text.h" 24 #include "chromeos/ime/extension_ime_util.h" 25 #include "chromeos/ime/input_method_manager.h" 26 #include "ui/aura/window.h" 27 #include "ui/aura/window_tree_host.h" 28 #include "ui/base/ime/candidate_window.h" 29 #include "ui/base/ime/chromeos/ime_keymap.h" 30 #include "ui/events/event.h" 31 #include "ui/events/event_processor.h" 32 #include "ui/events/keycodes/dom4/keycode_converter.h" 33 #include "ui/keyboard/keyboard_controller.h" 34 #include "ui/keyboard/keyboard_util.h" 35 36 namespace chromeos { 37 const char* kErrorNotActive = "IME is not active"; 38 const char* kErrorWrongContext = "Context is not active"; 39 const char* kCandidateNotFound = "Candidate not found"; 40 41 namespace { 42 43 // Notifies InputContextHandler that the composition is changed. 44 void UpdateComposition(const CompositionText& composition_text, 45 uint32 cursor_pos, 46 bool is_visible) { 47 IMEInputContextHandlerInterface* input_context = 48 IMEBridge::Get()->GetInputContextHandler(); 49 if (input_context) 50 input_context->UpdateCompositionText( 51 composition_text, cursor_pos, is_visible); 52 } 53 54 // Returns the length of characters of a UTF-8 string with unknown string 55 // length. Cannot apply faster algorithm to count characters in an utf-8 56 // string without knowing the string length, so just does a full scan. 57 size_t GetUtf8StringLength(const char* s) { 58 size_t ret = 0; 59 while (*s) { 60 if ((*s & 0xC0) != 0x80) 61 ret++; 62 ++s; 63 } 64 return ret; 65 } 66 67 std::string GetKeyFromEvent(const ui::KeyEvent& event) { 68 const std::string& code = event.code(); 69 if (StartsWithASCII(code, "Control", true)) 70 return "Ctrl"; 71 if (StartsWithASCII(code, "Shift", true)) 72 return "Shift"; 73 if (StartsWithASCII(code, "Alt", true)) 74 return "Alt"; 75 if (StartsWithASCII(code, "Arrow", true)) 76 return code.substr(5); 77 if (code == "Escape") 78 return "Esc"; 79 if (code == "Backspace" || code == "Tab" || 80 code == "Enter" || code == "CapsLock" || 81 code == "Power") 82 return code; 83 // Cases for media keys. 84 switch (event.key_code()) { 85 case ui::VKEY_BROWSER_BACK: 86 case ui::VKEY_F1: 87 return "HistoryBack"; 88 case ui::VKEY_BROWSER_FORWARD: 89 case ui::VKEY_F2: 90 return "HistoryForward"; 91 case ui::VKEY_BROWSER_REFRESH: 92 case ui::VKEY_F3: 93 return "BrowserRefresh"; 94 case ui::VKEY_MEDIA_LAUNCH_APP2: 95 case ui::VKEY_F4: 96 return "ChromeOSFullscreen"; 97 case ui::VKEY_MEDIA_LAUNCH_APP1: 98 case ui::VKEY_F5: 99 return "ChromeOSSwitchWindow"; 100 case ui::VKEY_BRIGHTNESS_DOWN: 101 case ui::VKEY_F6: 102 return "BrightnessDown"; 103 case ui::VKEY_BRIGHTNESS_UP: 104 case ui::VKEY_F7: 105 return "BrightnessUp"; 106 case ui::VKEY_VOLUME_MUTE: 107 case ui::VKEY_F8: 108 return "AudioVolumeMute"; 109 case ui::VKEY_VOLUME_DOWN: 110 case ui::VKEY_F9: 111 return "AudioVolumeDown"; 112 case ui::VKEY_VOLUME_UP: 113 case ui::VKEY_F10: 114 return "AudioVolumeUp"; 115 default: 116 break; 117 } 118 uint16 ch = 0; 119 // Ctrl+? cases, gets key value for Ctrl is not down. 120 if (event.flags() & ui::EF_CONTROL_DOWN) { 121 ui::KeyEvent event_no_ctrl(event.type(), 122 event.key_code(), 123 event.flags() ^ ui::EF_CONTROL_DOWN, 124 false); 125 ch = event_no_ctrl.GetCharacter(); 126 } else { 127 ch = event.GetCharacter(); 128 } 129 return base::UTF16ToUTF8(base::string16(1, ch)); 130 } 131 132 void GetExtensionKeyboardEventFromKeyEvent( 133 const ui::KeyEvent& event, 134 InputMethodEngine::KeyboardEvent* ext_event) { 135 DCHECK(event.type() == ui::ET_KEY_RELEASED || 136 event.type() == ui::ET_KEY_PRESSED); 137 DCHECK(ext_event); 138 ext_event->type = (event.type() == ui::ET_KEY_RELEASED) ? "keyup" : "keydown"; 139 140 std::string dom_code = event.code(); 141 if (dom_code == 142 ui::KeycodeConverter::GetInstance()->InvalidKeyboardEventCode()) 143 dom_code = ui::KeyboardCodeToDomKeycode(event.key_code()); 144 ext_event->code = dom_code; 145 ext_event->key_code = static_cast<int>(event.key_code()); 146 ext_event->alt_key = event.IsAltDown(); 147 ext_event->ctrl_key = event.IsControlDown(); 148 ext_event->shift_key = event.IsShiftDown(); 149 ext_event->caps_lock = event.IsCapsLockDown(); 150 ext_event->key = GetKeyFromEvent(event); 151 } 152 153 } // namespace 154 155 InputMethodEngine::InputMethodEngine() 156 : current_input_type_(ui::TEXT_INPUT_TYPE_NONE), 157 active_(false), 158 context_id_(0), 159 next_context_id_(1), 160 composition_text_(new CompositionText()), 161 composition_cursor_(0), 162 candidate_window_(new ui::CandidateWindow()), 163 window_visible_(false), 164 sent_key_event_(NULL), 165 profile_(NULL) { 166 } 167 168 InputMethodEngine::~InputMethodEngine() { 169 if (start_time_.ToInternalValue()) 170 RecordHistogram("WorkingTime", (end_time_ - start_time_).InSeconds()); 171 input_method::InputMethodManager::Get()->RemoveInputMethodExtension(profile_, 172 imm_id_); 173 } 174 175 void InputMethodEngine::Initialize( 176 Profile* profile, 177 scoped_ptr<InputMethodEngineInterface::Observer> observer, 178 const char* engine_name, 179 const char* extension_id, 180 const char* engine_id, 181 const std::vector<std::string>& languages, 182 const std::vector<std::string>& layouts, 183 const GURL& options_page, 184 const GURL& input_view) { 185 DCHECK(observer) << "Observer must not be null."; 186 187 profile_ = profile; 188 189 // TODO(komatsu): It is probably better to set observer out of Initialize. 190 observer_ = observer.Pass(); 191 engine_id_ = engine_id; 192 extension_id_ = extension_id; 193 194 input_method::InputMethodManager* manager = 195 input_method::InputMethodManager::Get(); 196 ComponentExtensionIMEManager* comp_ext_ime_manager = 197 manager->GetComponentExtensionIMEManager(); 198 199 if (comp_ext_ime_manager && comp_ext_ime_manager->IsInitialized() && 200 comp_ext_ime_manager->IsWhitelistedExtension(extension_id)) { 201 imm_id_ = comp_ext_ime_manager->GetId(extension_id, engine_id); 202 } else { 203 imm_id_ = extension_ime_util::GetInputMethodID(extension_id, engine_id); 204 } 205 206 input_view_url_ = input_view; 207 descriptor_ = input_method::InputMethodDescriptor( 208 imm_id_, 209 engine_name, 210 std::string(), // TODO(uekawa): Set short name. 211 layouts, 212 languages, 213 extension_ime_util::IsKeyboardLayoutExtension( 214 imm_id_), // is_login_keyboard 215 options_page, 216 input_view); 217 218 // TODO(komatsu): It is probably better to call AddInputMethodExtension 219 // out of Initialize. 220 manager->AddInputMethodExtension(profile, imm_id_, this); 221 } 222 223 const input_method::InputMethodDescriptor& InputMethodEngine::GetDescriptor() 224 const { 225 return descriptor_; 226 } 227 228 void InputMethodEngine::RecordHistogram(const char* name, int count) { 229 std::string histo_name = 230 base::StringPrintf("InputMethod.%s.%s", name, engine_id_.c_str()); 231 base::HistogramBase* counter = base::Histogram::FactoryGet( 232 histo_name, 0, 1000000, 50, base::HistogramBase::kNoFlags); 233 if (counter) 234 counter->Add(count); 235 } 236 237 void InputMethodEngine::NotifyImeReady() { 238 input_method::InputMethodManager* manager = 239 input_method::InputMethodManager::Get(); 240 if (manager && imm_id_ == manager->GetCurrentInputMethod().id()) 241 Enable(); 242 } 243 244 bool InputMethodEngine::SetComposition( 245 int context_id, 246 const char* text, 247 int selection_start, 248 int selection_end, 249 int cursor, 250 const std::vector<SegmentInfo>& segments, 251 std::string* error) { 252 if (!active_) { 253 *error = kErrorNotActive; 254 return false; 255 } 256 if (context_id != context_id_ || context_id_ == -1) { 257 *error = kErrorWrongContext; 258 return false; 259 } 260 261 composition_cursor_ = cursor; 262 composition_text_.reset(new CompositionText()); 263 composition_text_->set_text(base::UTF8ToUTF16(text)); 264 265 composition_text_->set_selection_start(selection_start); 266 composition_text_->set_selection_end(selection_end); 267 268 // TODO: Add support for displaying selected text in the composition string. 269 for (std::vector<SegmentInfo>::const_iterator segment = segments.begin(); 270 segment != segments.end(); ++segment) { 271 CompositionText::UnderlineAttribute underline; 272 273 switch (segment->style) { 274 case SEGMENT_STYLE_UNDERLINE: 275 underline.type = CompositionText::COMPOSITION_TEXT_UNDERLINE_SINGLE; 276 break; 277 case SEGMENT_STYLE_DOUBLE_UNDERLINE: 278 underline.type = CompositionText::COMPOSITION_TEXT_UNDERLINE_DOUBLE; 279 break; 280 default: 281 continue; 282 } 283 284 underline.start_index = segment->start; 285 underline.end_index = segment->end; 286 composition_text_->mutable_underline_attributes()->push_back(underline); 287 } 288 289 // TODO(nona): Makes focus out mode configuable, if necessary. 290 UpdateComposition(*composition_text_, composition_cursor_, true); 291 return true; 292 } 293 294 bool InputMethodEngine::ClearComposition(int context_id, 295 std::string* error) { 296 if (!active_) { 297 *error = kErrorNotActive; 298 return false; 299 } 300 if (context_id != context_id_ || context_id_ == -1) { 301 *error = kErrorWrongContext; 302 return false; 303 } 304 305 composition_cursor_ = 0; 306 composition_text_.reset(new CompositionText()); 307 UpdateComposition(*composition_text_, composition_cursor_, false); 308 return true; 309 } 310 311 bool InputMethodEngine::CommitText(int context_id, const char* text, 312 std::string* error) { 313 if (!active_) { 314 // TODO: Commit the text anyways. 315 *error = kErrorNotActive; 316 return false; 317 } 318 if (context_id != context_id_ || context_id_ == -1) { 319 *error = kErrorWrongContext; 320 return false; 321 } 322 323 IMEBridge::Get()->GetInputContextHandler()->CommitText(text); 324 325 // Records times for using input method. 326 if (!start_time_.ToInternalValue()) 327 start_time_ = base::Time::Now(); 328 end_time_ = base::Time::Now(); 329 // Records histograms for counts of commits and committed characters. 330 RecordHistogram("Commit", 1); 331 RecordHistogram("CommitCharacter", GetUtf8StringLength(text)); 332 return true; 333 } 334 335 bool InputMethodEngine::SendKeyEvents( 336 int context_id, 337 const std::vector<KeyboardEvent>& events) { 338 if (!active_) { 339 return false; 340 } 341 // context_id == 0, means sending key events to non-input field. 342 // context_id_ == -1, means the focus is not in an input field. 343 if (context_id != 0 && (context_id != context_id_ || context_id_ == -1)) { 344 return false; 345 } 346 347 ui::EventProcessor* dispatcher = 348 ash::Shell::GetPrimaryRootWindow()->GetHost()->event_processor(); 349 350 for (size_t i = 0; i < events.size(); ++i) { 351 const KeyboardEvent& event = events[i]; 352 const ui::EventType type = 353 (event.type == "keyup") ? ui::ET_KEY_RELEASED : ui::ET_KEY_PRESSED; 354 ui::KeyboardCode key_code = static_cast<ui::KeyboardCode>(event.key_code); 355 if (key_code == ui::VKEY_UNKNOWN) 356 key_code = ui::DomKeycodeToKeyboardCode(event.code); 357 358 int flags = ui::EF_NONE; 359 flags |= event.alt_key ? ui::EF_ALT_DOWN : ui::EF_NONE; 360 flags |= event.ctrl_key ? ui::EF_CONTROL_DOWN : ui::EF_NONE; 361 flags |= event.shift_key ? ui::EF_SHIFT_DOWN : ui::EF_NONE; 362 flags |= event.caps_lock ? ui::EF_CAPS_LOCK_DOWN : ui::EF_NONE; 363 364 ui::KeyEvent ui_event(type, 365 key_code, 366 event.code, 367 flags, 368 false /* is_char */); 369 // 4-bytes UTF-8 string is at least 2-characters UTF-16 string. 370 // And Key char can only be single UTF-16 character. 371 if (!event.key.empty() && event.key.size() < 4) { 372 base::string16 key_char = base::UTF8ToUTF16(event.key); 373 if (key_char.size() == 1) 374 ui_event.set_character(key_char[0]); 375 } 376 base::AutoReset<const ui::KeyEvent*> reset_sent_key(&sent_key_event_, 377 &ui_event); 378 ui::EventDispatchDetails details = dispatcher->OnEventFromSource(&ui_event); 379 if (details.dispatcher_destroyed) 380 break; 381 } 382 return true; 383 } 384 385 const InputMethodEngine::CandidateWindowProperty& 386 InputMethodEngine::GetCandidateWindowProperty() const { 387 return candidate_window_property_; 388 } 389 390 void InputMethodEngine::SetCandidateWindowProperty( 391 const CandidateWindowProperty& property) { 392 // Type conversion from InputMethodEngineInterface::CandidateWindowProperty to 393 // CandidateWindow::CandidateWindowProperty defined in chromeos/ime/. 394 ui::CandidateWindow::CandidateWindowProperty dest_property; 395 dest_property.page_size = property.page_size; 396 dest_property.is_cursor_visible = property.is_cursor_visible; 397 dest_property.is_vertical = property.is_vertical; 398 dest_property.show_window_at_composition = 399 property.show_window_at_composition; 400 dest_property.cursor_position = 401 candidate_window_->GetProperty().cursor_position; 402 dest_property.auxiliary_text = property.auxiliary_text; 403 dest_property.is_auxiliary_text_visible = property.is_auxiliary_text_visible; 404 405 candidate_window_->SetProperty(dest_property); 406 candidate_window_property_ = property; 407 408 if (active_) { 409 IMECandidateWindowHandlerInterface* cw_handler = 410 IMEBridge::Get()->GetCandidateWindowHandler(); 411 if (cw_handler) 412 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_); 413 } 414 } 415 416 bool InputMethodEngine::SetCandidateWindowVisible(bool visible, 417 std::string* error) { 418 if (!active_) { 419 *error = kErrorNotActive; 420 return false; 421 } 422 423 window_visible_ = visible; 424 IMECandidateWindowHandlerInterface* cw_handler = 425 IMEBridge::Get()->GetCandidateWindowHandler(); 426 if (cw_handler) 427 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_); 428 return true; 429 } 430 431 bool InputMethodEngine::SetCandidates( 432 int context_id, 433 const std::vector<Candidate>& candidates, 434 std::string* error) { 435 if (!active_) { 436 *error = kErrorNotActive; 437 return false; 438 } 439 if (context_id != context_id_ || context_id_ == -1) { 440 *error = kErrorWrongContext; 441 return false; 442 } 443 444 // TODO: Nested candidates 445 candidate_ids_.clear(); 446 candidate_indexes_.clear(); 447 candidate_window_->mutable_candidates()->clear(); 448 for (std::vector<Candidate>::const_iterator ix = candidates.begin(); 449 ix != candidates.end(); ++ix) { 450 ui::CandidateWindow::Entry entry; 451 entry.value = base::UTF8ToUTF16(ix->value); 452 entry.label = base::UTF8ToUTF16(ix->label); 453 entry.annotation = base::UTF8ToUTF16(ix->annotation); 454 entry.description_title = base::UTF8ToUTF16(ix->usage.title); 455 entry.description_body = base::UTF8ToUTF16(ix->usage.body); 456 457 // Store a mapping from the user defined ID to the candidate index. 458 candidate_indexes_[ix->id] = candidate_ids_.size(); 459 candidate_ids_.push_back(ix->id); 460 461 candidate_window_->mutable_candidates()->push_back(entry); 462 } 463 if (active_) { 464 IMECandidateWindowHandlerInterface* cw_handler = 465 IMEBridge::Get()->GetCandidateWindowHandler(); 466 if (cw_handler) 467 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_); 468 } 469 return true; 470 } 471 472 bool InputMethodEngine::SetCursorPosition(int context_id, int candidate_id, 473 std::string* error) { 474 if (!active_) { 475 *error = kErrorNotActive; 476 return false; 477 } 478 if (context_id != context_id_ || context_id_ == -1) { 479 *error = kErrorWrongContext; 480 return false; 481 } 482 483 std::map<int, int>::const_iterator position = 484 candidate_indexes_.find(candidate_id); 485 if (position == candidate_indexes_.end()) { 486 *error = kCandidateNotFound; 487 return false; 488 } 489 490 candidate_window_->set_cursor_position(position->second); 491 IMECandidateWindowHandlerInterface* cw_handler = 492 IMEBridge::Get()->GetCandidateWindowHandler(); 493 if (cw_handler) 494 cw_handler->UpdateLookupTable(*candidate_window_, window_visible_); 495 return true; 496 } 497 498 bool InputMethodEngine::SetMenuItems(const std::vector<MenuItem>& items) { 499 return UpdateMenuItems(items); 500 } 501 502 bool InputMethodEngine::UpdateMenuItems( 503 const std::vector<MenuItem>& items) { 504 if (!active_) 505 return false; 506 507 ash::ime::InputMethodMenuItemList menu_item_list; 508 for (std::vector<MenuItem>::const_iterator item = items.begin(); 509 item != items.end(); ++item) { 510 ash::ime::InputMethodMenuItem property; 511 MenuItemToProperty(*item, &property); 512 menu_item_list.push_back(property); 513 } 514 515 ash::ime::InputMethodMenuManager::GetInstance()-> 516 SetCurrentInputMethodMenuItemList( 517 menu_item_list); 518 return true; 519 } 520 521 bool InputMethodEngine::IsActive() const { 522 return active_; 523 } 524 525 bool InputMethodEngine::DeleteSurroundingText(int context_id, 526 int offset, 527 size_t number_of_chars, 528 std::string* error) { 529 if (!active_) { 530 *error = kErrorNotActive; 531 return false; 532 } 533 if (context_id != context_id_ || context_id_ == -1) { 534 *error = kErrorWrongContext; 535 return false; 536 } 537 538 if (offset < 0 && static_cast<size_t>(-1 * offset) != size_t(number_of_chars)) 539 return false; // Currently we can only support preceding text. 540 541 // TODO(nona): Return false if there is ongoing composition. 542 543 IMEInputContextHandlerInterface* input_context = 544 IMEBridge::Get()->GetInputContextHandler(); 545 if (input_context) 546 input_context->DeleteSurroundingText(offset, number_of_chars); 547 548 return true; 549 } 550 551 void InputMethodEngine::HideInputView() { 552 keyboard::KeyboardController* keyboard_controller = 553 keyboard::KeyboardController::GetInstance(); 554 if (keyboard_controller) { 555 keyboard_controller->HideKeyboard( 556 keyboard::KeyboardController::HIDE_REASON_MANUAL); 557 } 558 } 559 560 void InputMethodEngine::EnableInputView(bool enabled) { 561 const GURL& url = enabled ? input_view_url_ : GURL(); 562 keyboard::SetOverrideContentUrl(url); 563 keyboard::KeyboardController* keyboard_controller = 564 keyboard::KeyboardController::GetInstance(); 565 if (keyboard_controller) 566 keyboard_controller->Reload(); 567 } 568 569 void InputMethodEngine::FocusIn( 570 const IMEEngineHandlerInterface::InputContext& input_context) { 571 current_input_type_ = input_context.type; 572 573 if (!active_ || current_input_type_ == ui::TEXT_INPUT_TYPE_NONE) 574 return; 575 576 context_id_ = next_context_id_; 577 ++next_context_id_; 578 579 InputMethodEngineInterface::InputContext context; 580 context.id = context_id_; 581 switch (current_input_type_) { 582 case ui::TEXT_INPUT_TYPE_SEARCH: 583 context.type = "search"; 584 break; 585 case ui::TEXT_INPUT_TYPE_TELEPHONE: 586 context.type = "tel"; 587 break; 588 case ui::TEXT_INPUT_TYPE_URL: 589 context.type = "url"; 590 break; 591 case ui::TEXT_INPUT_TYPE_EMAIL: 592 context.type = "email"; 593 break; 594 case ui::TEXT_INPUT_TYPE_NUMBER: 595 context.type = "number"; 596 break; 597 case ui::TEXT_INPUT_TYPE_PASSWORD: 598 context.type = "password"; 599 break; 600 default: 601 context.type = "text"; 602 break; 603 } 604 605 observer_->OnFocus(context); 606 } 607 608 void InputMethodEngine::FocusOut() { 609 if (!active_ || current_input_type_ == ui::TEXT_INPUT_TYPE_NONE) 610 return; 611 612 current_input_type_ = ui::TEXT_INPUT_TYPE_NONE; 613 614 int context_id = context_id_; 615 context_id_ = -1; 616 observer_->OnBlur(context_id); 617 } 618 619 void InputMethodEngine::Enable() { 620 active_ = true; 621 observer_->OnActivate(engine_id_); 622 current_input_type_ = IMEBridge::Get()->GetCurrentTextInputType(); 623 FocusIn(IMEEngineHandlerInterface::InputContext( 624 current_input_type_, ui::TEXT_INPUT_MODE_DEFAULT)); 625 EnableInputView(true); 626 627 start_time_ = base::Time(); 628 end_time_ = base::Time(); 629 RecordHistogram("Enable", 1); 630 } 631 632 void InputMethodEngine::Disable() { 633 active_ = false; 634 observer_->OnDeactivated(engine_id_); 635 636 if (start_time_.ToInternalValue()) 637 RecordHistogram("WorkingTime", (end_time_ - start_time_).InSeconds()); 638 } 639 640 void InputMethodEngine::PropertyActivate(const std::string& property_name) { 641 observer_->OnMenuItemActivated(engine_id_, property_name); 642 } 643 644 void InputMethodEngine::Reset() { 645 observer_->OnReset(engine_id_); 646 } 647 648 void InputMethodEngine::ProcessKeyEvent( 649 const ui::KeyEvent& key_event, 650 const KeyEventDoneCallback& callback) { 651 652 KeyEventDoneCallback *handler = new KeyEventDoneCallback(); 653 *handler = callback; 654 655 KeyboardEvent ext_event; 656 GetExtensionKeyboardEventFromKeyEvent(key_event, &ext_event); 657 658 // If the given key event is equal to the key event sent by 659 // SendKeyEvents, this engine ID is propagated to the extension IME. 660 // Note, this check relies on that ui::KeyEvent is propagated as 661 // reference without copying. 662 if (&key_event == sent_key_event_) 663 ext_event.extension_id = extension_id_; 664 665 observer_->OnKeyEvent( 666 engine_id_, 667 ext_event, 668 reinterpret_cast<input_method::KeyEventHandle*>(handler)); 669 } 670 671 void InputMethodEngine::CandidateClicked(uint32 index) { 672 if (index > candidate_ids_.size()) { 673 return; 674 } 675 676 // Only left button click is supported at this moment. 677 observer_->OnCandidateClicked( 678 engine_id_, candidate_ids_.at(index), MOUSE_BUTTON_LEFT); 679 } 680 681 void InputMethodEngine::SetSurroundingText(const std::string& text, 682 uint32 cursor_pos, 683 uint32 anchor_pos) { 684 observer_->OnSurroundingTextChanged(engine_id_, 685 text, 686 static_cast<int>(cursor_pos), 687 static_cast<int>(anchor_pos)); 688 } 689 690 // TODO(uekawa): rename this method to a more reasonable name. 691 void InputMethodEngine::MenuItemToProperty( 692 const MenuItem& item, 693 ash::ime::InputMethodMenuItem* property) { 694 property->key = item.id; 695 696 if (item.modified & MENU_ITEM_MODIFIED_LABEL) { 697 property->label = item.label; 698 } 699 if (item.modified & MENU_ITEM_MODIFIED_VISIBLE) { 700 // TODO(nona): Implement it. 701 } 702 if (item.modified & MENU_ITEM_MODIFIED_CHECKED) { 703 property->is_selection_item_checked = item.checked; 704 } 705 if (item.modified & MENU_ITEM_MODIFIED_ENABLED) { 706 // TODO(nona): implement sensitive entry(crbug.com/140192). 707 } 708 if (item.modified & MENU_ITEM_MODIFIED_STYLE) { 709 if (!item.children.empty()) { 710 // TODO(nona): Implement it. 711 } else { 712 switch (item.style) { 713 case MENU_ITEM_STYLE_NONE: 714 NOTREACHED(); 715 break; 716 case MENU_ITEM_STYLE_CHECK: 717 // TODO(nona): Implement it. 718 break; 719 case MENU_ITEM_STYLE_RADIO: 720 property->is_selection_item = true; 721 break; 722 case MENU_ITEM_STYLE_SEPARATOR: 723 // TODO(nona): Implement it. 724 break; 725 } 726 } 727 } 728 729 // TODO(nona): Support item.children. 730 } 731 732 } // namespace chromeos 733