blob: 71fdd0cc09e067693e71e0c1495a5683aa2b3884 [file] [log] [blame]
Jun Mukaia446ee7f2018-07-20 22:55:151// Copyright 2018 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 "components/exo/text_input.h"
6
Jun Mukaicf1f0362019-02-07 00:39:337#include <algorithm>
8
Darren Shencb2508442019-07-03 21:48:239#include "ash/keyboard/ui/keyboard_ui_controller.h"
Jun Mukaicf1f0362019-02-07 00:39:3310#include "base/strings/utf_string_conversions.h"
Jun Mukaia446ee7f2018-07-20 22:55:1511#include "components/exo/surface.h"
12#include "components/exo/wm_helper.h"
13#include "third_party/icu/source/common/unicode/uchar.h"
14#include "ui/aura/window.h"
15#include "ui/aura/window_tree_host.h"
16#include "ui/base/ime/input_method.h"
17#include "ui/events/event.h"
Jun Mukaia446ee7f2018-07-20 22:55:1518
19namespace exo {
20
21namespace {
22
23ui::InputMethod* GetInputMethod(aura::Window* window) {
24 if (!window || !window->GetHost())
25 return nullptr;
26 return window->GetHost()->GetInputMethod();
27}
28
29} // namespace
30
Jun Mukaicf1f0362019-02-07 00:39:3331size_t OffsetFromUTF8Offset(const base::StringPiece& text, uint32_t offset) {
32 return base::UTF8ToUTF16(text.substr(0, offset)).size();
33}
34
35size_t OffsetFromUTF16Offset(const base::StringPiece16& text, uint32_t offset) {
36 return base::UTF16ToUTF8(text.substr(0, offset)).size();
37}
38
Jun Mukaia446ee7f2018-07-20 22:55:1539TextInput::TextInput(std::unique_ptr<Delegate> delegate)
40 : delegate_(std::move(delegate)) {}
41
42TextInput::~TextInput() {
Darren Shencb2508442019-07-03 21:48:2343 if (keyboard_ui_controller_)
44 keyboard_ui_controller_->RemoveObserver(this);
Jun Mukaia446ee7f2018-07-20 22:55:1545 if (input_method_)
46 Deactivate();
47}
48
49void TextInput::Activate(Surface* surface) {
50 DLOG_IF(ERROR, window_) << "Already activated with " << window_;
51 DCHECK(surface);
52
53 window_ = surface->window();
54 AttachInputMethod();
55}
56
57void TextInput::Deactivate() {
58 DetachInputMethod();
59 window_ = nullptr;
60}
61
62void TextInput::ShowVirtualKeyboardIfEnabled() {
63 // Some clients may ask showing virtual keyboard before sending activation.
64 if (!input_method_) {
65 pending_vk_visible_ = true;
66 return;
67 }
68 input_method_->ShowVirtualKeyboardIfEnabled();
69}
70
71void TextInput::HideVirtualKeyboard() {
Darren Shencb2508442019-07-03 21:48:2372 if (keyboard_ui_controller_)
73 keyboard_ui_controller_->HideKeyboardByUser();
Jun Mukaia446ee7f2018-07-20 22:55:1574 pending_vk_visible_ = false;
75}
76
77void TextInput::Resync() {
78 if (input_method_)
79 input_method_->OnCaretBoundsChanged(this);
80}
81
82void TextInput::SetSurroundingText(const base::string16& text,
Jun Mukaicf1f0362019-02-07 00:39:3383 uint32_t cursor_pos,
84 uint32_t anchor) {
85 surrounding_text_ = text;
86 cursor_pos_ = gfx::Range(cursor_pos);
87 if (anchor < cursor_pos)
88 cursor_pos_->set_start(anchor);
89 else
90 cursor_pos_->set_end(anchor);
Jun Mukaia446ee7f2018-07-20 22:55:1591}
92
93void TextInput::SetTypeModeFlags(ui::TextInputType type,
94 ui::TextInputMode mode,
95 int flags,
96 bool should_do_learning) {
97 if (!input_method_)
98 return;
99 bool changed = (input_type_ != type);
100 input_type_ = type;
101 input_mode_ = mode;
102 flags_ = flags;
103 should_do_learning_ = should_do_learning;
104 if (changed)
105 input_method_->OnTextInputTypeChanged(this);
106}
107
108void TextInput::SetCaretBounds(const gfx::Rect& bounds) {
109 if (caret_bounds_ == bounds)
110 return;
111 caret_bounds_ = bounds;
112 if (!input_method_)
113 return;
114 input_method_->OnCaretBoundsChanged(this);
115}
116
117void TextInput::SetCompositionText(const ui::CompositionText& composition) {
118 composition_ = composition;
119 delegate_->SetCompositionText(composition);
120}
121
Keith Leee10ead42020-07-28 08:16:33122uint32_t TextInput::ConfirmCompositionText(bool keep_selection) {
Keith Lee407770c22019-11-08 05:29:48123 // TODO(b/134473433) Modify this function so that when keep_selection is
124 // true, the selection is not changed when text committed
125 if (keep_selection) {
126 NOTIMPLEMENTED_LOG_ONCE();
127 }
Keith Leee10ead42020-07-28 08:16:33128 const uint32_t composition_text_length =
129 static_cast<uint32_t>(composition_.text.length());
Jun Mukaia446ee7f2018-07-20 22:55:15130 delegate_->Commit(composition_.text);
Keith Leee10ead42020-07-28 08:16:33131 return composition_text_length;
Jun Mukaia446ee7f2018-07-20 22:55:15132}
133
134void TextInput::ClearCompositionText() {
135 composition_ = ui::CompositionText();
136 delegate_->SetCompositionText(composition_);
137}
138
139void TextInput::InsertText(const base::string16& text) {
140 delegate_->Commit(text);
141}
142
143void TextInput::InsertChar(const ui::KeyEvent& event) {
144 base::char16 ch = event.GetCharacter();
145 if (u_isprint(ch)) {
146 InsertText(base::string16(1, ch));
147 return;
148 }
149 delegate_->SendKey(event);
150}
151
152ui::TextInputType TextInput::GetTextInputType() const {
153 return input_type_;
154}
155
156ui::TextInputMode TextInput::GetTextInputMode() const {
157 return input_mode_;
158}
159
160base::i18n::TextDirection TextInput::GetTextDirection() const {
161 return direction_;
162}
163
164int TextInput::GetTextInputFlags() const {
165 return flags_;
166}
167
168bool TextInput::CanComposeInline() const {
169 return true;
170}
171
172gfx::Rect TextInput::GetCaretBounds() const {
173 return caret_bounds_ + window_->GetBoundsInScreen().OffsetFromOrigin();
174}
175
176bool TextInput::GetCompositionCharacterBounds(uint32_t index,
177 gfx::Rect* rect) const {
178 return false;
179}
180
181bool TextInput::HasCompositionText() const {
182 return !composition_.text.empty();
183}
184
185ui::TextInputClient::FocusReason TextInput::GetFocusReason() const {
186 NOTIMPLEMENTED_LOG_ONCE();
187 return ui::TextInputClient::FOCUS_REASON_OTHER;
188}
189
190bool TextInput::GetTextRange(gfx::Range* range) const {
Jun Mukaicf1f0362019-02-07 00:39:33191 if (!cursor_pos_)
192 return false;
193 range->set_start(0);
194 if (composition_.text.empty()) {
195 range->set_end(surrounding_text_.size());
196 } else {
197 range->set_end(surrounding_text_.size() - cursor_pos_->length() +
198 composition_.text.size());
199 }
200 return true;
Jun Mukaia446ee7f2018-07-20 22:55:15201}
202
203bool TextInput::GetCompositionTextRange(gfx::Range* range) const {
Jun Mukaicf1f0362019-02-07 00:39:33204 if (!cursor_pos_ || composition_.text.empty())
205 return false;
206
207 range->set_start(cursor_pos_->start());
208 range->set_end(cursor_pos_->start() + composition_.text.size());
209 return true;
Jun Mukaia446ee7f2018-07-20 22:55:15210}
211
Yuichiro Hanada65eaaf42018-12-14 07:18:38212bool TextInput::GetEditableSelectionRange(gfx::Range* range) const {
Jun Mukaicf1f0362019-02-07 00:39:33213 if (!cursor_pos_)
214 return false;
215 range->set_start(cursor_pos_->start());
216 range->set_end(cursor_pos_->end());
217 return true;
Jun Mukaia446ee7f2018-07-20 22:55:15218}
219
Yuichiro Hanada65eaaf42018-12-14 07:18:38220bool TextInput::SetEditableSelectionRange(const gfx::Range& range) {
Jun Mukaicf1f0362019-02-07 00:39:33221 if (surrounding_text_.size() < range.GetMax())
222 return false;
223 delegate_->SetCursor(
224 gfx::Range(OffsetFromUTF16Offset(surrounding_text_, range.start()),
225 OffsetFromUTF16Offset(surrounding_text_, range.end())));
226 return true;
Jun Mukaia446ee7f2018-07-20 22:55:15227}
228
229bool TextInput::DeleteRange(const gfx::Range& range) {
Jun Mukaicf1f0362019-02-07 00:39:33230 if (surrounding_text_.size() < range.GetMax())
231 return false;
232 delegate_->DeleteSurroundingText(
233 gfx::Range(OffsetFromUTF16Offset(surrounding_text_, range.start()),
234 OffsetFromUTF16Offset(surrounding_text_, range.end())));
235 return true;
Jun Mukaia446ee7f2018-07-20 22:55:15236}
237
238bool TextInput::GetTextFromRange(const gfx::Range& range,
239 base::string16* text) const {
Jun Mukaicf1f0362019-02-07 00:39:33240 gfx::Range text_range;
241 if (!GetTextRange(&text_range) || !text_range.Contains(range))
242 return false;
243 if (composition_.text.empty() || range.GetMax() <= cursor_pos_->GetMin()) {
244 text->assign(surrounding_text_, range.GetMin(), range.length());
245 return true;
246 }
247 size_t composition_end = cursor_pos_->GetMin() + composition_.text.size();
248 if (range.GetMin() >= composition_end) {
249 size_t start =
250 range.GetMin() - composition_.text.size() + cursor_pos_->length();
251 text->assign(surrounding_text_, start, range.length());
252 return true;
253 }
254
255 size_t start_in_composition = 0;
256 if (range.GetMin() <= cursor_pos_->GetMin()) {
257 text->assign(surrounding_text_, range.GetMin(),
258 cursor_pos_->GetMin() - range.GetMin());
259 } else {
260 start_in_composition = range.GetMin() - cursor_pos_->GetMin();
261 }
262 if (range.GetMax() <= composition_end) {
263 text->append(composition_.text, start_in_composition,
264 range.GetMax() - cursor_pos_->GetMin() - start_in_composition);
265 } else {
266 text->append(composition_.text, start_in_composition,
267 composition_.text.size() - start_in_composition);
268 text->append(surrounding_text_, cursor_pos_->GetMax(),
269 range.GetMax() - composition_end);
270 }
271 return true;
Jun Mukaia446ee7f2018-07-20 22:55:15272}
273
274void TextInput::OnInputMethodChanged() {
275 ui::InputMethod* input_method = GetInputMethod(window_);
276 if (input_method == input_method_)
277 return;
278 input_method_->DetachTextInputClient(this);
279 input_method_ = input_method;
280 input_method_->SetFocusedTextInputClient(this);
281}
282
283bool TextInput::ChangeTextDirectionAndLayoutAlignment(
284 base::i18n::TextDirection direction) {
285 if (direction == direction_)
286 return true;
287 direction_ = direction;
288 delegate_->OnTextDirectionChanged(direction_);
289 return true;
290}
291
Jun Mukaicf1f0362019-02-07 00:39:33292void TextInput::ExtendSelectionAndDelete(size_t before, size_t after) {
293 if (!cursor_pos_)
294 return;
295 uint32_t start =
296 (cursor_pos_->GetMin() < before) ? 0 : (cursor_pos_->GetMin() - before);
297 uint32_t end =
298 std::min(cursor_pos_->GetMax() + after, surrounding_text_.size());
299 delegate_->DeleteSurroundingText(
300 gfx::Range(OffsetFromUTF16Offset(surrounding_text_, start),
301 OffsetFromUTF16Offset(surrounding_text_, end)));
302}
Jun Mukaia446ee7f2018-07-20 22:55:15303
304void TextInput::EnsureCaretNotInRect(const gfx::Rect& rect) {}
305
306bool TextInput::IsTextEditCommandEnabled(ui::TextEditCommand command) const {
307 return false;
308}
309
310void TextInput::SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command) {
311}
312
313ukm::SourceId TextInput::GetClientSourceForMetrics() const {
314 NOTIMPLEMENTED_LOG_ONCE();
315 return ukm::kInvalidSourceId;
316}
317
318bool TextInput::ShouldDoLearning() {
319 return should_do_learning_;
320}
321
Darren Shen8f63c552019-05-09 02:07:58322bool TextInput::SetCompositionFromExistingText(
Darren Shenff9457f32019-05-01 07:46:26323 const gfx::Range& range,
Darren Shen8f63c552019-05-09 02:07:58324 const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) {
325 // TODO(https://crbug.com/952757): Implement this method.
326 NOTIMPLEMENTED_LOG_ONCE();
327 return false;
328}
Darren Shenff9457f32019-05-01 07:46:26329
Keith Lee8c0b4872020-07-29 07:20:47330gfx::Range TextInput::GetAutocorrectRange() const {
331 // TODO(https://crbug.com/952757): Implement this method.
332 NOTIMPLEMENTED_LOG_ONCE();
333 return gfx::Range();
334}
335
Keith Leef7c2bc22020-07-27 23:18:31336gfx::Rect TextInput::GetAutocorrectCharacterBounds() const {
337 // TODO(https://crbug.com/952757): Implement this method.
338 NOTIMPLEMENTED_LOG_ONCE();
339 return gfx::Rect();
340}
341
Keith Lee5e1d2bc2020-06-18 09:26:53342// TODO(crbug.com/1091088) Implement setAutocorrectRange
343bool TextInput::SetAutocorrectRange(const base::string16& autocorrect_text,
344 const gfx::Range& range) {
345 NOTIMPLEMENTED_LOG_ONCE();
346 return false;
347}
348
Keith Leef92d3c0d2020-08-14 06:23:07349// TODO(crbug.com/1091088) Implement ClearAutocorrectRange
350void TextInput::ClearAutocorrectRange() {
351 NOTIMPLEMENTED_LOG_ONCE();
352}
353
Darren Shen4f67ec22019-06-13 00:25:36354void TextInput::OnKeyboardVisibilityChanged(bool is_visible) {
Jun Mukaia446ee7f2018-07-20 22:55:15355 delegate_->OnVirtualKeyboardVisibilityChanged(is_visible);
356}
357
358void TextInput::AttachInputMethod() {
359 DCHECK(!input_method_);
360
361 ui::InputMethod* input_method = GetInputMethod(window_);
362 if (!input_method) {
363 LOG(ERROR) << "input method not found";
364 return;
365 }
366
367 input_mode_ = ui::TEXT_INPUT_MODE_TEXT;
368 input_type_ = ui::TEXT_INPUT_TYPE_TEXT;
369 input_method_ = input_method;
370 input_method_->SetFocusedTextInputClient(this);
371 delegate_->Activated();
372
Darren Shencb2508442019-07-03 21:48:23373 if (!keyboard_ui_controller_ &&
374 keyboard::KeyboardUIController::HasInstance()) {
375 auto* keyboard_ui_controller = keyboard::KeyboardUIController::Get();
376 if (keyboard_ui_controller->IsEnabled()) {
377 keyboard_ui_controller_ = keyboard_ui_controller;
378 keyboard_ui_controller_->AddObserver(this);
Jun Mukaia446ee7f2018-07-20 22:55:15379 }
380 }
381
382 if (pending_vk_visible_) {
383 input_method_->ShowVirtualKeyboardIfEnabled();
384 pending_vk_visible_ = false;
385 }
386}
387
388void TextInput::DetachInputMethod() {
389 if (!input_method_) {
390 DLOG(ERROR) << "input method already detached";
391 return;
392 }
393 input_mode_ = ui::TEXT_INPUT_MODE_DEFAULT;
394 input_type_ = ui::TEXT_INPUT_TYPE_NONE;
395 input_method_->DetachTextInputClient(this);
396 input_method_ = nullptr;
397 delegate_->Deactivated();
398}
399
400} // namespace exo