• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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