blob: dc3358c2a0e97ecb8a9ce5819d0334660d2b98a9 [file] [log] [blame]
// Copyright 2018 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 "components/exo/text_input.h"
#include <algorithm>
#include <utility>
#include "base/check.h"
#include "base/logging.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_offset_string_conversions.h"
#include "components/exo/seat.h"
#include "components/exo/shell_surface_util.h"
#include "components/exo/surface.h"
#include "components/exo/wm_helper.h"
#include "third_party/icu/source/common/unicode/uchar.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/utf_offset.h"
#include "ui/base/ime/virtual_keyboard_controller.h"
#include "ui/events/event.h"
namespace exo {
namespace {
constexpr int kTextInputSeatObserverPriority = 1;
static_assert(Seat::IsValidObserverPriority(kTextInputSeatObserverPriority),
"kTextInputSeatObserverPriority is not in the valid range.");
ui::InputMethod* GetInputMethod(aura::Window* window) {
if (!window || !window->GetHost())
return nullptr;
return window->GetHost()->GetInputMethod();
}
ui::CompositionText GenerateCompositionTextFrom(
const std::u16string& surrounding_text,
const gfx::Range& composition_range,
const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) {
ui::CompositionText composition;
composition.text = surrounding_text.substr(composition_range.GetMin(),
composition_range.length());
composition.ime_text_spans = ui_ime_text_spans;
return composition;
}
} // namespace
TextInput::TextInput(std::unique_ptr<Delegate> delegate)
: delegate_(std::move(delegate)) {}
TextInput::~TextInput() {
Deactivate();
}
void TextInput::Activate(Seat* seat, Surface* surface) {
DCHECK(surface);
DCHECK(seat);
if (surface_ == surface)
return;
DetachInputMethod();
surface_ = surface;
seat_ = seat;
seat_->AddObserver(this, kTextInputSeatObserverPriority);
if (seat_->GetFocusedSurface() == surface_)
AttachInputMethod();
}
void TextInput::Deactivate() {
if (!surface_)
return;
DetachInputMethod();
seat_->RemoveObserver(this);
surface_ = nullptr;
seat_ = nullptr;
}
void TextInput::ShowVirtualKeyboardIfEnabled() {
// Some clients may ask showing virtual keyboard before sending activation.
if (!input_method_) {
pending_vk_visible_ = true;
return;
}
input_method_->SetVirtualKeyboardVisibilityIfEnabled(true);
}
void TextInput::HideVirtualKeyboard() {
if (input_method_)
input_method_->SetVirtualKeyboardVisibilityIfEnabled(false);
pending_vk_visible_ = false;
}
void TextInput::Resync() {
if (input_method_)
input_method_->OnCaretBoundsChanged(this);
}
void TextInput::Reset() {
ResetCompositionTextCache();
if (input_method_)
input_method_->CancelComposition(this);
}
void TextInput::SetSurroundingText(const std::u16string& text,
const gfx::Range& cursor_pos) {
surrounding_text_ = text;
cursor_pos_ = cursor_pos;
// Convert utf8 grammar fragment to utf16.
if (grammar_fragment_at_cursor_utf8_) {
std::string utf8_text = base::UTF16ToUTF8(text);
ui::GrammarFragment fragment = *grammar_fragment_at_cursor_utf8_;
std::vector<size_t> offsets = {fragment.range.start(),
fragment.range.end()};
base::UTF8ToUTF16AndAdjustOffsets(utf8_text, &offsets);
grammar_fragment_at_cursor_utf16_ = ui::GrammarFragment(
gfx::Range(offsets[0], offsets[1]), fragment.suggestion);
} else {
grammar_fragment_at_cursor_utf16_ = absl::nullopt;
}
if (pending_autocorrect_info_) {
autocorrect_info_ = *pending_autocorrect_info_;
pending_autocorrect_info_ = absl::nullopt;
}
// TODO(b/206068262): Consider introducing an API to notify surrounding text
// update explicitly.
if (input_method_)
input_method_->OnCaretBoundsChanged(this);
}
void TextInput::SetTypeModeFlags(ui::TextInputType type,
ui::TextInputMode mode,
int flags,
bool should_do_learning) {
if (!input_method_)
return;
bool changed = (input_type_ != type);
input_type_ = type;
input_mode_ = mode;
flags_ = flags;
should_do_learning_ = should_do_learning;
if (changed)
input_method_->OnTextInputTypeChanged(this);
}
void TextInput::SetCaretBounds(const gfx::Rect& bounds) {
if (caret_bounds_ == bounds)
return;
caret_bounds_ = bounds;
if (!input_method_)
return;
input_method_->OnCaretBoundsChanged(this);
}
void TextInput::SetGrammarFragmentAtCursor(
const absl::optional<ui::GrammarFragment>& fragment) {
grammar_fragment_at_cursor_utf16_ = absl::nullopt;
grammar_fragment_at_cursor_utf8_ = fragment;
}
void TextInput::SetAutocorrectInfo(const gfx::Range& autocorrect_range,
const gfx::Rect& autocorrect_bounds) {
// Since we receive the autocorrect information separately from the
// surrounding text information, the range and bounds may be invalid at this
// point, because the surrounding text this class holds is stale.
// Save it as the "pending" information a surrounding text update is received.
pending_autocorrect_info_ = {autocorrect_range, autocorrect_bounds};
}
void TextInput::SetCompositionText(const ui::CompositionText& composition) {
composition_ = composition;
// Identify the starting index of the current composition. If a composition
// range has been set previously, then use it's starting index, otherwise
// use the current cursor position as the start of the composition. If the
// user has a selection, then we can assume the min value of the cursor_pos
// range as the start of the composition, as the selection will be replaced
// by the composition text being set.
size_t composition_start = cursor_pos_.IsValid() ? cursor_pos_.GetMin() : 0;
if (composition_range_.IsValid())
composition_start = composition_range_.GetMin();
composition_range_ = gfx::Range(composition_start,
composition_start + composition.text.size());
delegate_->SetCompositionText(composition);
}
uint32_t TextInput::ConfirmCompositionText(bool keep_selection) {
// TODO(b/134473433) Modify this function so that when keep_selection is
// true, the selection is not changed when text committed
if (keep_selection) {
NOTIMPLEMENTED_LOG_ONCE();
}
const uint32_t composition_text_length =
static_cast<uint32_t>(composition_.text.length());
delegate_->Commit(composition_.text);
ResetCompositionTextCache();
return composition_text_length;
}
void TextInput::ClearCompositionText() {
if (composition_.text.empty())
return;
ResetCompositionTextCache();
delegate_->SetCompositionText(composition_);
}
void TextInput::InsertText(const std::u16string& text,
InsertTextCursorBehavior cursor_behavior) {
// TODO(crbug.com/1155331): Handle |cursor_behavior| correctly.
delegate_->Commit(text);
ResetCompositionTextCache();
}
void TextInput::InsertChar(const ui::KeyEvent& event) {
// TextInput is currently used only for Lacros, and this is the
// short term workaround not to duplicate KeyEvent there.
// This is what we do for ARC, which is being removed in the near
// future.
// TODO(fukino): Get rid of this, too, when the wl_keyboard::key
// and text_input::keysym events are handled properly in Lacros.
if (ConsumedByIme(surface_->window(), event))
delegate_->SendKey(event);
}
ui::TextInputType TextInput::GetTextInputType() const {
return input_type_;
}
ui::TextInputMode TextInput::GetTextInputMode() const {
return input_mode_;
}
base::i18n::TextDirection TextInput::GetTextDirection() const {
return direction_;
}
int TextInput::GetTextInputFlags() const {
return flags_;
}
bool TextInput::CanComposeInline() const {
return true;
}
gfx::Rect TextInput::GetCaretBounds() const {
return caret_bounds_ +
surface_->window()->GetBoundsInScreen().OffsetFromOrigin();
}
gfx::Rect TextInput::GetSelectionBoundingBox() const {
NOTIMPLEMENTED();
return gfx::Rect();
}
bool TextInput::GetCompositionCharacterBounds(uint32_t index,
gfx::Rect* rect) const {
return false;
}
bool TextInput::HasCompositionText() const {
return !composition_.text.empty();
}
ui::TextInputClient::FocusReason TextInput::GetFocusReason() const {
NOTIMPLEMENTED_LOG_ONCE();
return ui::TextInputClient::FOCUS_REASON_OTHER;
}
bool TextInput::GetTextRange(gfx::Range* range) const {
if (!cursor_pos_.IsValid())
return false;
range->set_start(0);
range->set_end(surrounding_text_.size());
return true;
}
bool TextInput::GetCompositionTextRange(gfx::Range* range) const {
DCHECK(range);
if (composition_range_.IsValid()) {
*range = composition_range_;
return true;
}
return false;
}
bool TextInput::GetEditableSelectionRange(gfx::Range* range) const {
if (!cursor_pos_.IsValid())
return false;
range->set_start(cursor_pos_.start());
range->set_end(cursor_pos_.end());
return true;
}
bool TextInput::SetEditableSelectionRange(const gfx::Range& range) {
if (surrounding_text_.size() < range.GetMax())
return false;
delegate_->SetCursor(surrounding_text_, range);
return true;
}
bool TextInput::GetTextFromRange(const gfx::Range& range,
std::u16string* text) const {
gfx::Range text_range;
if (!GetTextRange(&text_range) || !text_range.Contains(range))
return false;
text->assign(surrounding_text_.substr(range.GetMin(), range.length()));
return true;
}
void TextInput::OnInputMethodChanged() {
DCHECK_EQ(surface_, seat_->GetFocusedSurface());
ui::InputMethod* input_method = GetInputMethod(surface_->window());
if (input_method == input_method_)
return;
input_method_->DetachTextInputClient(this);
virtual_keyboard_observation_.Reset();
input_method_ = input_method;
if (auto* controller = input_method_->GetVirtualKeyboardController())
virtual_keyboard_observation_.Observe(controller);
input_method_->SetFocusedTextInputClient(this);
}
bool TextInput::ChangeTextDirectionAndLayoutAlignment(
base::i18n::TextDirection direction) {
if (direction == direction_)
return true;
direction_ = direction;
delegate_->OnTextDirectionChanged(direction_);
return true;
}
void TextInput::ExtendSelectionAndDelete(size_t before, size_t after) {
if (!cursor_pos_.IsValid())
return;
size_t utf16_start =
std::max(static_cast<size_t>(cursor_pos_.GetMin()), before) - before;
size_t utf16_end =
std::min(cursor_pos_.GetMax() + after, surrounding_text_.size());
delegate_->DeleteSurroundingText(surrounding_text_,
gfx::Range(utf16_start, utf16_end));
}
void TextInput::EnsureCaretNotInRect(const gfx::Rect& rect) {}
bool TextInput::IsTextEditCommandEnabled(ui::TextEditCommand command) const {
return false;
}
void TextInput::SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command) {
}
ukm::SourceId TextInput::GetClientSourceForMetrics() const {
NOTIMPLEMENTED_LOG_ONCE();
return ukm::kInvalidSourceId;
}
bool TextInput::ShouldDoLearning() {
return should_do_learning_;
}
bool TextInput::SetCompositionFromExistingText(
const gfx::Range& range,
const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) {
if (!cursor_pos_.IsValid())
return false;
if (surrounding_text_.size() < range.GetMax())
return false;
const auto composition_length = range.length();
for (const auto& span : ui_ime_text_spans) {
if (composition_length < std::max(span.start_offset, span.end_offset))
return false;
}
composition_ =
GenerateCompositionTextFrom(surrounding_text_, range, ui_ime_text_spans);
composition_range_.set_start(range.GetMin());
composition_range_.set_end(range.GetMax());
delegate_->SetCompositionFromExistingText(surrounding_text_, cursor_pos_,
range, ui_ime_text_spans);
return true;
}
gfx::Range TextInput::GetAutocorrectRange() const {
return autocorrect_info_.range;
}
gfx::Rect TextInput::GetAutocorrectCharacterBounds() const {
return autocorrect_info_.bounds;
}
bool TextInput::SetAutocorrectRange(const gfx::Range& range) {
delegate_->SetAutocorrectRange(surrounding_text_, range);
return true;
}
absl::optional<ui::GrammarFragment> TextInput::GetGrammarFragmentAtCursor()
const {
return grammar_fragment_at_cursor_utf16_;
}
bool TextInput::ClearGrammarFragments(const gfx::Range& range) {
if (surrounding_text_.size() < range.GetMax())
return false;
delegate_->ClearGrammarFragments(surrounding_text_, range);
return true;
}
bool TextInput::AddGrammarFragments(
const std::vector<ui::GrammarFragment>& fragments) {
for (auto& fragment : fragments) {
if (surrounding_text_.size() < fragment.range.GetMax())
continue;
delegate_->AddGrammarFragment(surrounding_text_, fragment);
}
return true;
}
void GetActiveTextInputControlLayoutBounds(
absl::optional<gfx::Rect>* control_bounds,
absl::optional<gfx::Rect>* selection_bounds) {
NOTIMPLEMENTED_LOG_ONCE();
}
void TextInput::OnKeyboardVisible(const gfx::Rect& keyboard_rect) {
delegate_->OnVirtualKeyboardVisibilityChanged(true);
}
void TextInput::OnKeyboardHidden() {
delegate_->OnVirtualKeyboardVisibilityChanged(false);
}
void TextInput::OnSurfaceFocused(Surface* gained_focus,
Surface* lost_focus,
bool has_focused_surface) {
DCHECK(surface_);
if (gained_focus == lost_focus)
return;
if (gained_focus == surface_) {
AttachInputMethod();
} else if (lost_focus == surface_) {
Deactivate();
}
}
void TextInput::AttachInputMethod() {
DCHECK(!input_method_);
DCHECK(surface_);
input_method_ = GetInputMethod(surface_->window());
if (!input_method_) {
LOG(ERROR) << "input method not found";
return;
}
input_mode_ = ui::TEXT_INPUT_MODE_TEXT;
input_type_ = ui::TEXT_INPUT_TYPE_TEXT;
if (auto* controller = input_method_->GetVirtualKeyboardController())
virtual_keyboard_observation_.Observe(controller);
input_method_->SetFocusedTextInputClient(this);
delegate_->Activated();
if (pending_vk_visible_) {
input_method_->SetVirtualKeyboardVisibilityIfEnabled(true);
pending_vk_visible_ = false;
}
}
void TextInput::DetachInputMethod() {
if (!input_method_)
return;
input_mode_ = ui::TEXT_INPUT_MODE_DEFAULT;
input_type_ = ui::TEXT_INPUT_TYPE_NONE;
input_method_->DetachTextInputClient(this);
virtual_keyboard_observation_.Reset();
input_method_ = nullptr;
delegate_->Deactivated();
}
void TextInput::ResetCompositionTextCache() {
composition_ = ui::CompositionText();
composition_range_ = gfx::Range::InvalidRange();
}
} // namespace exo