1 /*
2 * Copyright (c) 2023-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include "core/components_ng/pattern/text_field/text_select_controller.h"
16
17 #include "base/geometry/ng/rect_t.h"
18 #include "base/geometry/offset.h"
19 #include "base/log/log_wrapper.h"
20 #include "base/utils/utils.h"
21 #include "core/common/ai/data_detector_mgr.h"
22 #include "core/components_ng/pattern/text_field/text_field_layout_property.h"
23 #include "core/components_ng/pattern/text_field/text_field_pattern.h"
24 #include "core/components_ng/pattern/text_field/text_input_ai_checker.h"
25
26 namespace OHOS::Ace::NG {
27 namespace {
28 const std::string NEWLINE = "\n";
29 const std::wstring WIDE_NEWLINE = StringUtils::ToWstring(NEWLINE);
30 } // namespace
UpdateHandleIndex(int32_t firstHandleIndex,int32_t secondHandleIndex)31 void TextSelectController::UpdateHandleIndex(int32_t firstHandleIndex, int32_t secondHandleIndex)
32 {
33 firstHandleInfo_.index = firstHandleIndex;
34 secondHandleInfo_.index = secondHandleIndex;
35 caretInfo_.index = std::max(firstHandleInfo_.index, secondHandleInfo_.index);
36 CalculateHandleOffset();
37 UpdateCaretOffset();
38 }
39
UpdateCaretIndex(int32_t index)40 void TextSelectController::UpdateCaretIndex(int32_t index)
41 {
42 auto newIndex = std::clamp(index, 0, static_cast<int32_t>(contentController_->GetWideText().length()));
43 caretInfo_.index = newIndex;
44 firstHandleInfo_.index = newIndex;
45 secondHandleInfo_.index = newIndex;
46 UpdateRecordCaretIndex(caretInfo_.index);
47 }
48
CalculateEmptyValueCaretRect() const49 RectF TextSelectController::CalculateEmptyValueCaretRect() const
50 {
51 RectF rect;
52 auto pattern = pattern_.Upgrade();
53 CHECK_NULL_RETURN(pattern, rect);
54 auto textFiled = DynamicCast<TextFieldPattern>(pattern);
55 CHECK_NULL_RETURN(textFiled, rect);
56 auto layoutProperty = textFiled->GetLayoutProperty<TextFieldLayoutProperty>();
57 CHECK_NULL_RETURN(layoutProperty, rect);
58 rect.SetLeft(contentRect_.Left());
59 rect.SetTop(contentRect_.Top());
60 rect.SetHeight(textFiled->PreferredLineHeight());
61 rect.SetWidth(caretInfo_.rect.Width());
62 auto textAlign = layoutProperty->GetTextAlignValue(TextAlign::START);
63 auto direction = layoutProperty->GetLayoutDirection();
64 textFiled->CheckTextAlignByDirection(textAlign, direction);
65
66 switch (textAlign) {
67 case TextAlign::START:
68 rect.SetLeft(contentRect_.GetX());
69 break;
70 case TextAlign::CENTER:
71 rect.SetLeft(static_cast<float>(contentRect_.GetX()) + contentRect_.Width() / 2.0f);
72 break;
73 case TextAlign::END:
74 rect.SetLeft(static_cast<float>(contentRect_.GetX()) + contentRect_.Width() -
75 static_cast<float>(caretInfo_.rect.Width()));
76 break;
77 default:
78 break;
79 }
80
81 auto align = Alignment::TOP_CENTER;
82 if (layoutProperty->GetPositionProperty()) {
83 align = layoutProperty->GetPositionProperty()->GetAlignment().value_or(align);
84 }
85 OffsetF offset = Alignment::GetAlignPosition(contentRect_.GetSize(), rect.GetSize(), align);
86 rect.SetTop(offset.GetY() + contentRect_.GetY());
87
88 AdjustHandleAtEdge(rect);
89 return rect;
90 }
91
FitCaretMetricsToContentRect(CaretMetricsF & caretMetrics)92 void TextSelectController::FitCaretMetricsToContentRect(CaretMetricsF& caretMetrics)
93 {
94 if (caretMetrics.height > contentRect_.Height()) {
95 caretMetrics.offset.SetY(caretMetrics.offset.GetY() + caretMetrics.height - contentRect_.Height());
96 caretMetrics.height = contentRect_.Height();
97 }
98 }
99
CalcCaretMetricsByPosition(int32_t extent,CaretMetricsF & caretCaretMetric,TextAffinity textAffinity)100 void TextSelectController::CalcCaretMetricsByPosition(
101 int32_t extent, CaretMetricsF& caretCaretMetric, TextAffinity textAffinity)
102 {
103 CHECK_NULL_VOID(paragraph_);
104 paragraph_->CalcCaretMetricsByPosition(extent, caretCaretMetric, textAffinity);
105 auto pattern = pattern_.Upgrade();
106 CHECK_NULL_VOID(pattern);
107 auto textFiled = DynamicCast<TextFieldPattern>(pattern);
108 CHECK_NULL_VOID(textFiled);
109 auto textRect = textFiled->GetTextRect();
110 caretCaretMetric.offset.AddX(textRect.GetX());
111 caretCaretMetric.offset.AddY(textRect.GetY());
112 FitCaretMetricsToContentRect(caretCaretMetric);
113 }
114
CalcCaretMetricsByPositionNearTouchOffset(int32_t extent,CaretMetricsF & caretMetrics,const OffsetF & touchOffset)115 void TextSelectController::CalcCaretMetricsByPositionNearTouchOffset(
116 int32_t extent, CaretMetricsF& caretMetrics, const OffsetF& touchOffset)
117 {
118 CHECK_NULL_VOID(paragraph_);
119 auto pattern = pattern_.Upgrade();
120 CHECK_NULL_VOID(pattern);
121 auto textFiled = DynamicCast<TextFieldPattern>(pattern);
122 CHECK_NULL_VOID(textFiled);
123 auto textRect = textFiled->GetTextRect();
124 paragraph_->CalcCaretMetricsByPosition(extent, caretMetrics, touchOffset - textRect.GetOffset(), textAffinity_);
125 caretMetrics.offset.AddX(textRect.GetX());
126 caretMetrics.offset.AddY(textRect.GetY());
127 FitCaretMetricsToContentRect(caretMetrics);
128 }
129
UpdateCaretRectByPositionNearTouchOffset(int32_t position,const Offset & touchOffset)130 void TextSelectController::UpdateCaretRectByPositionNearTouchOffset(int32_t position, const Offset& touchOffset)
131 {
132 CaretMetricsF caretMetrics;
133 CalcCaretMetricsByPositionNearTouchOffset(position, caretMetrics,
134 OffsetF(static_cast<float>(touchOffset.GetX()), static_cast<float>(touchOffset.GetY())));
135
136 caretInfo_.UpdateOffset(caretMetrics.offset);
137 UpdateCaretHeight(caretMetrics.height);
138 }
139
UpdateCaretInfoByOffset(const Offset & localOffset)140 void TextSelectController::UpdateCaretInfoByOffset(const Offset& localOffset)
141 {
142 auto index = ConvertTouchOffsetToPosition(localOffset);
143 AdjustCursorPosition(index, localOffset);
144 UpdateCaretIndex(index);
145 if (!contentController_->IsEmpty()) {
146 UpdateCaretRectByPositionNearTouchOffset(index, localOffset);
147 MoveHandleToContentRect(caretInfo_.rect, 0.0f);
148 UpdateRecordCaretIndex(caretInfo_.index);
149 } else {
150 caretInfo_.rect = CalculateEmptyValueCaretRect();
151 }
152 }
153
ConvertTouchOffsetToPosition(const Offset & localOffset,bool isSelectionPos)154 int32_t TextSelectController::ConvertTouchOffsetToPosition(const Offset& localOffset, bool isSelectionPos)
155 {
156 CHECK_NULL_RETURN(paragraph_, 0);
157 if (contentController_->IsEmpty()) {
158 return 0;
159 }
160 auto pattern = pattern_.Upgrade();
161 CHECK_NULL_RETURN(pattern, 0);
162 auto textFiled = DynamicCast<TextFieldPattern>(pattern);
163 CHECK_NULL_RETURN(textFiled, 0);
164 auto textRect = textFiled->GetTextRect();
165 auto offset = localOffset - Offset(textRect.GetX(), textRect.GetY());
166 return paragraph_->GetGlyphIndexByCoordinate(offset, isSelectionPos);
167 }
168
UpdateSelectByOffset(const Offset & localOffset)169 void TextSelectController::UpdateSelectByOffset(const Offset& localOffset)
170 {
171 CHECK_NULL_VOID(paragraph_ && !contentController_->IsEmpty());
172
173 auto range = GetSelectRangeByOffset(localOffset);
174 int32_t start = range.first;
175 int32_t end = range.second;
176 UpdateHandleIndex(start, end);
177 int32_t index = 0;
178 if (start != end) {
179 index = std::max(start, end);
180 } else {
181 index = ConvertTouchOffsetToPosition(localOffset);
182 }
183 auto textLength = static_cast<int32_t>(contentController_->GetWideText().length());
184 if (index == textLength && GreatNotEqual(localOffset.GetX(), caretInfo_.rect.GetOffset().GetX())) {
185 UpdateHandleIndex(GetCaretIndex());
186 }
187 if (IsSelected()) {
188 MoveFirstHandleToContentRect(GetFirstHandleIndex());
189 MoveSecondHandleToContentRect(GetSecondHandleIndex());
190 } else {
191 MoveCaretToContentRect(GetCaretIndex());
192 }
193 }
194
GetSelectRangeByOffset(const Offset & localOffset)195 std::pair<int32_t, int32_t> TextSelectController::GetSelectRangeByOffset(const Offset& localOffset)
196 {
197 std::pair<int32_t, int32_t> err(-1, -1);
198 CHECK_NULL_RETURN(paragraph_ && !contentController_->IsEmpty(), err);
199 int32_t start = 0;
200 int32_t end = 0;
201 auto pos = ConvertTouchOffsetToPosition(localOffset, true);
202 if (IsLineBreakOrEndOfParagraph(pos)) {
203 pos--;
204 }
205 // Ensure that the end is selected.
206 if (pos >= static_cast<int32_t>(paragraph_->GetParagraphText().length())) {
207 pos -= 1;
208 }
209
210 auto pattern = pattern_.Upgrade();
211 CHECK_NULL_RETURN(pattern, err);
212 auto textFiled = DynamicCast<TextFieldPattern>(pattern);
213 CHECK_NULL_RETURN(textFiled, err);
214 bool smartSelect = false;
215 if (!textFiled->IsUsingMouse()) {
216 smartSelect = AdjustWordSelection(pos, start, end, localOffset);
217 }
218
219 if (!smartSelect && !paragraph_->GetWordBoundary(pos, start, end)) {
220 start = pos;
221 end = std::min(static_cast<int32_t>(contentController_->GetWideText().length()),
222 pos + GetGraphemeClusterLength(contentController_->GetWideText(), pos, true));
223 }
224
225 if (SystemProperties::GetDebugEnabled()) {
226 TAG_LOGI(AceLogTag::ACE_TEXT,
227 "current word position = %{public}d, select position {start:%{public}d, end:%{public}d}", pos, start, end);
228 }
229 return { start, end };
230 }
231
IsLineBreakOrEndOfParagraph(int32_t pos) const232 bool TextSelectController::IsLineBreakOrEndOfParagraph(int32_t pos) const
233 {
234 CHECK_NULL_RETURN(pos < static_cast<int32_t>(contentController_->GetWideText().length()), true);
235 auto data = contentController_->GetWideText();
236 CHECK_NULL_RETURN(data[pos] == WIDE_NEWLINE[0], false);
237 return true;
238 }
239
GetGraphemeClusterLength(const std::wstring & text,int32_t extend,bool checkPrev)240 int32_t TextSelectController::GetGraphemeClusterLength(const std::wstring& text, int32_t extend, bool checkPrev)
241 {
242 char16_t aroundChar = 0;
243 if (checkPrev) {
244 if (static_cast<size_t>(extend) <= text.length()) {
245 aroundChar = text[std::max(0, extend - 1)];
246 }
247 } else {
248 if (static_cast<size_t>(extend) <= (text.length())) {
249 aroundChar = text[std::min(static_cast<int32_t>(text.length() - 1), extend)];
250 }
251 }
252 return StringUtils::NotInUtf16Bmp(aroundChar) ? 2 : 1;
253 }
254
CalculateHandleOffset()255 void TextSelectController::CalculateHandleOffset()
256 {
257 // calculate firstHandleOffset, secondHandleOffset and handlePaintSize
258 if (contentController_->IsEmpty()) {
259 caretInfo_.rect = CalculateEmptyValueCaretRect();
260 return;
261 }
262 CaretMetricsF secondHandleMetrics;
263 CalcCaretMetricsByPosition(GetSecondHandleIndex(), secondHandleMetrics, TextAffinity::UPSTREAM);
264 OffsetF secondHandleOffset = secondHandleMetrics.offset;
265 RectF secondHandle;
266 secondHandle.SetOffset(secondHandleOffset);
267 secondHandle.SetSize({ SelectHandleInfo::GetDefaultLineWidth().ConvertToPx(), secondHandleMetrics.height });
268 secondHandle.SetHeight(secondHandleMetrics.height);
269 secondHandleInfo_.rect = secondHandle;
270
271 if (!IsSelected()) {
272 return;
273 }
274
275 CaretMetricsF firstHandleMetrics;
276 CalcCaretMetricsByPosition(GetFirstHandleIndex(), firstHandleMetrics, TextAffinity::DOWNSTREAM);
277 OffsetF firstHandleOffset = firstHandleMetrics.offset;
278
279 RectF firstHandle;
280 firstHandle.SetOffset(firstHandleOffset);
281 firstHandle.SetSize({ SelectHandleInfo::GetDefaultLineWidth().ConvertToPx(), firstHandleMetrics.height });
282 firstHandleInfo_.rect = firstHandle;
283 }
284
ToString() const285 std::string TextSelectController::ToString() const
286 {
287 std::string result;
288 result.append("first handle offset: ");
289 result.append(std::to_string(firstHandleInfo_.index));
290 result.append(", second handle offset: ");
291 result.append(std::to_string(secondHandleInfo_.index));
292 return result;
293 }
294
GetSelectedRects() const295 std::vector<RectF> TextSelectController::GetSelectedRects() const
296 {
297 if (!IsSelected()) {
298 return {};
299 }
300 std::vector<RectF> selectedRects;
301 CHECK_NULL_RETURN(paragraph_, selectedRects);
302 paragraph_->GetRectsForRange(GetStartIndex(), GetEndIndex(), selectedRects);
303 return selectedRects;
304 }
305
MoveHandleToContentRect(RectF & handleRect,float boundaryAdjustment) const306 void TextSelectController::MoveHandleToContentRect(RectF& handleRect, float boundaryAdjustment) const
307 {
308 auto pattern = pattern_.Upgrade();
309 CHECK_NULL_VOID(pattern);
310 auto textFiled = DynamicCast<TextFieldPattern>(pattern);
311 CHECK_NULL_VOID(textFiled);
312 auto textRect = textFiled->GetTextRect();
313 if (textRect.Height() > contentRect_.Height()) {
314 auto contentBottomBoundary = contentRect_.GetY() + contentRect_.Height();
315 if (LessNotEqual(handleRect.GetY(), contentRect_.GetY()) &&
316 LessOrEqual(handleRect.Height(), contentRect_.Height())) {
317 auto dy = contentRect_.GetY() - handleRect.GetY();
318 textRect.SetOffset(OffsetF(textRect.GetX(), textRect.GetY() + dy));
319 handleRect.SetOffset(OffsetF(handleRect.GetX(), handleRect.GetY() + dy));
320 } else if (GreatNotEqual(handleRect.GetY() + handleRect.Height(), contentBottomBoundary)) {
321 auto dy = handleRect.GetY() + handleRect.Height() - contentBottomBoundary;
322 textRect.SetOffset(OffsetF(textRect.GetX(), textRect.GetY() - dy));
323 handleRect.SetOffset(OffsetF(handleRect.GetX(), handleRect.GetY() - dy));
324 }
325 }
326
327 if (textRect.Width() > contentRect_.Width()) {
328 auto contentRightBoundary = contentRect_.GetX() + contentRect_.Width() - boundaryAdjustment;
329 if (handleRect.GetX() < contentRect_.GetX()) {
330 auto dx = contentRect_.GetX() - handleRect.GetX();
331 textRect.SetOffset(OffsetF(textRect.GetX() + dx, textRect.GetY()));
332 handleRect.SetOffset(OffsetF(handleRect.GetX() + dx, handleRect.GetY()));
333 } else if (handleRect.GetX() > contentRightBoundary) {
334 auto dx = handleRect.GetX() - contentRightBoundary;
335 textRect.SetOffset(OffsetF(textRect.GetX() - dx, textRect.GetY()));
336 handleRect.SetOffset(OffsetF(handleRect.GetX() - dx, handleRect.GetY()));
337 }
338 }
339 textFiled->SetTextRect(textRect);
340 AdjustHandleAtEdge(handleRect);
341 }
342
AdjustHandleAtEdge(RectF & handleRect) const343 void TextSelectController::AdjustHandleAtEdge(RectF& handleRect) const
344 {
345 auto pattern = pattern_.Upgrade();
346 CHECK_NULL_VOID(pattern);
347 auto textFiled = DynamicCast<TextFieldPattern>(pattern);
348 CHECK_NULL_VOID(textFiled);
349 handleRect.SetOffset(OffsetF(handleRect.GetX() - handleRect.Width() / 2, handleRect.GetY()));
350 // Adjusted handle to the content area when they are at the content area boundary.
351 if (handleRect.GetX() < contentRect_.GetX()) {
352 handleRect.SetOffset(OffsetF(contentRect_.GetX(), handleRect.GetY()));
353 }
354
355 auto textRectRightBoundary = contentRect_.GetX() + contentRect_.Width();
356 if (GreatNotEqual(handleRect.GetX() + handleRect.Width(), textRectRightBoundary) &&
357 GreatNotEqual(contentRect_.Width(), 0.0) && !textFiled->GetTextValue().empty()) {
358 handleRect.SetLeft(textRectRightBoundary - handleRect.Width());
359 }
360 }
361
MoveFirstHandleToContentRect(int32_t index)362 void TextSelectController::MoveFirstHandleToContentRect(int32_t index)
363 {
364 CaretMetricsF firstHandleMetrics;
365 firstHandleInfo_.index = index;
366 CalcCaretMetricsByPosition(GetFirstHandleIndex(), firstHandleMetrics, TextAffinity::DOWNSTREAM);
367 OffsetF firstHandleOffset = firstHandleMetrics.offset;
368 RectF firstHandle;
369 firstHandle.SetOffset(firstHandleOffset);
370 firstHandle.SetSize({ SelectHandleInfo::GetDefaultLineWidth().ConvertToPx(), firstHandleMetrics.height });
371 MoveHandleToContentRect(firstHandle);
372 firstHandleInfo_.rect = firstHandle;
373
374 caretInfo_.index = std::max(firstHandleInfo_.index, secondHandleInfo_.index);
375 UpdateCaretOffset();
376 UpdateSecondHandleOffset();
377 }
378
MoveSecondHandleToContentRect(int32_t index)379 void TextSelectController::MoveSecondHandleToContentRect(int32_t index)
380 {
381 CaretMetricsF secondHandleMetrics;
382 secondHandleInfo_.index = index;
383 CalcCaretMetricsByPosition(GetSecondHandleIndex(), secondHandleMetrics, TextAffinity::UPSTREAM);
384 OffsetF secondHandleOffset = secondHandleMetrics.offset;
385 RectF secondHandle;
386 secondHandle.SetOffset(secondHandleOffset);
387 secondHandle.SetSize({ SelectHandleInfo::GetDefaultLineWidth().ConvertToPx(), secondHandleMetrics.height });
388 MoveHandleToContentRect(secondHandle);
389 secondHandleInfo_.rect = secondHandle;
390
391 caretInfo_.index = std::max(firstHandleInfo_.index, secondHandleInfo_.index);
392 UpdateCaretOffset();
393 UpdateFirstHandleOffset();
394 }
395
MoveCaretToContentRect(int32_t index,TextAffinity textAffinity,bool isEditorValueChanged)396 void TextSelectController::MoveCaretToContentRect(int32_t index, TextAffinity textAffinity, bool isEditorValueChanged)
397 {
398 if (isEditorValueChanged) {
399 textAffinity_ = textAffinity;
400 }
401 index = std::clamp(index, 0, static_cast<int32_t>(contentController_->GetWideText().length()));
402 CaretMetricsF CaretMetrics;
403 caretInfo_.index = index;
404 firstHandleInfo_.index = index;
405 secondHandleInfo_.index = index;
406 if (contentController_->IsEmpty()) {
407 caretInfo_.rect = CalculateEmptyValueCaretRect();
408 return;
409 }
410 CalcCaretMetricsByPosition(GetCaretIndex(), CaretMetrics, textAffinity_);
411 OffsetF CaretOffset = CaretMetrics.offset;
412 RectF caretRect;
413 caretRect.SetOffset(CaretOffset);
414 auto pattern = pattern_.Upgrade();
415 CHECK_NULL_VOID(pattern);
416 auto textFiled = DynamicCast<TextFieldPattern>(pattern);
417 CHECK_NULL_VOID(textFiled);
418 caretRect.SetSize({ caretInfo_.rect.Width(),
419 LessOrEqual(CaretMetrics.height, 0.0) ? textFiled->PreferredLineHeight() : CaretMetrics.height });
420
421 // Adjusts one character width.
422 float boundaryAdjustment = 0.0f;
423 if (isEditorValueChanged) {
424 auto textRect = textFiled->GetTextRect();
425 if (GreatNotEqual(textRect.Width(), contentRect_.Width()) && GreatNotEqual(contentRect_.Width(), 0.0) &&
426 caretInfo_.index < static_cast<int32_t>(contentController_->GetWideText().length())) {
427 boundaryAdjustment = paragraph_->GetCharacterWidth(caretInfo_.index);
428 if (SystemProperties::GetDebugEnabled()) {
429 TAG_LOGI(AceLogTag::ACE_TEXT, "caretInfo_.index = %{public}d, boundaryAdjustment =%{public}f",
430 caretInfo_.index, boundaryAdjustment);
431 }
432 }
433 }
434
435 MoveHandleToContentRect(caretRect, boundaryAdjustment);
436 caretInfo_.rect = caretRect;
437 caretRect.SetWidth(SelectHandleInfo::GetDefaultLineWidth().ConvertToPx());
438 UpdateRecordCaretIndex(caretInfo_.index);
439 }
440
UpdateFirstHandleOffset()441 void TextSelectController::UpdateFirstHandleOffset()
442 {
443 CaretMetricsF caretMetrics;
444 CalcCaretMetricsByPosition(GetFirstHandleIndex(), caretMetrics, TextAffinity::DOWNSTREAM);
445 firstHandleInfo_.rect.SetOffset(caretMetrics.offset);
446 firstHandleInfo_.rect.SetHeight(caretMetrics.height);
447 }
448
UpdateSecondHandleOffset()449 void TextSelectController::UpdateSecondHandleOffset()
450 {
451 CaretMetricsF caretMetrics;
452 CalcCaretMetricsByPosition(GetSecondHandleIndex(), caretMetrics, TextAffinity::UPSTREAM);
453 secondHandleInfo_.rect.SetOffset(caretMetrics.offset);
454 secondHandleInfo_.rect.SetHeight(caretMetrics.height);
455 }
456
UpdateCaretOffset(TextAffinity textAffinity)457 void TextSelectController::UpdateCaretOffset(TextAffinity textAffinity)
458 {
459 textAffinity_ = textAffinity;
460 if (contentController_->IsEmpty()) {
461 caretInfo_.rect = CalculateEmptyValueCaretRect();
462 return;
463 }
464 CaretMetricsF caretMetrics;
465 CalcCaretMetricsByPosition(GetCaretIndex(), caretMetrics, textAffinity);
466
467 RectF caretRect;
468 caretRect.SetOffset(caretMetrics.offset);
469 auto pattern = pattern_.Upgrade();
470 CHECK_NULL_VOID(pattern);
471 auto textFiled = DynamicCast<TextFieldPattern>(pattern);
472 CHECK_NULL_VOID(textFiled);
473 caretRect.SetSize(SizeF(caretInfo_.rect.Width(),
474 LessOrEqual(caretMetrics.height, 0.0) ? textFiled->PreferredLineHeight() : caretMetrics.height));
475 caretInfo_.rect = caretRect;
476 }
477
UpdateCaretOffset(const OffsetF & offset)478 void TextSelectController::UpdateCaretOffset(const OffsetF& offset)
479 {
480 caretInfo_.rect.SetOffset(offset);
481 secondHandleInfo_.UpdateOffset(offset);
482 }
483
UpdateSecondHandleInfoByMouseOffset(const Offset & localOffset)484 void TextSelectController::UpdateSecondHandleInfoByMouseOffset(const Offset& localOffset)
485 {
486 auto index = ConvertTouchOffsetToPosition(localOffset);
487 MoveSecondHandleToContentRect(index);
488 caretInfo_.index = index;
489 UpdateCaretOffset(TextAffinity::UPSTREAM);
490 }
491
MoveSecondHandleByKeyBoard(int32_t index)492 void TextSelectController::MoveSecondHandleByKeyBoard(int32_t index)
493 {
494 index = std::clamp(index, 0, static_cast<int32_t>(contentController_->GetWideText().length()));
495 MoveSecondHandleToContentRect(index);
496 caretInfo_.index = index;
497 UpdateCaretOffset(HasReverse() ? TextAffinity::DOWNSTREAM : TextAffinity::UPSTREAM);
498 auto caretRect = GetCaretRect();
499 MoveHandleToContentRect(caretRect);
500 caretInfo_.rect = caretRect;
501 }
502
FireSelectEvent()503 void TextSelectController::FireSelectEvent()
504 {
505 if (!onAccessibilityCallback_) {
506 return;
507 }
508 bool needReport = !GetFirstIndex().has_value() || !GetSecondIndex().has_value();
509 if (GetFirstIndex().has_value()) {
510 needReport |= GetFirstIndex().value() != firstHandleInfo_.index;
511 }
512
513 if (GetSecondIndex().has_value()) {
514 needReport |= GetSecondIndex().value() != secondHandleInfo_.index;
515 }
516
517 auto pattern = pattern_.Upgrade();
518 CHECK_NULL_VOID(pattern);
519 auto textFiled = DynamicCast<TextFieldPattern>(pattern);
520 CHECK_NULL_VOID(textFiled);
521 auto eventHub = textFiled->GetEventHub<TextFieldEventHub>();
522 CHECK_NULL_VOID(eventHub);
523
524 if (needReport && textFiled->IsModifyDone() && textFiled->HasFocus()) {
525 UpdateFirstIndex(firstHandleInfo_.index);
526 UpdateSecondIndex(secondHandleInfo_.index);
527 onAccessibilityCallback_();
528 eventHub->FireOnSelectionChange(std::min(firstHandleInfo_.index, secondHandleInfo_.index),
529 std::max(firstHandleInfo_.index, secondHandleInfo_.index));
530 }
531 }
532
UpdateRecordCaretIndex(int32_t index) const533 void TextSelectController::UpdateRecordCaretIndex(int32_t index) const
534 {
535 auto pattern = pattern_.Upgrade();
536 CHECK_NULL_VOID(pattern);
537 auto textFiled = DynamicCast<TextFieldPattern>(pattern);
538 CHECK_NULL_VOID(textFiled);
539 textFiled->UpdateRecordCaretIndex(index);
540 textFiled->UpdateCaretInfoToController();
541 }
542
ResetHandles()543 void TextSelectController::ResetHandles()
544 {
545 firstHandleInfo_.index = caretInfo_.index;
546 secondHandleInfo_.index = caretInfo_.index;
547 UpdateFirstHandleOffset();
548 UpdateSecondHandleOffset();
549 }
550
NeedAIAnalysis(int32_t & index,const CaretUpdateType targetType,const Offset & touchOffset,std::chrono::duration<float,std::ratio<1,SECONDS_TO_MILLISECONDS>> timeout)551 bool TextSelectController::NeedAIAnalysis(int32_t& index, const CaretUpdateType targetType, const Offset& touchOffset,
552 std::chrono::duration<float, std::ratio<1, SECONDS_TO_MILLISECONDS>> timeout)
553 {
554 if (!InputAIChecker::NeedAIAnalysis(contentController_->GetTextValue(), targetType, timeout)) {
555 return false;
556 }
557 if (IsClickAtBoundary(index, touchOffset) && targetType == CaretUpdateType::PRESSED) {
558 TAG_LOGI(AceLogTag::ACE_TEXTINPUT, "NeedAIAnalysis IsClickAtBoundary is boundary ,return!");
559 return false;
560 }
561 return true;
562 }
563
AdjustCursorPosition(int32_t & index,const OHOS::Ace::Offset & touchOffset)564 void TextSelectController::AdjustCursorPosition(int32_t& index, const OHOS::Ace::Offset& touchOffset)
565 {
566 auto timeout = GetLastClickTime() - lastAiPosTimeStamp_;
567 if (NeedAIAnalysis(index, CaretUpdateType::PRESSED, touchOffset, timeout)) {
568 // consider to limit the whole string length
569 int32_t startIndex = -1;
570 int32_t subIndex = index;
571 // the subindex match the sub content,we do choose the subtext to ai analysis to avoid the content too large
572 std::string content = contentController_->GetSelectedLimitValue(subIndex, startIndex);
573 DataDetectorMgr::GetInstance().AdjustCursorPosition(subIndex, content, lastAiPosTimeStamp_, GetLastClickTime());
574 index = startIndex + subIndex;
575 TAG_LOGI(AceLogTag::ACE_RICH_TEXT, "ai pos, startIndex:%{public}d,subIndex:%{public}d", startIndex, subIndex);
576 }
577 }
578
AdjustWordSelection(int32_t & index,int32_t & start,int32_t & end,const OHOS::Ace::Offset & touchOffset)579 bool TextSelectController::AdjustWordSelection(
580 int32_t& index, int32_t& start, int32_t& end, const OHOS::Ace::Offset& touchOffset)
581 {
582 auto timeout = GetLastClickTime() - lastAiPosTimeStamp_;
583 if (NeedAIAnalysis(index, CaretUpdateType::DOUBLE_CLICK, touchOffset, timeout)) {
584 // consider the limint the whole string length
585 int32_t startIndex = -1;
586 int32_t subIndex = index;
587 // to avoid the content too large
588 std::string content = contentController_->GetSelectedLimitValue(subIndex, startIndex);
589 int32_t aiPosStart = -1;
590 int32_t aiPosEnd = -1;
591 DataDetectorMgr::GetInstance().AdjustWordSelection(subIndex, content, aiPosStart, aiPosEnd);
592 TAG_LOGI(AceLogTag::ACE_TEXTINPUT, "after ai ,startIndex:%{public}d-sub:%{public}d", aiPosStart, aiPosEnd);
593 if (aiPosStart < 0 || aiPosEnd < 0) {
594 return false;
595 }
596 index = startIndex + subIndex;
597 start = startIndex + aiPosStart;
598 end = startIndex + aiPosEnd;
599 return true;
600 }
601
602 return false;
603 }
604
IsClickAtBoundary(int32_t index,const OHOS::Ace::Offset & touchOffset)605 bool TextSelectController::IsClickAtBoundary(int32_t index, const OHOS::Ace::Offset& touchOffset)
606 {
607 if (InputAIChecker::IsSingleClickAtBoundary(index, contentController_->GetWideText().length())) {
608 return true;
609 }
610
611 auto pattern = pattern_.Upgrade();
612 CHECK_NULL_RETURN(pattern, false);
613 auto textField = DynamicCast<TextFieldPattern>(pattern);
614 CHECK_NULL_RETURN(textField, false);
615 auto textRect = textField->GetTextRect();
616
617 CaretMetricsF caretMetrics;
618 CalcCaretMetricsByPositionNearTouchOffset(
619 index, caretMetrics, OffsetF(static_cast<float>(touchOffset.GetX()), static_cast<float>(touchOffset.GetY())));
620
621 if (InputAIChecker::IsMultiClickAtBoundary(caretMetrics.offset, textRect)) {
622 return true;
623 }
624
625 return false;
626 }
627
GetLastClickTime()628 const TimeStamp& TextSelectController::GetLastClickTime()
629 {
630 auto pattern = pattern_.Upgrade();
631 CHECK_NULL_RETURN(pattern, lastAiPosTimeStamp_);
632 auto textField = DynamicCast<TextFieldPattern>(pattern);
633 CHECK_NULL_RETURN(textField, lastAiPosTimeStamp_);
634 return textField->GetLastClickTime();
635 }
636 } // namespace OHOS::Ace::NG