1 // 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
7 #include <algorithm>
8
9 #include "ash/keyboard/ui/keyboard_ui_controller.h"
10 #include "base/strings/string_piece.h"
11 #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/base/ime/utf_offset.h"
18 #include "ui/events/event.h"
19
20 namespace exo {
21
22 namespace {
23
GetInputMethod(aura::Window * window)24 ui::InputMethod* GetInputMethod(aura::Window* window) {
25 if (!window || !window->GetHost())
26 return nullptr;
27 return window->GetHost()->GetInputMethod();
28 }
29
30 } // namespace
31
TextInput(std::unique_ptr<Delegate> delegate)32 TextInput::TextInput(std::unique_ptr<Delegate> delegate)
33 : delegate_(std::move(delegate)) {}
34
~TextInput()35 TextInput::~TextInput() {
36 if (keyboard_ui_controller_)
37 keyboard_ui_controller_->RemoveObserver(this);
38 if (input_method_)
39 Deactivate();
40 }
41
Activate(Surface * surface)42 void TextInput::Activate(Surface* surface) {
43 DLOG_IF(ERROR, window_) << "Already activated with " << window_;
44 DCHECK(surface);
45
46 window_ = surface->window();
47 AttachInputMethod();
48 }
49
Deactivate()50 void TextInput::Deactivate() {
51 DetachInputMethod();
52 window_ = nullptr;
53 }
54
ShowVirtualKeyboardIfEnabled()55 void TextInput::ShowVirtualKeyboardIfEnabled() {
56 // Some clients may ask showing virtual keyboard before sending activation.
57 if (!input_method_) {
58 pending_vk_visible_ = true;
59 return;
60 }
61 input_method_->ShowVirtualKeyboardIfEnabled();
62 }
63
HideVirtualKeyboard()64 void TextInput::HideVirtualKeyboard() {
65 if (keyboard_ui_controller_)
66 keyboard_ui_controller_->HideKeyboardByUser();
67 pending_vk_visible_ = false;
68 }
69
Resync()70 void TextInput::Resync() {
71 if (input_method_)
72 input_method_->OnCaretBoundsChanged(this);
73 }
74
SetSurroundingText(const base::string16 & text,uint32_t cursor_pos,uint32_t anchor)75 void TextInput::SetSurroundingText(const base::string16& text,
76 uint32_t cursor_pos,
77 uint32_t anchor) {
78 surrounding_text_ = text;
79 cursor_pos_ = gfx::Range(cursor_pos);
80 if (anchor < cursor_pos)
81 cursor_pos_->set_start(anchor);
82 else
83 cursor_pos_->set_end(anchor);
84 }
85
SetTypeModeFlags(ui::TextInputType type,ui::TextInputMode mode,int flags,bool should_do_learning)86 void TextInput::SetTypeModeFlags(ui::TextInputType type,
87 ui::TextInputMode mode,
88 int flags,
89 bool should_do_learning) {
90 if (!input_method_)
91 return;
92 bool changed = (input_type_ != type);
93 input_type_ = type;
94 input_mode_ = mode;
95 flags_ = flags;
96 should_do_learning_ = should_do_learning;
97 if (changed)
98 input_method_->OnTextInputTypeChanged(this);
99 }
100
SetCaretBounds(const gfx::Rect & bounds)101 void TextInput::SetCaretBounds(const gfx::Rect& bounds) {
102 if (caret_bounds_ == bounds)
103 return;
104 caret_bounds_ = bounds;
105 if (!input_method_)
106 return;
107 input_method_->OnCaretBoundsChanged(this);
108 }
109
SetCompositionText(const ui::CompositionText & composition)110 void TextInput::SetCompositionText(const ui::CompositionText& composition) {
111 composition_ = composition;
112 delegate_->SetCompositionText(composition);
113 }
114
ConfirmCompositionText(bool keep_selection)115 uint32_t TextInput::ConfirmCompositionText(bool keep_selection) {
116 // TODO(b/134473433) Modify this function so that when keep_selection is
117 // true, the selection is not changed when text committed
118 if (keep_selection) {
119 NOTIMPLEMENTED_LOG_ONCE();
120 }
121 const uint32_t composition_text_length =
122 static_cast<uint32_t>(composition_.text.length());
123 delegate_->Commit(composition_.text);
124 return composition_text_length;
125 }
126
ClearCompositionText()127 void TextInput::ClearCompositionText() {
128 if (composition_.text.empty())
129 return;
130 composition_ = ui::CompositionText();
131 delegate_->SetCompositionText(composition_);
132 }
133
InsertText(const base::string16 & text)134 void TextInput::InsertText(const base::string16& text) {
135 delegate_->Commit(text);
136 }
137
InsertChar(const ui::KeyEvent & event)138 void TextInput::InsertChar(const ui::KeyEvent& event) {
139 base::char16 ch = event.GetCharacter();
140 if (u_isprint(ch)) {
141 InsertText(base::string16(1, ch));
142 return;
143 }
144 delegate_->SendKey(event);
145 }
146
GetTextInputType() const147 ui::TextInputType TextInput::GetTextInputType() const {
148 return input_type_;
149 }
150
GetTextInputMode() const151 ui::TextInputMode TextInput::GetTextInputMode() const {
152 return input_mode_;
153 }
154
GetTextDirection() const155 base::i18n::TextDirection TextInput::GetTextDirection() const {
156 return direction_;
157 }
158
GetTextInputFlags() const159 int TextInput::GetTextInputFlags() const {
160 return flags_;
161 }
162
CanComposeInline() const163 bool TextInput::CanComposeInline() const {
164 return true;
165 }
166
GetCaretBounds() const167 gfx::Rect TextInput::GetCaretBounds() const {
168 return caret_bounds_ + window_->GetBoundsInScreen().OffsetFromOrigin();
169 }
170
GetCompositionCharacterBounds(uint32_t index,gfx::Rect * rect) const171 bool TextInput::GetCompositionCharacterBounds(uint32_t index,
172 gfx::Rect* rect) const {
173 return false;
174 }
175
HasCompositionText() const176 bool TextInput::HasCompositionText() const {
177 return !composition_.text.empty();
178 }
179
GetFocusReason() const180 ui::TextInputClient::FocusReason TextInput::GetFocusReason() const {
181 NOTIMPLEMENTED_LOG_ONCE();
182 return ui::TextInputClient::FOCUS_REASON_OTHER;
183 }
184
GetTextRange(gfx::Range * range) const185 bool TextInput::GetTextRange(gfx::Range* range) const {
186 if (!cursor_pos_)
187 return false;
188 range->set_start(0);
189 if (composition_.text.empty()) {
190 range->set_end(surrounding_text_.size());
191 } else {
192 range->set_end(surrounding_text_.size() - cursor_pos_->length() +
193 composition_.text.size());
194 }
195 return true;
196 }
197
GetCompositionTextRange(gfx::Range * range) const198 bool TextInput::GetCompositionTextRange(gfx::Range* range) const {
199 if (!cursor_pos_ || composition_.text.empty())
200 return false;
201
202 range->set_start(cursor_pos_->start());
203 range->set_end(cursor_pos_->start() + composition_.text.size());
204 return true;
205 }
206
GetEditableSelectionRange(gfx::Range * range) const207 bool TextInput::GetEditableSelectionRange(gfx::Range* range) const {
208 if (!cursor_pos_)
209 return false;
210 range->set_start(cursor_pos_->start());
211 range->set_end(cursor_pos_->end());
212 return true;
213 }
214
SetEditableSelectionRange(const gfx::Range & range)215 bool TextInput::SetEditableSelectionRange(const gfx::Range& range) {
216 if (surrounding_text_.size() < range.GetMax())
217 return false;
218 auto start = ui::Utf8OffsetFromUtf16Offset(surrounding_text_, range.start());
219 if (!start)
220 return false;
221 auto end = ui::Utf8OffsetFromUtf16Offset(surrounding_text_, range.end());
222 if (!end)
223 return false;
224 delegate_->SetCursor(gfx::Range(*start, *end));
225 return true;
226 }
227
DeleteRange(const gfx::Range & range)228 bool TextInput::DeleteRange(const gfx::Range& range) {
229 if (surrounding_text_.size() < range.GetMax())
230 return false;
231 auto start = ui::Utf8OffsetFromUtf16Offset(surrounding_text_, range.start());
232 if (!start)
233 return false;
234 auto end = ui::Utf8OffsetFromUtf16Offset(surrounding_text_, range.end());
235 if (!end)
236 return false;
237 delegate_->DeleteSurroundingText(gfx::Range(*start, *end));
238 return true;
239 }
240
GetTextFromRange(const gfx::Range & range,base::string16 * text) const241 bool TextInput::GetTextFromRange(const gfx::Range& range,
242 base::string16* text) const {
243 gfx::Range text_range;
244 if (!GetTextRange(&text_range) || !text_range.Contains(range))
245 return false;
246 if (composition_.text.empty() || range.GetMax() <= cursor_pos_->GetMin()) {
247 text->assign(surrounding_text_, range.GetMin(), range.length());
248 return true;
249 }
250 size_t composition_end = cursor_pos_->GetMin() + composition_.text.size();
251 if (range.GetMin() >= composition_end) {
252 size_t start =
253 range.GetMin() - composition_.text.size() + cursor_pos_->length();
254 text->assign(surrounding_text_, start, range.length());
255 return true;
256 }
257
258 size_t start_in_composition = 0;
259 if (range.GetMin() <= cursor_pos_->GetMin()) {
260 text->assign(surrounding_text_, range.GetMin(),
261 cursor_pos_->GetMin() - range.GetMin());
262 } else {
263 start_in_composition = range.GetMin() - cursor_pos_->GetMin();
264 }
265 if (range.GetMax() <= composition_end) {
266 text->append(composition_.text, start_in_composition,
267 range.GetMax() - cursor_pos_->GetMin() - start_in_composition);
268 } else {
269 text->append(composition_.text, start_in_composition,
270 composition_.text.size() - start_in_composition);
271 text->append(surrounding_text_, cursor_pos_->GetMax(),
272 range.GetMax() - composition_end);
273 }
274 return true;
275 }
276
OnInputMethodChanged()277 void TextInput::OnInputMethodChanged() {
278 ui::InputMethod* input_method = GetInputMethod(window_);
279 if (input_method == input_method_)
280 return;
281 input_method_->DetachTextInputClient(this);
282 input_method_ = input_method;
283 input_method_->SetFocusedTextInputClient(this);
284 }
285
ChangeTextDirectionAndLayoutAlignment(base::i18n::TextDirection direction)286 bool TextInput::ChangeTextDirectionAndLayoutAlignment(
287 base::i18n::TextDirection direction) {
288 if (direction == direction_)
289 return true;
290 direction_ = direction;
291 delegate_->OnTextDirectionChanged(direction_);
292 return true;
293 }
294
ExtendSelectionAndDelete(size_t before,size_t after)295 void TextInput::ExtendSelectionAndDelete(size_t before, size_t after) {
296 if (!cursor_pos_)
297 return;
298 size_t utf16_start =
299 (cursor_pos_->GetMin() < before) ? 0 : (cursor_pos_->GetMin() - before);
300 size_t utf16_end =
301 std::min(cursor_pos_->GetMax() + after, surrounding_text_.size());
302 auto start = ui::Utf8OffsetFromUtf16Offset(surrounding_text_, utf16_start);
303 if (!start)
304 return;
305 auto end = ui::Utf8OffsetFromUtf16Offset(surrounding_text_, utf16_end);
306 if (!end)
307 return;
308
309 delegate_->DeleteSurroundingText(gfx::Range(*start, *end));
310 }
311
EnsureCaretNotInRect(const gfx::Rect & rect)312 void TextInput::EnsureCaretNotInRect(const gfx::Rect& rect) {}
313
IsTextEditCommandEnabled(ui::TextEditCommand command) const314 bool TextInput::IsTextEditCommandEnabled(ui::TextEditCommand command) const {
315 return false;
316 }
317
SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command)318 void TextInput::SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command) {
319 }
320
GetClientSourceForMetrics() const321 ukm::SourceId TextInput::GetClientSourceForMetrics() const {
322 NOTIMPLEMENTED_LOG_ONCE();
323 return ukm::kInvalidSourceId;
324 }
325
ShouldDoLearning()326 bool TextInput::ShouldDoLearning() {
327 return should_do_learning_;
328 }
329
SetCompositionFromExistingText(const gfx::Range & range,const std::vector<ui::ImeTextSpan> & ui_ime_text_spans)330 bool TextInput::SetCompositionFromExistingText(
331 const gfx::Range& range,
332 const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) {
333 // TODO(https://crbug.com/952757): Implement this method.
334 NOTIMPLEMENTED_LOG_ONCE();
335 return false;
336 }
337
GetAutocorrectRange() const338 gfx::Range TextInput::GetAutocorrectRange() const {
339 // TODO(https://crbug.com/952757): Implement this method.
340 NOTIMPLEMENTED_LOG_ONCE();
341 return gfx::Range();
342 }
343
GetAutocorrectCharacterBounds() const344 gfx::Rect TextInput::GetAutocorrectCharacterBounds() const {
345 // TODO(https://crbug.com/952757): Implement this method.
346 NOTIMPLEMENTED_LOG_ONCE();
347 return gfx::Rect();
348 }
349
350 // TODO(crbug.com/1091088) Implement setAutocorrectRange
SetAutocorrectRange(const base::string16 & autocorrect_text,const gfx::Range & range)351 bool TextInput::SetAutocorrectRange(const base::string16& autocorrect_text,
352 const gfx::Range& range) {
353 NOTIMPLEMENTED_LOG_ONCE();
354 return false;
355 }
356
357 // TODO(crbug.com/1091088) Implement ClearAutocorrectRange
ClearAutocorrectRange()358 void TextInput::ClearAutocorrectRange() {
359 NOTIMPLEMENTED_LOG_ONCE();
360 }
361
OnKeyboardVisibilityChanged(bool is_visible)362 void TextInput::OnKeyboardVisibilityChanged(bool is_visible) {
363 delegate_->OnVirtualKeyboardVisibilityChanged(is_visible);
364 }
365
AttachInputMethod()366 void TextInput::AttachInputMethod() {
367 DCHECK(!input_method_);
368
369 ui::InputMethod* input_method = GetInputMethod(window_);
370 if (!input_method) {
371 LOG(ERROR) << "input method not found";
372 return;
373 }
374
375 input_mode_ = ui::TEXT_INPUT_MODE_TEXT;
376 input_type_ = ui::TEXT_INPUT_TYPE_TEXT;
377 input_method_ = input_method;
378 input_method_->SetFocusedTextInputClient(this);
379 delegate_->Activated();
380
381 if (!keyboard_ui_controller_ &&
382 keyboard::KeyboardUIController::HasInstance()) {
383 auto* keyboard_ui_controller = keyboard::KeyboardUIController::Get();
384 if (keyboard_ui_controller->IsEnabled()) {
385 keyboard_ui_controller_ = keyboard_ui_controller;
386 keyboard_ui_controller_->AddObserver(this);
387 }
388 }
389
390 if (pending_vk_visible_) {
391 input_method_->ShowVirtualKeyboardIfEnabled();
392 pending_vk_visible_ = false;
393 }
394 }
395
DetachInputMethod()396 void TextInput::DetachInputMethod() {
397 if (!input_method_) {
398 DLOG(ERROR) << "input method already detached";
399 return;
400 }
401 input_mode_ = ui::TEXT_INPUT_MODE_DEFAULT;
402 input_type_ = ui::TEXT_INPUT_TYPE_NONE;
403 input_method_->DetachTextInputClient(this);
404 input_method_ = nullptr;
405 delegate_->Deactivated();
406 }
407
408 } // namespace exo
409