e0c2306ab6e5336929ba1849bcfef16684b118d9
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / text-controls / common-text-utils.cpp
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 // EXTERNAL INCLUDES
18 #include <dali/public-api/actors/layer.h>
19
20 // INTERNAL INCLUDES
21 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
22 #include <dali-toolkit/devel-api/controls/control-devel.h>
23 #include <dali-toolkit/devel-api/focus-manager/keyinput-focus-manager.h>
24 #include <dali-toolkit/internal/controls/text-controls/common-text-utils.h>
25 #include <dali-toolkit/internal/text/character-set-conversion.h>
26 #include <dali-toolkit/internal/text/hidden-text.h>
27 #include <dali-toolkit/internal/text/text-view.h>
28
29 namespace Dali::Toolkit::Internal
30 {
31 void CommonTextUtils::SynchronizeTextAnchorsInParent(
32   Actor                             parent,
33   Text::ControllerPtr               controller,
34   std::vector<Toolkit::TextAnchor>& anchorActors)
35 {
36   for(auto& anchorActor : anchorActors)
37   {
38     parent.Remove(anchorActor);
39   }
40   if(Dali::Accessibility::IsUp())
41   {
42     controller->GetAnchorActors(anchorActors);
43     for(auto& anchorActor : anchorActors)
44     {
45       parent.Add(anchorActor);
46     }
47   }
48 }
49
50 void CommonTextUtils::RenderText(
51   Actor                             textActor,
52   Text::RendererPtr                 renderer,
53   Text::ControllerPtr               controller,
54   Text::DecoratorPtr                decorator,
55   float&                            alignmentOffset,
56   Actor&                            renderableActor,
57   Actor&                            backgroundActor,
58   Toolkit::Control&                 stencil,
59   std::vector<Actor>&               clippingDecorationActors,
60   std::vector<Toolkit::TextAnchor>& anchorActors,
61   Text::Controller::UpdateTextType  updateTextType)
62 {
63   Actor newRenderableActor;
64
65   if(Text::Controller::NONE_UPDATED != (Text::Controller::MODEL_UPDATED & updateTextType))
66   {
67     if(renderer)
68     {
69       newRenderableActor = renderer->Render(controller->GetView(),
70                                             textActor,
71                                             Property::INVALID_INDEX, // Animatable property not supported
72                                             alignmentOffset,
73                                             DepthIndex::CONTENT);
74     }
75
76     if(renderableActor != newRenderableActor)
77     {
78       UnparentAndReset(backgroundActor);
79       UnparentAndReset(renderableActor);
80       renderableActor = newRenderableActor;
81
82       if(renderableActor)
83       {
84         backgroundActor = controller->CreateBackgroundActor();
85       }
86     }
87   }
88
89   if(renderableActor)
90   {
91     const Vector2& scrollOffset = controller->GetTextModel()->GetScrollPosition();
92
93     float renderableActorPositionX, renderableActorPositionY;
94
95     if(stencil)
96     {
97       renderableActorPositionX = scrollOffset.x + alignmentOffset;
98       renderableActorPositionY = scrollOffset.y;
99     }
100     else
101     {
102       Extents padding;
103       padding = textActor.GetProperty<Extents>(Toolkit::Control::Property::PADDING);
104
105       // Support Right-To-Left of padding
106       Dali::LayoutDirection::Type layoutDirection = static_cast<Dali::LayoutDirection::Type>(textActor.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
107       if(Dali::LayoutDirection::RIGHT_TO_LEFT == layoutDirection)
108       {
109         std::swap(padding.start, padding.end);
110       }
111
112       renderableActorPositionX = scrollOffset.x + alignmentOffset + padding.start;
113       renderableActorPositionY = scrollOffset.y + padding.top;
114     }
115
116     renderableActor.SetProperty(Actor::Property::POSITION, Vector2(renderableActorPositionX, renderableActorPositionY));
117
118     // Make sure the actors are parented correctly with/without clipping
119     Actor self = stencil ? stencil : textActor;
120
121     Actor highlightActor;
122
123     for(std::vector<Actor>::iterator it    = clippingDecorationActors.begin(),
124                                      endIt = clippingDecorationActors.end();
125         it != endIt;
126         ++it)
127     {
128       self.Add(*it);
129       it->LowerToBottom();
130
131       if(it->GetProperty<std::string>(Dali::Actor::Property::NAME) == "HighlightActor")
132       {
133         highlightActor = *it;
134       }
135       else if(it->GetProperty<std::string>(Dali::Actor::Property::NAME) == "CursorLayerActor")
136       {
137         it->RaiseToTop();
138       }
139     }
140     clippingDecorationActors.clear();
141
142     self.Add(renderableActor);
143
144     if(backgroundActor)
145     {
146       if(decorator && decorator->IsHighlightVisible())
147       {
148         self.Add(backgroundActor);
149         backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(renderableActorPositionX, renderableActorPositionY)); // In text field's coords.
150         backgroundActor.LowerBelow(highlightActor);
151       }
152       else
153       {
154         renderableActor.Add(backgroundActor);
155         backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(0.0f, 0.0f)); // In renderable actor's coords.
156         backgroundActor.LowerToBottom();
157       }
158     }
159     SynchronizeTextAnchorsInParent(textActor, controller, anchorActors);
160   }
161 }
162
163 std::size_t TextControlAccessible::GetCharacterCount() const
164 {
165   return GetWholeText().size();
166 }
167
168 std::size_t TextControlAccessible::GetCursorOffset() const
169 {
170   return 0u;
171 }
172
173 Rect<> TextControlAccessible::GetRangeExtents(std::size_t startOffset, std::size_t endOffset, Accessibility::CoordinateType type)
174 {
175   if(!ValidateRange(GetWholeText(), startOffset, endOffset))
176   {
177     return {0, 0, 0, 0};
178   }
179
180   auto rect    = GetTextController()->GetTextBoundingRectangle(startOffset, endOffset - 1);
181   auto extents = GetExtents(type);
182
183   rect.x += extents.x;
184   rect.y += extents.y;
185
186   return rect;
187 }
188
189 Accessibility::Range TextControlAccessible::GetRangeOfSelection(std::size_t selectionIndex) const
190 {
191   // Since DALi supports only one selection, indices other than 0 are ignored
192   if(selectionIndex > 0)
193   {
194     return {};
195   }
196
197   auto indices     = GetTextController()->GetSelectionIndexes();
198   auto startOffset = static_cast<std::size_t>(indices.first);
199   auto endOffset   = static_cast<std::size_t>(indices.second);
200   auto text        = GetText(startOffset, endOffset);
201
202   return {startOffset, endOffset, text};
203 }
204
205 std::string TextControlAccessible::GetText(std::size_t startOffset, std::size_t endOffset) const
206 {
207   auto text = GetWholeText();
208
209   if(!ValidateRange(text, startOffset, endOffset))
210   {
211     return {};
212   }
213
214   if(IsHiddenInput())
215   {
216     std::uint32_t substituteCharacterUtf32 = GetSubstituteCharacter();
217     std::string   substituteCharacterUtf8;
218     std::string   substituteText;
219
220     Toolkit::Text::Utf32ToUtf8(&substituteCharacterUtf32, 1, substituteCharacterUtf8);
221
222     while(substituteText.length() < endOffset - startOffset)
223     {
224       substituteText.append(substituteCharacterUtf8);
225     }
226
227     return substituteText;
228   }
229
230   return text.substr(startOffset, endOffset - startOffset);
231 }
232
233 Accessibility::Range TextControlAccessible::GetTextAtOffset(std::size_t offset, Accessibility::TextBoundary boundary) const
234 {
235   Accessibility::Range range{};
236
237   if(IsHiddenInput())
238   {
239     // Returning empty object, as there is no possibility to parse the textfield
240     // when its content is hidden.
241     return range;
242   }
243
244   auto text     = GetWholeText();
245   auto textSize = text.size();
246
247   switch(boundary)
248   {
249     case Dali::Accessibility::TextBoundary::CHARACTER:
250     {
251       if(offset < textSize)
252       {
253         range.content     = text[offset];
254         range.startOffset = offset;
255         range.endOffset   = offset + 1;
256       }
257       break;
258     }
259
260     case Dali::Accessibility::TextBoundary::WORD:
261     case Dali::Accessibility::TextBoundary::LINE:
262     {
263       std::vector<char> breaks(textSize, '\0');
264
265       if(boundary == Dali::Accessibility::TextBoundary::WORD)
266       {
267         Accessibility::Accessible::FindWordSeparationsUtf8(reinterpret_cast<const utf8_t*>(text.c_str()), textSize, "", breaks.data());
268       }
269       else
270       {
271         Accessibility::Accessible::FindLineSeparationsUtf8(reinterpret_cast<const utf8_t*>(text.c_str()), textSize, "", breaks.data());
272       }
273
274       std::size_t index   = 0u;
275       std::size_t counter = 0u;
276
277       while(index < textSize && counter <= offset)
278       {
279         auto start = index;
280         if(breaks[index])
281         {
282           while(breaks[index])
283           {
284             index++;
285           }
286           counter++;
287         }
288         else
289         {
290           if(boundary == Dali::Accessibility::TextBoundary::WORD)
291           {
292             index++;
293           }
294           if(boundary == Dali::Accessibility::TextBoundary::LINE)
295           {
296             counter++;
297           }
298         }
299
300         if((counter > 0) && ((counter - 1) == offset))
301         {
302           range.content     = text.substr(start, index - start + 1);
303           range.startOffset = start;
304           range.endOffset   = index + 1;
305         }
306
307         if(boundary == Dali::Accessibility::TextBoundary::LINE)
308         {
309           index++;
310         }
311       }
312       break;
313     }
314
315     case Dali::Accessibility::TextBoundary::SENTENCE: // Not supported by default
316     case Dali::Accessibility::TextBoundary::PARAGRAPH: // Not supported by libunibreak library
317     default:
318     {
319       break;
320     }
321   }
322
323   return range;
324 }
325
326 bool TextControlAccessible::RemoveSelection(std::size_t selectionIndex)
327 {
328   // Since DALi supports only one selection, indices other than 0 are ignored
329   if(selectionIndex > 0)
330   {
331     return false;
332   }
333
334   GetTextController()->SetSelection(0, 0);
335
336   return true;
337 }
338
339 bool TextControlAccessible::SetCursorOffset(std::size_t offset)
340 {
341   return false;
342 }
343
344 bool TextControlAccessible::SetRangeOfSelection(std::size_t selectionIndex, std::size_t startOffset, std::size_t endOffset)
345 {
346   // Since DALi supports only one selection, indices other than 0 are ignored
347   if(selectionIndex > 0)
348   {
349     return false;
350   }
351
352   // Lack of ValidateRange() is intentional
353
354   GetTextController()->SetSelection(startOffset, endOffset);
355
356   return true;
357 }
358
359 Accessibility::Hyperlink* TextControlAccessible::GetLink(std::int32_t linkIndex) const
360 {
361   if(linkIndex < 0 || linkIndex >= GetLinkCount())
362   {
363     return nullptr;
364   }
365
366   auto anchor = GetTextAnchors()[linkIndex];
367
368   return Accessibility::Hyperlink::DownCast(Accessibility::Accessible::Get(anchor));
369 }
370
371 std::int32_t TextControlAccessible::GetLinkCount() const
372 {
373   return static_cast<std::int32_t>(GetTextAnchors().size());
374 }
375
376 std::int32_t TextControlAccessible::GetLinkIndex(std::int32_t characterOffset) const
377 {
378   return GetTextController()->GetAnchorIndex(static_cast<std::size_t>(characterOffset));
379 }
380
381 std::string TextControlAccessible::GetWholeText() const
382 {
383   std::string text;
384
385   GetTextController()->GetText(text);
386
387   return text;
388 }
389
390 std::uint32_t TextControlAccessible::GetSubstituteCharacter() const
391 {
392   return Toolkit::Text::STAR;
393 }
394
395 bool TextControlAccessible::IsHiddenInput() const
396 {
397   return false;
398 }
399
400 bool TextControlAccessible::ValidateRange(const std::string& string, std::size_t begin, std::size_t end)
401 {
402   auto size = string.size();
403
404   if(end <= begin || begin >= size || end > size)
405   {
406     return false;
407   }
408
409   // TODO: Check whether the range [begin, end) describes a valid substring:
410   // 1. It does not break multi-byte UTF-8 sequences.
411   // 2. It does not break graphemes (compound emojis, glyphs with combining characters etc.).
412
413   return true;
414 }
415
416 Accessibility::States EditableTextControlAccessible::CalculateStates()
417 {
418   using Dali::Accessibility::State;
419
420   auto states       = DevelControl::ControlAccessible::CalculateStates();
421   auto focusControl = Toolkit::KeyInputFocusManager::Get().GetCurrentFocusControl();
422
423   states[State::EDITABLE]  = true;
424   states[State::FOCUSABLE] = true;
425   states[State::FOCUSED]   = (Self() == focusControl);
426
427   return states;
428 }
429
430 std::size_t EditableTextControlAccessible::GetCursorOffset() const
431 {
432   return GetTextController()->GetCursorPosition();
433 }
434
435 bool EditableTextControlAccessible::SetCursorOffset(std::size_t offset)
436 {
437   if(offset > GetCharacterCount())
438   {
439     return false;
440   }
441
442   GetTextController()->ResetCursorPosition(offset);
443   RequestTextRelayout();
444
445   return true;
446 }
447
448 bool EditableTextControlAccessible::CopyText(std::size_t startPosition, std::size_t endPosition)
449 {
450   auto text = GetWholeText();
451
452   if(!ValidateRange(text, startPosition, endPosition))
453   {
454     return false;
455   }
456
457   GetTextController()->CopyStringToClipboard(text.substr(startPosition, endPosition - startPosition));
458
459   return true;
460 }
461
462 bool EditableTextControlAccessible::CutText(std::size_t startPosition, std::size_t endPosition)
463 {
464   if(!CopyText(startPosition, endPosition))
465   {
466     return false;
467   }
468
469   return DeleteText(startPosition, endPosition);
470 }
471
472 bool EditableTextControlAccessible::DeleteText(std::size_t startPosition, std::size_t endPosition)
473 {
474   auto text = GetWholeText();
475
476   if(!ValidateRange(text, startPosition, endPosition))
477   {
478     return false;
479   }
480
481   return SetTextContents(text.erase(startPosition, endPosition - startPosition));
482 }
483
484 bool EditableTextControlAccessible::InsertText(std::size_t startPosition, std::string newText)
485 {
486   auto text = GetWholeText();
487
488   if(!ValidateRange(text, startPosition, startPosition + 1))
489   {
490     return false;
491   }
492
493   return SetTextContents(text.insert(startPosition, std::move(newText)));
494 }
495
496 bool EditableTextControlAccessible::SetTextContents(std::string newContents)
497 {
498   GetTextController()->SetText(std::move(newContents));
499
500   return true;
501 }
502
503 } // namespace Dali::Toolkit::Internal