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