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