[dali_2.3.20] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / devel-api / controls / control-accessible.cpp
1 /*
2  * Copyright (c) 2024 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
18 // CLASS HEADER
19 #include "control-accessible.h"
20
21 // EXTERNAL INCLUDES
22 #ifdef DGETTEXT_ENABLED
23 #include <libintl.h>
24 #endif
25
26 #include <dali/devel-api/actors/actor-devel.h>
27 #include <dali/devel-api/adaptor-framework/window-devel.h>
28 #include <dali/public-api/object/property-map.h>
29 #include <dali/public-api/object/type-info.h>
30
31 // INTERNAL INCLUDES
32 #include <dali-toolkit/devel-api/asset-manager/asset-manager.h>
33 #include <dali-toolkit/internal/controls/control/control-data-impl.h>
34 #include <dali-toolkit/internal/visuals/image/image-visual.h>
35 #include <dali-toolkit/public-api/controls/control-impl.h>
36 #include <dali-toolkit/public-api/controls/control.h>
37 #include <dali-toolkit/public-api/controls/image-view/image-view.h>
38 #include <dali-toolkit/public-api/focus-manager/keyboard-focus-manager.h>
39
40 namespace Dali::Toolkit::DevelControl
41 {
42 namespace
43 {
44 constexpr const char* ATTR_IMG_SRC_KEY = "imgSrc";
45
46 std::string GetLocaleText(std::string string, const char* domain = "dali-toolkit")
47 {
48 #ifdef DGETTEXT_ENABLED
49   /*TODO: currently non-localized string is used as a key for translation lookup. In case the lookup key formatting is forced
50           consider calling utility function for converting non-localized string into well-formatted key before lookup. */
51   return dgettext(domain, string.c_str());
52 #else
53   return string;
54 #endif
55 }
56
57 Dali::Actor CreateHighlightIndicatorActor()
58 {
59   std::string focusBorderImagePath(AssetManager::GetDaliImagePath());
60   focusBorderImagePath += "/keyboard_focus.9.png";
61
62   // Create the default if it hasn't been set and one that's shared by all the
63   // keyboard focusable actors
64   auto actor = Toolkit::ImageView::New(focusBorderImagePath);
65   actor.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
66
67   DevelControl::AppendAccessibilityAttribute(actor, "highlight", std::string());
68   actor.SetProperty(Toolkit::DevelControl::Property::ACCESSIBILITY_HIGHLIGHTABLE, false);
69
70   return actor;
71 }
72
73 std::string FetchImageSrcFromMap(const Dali::Property::Map& imageMap)
74 {
75   auto urlVal = imageMap.Find(Toolkit::ImageVisual::Property::URL);
76   if(urlVal)
77   {
78     if(urlVal->GetType() == Dali::Property::STRING)
79     {
80       return urlVal->Get<std::string>();
81     }
82     else if(urlVal->GetType() == Dali::Property::ARRAY)
83     {
84       auto urlArray = urlVal->GetArray();
85       if(urlArray && !urlArray->Empty())
86       {
87         // Returns first element if url is an array
88         return (*urlArray)[0].Get<std::string>();
89       }
90     }
91   }
92   return {};
93 }
94
95 std::string FetchImageSrc(const Toolkit::ImageView& imageView)
96 {
97   const auto imageUrl = imageView.GetProperty<std::string>(Toolkit::ImageView::Property::IMAGE);
98   if(!imageUrl.empty())
99   {
100     return imageUrl;
101   }
102
103   const auto imageMap = imageView.GetProperty<Dali::Property::Map>(Toolkit::ImageView::Property::IMAGE);
104   if(!imageMap.Empty())
105   {
106     return FetchImageSrcFromMap(imageMap);
107   }
108   return {};
109 }
110
111 } // unnamed namespace
112
113 ControlAccessible::ControlAccessible(Dali::Actor self)
114 : ActorAccessible(self)
115 {
116 }
117
118 std::string ControlAccessible::GetName() const
119 {
120   auto control = Dali::Toolkit::Control::DownCast(Self());
121
122   Internal::Control&       internalControl = Toolkit::Internal::GetImplementation(control);
123   Internal::Control::Impl& controlImpl     = Internal::Control::Impl::Get(internalControl);
124   std::string              name;
125
126   if(!controlImpl.mAccessibilityGetNameSignal.Empty())
127   {
128     controlImpl.mAccessibilityGetNameSignal.Emit(name);
129   }
130   else if(!controlImpl.mAccessibilityName.empty())
131   {
132     name = controlImpl.mAccessibilityName;
133   }
134   else if(auto raw = GetNameRaw(); !raw.empty())
135   {
136     name = raw;
137   }
138   else
139   {
140     name = Self().GetProperty<std::string>(Actor::Property::NAME);
141   }
142
143   if(!controlImpl.mAccessibilityTranslationDomain.empty())
144   {
145     return GetLocaleText(name, controlImpl.mAccessibilityTranslationDomain.c_str());
146   }
147
148   return GetLocaleText(name);
149 }
150
151 std::string ControlAccessible::GetNameRaw() const
152 {
153   return {};
154 }
155
156 std::string ControlAccessible::GetDescription() const
157 {
158   auto control = Dali::Toolkit::Control::DownCast(Self());
159
160   Internal::Control&       internalControl = Toolkit::Internal::GetImplementation(control);
161   Internal::Control::Impl& controlImpl     = Internal::Control::Impl::Get(internalControl);
162   std::string              description;
163
164   if(!controlImpl.mAccessibilityGetDescriptionSignal.Empty())
165   {
166     controlImpl.mAccessibilityGetDescriptionSignal.Emit(description);
167   }
168   else if(!controlImpl.mAccessibilityDescription.empty())
169   {
170     description = controlImpl.mAccessibilityDescription;
171   }
172   else
173   {
174     description = GetDescriptionRaw();
175   }
176
177   if(!controlImpl.mAccessibilityTranslationDomain.empty())
178   {
179     return GetLocaleText(description, controlImpl.mAccessibilityTranslationDomain.c_str());
180   }
181
182   return GetLocaleText(description);
183 }
184
185 std::string ControlAccessible::GetDescriptionRaw() const
186 {
187   return {};
188 }
189
190 Dali::Accessibility::Role ControlAccessible::GetRole() const
191 {
192   return Self().GetProperty<Dali::Accessibility::Role>(Toolkit::DevelControl::Property::ACCESSIBILITY_ROLE);
193 }
194
195 std::string ControlAccessible::GetLocalizedRoleName() const
196 {
197   return GetLocaleText(GetRoleName());
198 }
199
200 bool ControlAccessible::IsShowing()
201 {
202   Dali::Actor self = Self();
203   if(!self.GetProperty<bool>(Actor::Property::VISIBLE) || Dali::EqualsZero(self.GetProperty<Vector4>(Actor::Property::WORLD_COLOR).a) || self.GetProperty<bool>(Dali::DevelActor::Property::CULLED))
204   {
205     return false;
206   }
207
208   auto* child  = this;
209   auto* parent = dynamic_cast<Toolkit::DevelControl::ControlAccessible*>(child->GetParent());
210   if(!parent)
211   {
212     return true;
213   }
214
215   while(parent)
216   {
217     auto control = Dali::Toolkit::Control::DownCast(parent->Self());
218     if(!control.GetProperty<bool>(Actor::Property::VISIBLE))
219     {
220       return false;
221     }
222     parent = dynamic_cast<Toolkit::DevelControl::ControlAccessible*>(parent->GetParent());
223   }
224
225   return true;
226 }
227
228 Dali::Accessibility::States ControlAccessible::CalculateStates()
229 {
230   using Dali::Accessibility::State;
231
232   Dali::Actor                 self = Self();
233   Dali::Accessibility::States states;
234
235   states[State::FOCUSABLE]     = self.GetProperty<bool>(Actor::Property::KEYBOARD_FOCUSABLE);
236   states[State::FOCUSED]       = Toolkit::KeyboardFocusManager::Get().GetCurrentFocusActor() == self;
237   states[State::HIGHLIGHTABLE] = self.GetProperty<bool>(Toolkit::DevelControl::Property::ACCESSIBILITY_HIGHLIGHTABLE);
238   states[State::HIGHLIGHTED]   = GetCurrentlyHighlightedActor() == self;
239   states[State::ENABLED]       = true;
240   states[State::SENSITIVE]     = (Dali::DevelActor::IsHittable(self) && Dali::DevelActor::GetTouchRequired(self));
241   states[State::VISIBLE]       = self.GetProperty<bool>(Actor::Property::VISIBLE);
242   states[State::SHOWING]       = IsShowing();
243   states[State::DEFUNCT]       = !self.GetProperty(Dali::DevelActor::Property::CONNECTED_TO_SCENE).Get<bool>();
244
245   return states;
246 }
247
248 Dali::Accessibility::States ControlAccessible::GetStates()
249 {
250   return CalculateStates();
251 }
252
253 Dali::Accessibility::Attributes ControlAccessible::GetAttributes() const
254 {
255   static const std::string automationIdKey = "automationId";
256   static const std::string classKey        = "class";
257
258   Accessibility::Attributes result;
259   Toolkit::Control          control        = Toolkit::Control::DownCast(Self());
260   Dali::Property::Value     property       = control.GetProperty(DevelControl::Property::ACCESSIBILITY_ATTRIBUTES);
261   Dali::Property::Map*      attributeMap   = property.GetMap();
262   std::size_t               attributeCount = attributeMap ? attributeMap->Count() : 0U;
263
264   for(std::size_t i = 0; i < attributeCount; i++)
265   {
266     Dali::Property::Key mapKey = attributeMap->GetKeyAt(i);
267     std::string         mapValue;
268
269     if(mapKey.type == Dali::Property::Key::STRING && attributeMap->GetValue(i).Get(mapValue))
270     {
271       result.emplace(std::move(mapKey.stringKey), std::move(mapValue));
272     }
273   }
274
275   auto automationId = control.GetProperty<std::string>(DevelControl::Property::AUTOMATION_ID);
276   if(!automationId.empty())
277   {
278     result.emplace(automationIdKey, std::move(automationId));
279   }
280
281   if(auto imageView = Toolkit::ImageView::DownCast(Self()))
282   {
283     auto imageSrc = FetchImageSrc(imageView);
284     if(!imageSrc.empty())
285     {
286       result.emplace(ATTR_IMG_SRC_KEY, std::move(imageSrc));
287     }
288   }
289
290   // Add "class" if not present already
291   if(result.find(classKey) == result.end())
292   {
293     Dali::TypeInfo typeInfo;
294     Self().GetTypeInfo(typeInfo);
295     if(typeInfo)
296     {
297       const std::string& typeName = typeInfo.GetName();
298
299       result.emplace(classKey, typeName);
300
301       // Save the 'typeName' so we don't have to calculate it again
302       DevelControl::AppendAccessibilityAttribute(control, classKey, typeName);
303     }
304   }
305
306   return result;
307 }
308
309 bool ControlAccessible::IsHidden() const
310 {
311   auto control = Dali::Toolkit::Control::DownCast(Self());
312
313   Internal::Control&       internalControl = Toolkit::Internal::GetImplementation(control);
314   Internal::Control::Impl& controlImpl     = Internal::Control::Impl::Get(internalControl);
315
316   return controlImpl.mAccessibilityHidden;
317 }
318
319 bool ControlAccessible::GrabFocus()
320 {
321   return Toolkit::KeyboardFocusManager::Get().SetCurrentFocusActor(Self());
322 }
323
324 void ControlAccessible::ScrollToSelf()
325 {
326   auto* child  = this;
327   auto* parent = dynamic_cast<Toolkit::DevelControl::ControlAccessible*>(child->GetParent());
328
329   while(parent)
330   {
331     if(parent->IsScrollable())
332     {
333       parent->ScrollToChild(child->Self());
334     }
335
336     parent = dynamic_cast<Toolkit::DevelControl::ControlAccessible*>(parent->GetParent());
337   }
338 }
339
340 void ControlAccessible::RegisterPositionPropertyNotification()
341 {
342   auto                     control         = Dali::Toolkit::Control::DownCast(Self());
343   Internal::Control&       internalControl = Toolkit::Internal::GetImplementation(control);
344   Internal::Control::Impl& controlImpl     = Internal::Control::Impl::Get(internalControl);
345   controlImpl.RegisterAccessibilityPositionPropertyNotification();
346 }
347
348 void ControlAccessible::UnregisterPositionPropertyNotification()
349 {
350   auto                     control         = Dali::Toolkit::Control::DownCast(Self());
351   Internal::Control&       internalControl = Toolkit::Internal::GetImplementation(control);
352   Internal::Control::Impl& controlImpl     = Internal::Control::Impl::Get(internalControl);
353   controlImpl.UnregisterAccessibilityPositionPropertyNotification();
354 }
355
356 void ControlAccessible::RegisterPropertySetSignal()
357 {
358   auto                     control         = Dali::Toolkit::Control::DownCast(Self());
359   Internal::Control&       internalControl = Toolkit::Internal::GetImplementation(control);
360   Internal::Control::Impl& controlImpl     = Internal::Control::Impl::Get(internalControl);
361   controlImpl.RegisterAccessibilityPropertySetSignal();
362 }
363
364 void ControlAccessible::UnregisterPropertySetSignal()
365 {
366   auto                     control         = Dali::Toolkit::Control::DownCast(Self());
367   Internal::Control&       internalControl = Toolkit::Internal::GetImplementation(control);
368   Internal::Control::Impl& controlImpl     = Internal::Control::Impl::Get(internalControl);
369   controlImpl.UnregisterAccessibilityPropertySetSignal();
370 }
371
372 bool ControlAccessible::GrabHighlight()
373 {
374   Dali::Actor self                = Self();
375   auto        oldHighlightedActor = GetCurrentlyHighlightedActor();
376
377   if(!Dali::Accessibility::IsUp())
378   {
379     return false;
380   }
381
382   if(self == oldHighlightedActor)
383   {
384     return true;
385   }
386
387   // Clear the old highlight.
388   if(oldHighlightedActor)
389   {
390     auto oldHighlightedObject = Dali::Accessibility::Component::DownCast(Accessible::Get(oldHighlightedActor));
391     if(oldHighlightedObject)
392     {
393       oldHighlightedObject->ClearHighlight();
394     }
395   }
396
397   auto highlight = GetHighlightActor();
398   if(!highlight)
399   {
400     highlight = CreateHighlightIndicatorActor();
401     SetHighlightActor(highlight);
402   }
403
404   highlight.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
405   highlight.SetProperty(Actor::Property::POSITION_Z, 1.0f);
406   highlight.SetProperty(Actor::Property::POSITION, Vector2(0.0f, 0.0f));
407
408   // Need to set resize policy again, to update SIZE property which is set by
409   // NUIViewAccessible. The highlight could move from NUIViewAccessible to
410   // ControlAccessible. In this case, highlight has incorrect size.
411   highlight.SetResizePolicy(ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS);
412
413   // Remember the highlight actor, so that when the default is changed with
414   // SetHighlightActor(), the currently displayed highlight can still be cleared.
415   mCurrentHighlightActor = highlight;
416   ScrollToSelf();
417   self.Add(highlight);
418   SetCurrentlyHighlightedActor(self);
419   EmitHighlighted(true);
420   RegisterPositionPropertyNotification();
421   RegisterPropertySetSignal();
422
423   return true;
424 }
425
426 bool ControlAccessible::ClearHighlight()
427 {
428   Dali::Actor self = Self();
429
430   if(!Dali::Accessibility::IsUp())
431   {
432     return false;
433   }
434
435   if(GetCurrentlyHighlightedActor() == self)
436   {
437     UnregisterPropertySetSignal();
438     UnregisterPositionPropertyNotification();
439     self.Remove(mCurrentHighlightActor.GetHandle());
440     mCurrentHighlightActor = {};
441     SetCurrentlyHighlightedActor({});
442     EmitHighlighted(false);
443     return true;
444   }
445   return false;
446 }
447
448 std::string ControlAccessible::GetActionName(size_t index) const
449 {
450   if(index >= GetActionCount())
451   {
452     return {};
453   }
454
455   Dali::TypeInfo type;
456   Self().GetTypeInfo(type);
457   DALI_ASSERT_ALWAYS(type && "no TypeInfo object");
458   return type.GetActionName(index);
459 }
460
461 std::string ControlAccessible::GetLocalizedActionName(size_t index) const
462 {
463   return GetLocaleText(GetActionName(index));
464 }
465
466 std::string ControlAccessible::GetActionDescription(size_t index) const
467 {
468   return {};
469 }
470
471 size_t ControlAccessible::GetActionCount() const
472 {
473   Dali::TypeInfo type;
474   Self().GetTypeInfo(type);
475   DALI_ASSERT_ALWAYS(type && "no TypeInfo object");
476   return type.GetActionCount();
477 }
478
479 std::string ControlAccessible::GetActionKeyBinding(size_t index) const
480 {
481   return {};
482 }
483
484 bool ControlAccessible::DoAction(size_t index)
485 {
486   std::string actionName = GetActionName(index);
487   return Self().DoAction(actionName, {});
488 }
489
490 bool ControlAccessible::DoAction(const std::string& name)
491 {
492   return Self().DoAction(name, {});
493 }
494
495 bool ControlAccessible::DoGesture(const Dali::Accessibility::GestureInfo& gestureInfo)
496 {
497   auto control = Dali::Toolkit::Control::DownCast(Self());
498
499   Internal::Control&       internalControl = Toolkit::Internal::GetImplementation(control);
500   Internal::Control::Impl& controlImpl     = Internal::Control::Impl::Get(internalControl);
501
502   if(!controlImpl.mAccessibilityDoGestureSignal.Empty())
503   {
504     auto ret = std::make_pair(gestureInfo, false);
505     controlImpl.mAccessibilityDoGestureSignal.Emit(ret);
506     return ret.second;
507   }
508
509   return false;
510 }
511
512 std::vector<Dali::Accessibility::Relation> ControlAccessible::GetRelationSet()
513 {
514   auto control = Dali::Toolkit::Control::DownCast(Self());
515
516   return DevelControl::GetAccessibilityRelations(control);
517 }
518
519 bool ControlAccessible::ScrollToChild(Actor child)
520 {
521   return false;
522 }
523
524 Dali::Property::Index ControlAccessible::GetNamePropertyIndex()
525 {
526   return Actor::Property::NAME;
527 }
528
529 Dali::Property::Index ControlAccessible::GetDescriptionPropertyIndex()
530 {
531   return Dali::Property::INVALID_INDEX;
532 }
533
534 void ControlAccessible::SetLastPosition(Vector2 position)
535 {
536   mLastPosition = position;
537 }
538
539 Vector2 ControlAccessible::GetLastPosition() const
540 {
541   return mLastPosition;
542 }
543
544 } // namespace Dali::Toolkit::DevelControl