Merge changes I6399a6ca,Iae5f8784,I40eb6797,I8ba65736 into devel/master
[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     }
136     clippingDecorationActors.clear();
137
138     self.Add(renderableActor);
139
140     if(backgroundActor)
141     {
142       if(decorator && decorator->IsHighlightVisible())
143       {
144         self.Add(backgroundActor);
145         backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(renderableActorPositionX, renderableActorPositionY)); // In text field's coords.
146         backgroundActor.LowerBelow(highlightActor);
147       }
148       else
149       {
150         renderableActor.Add(backgroundActor);
151         backgroundActor.SetProperty(Actor::Property::POSITION, Vector2(0.0f, 0.0f)); // In renderable actor's coords.
152         backgroundActor.LowerToBottom();
153       }
154     }
155     SynchronizeTextAnchorsInParent(textActor, controller, anchorActors);
156   }
157 }
158
159 std::size_t TextControlAccessible::GetCharacterCount() const
160 {
161   return GetWholeText().size();
162 }
163
164 std::size_t TextControlAccessible::GetCursorOffset() const
165 {
166   return 0u;
167 }
168
169 Rect<> TextControlAccessible::GetRangeExtents(std::size_t startOffset, std::size_t endOffset, Accessibility::CoordinateType type)
170 {
171   if(!ValidateRange(GetWholeText(), startOffset, endOffset))
172   {
173     return {0, 0, 0, 0};
174   }
175
176   auto rect    = GetTextController()->GetTextBoundingRectangle(startOffset, endOffset - 1);
177   auto extents = GetExtents(type);
178
179   rect.x += extents.x;
180   rect.y += extents.y;
181
182   return rect;
183 }
184
185 Accessibility::Range TextControlAccessible::GetRangeOfSelection(std::size_t selectionIndex) const
186 {
187   // Since DALi supports only one selection, indices other than 0 are ignored
188   if(selectionIndex > 0)
189   {
190     return {};
191   }
192
193   auto indices     = GetTextController()->GetSelectionIndexes();
194   auto startOffset = static_cast<std::size_t>(indices.first);
195   auto endOffset   = static_cast<std::size_t>(indices.second);
196   auto text        = GetText(startOffset, endOffset);
197
198   return {startOffset, endOffset, text};
199 }
200
201 std::string TextControlAccessible::GetText(std::size_t startOffset, std::size_t endOffset) const
202 {
203   auto text = GetWholeText();
204
205   if(!ValidateRange(text, startOffset, endOffset))
206   {
207     return {};
208   }
209
210   if(IsHiddenInput())
211   {
212     std::uint32_t substituteCharacterUtf32 = GetSubstituteCharacter();
213     std::string   substituteCharacterUtf8;
214     std::string   substituteText;
215
216     Toolkit::Text::Utf32ToUtf8(&substituteCharacterUtf32, 1, substituteCharacterUtf8);
217
218     while(substituteText.length() < endOffset - startOffset)
219     {
220       substituteText.append(substituteCharacterUtf8);
221     }
222
223     return substituteText;
224   }
225
226   return text.substr(startOffset, endOffset - startOffset);
227 }
228
229 Accessibility::Range TextControlAccessible::GetTextAtOffset(std::size_t offset, Accessibility::TextBoundary boundary) const
230 {
231   Accessibility::Range range{};
232
233   if(IsHiddenInput())
234   {
235     // Returning empty object, as there is no possibility to parse the textfield
236     // when its content is hidden.
237     return range;
238   }
239
240   auto text     = GetWholeText();
241   auto textSize = text.size();
242
243   switch(boundary)
244   {
245     case Dali::Accessibility::TextBoundary::CHARACTER:
246     {
247       if(offset < textSize)
248       {
249         range.content     = text[offset];
250         range.startOffset = offset;
251         range.endOffset   = offset + 1;
252       }
253       break;
254     }
255
256     case Dali::Accessibility::TextBoundary::WORD:
257     case Dali::Accessibility::TextBoundary::LINE:
258     {
259       std::vector<char> breaks(textSize, '\0');
260
261       if(boundary == Dali::Accessibility::TextBoundary::WORD)
262       {
263         Accessibility::Accessible::FindWordSeparationsUtf8(reinterpret_cast<const utf8_t*>(text.c_str()), textSize, "", breaks.data());
264       }
265       else
266       {
267         Accessibility::Accessible::FindLineSeparationsUtf8(reinterpret_cast<const utf8_t*>(text.c_str()), textSize, "", breaks.data());
268       }
269
270       std::size_t index   = 0u;
271       std::size_t counter = 0u;
272
273       while(index < textSize && counter <= offset)
274       {
275         auto start = index;
276         if(breaks[index])
277         {
278           while(breaks[index])
279           {
280             index++;
281           }
282           counter++;
283         }
284         else
285         {
286           if(boundary == Dali::Accessibility::TextBoundary::WORD)
287           {
288             index++;
289           }
290           if(boundary == Dali::Accessibility::TextBoundary::LINE)
291           {
292             counter++;
293           }
294         }
295
296         if((counter > 0) && ((counter - 1) == offset))
297         {
298           range.content     = text.substr(start, index - start + 1);
299           range.startOffset = start;
300           range.endOffset   = index + 1;
301         }
302
303         if(boundary == Dali::Accessibility::TextBoundary::LINE)
304         {
305           index++;
306         }
307       }
308       break;
309     }
310
311     case Dali::Accessibility::TextBoundary::SENTENCE: // Not supported by default
312     case Dali::Accessibility::TextBoundary::PARAGRAPH: // Not supported by libunibreak library
313     default:
314     {
315       break;
316     }
317   }
318
319   return range;
320 }
321
322 bool TextControlAccessible::RemoveSelection(std::size_t selectionIndex)
323 {
324   // Since DALi supports only one selection, indices other than 0 are ignored
325   if(selectionIndex > 0)
326   {
327     return false;
328   }
329
330   GetTextController()->SetSelection(0, 0);
331
332   return true;
333 }
334
335 bool TextControlAccessible::SetCursorOffset(std::size_t offset)
336 {
337   return false;
338 }
339
340 bool TextControlAccessible::SetRangeOfSelection(std::size_t selectionIndex, std::size_t startOffset, std::size_t endOffset)
341 {
342   // Since DALi supports only one selection, indices other than 0 are ignored
343   if(selectionIndex > 0)
344   {
345     return false;
346   }
347
348   // Lack of ValidateRange() is intentional
349
350   GetTextController()->SetSelection(startOffset, endOffset);
351
352   return true;
353 }
354
355 Accessibility::Hyperlink* TextControlAccessible::GetLink(std::int32_t linkIndex) const
356 {
357   if(linkIndex < 0 || linkIndex >= GetLinkCount())
358   {
359     return nullptr;
360   }
361
362   auto anchor = GetTextAnchors()[linkIndex];
363
364   return Accessibility::Hyperlink::DownCast(Accessibility::Accessible::Get(anchor));
365 }
366
367 std::int32_t TextControlAccessible::GetLinkCount() const
368 {
369   return static_cast<std::int32_t>(GetTextAnchors().size());
370 }
371
372 std::int32_t TextControlAccessible::GetLinkIndex(std::int32_t characterOffset) const
373 {
374   return GetTextController()->GetAnchorIndex(static_cast<std::size_t>(characterOffset));
375 }
376
377 std::string TextControlAccessible::GetWholeText() const
378 {
379   std::string text;
380
381   GetTextController()->GetText(text);
382
383   return text;
384 }
385
386 std::uint32_t TextControlAccessible::GetSubstituteCharacter() const
387 {
388   return Toolkit::Text::STAR;
389 }
390
391 bool TextControlAccessible::IsHiddenInput() const
392 {
393   return false;
394 }
395
396 bool TextControlAccessible::ValidateRange(const std::string& string, std::size_t begin, std::size_t end)
397 {
398   auto size = string.size();
399
400   if(end <= begin || begin >= size || end > size)
401   {
402     return false;
403   }
404
405   // TODO: Check whether the range [begin, end) describes a valid substring:
406   // 1. It does not break multi-byte UTF-8 sequences.
407   // 2. It does not break graphemes (compound emojis, glyphs with combining characters etc.).
408
409   return true;
410 }
411
412 Accessibility::States EditableTextControlAccessible::CalculateStates()
413 {
414   using Dali::Accessibility::State;
415
416   auto states       = DevelControl::ControlAccessible::CalculateStates();
417   auto focusControl = Toolkit::KeyInputFocusManager::Get().GetCurrentFocusControl();
418
419   states[State::EDITABLE]  = true;
420   states[State::FOCUSABLE] = true;
421   states[State::FOCUSED]   = (Self() == focusControl);
422
423   return states;
424 }
425
426 std::size_t EditableTextControlAccessible::GetCursorOffset() const
427 {
428   return GetTextController()->GetCursorPosition();
429 }
430
431 bool EditableTextControlAccessible::SetCursorOffset(std::size_t offset)
432 {
433   if(offset > GetCharacterCount())
434   {
435     return false;
436   }
437
438   GetTextController()->ResetCursorPosition(offset);
439   RequestTextRelayout();
440
441   return true;
442 }
443
444 bool EditableTextControlAccessible::CopyText(std::size_t startPosition, std::size_t endPosition)
445 {
446   auto text = GetWholeText();
447
448   if(!ValidateRange(text, startPosition, endPosition))
449   {
450     return false;
451   }
452
453   GetTextController()->CopyStringToClipboard(text.substr(startPosition, endPosition - startPosition));
454
455   return true;
456 }
457
458 bool EditableTextControlAccessible::CutText(std::size_t startPosition, std::size_t endPosition)
459 {
460   if(!CopyText(startPosition, endPosition))
461   {
462     return false;
463   }
464
465   return DeleteText(startPosition, endPosition);
466 }
467
468 bool EditableTextControlAccessible::DeleteText(std::size_t startPosition, std::size_t endPosition)
469 {
470   auto text = GetWholeText();
471
472   if(!ValidateRange(text, startPosition, endPosition))
473   {
474     return false;
475   }
476
477   return SetTextContents(text.erase(startPosition, endPosition - startPosition));
478 }
479
480 bool EditableTextControlAccessible::InsertText(std::size_t startPosition, std::string newText)
481 {
482   auto text = GetWholeText();
483
484   if(!ValidateRange(text, startPosition, startPosition + 1))
485   {
486     return false;
487   }
488
489   return SetTextContents(text.insert(startPosition, std::move(newText)));
490 }
491
492 bool EditableTextControlAccessible::SetTextContents(std::string newContents)
493 {
494   GetTextController()->SetText(std::move(newContents));
495
496   return true;
497 }
498
499 } // namespace Dali::Toolkit::Internal