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/utf_string_conversions.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/events/event.h"
18
19 namespace exo {
20
21 namespace {
22
GetInputMethod(aura::Window * window)23 ui::InputMethod* GetInputMethod(aura::Window* window) {
24 if (!window || !window->GetHost())
25 return nullptr;
26 return window->GetHost()->GetInputMethod();
27 }
28
29 } // namespace
30
OffsetFromUTF8Offset(const base::StringPiece & text,uint32_t offset)31 size_t OffsetFromUTF8Offset(const base::StringPiece& text, uint32_t offset) {
32 return base::UTF8ToUTF16(text.substr(0, offset)).size();
33 }
34
OffsetFromUTF16Offset(const base::StringPiece16 & text,uint32_t offset)35 size_t OffsetFromUTF16Offset(const base::StringPiece16& text, uint32_t offset) {
36 return base::UTF16ToUTF8(text.substr(0, offset)).size();
37 }
38
TextInput(std::unique_ptr<Delegate> delegate)39 TextInput::TextInput(std::unique_ptr<Delegate> delegate)
40 : delegate_(std::move(delegate)) {}
41
~TextInput()42 TextInput::~TextInput() {
43 if (keyboard_ui_controller_)
44 keyboard_ui_controller_->RemoveObserver(this);
45 if (input_method_)
46 Deactivate();
47 }
48
Activate(Surface * surface)49 void 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
Deactivate()57 void TextInput::Deactivate() {
58 DetachInputMethod();
59 window_ = nullptr;
60 }
61
ShowVirtualKeyboardIfEnabled()62 void 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
HideVirtualKeyboard()71 void TextInput::HideVirtualKeyboard() {
72 if (keyboard_ui_controller_)
73 keyboard_ui_controller_->HideKeyboardByUser();
74 pending_vk_visible_ = false;
75 }
76
Resync()77 void TextInput::Resync() {
78 if (input_method_)
79 input_method_->OnCaretBoundsChanged(this);
80 }
81
SetSurroundingText(const base::string16 & text,uint32_t cursor_pos,uint32_t anchor)82 void TextInput::SetSurroundingText(const base::string16& text,
83 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);
91 }
92
SetTypeModeFlags(ui::TextInputType type,ui::TextInputMode mode,int flags,bool should_do_learning)93 void 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
SetCaretBounds(const gfx::Rect & bounds)108 void 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
SetCompositionText(const ui::CompositionText & composition)117 void TextInput::SetCompositionText(const ui::CompositionText& composition) {
118 composition_ = composition;
119 delegate_->SetCompositionText(composition);
120 }
121
ConfirmCompositionText(bool keep_selection)122 void TextInput::ConfirmCompositionText(bool keep_selection) {
123 // 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 }
128 delegate_->Commit(composition_.text);
129 }
130
ClearCompositionText()131 void TextInput::ClearCompositionText() {
132 composition_ = ui::CompositionText();
133 delegate_->SetCompositionText(composition_);
134 }
135
InsertText(const base::string16 & text)136 void TextInput::InsertText(const base::string16& text) {
137 delegate_->Commit(text);
138 }
139
InsertChar(const ui::KeyEvent & event)140 void TextInput::InsertChar(const ui::KeyEvent& event) {
141 base::char16 ch = event.GetCharacter();
142 if (u_isprint(ch)) {
143 InsertText(base::string16(1, ch));
144 return;
145 }
146 delegate_->SendKey(event);
147 }
148
GetTextInputType() const149 ui::TextInputType TextInput::GetTextInputType() const {
150 return input_type_;
151 }
152
GetTextInputMode() const153 ui::TextInputMode TextInput::GetTextInputMode() const {
154 return input_mode_;
155 }
156
GetTextDirection() const157 base::i18n::TextDirection TextInput::GetTextDirection() const {
158 return direction_;
159 }
160
GetTextInputFlags() const161 int TextInput::GetTextInputFlags() const {
162 return flags_;
163 }
164
CanComposeInline() const165 bool TextInput::CanComposeInline() const {
166 return true;
167 }
168
GetCaretBounds() const169 gfx::Rect TextInput::GetCaretBounds() const {
170 return caret_bounds_ + window_->GetBoundsInScreen().OffsetFromOrigin();
171 }
172
GetCompositionCharacterBounds(uint32_t index,gfx::Rect * rect) const173 bool TextInput::GetCompositionCharacterBounds(uint32_t index,
174 gfx::Rect* rect) const {
175 return false;
176 }
177
HasCompositionText() const178 bool TextInput::HasCompositionText() const {
179 return !composition_.text.empty();
180 }
181
GetFocusReason() const182 ui::TextInputClient::FocusReason TextInput::GetFocusReason() const {
183 NOTIMPLEMENTED_LOG_ONCE();
184 return ui::TextInputClient::FOCUS_REASON_OTHER;
185 }
186
GetTextRange(gfx::Range * range) const187 bool TextInput::GetTextRange(gfx::Range* range) const {
188 if (!cursor_pos_)
189 return false;
190 range->set_start(0);
191 if (composition_.text.empty()) {
192 range->set_end(surrounding_text_.size());
193 } else {
194 range->set_end(surrounding_text_.size() - cursor_pos_->length() +
195 composition_.text.size());
196 }
197 return true;
198 }
199
GetCompositionTextRange(gfx::Range * range) const200 bool TextInput::GetCompositionTextRange(gfx::Range* range) const {
201 if (!cursor_pos_ || composition_.text.empty())
202 return false;
203
204 range->set_start(cursor_pos_->start());
205 range->set_end(cursor_pos_->start() + composition_.text.size());
206 return true;
207 }
208
GetEditableSelectionRange(gfx::Range * range) const209 bool TextInput::GetEditableSelectionRange(gfx::Range* range) const {
210 if (!cursor_pos_)
211 return false;
212 range->set_start(cursor_pos_->start());
213 range->set_end(cursor_pos_->end());
214 return true;
215 }
216
SetEditableSelectionRange(const gfx::Range & range)217 bool TextInput::SetEditableSelectionRange(const gfx::Range& range) {
218 if (surrounding_text_.size() < range.GetMax())
219 return false;
220 delegate_->SetCursor(
221 gfx::Range(OffsetFromUTF16Offset(surrounding_text_, range.start()),
222 OffsetFromUTF16Offset(surrounding_text_, range.end())));
223 return true;
224 }
225
DeleteRange(const gfx::Range & range)226 bool TextInput::DeleteRange(const gfx::Range& range) {
227 if (surrounding_text_.size() < range.GetMax())
228 return false;
229 delegate_->DeleteSurroundingText(
230 gfx::Range(OffsetFromUTF16Offset(surrounding_text_, range.start()),
231 OffsetFromUTF16Offset(surrounding_text_, range.end())));
232 return true;
233 }
234
GetTextFromRange(const gfx::Range & range,base::string16 * text) const235 bool TextInput::GetTextFromRange(const gfx::Range& range,
236 base::string16* text) const {
237 gfx::Range text_range;
238 if (!GetTextRange(&text_range) || !text_range.Contains(range))
239 return false;
240 if (composition_.text.empty() || range.GetMax() <= cursor_pos_->GetMin()) {
241 text->assign(surrounding_text_, range.GetMin(), range.length());
242 return true;
243 }
244 size_t composition_end = cursor_pos_->GetMin() + composition_.text.size();
245 if (range.GetMin() >= composition_end) {
246 size_t start =
247 range.GetMin() - composition_.text.size() + cursor_pos_->length();
248 text->assign(surrounding_text_, start, range.length());
249 return true;
250 }
251
252 size_t start_in_composition = 0;
253 if (range.GetMin() <= cursor_pos_->GetMin()) {
254 text->assign(surrounding_text_, range.GetMin(),
255 cursor_pos_->GetMin() - range.GetMin());
256 } else {
257 start_in_composition = range.GetMin() - cursor_pos_->GetMin();
258 }
259 if (range.GetMax() <= composition_end) {
260 text->append(composition_.text, start_in_composition,
261 range.GetMax() - cursor_pos_->GetMin() - start_in_composition);
262 } else {
263 text->append(composition_.text, start_in_composition,
264 composition_.text.size() - start_in_composition);
265 text->append(surrounding_text_, cursor_pos_->GetMax(),
266 range.GetMax() - composition_end);
267 }
268 return true;
269 }
270
OnInputMethodChanged()271 void TextInput::OnInputMethodChanged() {
272 ui::InputMethod* input_method = GetInputMethod(window_);
273 if (input_method == input_method_)
274 return;
275 input_method_->DetachTextInputClient(this);
276 input_method_ = input_method;
277 input_method_->SetFocusedTextInputClient(this);
278 }
279
ChangeTextDirectionAndLayoutAlignment(base::i18n::TextDirection direction)280 bool TextInput::ChangeTextDirectionAndLayoutAlignment(
281 base::i18n::TextDirection direction) {
282 if (direction == direction_)
283 return true;
284 direction_ = direction;
285 delegate_->OnTextDirectionChanged(direction_);
286 return true;
287 }
288
ExtendSelectionAndDelete(size_t before,size_t after)289 void TextInput::ExtendSelectionAndDelete(size_t before, size_t after) {
290 if (!cursor_pos_)
291 return;
292 uint32_t start =
293 (cursor_pos_->GetMin() < before) ? 0 : (cursor_pos_->GetMin() - before);
294 uint32_t end =
295 std::min(cursor_pos_->GetMax() + after, surrounding_text_.size());
296 delegate_->DeleteSurroundingText(
297 gfx::Range(OffsetFromUTF16Offset(surrounding_text_, start),
298 OffsetFromUTF16Offset(surrounding_text_, end)));
299 }
300
EnsureCaretNotInRect(const gfx::Rect & rect)301 void TextInput::EnsureCaretNotInRect(const gfx::Rect& rect) {}
302
IsTextEditCommandEnabled(ui::TextEditCommand command) const303 bool TextInput::IsTextEditCommandEnabled(ui::TextEditCommand command) const {
304 return false;
305 }
306
SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command)307 void TextInput::SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command) {
308 }
309
GetClientSourceForMetrics() const310 ukm::SourceId TextInput::GetClientSourceForMetrics() const {
311 NOTIMPLEMENTED_LOG_ONCE();
312 return ukm::kInvalidSourceId;
313 }
314
ShouldDoLearning()315 bool TextInput::ShouldDoLearning() {
316 return should_do_learning_;
317 }
318
SetCompositionFromExistingText(const gfx::Range & range,const std::vector<ui::ImeTextSpan> & ui_ime_text_spans)319 bool TextInput::SetCompositionFromExistingText(
320 const gfx::Range& range,
321 const std::vector<ui::ImeTextSpan>& ui_ime_text_spans) {
322 // TODO(https://crbug.com/952757): Implement this method.
323 NOTIMPLEMENTED_LOG_ONCE();
324 return false;
325 }
326
OnKeyboardVisibilityChanged(bool is_visible)327 void TextInput::OnKeyboardVisibilityChanged(bool is_visible) {
328 delegate_->OnVirtualKeyboardVisibilityChanged(is_visible);
329 }
330
AttachInputMethod()331 void TextInput::AttachInputMethod() {
332 DCHECK(!input_method_);
333
334 ui::InputMethod* input_method = GetInputMethod(window_);
335 if (!input_method) {
336 LOG(ERROR) << "input method not found";
337 return;
338 }
339
340 input_mode_ = ui::TEXT_INPUT_MODE_TEXT;
341 input_type_ = ui::TEXT_INPUT_TYPE_TEXT;
342 input_method_ = input_method;
343 input_method_->SetFocusedTextInputClient(this);
344 delegate_->Activated();
345
346 if (!keyboard_ui_controller_ &&
347 keyboard::KeyboardUIController::HasInstance()) {
348 auto* keyboard_ui_controller = keyboard::KeyboardUIController::Get();
349 if (keyboard_ui_controller->IsEnabled()) {
350 keyboard_ui_controller_ = keyboard_ui_controller;
351 keyboard_ui_controller_->AddObserver(this);
352 }
353 }
354
355 if (pending_vk_visible_) {
356 input_method_->ShowVirtualKeyboardIfEnabled();
357 pending_vk_visible_ = false;
358 }
359 }
360
DetachInputMethod()361 void TextInput::DetachInputMethod() {
362 if (!input_method_) {
363 DLOG(ERROR) << "input method already detached";
364 return;
365 }
366 input_mode_ = ui::TEXT_INPUT_MODE_DEFAULT;
367 input_type_ = ui::TEXT_INPUT_TYPE_NONE;
368 input_method_->DetachTextInputClient(this);
369 input_method_ = nullptr;
370 delegate_->Deactivated();
371 }
372
373 } // namespace exo
374