// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/base/ime/input_method_chromeos.h" #include #include #include #include #include "base/basictypes.h" #include "base/bind.h" #include "base/i18n/char_iterator.h" #include "base/logging.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/sys_info.h" #include "base/third_party/icu/icu_utf.h" #include "chromeos/ime/composition_text.h" #include "chromeos/ime/ime_keyboard.h" #include "chromeos/ime/input_method_manager.h" #include "ui/base/ime/text_input_client.h" #include "ui/events/event.h" #include "ui/gfx/rect.h" namespace { chromeos::IMEEngineHandlerInterface* GetEngine() { return chromeos::IMEBridge::Get()->GetCurrentEngineHandler(); } } // namespace namespace ui { // InputMethodChromeOS implementation ----------------------------------------- InputMethodChromeOS::InputMethodChromeOS( internal::InputMethodDelegate* delegate) : composing_text_(false), composition_changed_(false), current_keyevent_id_(0), weak_ptr_factory_(this) { SetDelegate(delegate); chromeos::IMEBridge::Get()->SetInputContextHandler(this); UpdateContextFocusState(); } InputMethodChromeOS::~InputMethodChromeOS() { AbandonAllPendingKeyEvents(); ConfirmCompositionText(); // We are dead, so we need to ask the client to stop relying on us. OnInputMethodChanged(); chromeos::IMEBridge::Get()->SetInputContextHandler(NULL); } void InputMethodChromeOS::OnFocus() { InputMethodBase::OnFocus(); OnTextInputTypeChanged(GetTextInputClient()); } void InputMethodChromeOS::OnBlur() { ConfirmCompositionText(); InputMethodBase::OnBlur(); OnTextInputTypeChanged(GetTextInputClient()); } bool InputMethodChromeOS::OnUntranslatedIMEMessage( const base::NativeEvent& event, NativeEventResult* result) { return false; } void InputMethodChromeOS::ProcessKeyEventDone(uint32 id, ui::KeyEvent* event, bool is_handled) { if (pending_key_events_.find(id) == pending_key_events_.end()) return; // Abandoned key event. DCHECK(event); if (event->type() == ET_KEY_PRESSED) { if (is_handled) { // IME event has a priority to be handled, so that character composer // should be reset. character_composer_.Reset(); } else { // If IME does not handle key event, passes keyevent to character composer // to be able to compose complex characters. is_handled = ExecuteCharacterComposer(*event); } } if (event->type() == ET_KEY_PRESSED || event->type() == ET_KEY_RELEASED) ProcessKeyEventPostIME(*event, is_handled); // ProcessKeyEventPostIME may change the |pending_key_events_|. pending_key_events_.erase(id); } bool InputMethodChromeOS::DispatchKeyEvent(const ui::KeyEvent& event) { DCHECK(event.type() == ET_KEY_PRESSED || event.type() == ET_KEY_RELEASED); DCHECK(system_toplevel_window_focused()); // For linux_chromeos, the ime keyboard cannot track the caps lock state by // itself, so need to call SetCapsLockEnabled() method to reflect the caps // lock state by the key event. if (!base::SysInfo::IsRunningOnChromeOS()) { chromeos::input_method::InputMethodManager* manager = chromeos::input_method::InputMethodManager::Get(); if (manager) { chromeos::input_method::ImeKeyboard* keyboard = manager->GetImeKeyboard(); if (keyboard && event.type() == ui::ET_KEY_PRESSED) { bool caps = (event.key_code() == ui::VKEY_CAPITAL) ? !keyboard->CapsLockIsEnabled() : (event.flags() & EF_CAPS_LOCK_DOWN); keyboard->SetCapsLockEnabled(caps); } } } // If |context_| is not usable, then we can only dispatch the key event as is. // We only dispatch the key event to input method when the |context_| is an // normal input field (not a password field). // Note: We need to send the key event to ibus even if the |context_| is not // enabled, so that ibus can have a chance to enable the |context_|. if (!IsInputFieldFocused() || !GetEngine()) { if (event.type() == ET_KEY_PRESSED) { if (ExecuteCharacterComposer(event)) { // Treating as PostIME event if character composer handles key event and // generates some IME event, ProcessKeyEventPostIME(event, true); return true; } ProcessUnfilteredKeyPressEvent(event); } else { DispatchKeyEventPostIME(event); } return true; } pending_key_events_.insert(current_keyevent_id_); ui::KeyEvent* copied_event = new ui::KeyEvent(event); GetEngine()->ProcessKeyEvent( event, base::Bind(&InputMethodChromeOS::ProcessKeyEventDone, weak_ptr_factory_.GetWeakPtr(), current_keyevent_id_, // Pass the ownership of |copied_event|. base::Owned(copied_event))); ++current_keyevent_id_; return true; } void InputMethodChromeOS::OnTextInputTypeChanged( const TextInputClient* client) { if (!IsTextInputClientFocused(client)) return; UpdateContextFocusState(); chromeos::IMEEngineHandlerInterface* engine = GetEngine(); if (engine) { // When focused input client is not changed, a text input type change should // cause blur/focus events to engine. // The focus in to or out from password field should also notify engine. engine->FocusOut(); chromeos::IMEEngineHandlerInterface::InputContext context( GetTextInputType(), GetTextInputMode()); engine->FocusIn(context); } InputMethodBase::OnTextInputTypeChanged(client); } void InputMethodChromeOS::OnCaretBoundsChanged(const TextInputClient* client) { if (!IsInputFieldFocused() || !IsTextInputClientFocused(client)) return; // The current text input type should not be NONE if |context_| is focused. DCHECK(!IsTextInputTypeNone()); const gfx::Rect rect = GetTextInputClient()->GetCaretBounds(); gfx::Rect composition_head; if (!GetTextInputClient()->GetCompositionCharacterBounds(0, &composition_head)) { composition_head = rect; } chromeos::IMECandidateWindowHandlerInterface* candidate_window = chromeos::IMEBridge::Get()->GetCandidateWindowHandler(); if (!candidate_window) return; candidate_window->SetCursorBounds(rect, composition_head); gfx::Range text_range; gfx::Range selection_range; base::string16 surrounding_text; if (!GetTextInputClient()->GetTextRange(&text_range) || !GetTextInputClient()->GetTextFromRange(text_range, &surrounding_text) || !GetTextInputClient()->GetSelectionRange(&selection_range)) { previous_surrounding_text_.clear(); previous_selection_range_ = gfx::Range::InvalidRange(); return; } if (previous_selection_range_ == selection_range && previous_surrounding_text_ == surrounding_text) return; previous_selection_range_ = selection_range; previous_surrounding_text_ = surrounding_text; if (!selection_range.IsValid()) { // TODO(nona): Ideally selection_range should not be invalid. // TODO(nona): If javascript changes the focus on page loading, even (0,0) // can not be obtained. Need investigation. return; } // Here SetSurroundingText accepts relative position of |surrounding_text|, so // we have to convert |selection_range| from node coordinates to // |surrounding_text| coordinates. if (!GetEngine()) return; GetEngine()->SetSurroundingText(base::UTF16ToUTF8(surrounding_text), selection_range.start() - text_range.start(), selection_range.end() - text_range.start()); } void InputMethodChromeOS::CancelComposition(const TextInputClient* client) { if (IsInputFieldFocused() && IsTextInputClientFocused(client)) ResetContext(); } void InputMethodChromeOS::OnInputLocaleChanged() { // Not supported. } std::string InputMethodChromeOS::GetInputLocale() { // Not supported. return ""; } bool InputMethodChromeOS::IsActive() { return true; } bool InputMethodChromeOS::IsCandidatePopupOpen() const { // TODO(yukishiino): Implement this method. return false; } void InputMethodChromeOS::OnWillChangeFocusedClient( TextInputClient* focused_before, TextInputClient* focused) { ConfirmCompositionText(); if (GetEngine()) GetEngine()->FocusOut(); } void InputMethodChromeOS::OnDidChangeFocusedClient( TextInputClient* focused_before, TextInputClient* focused) { // Force to update the input type since client's TextInputStateChanged() // function might not be called if text input types before the client loses // focus and after it acquires focus again are the same. UpdateContextFocusState(); if (GetEngine()) { chromeos::IMEEngineHandlerInterface::InputContext context( GetTextInputType(), GetTextInputMode()); GetEngine()->FocusIn(context); } } void InputMethodChromeOS::ConfirmCompositionText() { TextInputClient* client = GetTextInputClient(); if (client && client->HasCompositionText()) client->ConfirmCompositionText(); ResetContext(); } void InputMethodChromeOS::ResetContext() { if (!IsInputFieldFocused() || !GetTextInputClient()) return; DCHECK(system_toplevel_window_focused()); composition_.Clear(); result_text_.clear(); composing_text_ = false; composition_changed_ = false; // We need to abandon all pending key events, but as above comment says, there // is no reliable way to abandon all results generated by these abandoned key // events. AbandonAllPendingKeyEvents(); // This function runs asynchronously. // Note: some input method engines may not support reset method, such as // ibus-anthy. But as we control all input method engines by ourselves, we can // make sure that all of the engines we are using support it correctly. if (GetEngine()) GetEngine()->Reset(); character_composer_.Reset(); } void InputMethodChromeOS::UpdateContextFocusState() { ResetContext(); OnInputMethodChanged(); // Propagate the focus event to the candidate window handler which also // manages the input method mode indicator. chromeos::IMECandidateWindowHandlerInterface* candidate_window = chromeos::IMEBridge::Get()->GetCandidateWindowHandler(); if (candidate_window) candidate_window->FocusStateChanged(IsInputFieldFocused()); chromeos::IMEBridge::Get()->SetCurrentTextInputType(GetTextInputType()); if (!IsTextInputTypeNone()) OnCaretBoundsChanged(GetTextInputClient()); } void InputMethodChromeOS::ProcessKeyEventPostIME( const ui::KeyEvent& event, bool handled) { TextInputClient* client = GetTextInputClient(); if (!client) { // As ibus works asynchronously, there is a chance that the focused client // loses focus before this method gets called. DispatchKeyEventPostIME(event); return; } if (event.type() == ET_KEY_PRESSED && handled) ProcessFilteredKeyPressEvent(event); // In case the focus was changed by the key event. The |context_| should have // been reset when the focused window changed. if (client != GetTextInputClient()) return; if (HasInputMethodResult()) ProcessInputMethodResult(event, handled); // In case the focus was changed when sending input method results to the // focused window. if (client != GetTextInputClient()) return; if (event.type() == ET_KEY_PRESSED && !handled) ProcessUnfilteredKeyPressEvent(event); else if (event.type() == ET_KEY_RELEASED) DispatchKeyEventPostIME(event); } void InputMethodChromeOS::ProcessFilteredKeyPressEvent( const ui::KeyEvent& event) { if (NeedInsertChar()) { DispatchKeyEventPostIME(event); } else { const ui::KeyEvent fabricated_event(ET_KEY_PRESSED, VKEY_PROCESSKEY, event.flags()); DispatchKeyEventPostIME(fabricated_event); } } void InputMethodChromeOS::ProcessUnfilteredKeyPressEvent( const ui::KeyEvent& event) { const TextInputClient* prev_client = GetTextInputClient(); DispatchKeyEventPostIME(event); // We shouldn't dispatch the character anymore if the key event dispatch // caused focus change. For example, in the following scenario, // 1. visit a web page which has a