Flutter Engine
text_input_model.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter 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 "flutter/shell/platform/common/cpp/text_input_model.h"
6 
7 #include <algorithm>
8 #include <codecvt>
9 #include <locale>
10 
11 #if defined(_MSC_VER)
12 // TODO(naifu): This temporary code is to solve link error.(VS2015/2017)
13 // https://social.msdn.microsoft.com/Forums/vstudio/en-US/8f40dcd8-c67f-4eba-9134-a19b9178e481/vs-2015-rc-linker-stdcodecvt-error
15 #endif // defined(_MSC_VER)
16 
17 namespace flutter {
18 
19 namespace {
20 
21 // Returns true if |code_point| is a leading surrogate of a surrogate pair.
22 bool IsLeadingSurrogate(char32_t code_point) {
23  return (code_point & 0xFFFFFC00) == 0xD800;
24 }
25 // Returns true if |code_point| is a trailing surrogate of a surrogate pair.
26 bool IsTrailingSurrogate(char32_t code_point) {
27  return (code_point & 0xFFFFFC00) == 0xDC00;
28 }
29 
30 } // namespace
31 
33 
35 
36 void TextInputModel::SetText(const std::string& text) {
37  std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
38  utf16_converter;
39  text_ = utf16_converter.from_bytes(text);
40  selection_ = TextRange(0);
41  composing_range_ = TextRange(0);
42 }
43 
45  if (composing_ && !range.collapsed()) {
46  return false;
47  }
48  if (!editable_range().Contains(range)) {
49  return false;
50  }
51  selection_ = range;
52  return true;
53 }
54 
56  size_t cursor_offset) {
57  if (!composing_ || !text_range().Contains(range)) {
58  return false;
59  }
60  composing_range_ = range;
61  selection_ = TextRange(range.start() + cursor_offset);
62  return true;
63 }
64 
66  composing_ = true;
67  composing_range_ = TextRange(selection_.start());
68 }
69 
70 void TextInputModel::UpdateComposingText(const std::string& composing_text) {
71  std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
72  utf16_converter;
73  std::u16string text = utf16_converter.from_bytes(composing_text);
74 
75  // Preserve selection if we get a no-op update to the composing region.
76  if (text.length() == 0 && composing_range_.collapsed()) {
77  return;
78  }
79  DeleteSelected();
80  text_.replace(composing_range_.start(), composing_range_.length(), text);
81  composing_range_.set_end(composing_range_.start() + text.length());
82  selection_ = TextRange(composing_range_.end());
83 }
84 
86  // Preserve selection if no composing text was entered.
87  if (composing_range_.collapsed()) {
88  return;
89  }
90  composing_range_ = TextRange(composing_range_.end());
91  selection_ = composing_range_;
92 }
93 
95  composing_ = false;
96  composing_range_ = TextRange(0);
97 }
98 
99 bool TextInputModel::DeleteSelected() {
100  if (selection_.collapsed()) {
101  return false;
102  }
103  size_t start = selection_.start();
104  text_.erase(start, selection_.length());
105  selection_ = TextRange(start);
106  if (composing_) {
107  // This occurs only immediately after composing has begun with a selection.
108  composing_range_ = selection_;
109  }
110  return true;
111 }
112 
114  if (c <= 0xFFFF) {
115  AddText(std::u16string({static_cast<char16_t>(c)}));
116  } else {
117  char32_t to_decompose = c - 0x10000;
118  AddText(std::u16string({
119  // High surrogate.
120  static_cast<char16_t>((to_decompose >> 10) + 0xd800),
121  // Low surrogate.
122  static_cast<char16_t>((to_decompose % 0x400) + 0xdc00),
123  }));
124  }
125 }
126 
127 void TextInputModel::AddText(const std::u16string& text) {
128  DeleteSelected();
129  if (composing_) {
130  // Delete the current composing text, set the cursor to composing start.
131  text_.erase(composing_range_.start(), composing_range_.length());
132  selection_ = TextRange(composing_range_.start());
133  composing_range_.set_end(composing_range_.start() + text.length());
134  }
135  size_t position = selection_.position();
136  text_.insert(position, text);
137  selection_ = TextRange(position + text.length());
138 }
139 
140 void TextInputModel::AddText(const std::string& text) {
141  std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
142  utf16_converter;
143  AddText(utf16_converter.from_bytes(text));
144 }
145 
147  if (DeleteSelected()) {
148  return true;
149  }
150  // There is no selection. Delete the preceding codepoint.
151  size_t position = selection_.position();
152  if (position != editable_range().start()) {
153  int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1;
154  text_.erase(position - count, count);
155  selection_ = TextRange(position - count);
156  if (composing_) {
157  composing_range_.set_end(composing_range_.end() - count);
158  }
159  return true;
160  }
161  return false;
162 }
163 
165  if (DeleteSelected()) {
166  return true;
167  }
168  // There is no selection. Delete the preceding codepoint.
169  size_t position = selection_.position();
170  if (position < editable_range().end()) {
171  int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1;
172  text_.erase(position, count);
173  if (composing_) {
174  composing_range_.set_end(composing_range_.end() - count);
175  }
176  return true;
177  }
178  return false;
179 }
180 
181 bool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) {
182  size_t max_pos = editable_range().end();
183  size_t start = selection_.extent();
184  if (offset_from_cursor < 0) {
185  for (int i = 0; i < -offset_from_cursor; i++) {
186  // If requested start is before the available text then reduce the
187  // number of characters to delete.
188  if (start == editable_range().start()) {
189  count = i;
190  break;
191  }
192  start -= IsTrailingSurrogate(text_.at(start - 1)) ? 2 : 1;
193  }
194  } else {
195  for (int i = 0; i < offset_from_cursor && start != max_pos; i++) {
196  start += IsLeadingSurrogate(text_.at(start)) ? 2 : 1;
197  }
198  }
199 
200  auto end = start;
201  for (int i = 0; i < count && end != max_pos; i++) {
202  end += IsLeadingSurrogate(text_.at(start)) ? 2 : 1;
203  }
204 
205  if (start == end) {
206  return false;
207  }
208 
209  auto deleted_length = end - start;
210  text_.erase(start, deleted_length);
211 
212  // Cursor moves only if deleted area is before it.
213  selection_ = TextRange(offset_from_cursor <= 0 ? start : selection_.start());
214 
215  // Adjust composing range.
216  if (composing_) {
217  composing_range_.set_end(composing_range_.end() - deleted_length);
218  }
219  return true;
220 }
221 
223  size_t min_pos = editable_range().start();
224  if (selection_.collapsed() && selection_.position() == min_pos) {
225  return false;
226  }
227  selection_ = TextRange(min_pos);
228  return true;
229 }
230 
232  size_t max_pos = editable_range().end();
233  if (selection_.collapsed() && selection_.position() == max_pos) {
234  return false;
235  }
236  selection_ = TextRange(max_pos);
237  return true;
238 }
239 
241  // If there's a selection, move to the end of the selection.
242  if (!selection_.collapsed()) {
243  selection_ = TextRange(selection_.end());
244  return true;
245  }
246  // Otherwise, move the cursor forward.
247  size_t position = selection_.position();
248  if (position != editable_range().end()) {
249  int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1;
250  selection_ = TextRange(position + count);
251  return true;
252  }
253  return false;
254 }
255 
257  // If there's a selection, move to the beginning of the selection.
258  if (!selection_.collapsed()) {
259  selection_ = TextRange(selection_.start());
260  return true;
261  }
262  // Otherwise, move the cursor backward.
263  size_t position = selection_.position();
264  if (position != editable_range().start()) {
265  int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1;
266  selection_ = TextRange(position - count);
267  return true;
268  }
269  return false;
270 }
271 
272 std::string TextInputModel::GetText() const {
273  std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
274  utf8_converter;
275  return utf8_converter.to_bytes(text_);
276 }
277 
279  // Measure the length of the current text up to the selection extent.
280  // There is probably a much more efficient way of doing this.
281  auto leading_text = text_.substr(0, selection_.extent());
282  std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
283  utf8_converter;
284  return utf8_converter.to_bytes(leading_text).size();
285 }
286 
287 } // namespace flutter
void SetText(const std::string &text)
std::string GetText() const
void AddCodePoint(char32_t c)
bool SetComposingRange(const TextRange &range, size_t cursor_offset)
void AddText(const std::u16string &text)
void UpdateComposingText(const std::string &composing_text)
size_t end() const
Definition: text_range.h:49
size_t extent() const
Definition: text_range.h:31
size_t length() const
Definition: text_range.h:69
size_t position() const
Definition: text_range.h:63
int32_t id
bool Contains(size_t position) const
Definition: text_range.h:78
void set_end(size_t pos)
Definition: text_range.h:52
size_t start() const
Definition: text_range.h:37
bool SetSelection(const TextRange &range)
bool DeleteSurrounding(int offset_from_cursor, int count)
bool collapsed() const
Definition: text_range.h:72