| // 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 <stddef.h> |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <set> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/i18n/char_iterator.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/third_party/icu/icu_utf.h" |
| #include "chromeos/system/devicemode.h" |
| #include "ui/base/ime/chromeos/ime_keyboard.h" |
| #include "ui/base/ime/chromeos/input_method_manager.h" |
| #include "ui/base/ime/composition_text.h" |
| #include "ui/base/ime/ime_bridge.h" |
| #include "ui/base/ime/ime_engine_handler_interface.h" |
| #include "ui/base/ime/input_method_delegate.h" |
| #include "ui/base/ime/text_input_client.h" |
| #include "ui/events/event.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| namespace ui { |
| |
| // InputMethodChromeOS implementation ----------------------------------------- |
| InputMethodChromeOS::InputMethodChromeOS( |
| internal::InputMethodDelegate* delegate) |
| : composing_text_(false), |
| composition_changed_(false), |
| handling_key_event_(false), |
| weak_ptr_factory_(this) { |
| SetDelegate(delegate); |
| ui::IMEBridge::Get()->SetInputContextHandler(this); |
| |
| UpdateContextFocusState(); |
| } |
| |
| InputMethodChromeOS::~InputMethodChromeOS() { |
| ConfirmCompositionText(); |
| // We are dead, so we need to ask the client to stop relying on us. |
| OnInputMethodChanged(); |
| |
| if (ui::IMEBridge::Get()) |
| ui::IMEBridge::Get()->SetInputContextHandler(NULL); |
| } |
| |
| ui::EventDispatchDetails InputMethodChromeOS::DispatchKeyEvent( |
| ui::KeyEvent* event, |
| AckCallback ack_callback) { |
| DCHECK(event->IsKeyEvent()); |
| DCHECK(!(event->flags() & ui::EF_IS_SYNTHESIZED)); |
| |
| // For OS_CHROMEOS build of Chrome running on Linux, 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. |
| chromeos::input_method::InputMethodManager* manager = |
| chromeos::input_method::InputMethodManager::Get(); |
| if (manager) { |
| chromeos::input_method::ImeKeyboard* keyboard = manager->GetImeKeyboard(); |
| if (keyboard && event->type() == ET_KEY_PRESSED && |
| event->key_code() != ui::VKEY_CAPITAL && |
| keyboard->CapsLockIsEnabled() != event->IsCapsLockOn()) { |
| // Synchronize the keyboard state with event's state if they do not |
| // match. Do not synchronize for Caps Lock key because it is already |
| // handled in event rewriter. |
| keyboard->SetCapsLockEnabled(event->IsCapsLockOn()); |
| } |
| |
| // For JP106 language input keys, makes sure they can be passed to the app |
| // so that the VDI web apps can be supported. See https://crbug.com/816341. |
| // VKEY_CONVERT: Henkan key |
| // VKEY_NONCONVERT: Muhenkan key |
| // VKEY_DBE_SBCSCHAR/VKEY_DBE_DBCSCHAR: ZenkakuHankaku key |
| chromeos::input_method::InputMethodManager::State* state = |
| manager->GetActiveIMEState().get(); |
| if (event->type() == ET_KEY_PRESSED && state) { |
| bool language_input_key = true; |
| switch (event->key_code()) { |
| case ui::VKEY_CONVERT: |
| state->ChangeInputMethodToJpIme(); |
| break; |
| case ui::VKEY_NONCONVERT: |
| state->ChangeInputMethodToJpKeyboard(); |
| break; |
| case ui::VKEY_DBE_SBCSCHAR: |
| case ui::VKEY_DBE_DBCSCHAR: |
| state->ToggleInputMethodForJpIme(); |
| break; |
| default: |
| language_input_key = false; |
| break; |
| } |
| if (language_input_key) { |
| // Dispatches the event to app/blink. |
| // TODO(shuchen): Eventually, the language input keys should be handed |
| // over to the IME extension to process. And IMF can handle if the IME |
| // extension didn't handle. |
| return DispatchKeyEventPostIME(event, std::move(ack_callback)); |
| } |
| } |
| } |
| |
| // 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 (!IsNonPasswordInputFieldFocused() || !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, |
| return ProcessKeyEventPostIME(event, std::move(ack_callback), false, |
| true); |
| } |
| return ProcessUnfilteredKeyPressEvent(event, std::move(ack_callback)); |
| } |
| return DispatchKeyEventPostIME(event, std::move(ack_callback)); |
| } |
| |
| handling_key_event_ = true; |
| if (GetEngine()->IsInterestedInKeyEvent()) { |
| ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback = |
| base::BindOnce(&InputMethodChromeOS::KeyEventDoneCallback, |
| weak_ptr_factory_.GetWeakPtr(), |
| // Pass the ownership of the new copied event. |
| base::Owned(new ui::KeyEvent(*event)), |
| std::move(ack_callback)); |
| GetEngine()->ProcessKeyEvent(*event, std::move(callback)); |
| return ui::EventDispatchDetails(); |
| } |
| return ProcessKeyEventDone(event, std::move(ack_callback), false); |
| } |
| |
| bool InputMethodChromeOS::OnUntranslatedIMEMessage(const PlatformEvent& event, |
| NativeEventResult* result) { |
| return false; |
| } |
| |
| void InputMethodChromeOS::KeyEventDoneCallback(ui::KeyEvent* event, |
| AckCallback ack_callback, |
| bool is_handled) { |
| ignore_result( |
| ProcessKeyEventDone(event, std::move(ack_callback), is_handled)); |
| } |
| |
| ui::EventDispatchDetails InputMethodChromeOS::ProcessKeyEventDone( |
| ui::KeyEvent* event, |
| AckCallback ack_callback, |
| bool is_handled) { |
| 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); |
| } |
| } |
| ui::EventDispatchDetails details; |
| if (event->type() == ET_KEY_PRESSED || event->type() == ET_KEY_RELEASED) { |
| details = ProcessKeyEventPostIME(event, std::move(ack_callback), false, |
| is_handled); |
| } |
| handling_key_event_ = false; |
| return details; |
| } |
| |
| ui::EventDispatchDetails InputMethodChromeOS::DispatchKeyEvent( |
| ui::KeyEvent* event) { |
| return DispatchKeyEvent(event, AckCallback()); |
| } |
| |
| void InputMethodChromeOS::OnTextInputTypeChanged( |
| const TextInputClient* client) { |
| if (!IsTextInputClientFocused(client)) |
| return; |
| |
| UpdateContextFocusState(); |
| |
| ui::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(); |
| ui::IMEEngineHandlerInterface::InputContext context( |
| GetTextInputType(), GetTextInputMode(), GetTextInputFlags()); |
| engine->FocusIn(context); |
| } |
| |
| InputMethodBase::OnTextInputTypeChanged(client); |
| } |
| |
| void InputMethodChromeOS::OnCaretBoundsChanged(const TextInputClient* client) { |
| if (!IsInputFieldFocused() || !IsTextInputClientFocused(client)) |
| return; |
| |
| NotifyTextInputCaretBoundsChanged(client); |
| |
| if (!IsNonPasswordInputFieldFocused()) |
| return; |
| |
| // The current text input type should not be NONE if |context_| is focused. |
| DCHECK(client == GetTextInputClient()); |
| DCHECK(!IsTextInputTypeNone()); |
| |
| if (GetEngine()) |
| GetEngine()->SetCompositionBounds(GetCompositionBounds(client)); |
| |
| chromeos::IMECandidateWindowHandlerInterface* candidate_window = |
| ui::IMEBridge::Get()->GetCandidateWindowHandler(); |
| if (!candidate_window) |
| return; |
| |
| const gfx::Rect caret_rect = client->GetCaretBounds(); |
| |
| gfx::Rect composition_head; |
| if (client->HasCompositionText()) |
| client->GetCompositionCharacterBounds(0, &composition_head); |
| |
| // Pepper doesn't support composition bounds, so fall back to caret bounds to |
| // avoid a bad user experience (the IME window moved to upper left corner). |
| if (composition_head.IsEmpty()) |
| composition_head = caret_rect; |
| candidate_window->SetCursorBounds(caret_rect, composition_head); |
| |
| gfx::Range text_range; |
| gfx::Range selection_range; |
| base::string16 surrounding_text; |
| if (!client->GetTextRange(&text_range) || |
| !client->GetTextFromRange(text_range, &surrounding_text) || |
| !client->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(), |
| text_range.start()); |
| } |
| |
| void InputMethodChromeOS::CancelComposition(const TextInputClient* client) { |
| if (IsNonPasswordInputFieldFocused() && IsTextInputClientFocused(client)) |
| ResetContext(); |
| } |
| |
| 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()) { |
| ui::IMEEngineHandlerInterface::InputContext context( |
| GetTextInputType(), GetTextInputMode(), GetTextInputFlags()); |
| GetEngine()->FocusIn(context); |
| } |
| } |
| |
| void InputMethodChromeOS::ConfirmCompositionText() { |
| TextInputClient* client = GetTextInputClient(); |
| if (client && client->HasCompositionText()) |
| client->ConfirmCompositionText(); |
| |
| ResetContext(); |
| } |
| |
| void InputMethodChromeOS::ResetContext() { |
| if (!IsNonPasswordInputFieldFocused() || !GetTextInputClient()) |
| return; |
| |
| composition_ = CompositionText(); |
| result_text_.clear(); |
| composing_text_ = false; |
| composition_changed_ = false; |
| |
| // 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 = |
| ui::IMEBridge::Get()->GetCandidateWindowHandler(); |
| if (candidate_window) |
| candidate_window->FocusStateChanged(IsNonPasswordInputFieldFocused()); |
| |
| ui::IMEEngineHandlerInterface::InputContext context( |
| GetTextInputType(), GetTextInputMode(), GetTextInputFlags()); |
| ui::IMEBridge::Get()->SetCurrentInputContext(context); |
| |
| if (!IsTextInputTypeNone()) |
| OnCaretBoundsChanged(GetTextInputClient()); |
| } |
| |
| ui::EventDispatchDetails InputMethodChromeOS::ProcessKeyEventPostIME( |
| ui::KeyEvent* event, |
| AckCallback ack_callback, |
| bool skip_process_filtered, |
| 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. |
| return DispatchKeyEventPostIME(event, std::move(ack_callback)); |
| } |
| |
| ui::EventDispatchDetails dispatch_details; |
| if (event->type() == ET_KEY_PRESSED && handled && !skip_process_filtered) |
| return ProcessFilteredKeyPressEvent(event, std::move(ack_callback)); |
| |
| // In case the focus was changed by the key event. The |context_| should have |
| // been reset when the focused window changed. |
| if (client != GetTextInputClient()) { |
| if (ack_callback) |
| std::move(ack_callback).Run(false); |
| return dispatch_details; |
| } |
| if (HasInputMethodResult()) |
| ProcessInputMethodResult(event, handled); |
| |
| // In case the focus was changed when sending input method results to the |
| // focused window. |
| if (client != GetTextInputClient()) { |
| if (ack_callback) |
| std::move(ack_callback).Run(false); |
| return dispatch_details; |
| } |
| |
| if (handled) { |
| if (ack_callback) |
| std::move(ack_callback).Run(true); |
| return dispatch_details; // IME handled the key event. do not forward. |
| } |
| |
| if (event->type() == ET_KEY_PRESSED) |
| return ProcessUnfilteredKeyPressEvent(event, std::move(ack_callback)); |
| |
| if (event->type() == ET_KEY_RELEASED) |
| return DispatchKeyEventPostIME(event, std::move(ack_callback)); |
| return dispatch_details; |
| } |
| |
| ui::EventDispatchDetails InputMethodChromeOS::ProcessFilteredKeyPressEvent( |
| ui::KeyEvent* event, |
| AckCallback ack_callback) { |
| auto callback = base::Bind( |
| &InputMethodChromeOS::PostProcessFilteredKeyPressEvent, |
| weak_ptr_factory_.GetWeakPtr(), base::Owned(new ui::KeyEvent(*event)), |
| GetTextInputClient(), Passed(&ack_callback)); |
| |
| if (NeedInsertChar()) |
| return DispatchKeyEventPostIME(event, std::move(callback)); |
| |
| ui::KeyEvent fabricated_event(ET_KEY_PRESSED, |
| VKEY_PROCESSKEY, |
| event->code(), |
| event->flags(), |
| event->GetDomKey(), |
| event->time_stamp()); |
| ui::EventDispatchDetails dispatch_details = |
| DispatchKeyEventPostIME(&fabricated_event, std::move(callback)); |
| if (fabricated_event.stopped_propagation()) |
| event->StopPropagation(); |
| return dispatch_details; |
| } |
| |
| void InputMethodChromeOS::PostProcessFilteredKeyPressEvent( |
| ui::KeyEvent* event, |
| TextInputClient* prev_client, |
| AckCallback ack_callback, |
| bool stopped_propagation) { |
| // In case the focus was changed by the key event. |
| if (GetTextInputClient() != prev_client) |
| return; |
| |
| if (stopped_propagation) { |
| ResetContext(); |
| if (ack_callback) |
| std::move(ack_callback).Run(true); |
| return; |
| } |
| ignore_result( |
| ProcessKeyEventPostIME(event, std::move(ack_callback), true, true)); |
| } |
| |
| ui::EventDispatchDetails InputMethodChromeOS::ProcessUnfilteredKeyPressEvent( |
| ui::KeyEvent* event, |
| AckCallback ack_callback) { |
| return DispatchKeyEventPostIME( |
| event, |
| base::BindOnce(&InputMethodChromeOS::PostProcessUnfilteredKeyPressEvent, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Owned(new ui::KeyEvent(*event)), |
| GetTextInputClient(), std::move(ack_callback))); |
| } |
| |
| void InputMethodChromeOS::PostProcessUnfilteredKeyPressEvent( |
| ui::KeyEvent* event, |
| TextInputClient* prev_client, |
| AckCallback ack_callback, |
| bool stopped_propagation) { |
| if (stopped_propagation) { |
| ResetContext(); |
| if (ack_callback) |
| std::move(ack_callback).Run(false); |
| return; |
| } |
| |
| // 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 <textarea>. |
| // 2. click Omnibox. |
| // 3. enable Korean IME, press A, then press Tab to move the focus to the web |
| // page. |
| // We should return here not to send the Tab key event to RWHV. |
| TextInputClient* client = GetTextInputClient(); |
| if (!client || client != prev_client) { |
| if (ack_callback) |
| std::move(ack_callback).Run(false); |
| return; |
| } |
| |
| // If a key event was not filtered by |context_| and |character_composer_|, |
| // then it means the key event didn't generate any result text. So we need |
| // to send corresponding character to the focused text input client. |
| uint16_t ch = event->GetCharacter(); |
| if (ch) |
| client->InsertChar(*event); |
| |
| if (ack_callback) |
| std::move(ack_callback).Run(false); |
| } |
| |
| void InputMethodChromeOS::ProcessInputMethodResult(ui::KeyEvent* event, |
| bool handled) { |
| TextInputClient* client = GetTextInputClient(); |
| DCHECK(client); |
| |
| if (result_text_.length()) { |
| if (handled && NeedInsertChar()) { |
| for (base::string16::const_iterator i = result_text_.begin(); |
| i != result_text_.end(); ++i) { |
| ui::KeyEvent ch_event(*event); |
| ch_event.set_character(*i); |
| client->InsertChar(ch_event); |
| } |
| } else { |
| client->InsertText(result_text_); |
| composing_text_ = false; |
| } |
| } |
| |
| if (composition_changed_ && !IsTextInputTypeNone()) { |
| if (composition_.text.length()) { |
| composing_text_ = true; |
| client->SetCompositionText(composition_); |
| } else if (result_text_.empty()) { |
| client->ClearCompositionText(); |
| } |
| } |
| |
| // We should not clear composition text here, as it may belong to the next |
| // composition session. |
| result_text_.clear(); |
| composition_changed_ = false; |
| } |
| |
| bool InputMethodChromeOS::NeedInsertChar() const { |
| return GetTextInputClient() && |
| (IsTextInputTypeNone() || |
| (!composing_text_ && result_text_.length() == 1)); |
| } |
| |
| bool InputMethodChromeOS::HasInputMethodResult() const { |
| return result_text_.length() || composition_changed_; |
| } |
| |
| void InputMethodChromeOS::CommitText(const std::string& text) { |
| if (text.empty()) |
| return; |
| |
| // We need to receive input method result even if the text input type is |
| // TEXT_INPUT_TYPE_NONE, to make sure we can always send correct |
| // character for each key event to the focused text input client. |
| if (!GetTextInputClient()) |
| return; |
| |
| const base::string16 utf16_text = base::UTF8ToUTF16(text); |
| if (utf16_text.empty()) |
| return; |
| |
| if (!CanComposeInline()) { |
| // Hides the candidate window for preedit text. |
| UpdateCompositionText(CompositionText(), 0, false); |
| } |
| |
| // Append the text to the buffer, because commit signal might be fired |
| // multiple times when processing a key event. |
| result_text_.append(utf16_text); |
| |
| // If we are not handling key event, do not bother sending text result if the |
| // focused text input client does not support text input. |
| if (!handling_key_event_ && !IsTextInputTypeNone()) { |
| if (!SendFakeProcessKeyEvent(true)) |
| GetTextInputClient()->InsertText(utf16_text); |
| SendFakeProcessKeyEvent(false); |
| result_text_.clear(); |
| } |
| } |
| |
| void InputMethodChromeOS::UpdateCompositionText(const CompositionText& text, |
| uint32_t cursor_pos, |
| bool visible) { |
| if (IsTextInputTypeNone()) |
| return; |
| |
| if (!CanComposeInline()) { |
| chromeos::IMECandidateWindowHandlerInterface* candidate_window = |
| ui::IMEBridge::Get()->GetCandidateWindowHandler(); |
| if (candidate_window) |
| candidate_window->UpdatePreeditText(text.text, cursor_pos, visible); |
| } |
| |
| // |visible| argument is very confusing. For example, what's the correct |
| // behavior when: |
| // 1. OnUpdatePreeditText() is called with a text and visible == false, then |
| // 2. OnShowPreeditText() is called afterwards. |
| // |
| // If it's only for clearing the current preedit text, then why not just use |
| // OnHidePreeditText()? |
| if (!visible) { |
| HidePreeditText(); |
| return; |
| } |
| |
| ExtractCompositionText(text, cursor_pos, &composition_); |
| |
| composition_changed_ = true; |
| |
| // In case OnShowPreeditText() is not called. |
| if (composition_.text.length()) |
| composing_text_ = true; |
| |
| if (!handling_key_event_) { |
| // If we receive a composition text without pending key event, then we need |
| // to send it to the focused text input client directly. |
| if (!SendFakeProcessKeyEvent(true)) |
| GetTextInputClient()->SetCompositionText(composition_); |
| SendFakeProcessKeyEvent(false); |
| composition_changed_ = false; |
| composition_ = CompositionText(); |
| } |
| } |
| |
| void InputMethodChromeOS::HidePreeditText() { |
| if (composition_.text.empty() || IsTextInputTypeNone()) |
| return; |
| |
| // Intentionally leaves |composing_text_| unchanged. |
| composition_changed_ = true; |
| composition_ = CompositionText(); |
| |
| if (!handling_key_event_) { |
| TextInputClient* client = GetTextInputClient(); |
| if (client && client->HasCompositionText()) { |
| if (!SendFakeProcessKeyEvent(true)) |
| client->ClearCompositionText(); |
| SendFakeProcessKeyEvent(false); |
| } |
| composition_changed_ = false; |
| } |
| } |
| |
| void InputMethodChromeOS::DeleteSurroundingText(int32_t offset, |
| uint32_t length) { |
| if (!composition_.text.empty()) |
| return; // do nothing if there is ongoing composition. |
| |
| if (GetTextInputClient()) { |
| uint32_t before = offset >= 0 ? 0U : static_cast<uint32_t>(-1 * offset); |
| GetTextInputClient()->ExtendSelectionAndDelete(before, length - before); |
| } |
| } |
| |
| bool InputMethodChromeOS::ExecuteCharacterComposer(const ui::KeyEvent& event) { |
| if (!character_composer_.FilterKeyPress(event)) |
| return false; |
| |
| // CharacterComposer consumed the key event. Update the composition text. |
| CompositionText preedit; |
| preedit.text = character_composer_.preedit_string(); |
| UpdateCompositionText(preedit, preedit.text.size(), !preedit.text.empty()); |
| std::string commit_text = |
| base::UTF16ToUTF8(character_composer_.composed_character()); |
| if (!commit_text.empty()) { |
| CommitText(commit_text); |
| } |
| return true; |
| } |
| |
| void InputMethodChromeOS::ExtractCompositionText( |
| const CompositionText& text, |
| uint32_t cursor_position, |
| CompositionText* out_composition) const { |
| *out_composition = CompositionText(); |
| out_composition->text = text.text; |
| |
| if (out_composition->text.empty()) |
| return; |
| |
| // ibus uses character index for cursor position and attribute range, but we |
| // use char16 offset for them. So we need to do conversion here. |
| std::vector<size_t> char16_offsets; |
| size_t length = out_composition->text.length(); |
| base::i18n::UTF16CharIterator char_iterator(&out_composition->text); |
| do { |
| char16_offsets.push_back(char_iterator.array_pos()); |
| } while (char_iterator.Advance()); |
| |
| // The text length in Unicode characters. |
| uint32_t char_length = static_cast<uint32_t>(char16_offsets.size()); |
| // Make sure we can convert the value of |char_length| as well. |
| char16_offsets.push_back(length); |
| |
| size_t cursor_offset = |
| char16_offsets[std::min(char_length, cursor_position)]; |
| |
| out_composition->selection = gfx::Range(cursor_offset); |
| |
| const ImeTextSpans text_ime_text_spans = text.ime_text_spans; |
| if (!text_ime_text_spans.empty()) { |
| for (size_t i = 0; i < text_ime_text_spans.size(); ++i) { |
| const uint32_t start = text_ime_text_spans[i].start_offset; |
| const uint32_t end = text_ime_text_spans[i].end_offset; |
| if (start >= end) |
| continue; |
| ImeTextSpan ime_text_span(ui::ImeTextSpan::Type::kComposition, |
| char16_offsets[start], char16_offsets[end], |
| text_ime_text_spans[i].thickness, |
| text_ime_text_spans[i].background_color); |
| ime_text_span.underline_color = text_ime_text_spans[i].underline_color; |
| out_composition->ime_text_spans.push_back(ime_text_span); |
| } |
| } |
| |
| DCHECK(text.selection.start() <= text.selection.end()); |
| if (text.selection.start() < text.selection.end()) { |
| const uint32_t start = text.selection.start(); |
| const uint32_t end = text.selection.end(); |
| ImeTextSpan ime_text_span(ui::ImeTextSpan::Type::kComposition, |
| char16_offsets[start], char16_offsets[end], |
| ui::ImeTextSpan::Thickness::kThick, |
| SK_ColorTRANSPARENT); |
| out_composition->ime_text_spans.push_back(ime_text_span); |
| |
| // If the cursor is at start or end of this ime_text_span, then we treat |
| // it as the selection range as well, but make sure to set the cursor |
| // position to the selection end. |
| if (ime_text_span.start_offset == cursor_offset) { |
| out_composition->selection.set_start(ime_text_span.end_offset); |
| out_composition->selection.set_end(cursor_offset); |
| } else if (ime_text_span.end_offset == cursor_offset) { |
| out_composition->selection.set_start(ime_text_span.start_offset); |
| out_composition->selection.set_end(cursor_offset); |
| } |
| } |
| |
| // Use a thin underline with text color by default. |
| if (out_composition->ime_text_spans.empty()) { |
| out_composition->ime_text_spans.push_back( |
| ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 0, length, |
| ui::ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT)); |
| } |
| } |
| |
| bool InputMethodChromeOS::IsNonPasswordInputFieldFocused() { |
| TextInputType type = GetTextInputType(); |
| return (type != TEXT_INPUT_TYPE_NONE) && (type != TEXT_INPUT_TYPE_PASSWORD); |
| } |
| |
| bool InputMethodChromeOS::IsInputFieldFocused() { |
| return GetTextInputType() != TEXT_INPUT_TYPE_NONE; |
| } |
| |
| } // namespace ui |