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