[AT-SPI] Require ControlAccessible for Control
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / text-controls / text-selection-toolbar-impl.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 <dali-toolkit/internal/controls/text-controls/text-selection-toolbar-impl.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/public-api/math/vector2.h>
23 #include <dali/public-api/math/vector4.h>
24 #include <dali/public-api/object/property-map.h>
25 #include <dali/public-api/object/type-registry-helper.h>
26 #include <cfloat>
27
28 // INTERNAL INCLUDES
29 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
30 #include <dali-toolkit/devel-api/controls/control-devel.h>
31 #include <dali-toolkit/internal/helpers/color-conversion.h>
32 #include <dali-toolkit/public-api/controls/image-view/image-view.h>
33
34 namespace Dali
35 {
36 namespace Toolkit
37 {
38 namespace Internal
39 {
40 namespace
41 {
42 const Dali::Vector2 DEFAULT_SCROLL_BAR_PADDING(8.0f, 6.0f);
43
44 BaseHandle Create()
45 {
46   return Toolkit::TextSelectionToolbar::New();
47 }
48
49 // Setup properties, signals and actions using the type-registry.
50
51 DALI_TYPE_REGISTRATION_BEGIN(Toolkit::TextSelectionToolbar, Toolkit::Control, Create);
52
53 DALI_PROPERTY_REGISTRATION(Toolkit, TextSelectionToolbar, "maxSize", VECTOR2, MAX_SIZE)
54 DALI_PROPERTY_REGISTRATION(Toolkit, TextSelectionToolbar, "enableOvershoot", BOOLEAN, ENABLE_OVERSHOOT)
55 DALI_PROPERTY_REGISTRATION(Toolkit, TextSelectionToolbar, "enableScrollBar", BOOLEAN, ENABLE_SCROLL_BAR)
56 DALI_PROPERTY_REGISTRATION(Toolkit, TextSelectionToolbar, "scrollBarPadding", VECTOR2, SCROLL_BAR_PADDING)
57 DALI_PROPERTY_REGISTRATION(Toolkit, TextSelectionToolbar, "scrollView", MAP, SCROLL_VIEW)
58
59 DALI_TYPE_REGISTRATION_END()
60
61 } // namespace
62
63 Dali::Toolkit::TextSelectionToolbar TextSelectionToolbar::New()
64 {
65   // Create the implementation, temporarily owned by this handle on stack
66   IntrusivePtr<TextSelectionToolbar> impl = new TextSelectionToolbar();
67
68   // Pass ownership to CustomActor handle
69   Dali::Toolkit::TextSelectionToolbar handle(*impl);
70
71   // Second-phase init of the implementation
72   // This can only be done after the CustomActor connection has been made...
73   impl->Initialize();
74
75   return handle;
76 }
77
78 void TextSelectionToolbar::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
79 {
80   Toolkit::TextSelectionToolbar selectionPopup = Toolkit::TextSelectionToolbar::DownCast(Dali::BaseHandle(object));
81
82   if(selectionPopup)
83   {
84     TextSelectionToolbar& impl(GetImpl(selectionPopup));
85
86     switch(index)
87     {
88       case Toolkit::TextSelectionToolbar::Property::MAX_SIZE:
89       {
90         impl.SetPopupMaxSize(value.Get<Vector2>());
91         break;
92       }
93       case Toolkit::TextSelectionToolbar::Property::ENABLE_OVERSHOOT:
94       {
95         if(!impl.mScrollView)
96         {
97           impl.mScrollView = Toolkit::ScrollView::New();
98         }
99         impl.mScrollView.SetOvershootEnabled(value.Get<bool>());
100         break;
101       }
102       case Toolkit::TextSelectionToolbar::Property::ENABLE_SCROLL_BAR:
103       {
104         impl.SetUpScrollBar(value.Get<bool>());
105         break;
106       }
107       case Toolkit::TextSelectionToolbar::Property::SCROLL_BAR_PADDING:
108       {
109         impl.SetScrollBarPadding(value.Get<Vector2>());
110         break;
111       }
112       case Toolkit::TextSelectionToolbar::Property::SCROLL_VIEW:
113       {
114         // Get a Property::Map from the property if possible.
115         Property::Map setPropertyMap;
116         if(value.Get(setPropertyMap))
117         {
118           impl.ConfigureScrollview(setPropertyMap);
119         }
120         break;
121       }
122     } // switch
123   }   // TextSelectionToolbar
124 }
125
126 Property::Value TextSelectionToolbar::GetProperty(BaseObject* object, Property::Index index)
127 {
128   Property::Value value;
129
130   Toolkit::TextSelectionToolbar selectionPopup = Toolkit::TextSelectionToolbar::DownCast(Dali::BaseHandle(object));
131
132   if(selectionPopup)
133   {
134     TextSelectionToolbar& impl(GetImpl(selectionPopup));
135
136     switch(index)
137     {
138       case Toolkit::TextSelectionToolbar::Property::MAX_SIZE:
139       {
140         value = impl.GetPopupMaxSize();
141         break;
142       }
143       case Toolkit::TextSelectionToolbar::Property::ENABLE_OVERSHOOT:
144       {
145         value = impl.mScrollView.IsOvershootEnabled();
146         break;
147       }
148       case Toolkit::TextSelectionToolbar::Property::ENABLE_SCROLL_BAR:
149       {
150         value = impl.mScrollBar ? true : false;
151         break;
152       }
153       case Toolkit::TextSelectionToolbar::Property::SCROLL_BAR_PADDING:
154       {
155         value = impl.GetScrollBarPadding();
156         break;
157       }
158     } // switch
159   }
160   return value;
161 }
162
163 void TextSelectionToolbar::OnInitialize()
164 {
165   SetUp();
166
167   DevelControl::SetAccessibilityConstructor(Self(), [](Dali::Actor actor) {
168     return std::make_unique<DevelControl::ControlAccessible>(actor, Dali::Accessibility::Role::TOOL_BAR);
169   });
170 }
171
172 void TextSelectionToolbar::OnRelayout(const Vector2& size, RelayoutContainer& container)
173 {
174   float width = std::max(mTableOfButtons.GetNaturalSize().width, size.width);
175   mRulerX->SetDomain(RulerDomain(0.0, width, true));
176   mScrollView.SetRulerX(mRulerX);
177
178   if(mScrollBar)
179   {
180     float barWidth = std::min(mTableOfButtons.GetNaturalSize().width, size.width) - 2.f * mScrollBarPadding.x;
181     mScrollBar.SetProperty(Actor::Property::SIZE, Vector2(0.0f, barWidth));
182   }
183 }
184
185 void TextSelectionToolbar::SetPopupMaxSize(const Size& maxSize)
186 {
187   mMaxSize = maxSize;
188   if(mScrollView && mToolbarActor)
189   {
190     mScrollView.SetProperty(Actor::Property::MAXIMUM_SIZE, mMaxSize);
191     mToolbarActor.SetProperty(Actor::Property::MAXIMUM_SIZE, mMaxSize);
192   }
193 }
194
195 const Dali::Vector2& TextSelectionToolbar::GetPopupMaxSize() const
196 {
197   return mMaxSize;
198 }
199
200 void TextSelectionToolbar::SetUpScrollView()
201 {
202   mScrollView.SetProperty(Dali::Actor::Property::NAME, "TextSelectionScrollView");
203   mScrollView.SetResizePolicy(ResizePolicy::FIT_TO_CHILDREN, Dimension::ALL_DIMENSIONS);
204   mScrollView.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER_LEFT);
205   mScrollView.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER_LEFT);
206
207   mScrollView.SetScrollingDirection(PanGestureDetector::DIRECTION_HORIZONTAL, Degree(40.0f));
208   mScrollView.SetAxisAutoLock(true);
209   mScrollView.ScrollStartedSignal().Connect(this, &TextSelectionToolbar::OnScrollStarted);
210   mScrollView.ScrollCompletedSignal().Connect(this, &TextSelectionToolbar::OnScrollCompleted);
211   mScrollView.SetProperty(Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_TO_BOUNDING_BOX); // In a new layer, so clip to scroll-view's bounding box
212
213   mRulerX = new DefaultRuler(); // IntrusivePtr which is unreferenced when ScrollView is destroyed.
214
215   RulerPtr rulerY = new DefaultRuler(); // IntrusivePtr which is unreferenced when ScrollView is destroyed.
216   rulerY->Disable();
217   mScrollView.SetRulerY(rulerY);
218
219   mScrollView.SetOvershootEnabled(true);
220 }
221
222 void TextSelectionToolbar::SetUp()
223 {
224   Actor self = Self();
225
226   self.SetResizePolicy(ResizePolicy::FIT_TO_CHILDREN, Dimension::ALL_DIMENSIONS);
227
228   // Create Actor to house the toolbar.
229   mToolbarActor = Actor::New();
230   mToolbarActor.SetResizePolicy(ResizePolicy::FIT_TO_CHILDREN, Dimension::ALL_DIMENSIONS);
231   mToolbarActor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
232   mToolbarActor.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER);
233
234   if(!mScrollView)
235   {
236     mScrollView = Toolkit::ScrollView::New();
237   }
238   SetUpScrollView();
239
240   // Toolbar must start with at least one option, adding further options with increase it's size
241   mTableOfButtons = Dali::Toolkit::TableView::New(1, 1);
242   mTableOfButtons.SetFitHeight(0);
243   mTableOfButtons.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER_LEFT);
244   mTableOfButtons.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER_LEFT);
245
246   mScrollView.Add(mTableOfButtons);
247   mToolbarActor.Add(mScrollView);
248
249   self.Add(mToolbarActor);
250 }
251
252 void TextSelectionToolbar::SetUpScrollBar(bool enable)
253 {
254   if(enable)
255   {
256     if(!mScrollBar)
257     {
258       Toolkit::ImageView indicator = Toolkit::ImageView::New();
259       indicator.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::TOP_LEFT);
260       indicator.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
261       indicator.SetStyleName("TextSelectionScrollIndicator");
262
263       mScrollBar = Toolkit::ScrollBar::New(Toolkit::ScrollBar::HORIZONTAL);
264       mScrollBar.SetProperty(Dali::Actor::Property::NAME, "Text popup scroll bar");
265       mScrollBar.SetStyleName("TextSelectionScrollBar");
266       mScrollBar.SetProperty(Actor::Property::PARENT_ORIGIN, ParentOrigin::BOTTOM_LEFT);
267       mScrollBar.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT);
268       mScrollBar.SetProperty(Actor::Property::POSITION, Vector2(mScrollBarPadding.x, -mScrollBarPadding.y));
269       mScrollBar.SetResizePolicy(Dali::ResizePolicy::FIT_TO_CHILDREN, Dali::Dimension::WIDTH);
270       mScrollBar.SetProperty(Actor::Property::ORIENTATION, Quaternion(Quaternion(Radian(1.5f * Math::PI), Vector3::ZAXIS)));
271       mScrollBar.SetScrollIndicator(indicator);
272       mScrollBar.GetPanGestureDetector().DetachAll();
273       mScrollView.Add(mScrollBar);
274     }
275   }
276   else
277   {
278     UnparentAndReset(mScrollBar);
279   }
280 }
281
282 void TextSelectionToolbar::OnScrollStarted(const Vector2& position)
283 {
284   if(mFirstScrollEnd)
285   {
286     mScrollView.SetOvershootEnabled(true);
287   }
288   mTableOfButtons.SetProperty(Actor::Property::SENSITIVE, false);
289 }
290
291 void TextSelectionToolbar::OnScrollCompleted(const Vector2& position)
292 {
293   mFirstScrollEnd = true;
294   mTableOfButtons.SetProperty(Actor::Property::SENSITIVE, true);
295 }
296
297 void TextSelectionToolbar::AddOption(Actor& option)
298 {
299   mTableOfButtons.AddChild(option, Toolkit::TableView::CellPosition(0, mIndexInTable));
300   mTableOfButtons.SetFitWidth(mIndexInTable);
301   mIndexInTable++;
302 }
303
304 void TextSelectionToolbar::AddDivider(Actor& divider)
305 {
306   AddOption(divider);
307   mDividerIndexes.PushBack(mIndexInTable - 1u);
308 }
309
310 void TextSelectionToolbar::ResizeDividers(Size& size)
311 {
312   for(unsigned int i = 0; i < mDividerIndexes.Count(); ++i)
313   {
314     Actor divider = mTableOfButtons.GetChildAt(Toolkit::TableView::CellPosition(0, mDividerIndexes[i]));
315     divider.SetProperty(Actor::Property::SIZE, size);
316   }
317   RelayoutRequest();
318 }
319
320 void TextSelectionToolbar::RaiseAbove(Actor target)
321 {
322   mToolbarActor.RaiseAbove(target);
323 }
324
325 void TextSelectionToolbar::SetScrollBarPadding(const Vector2& padding)
326 {
327   mScrollBarPadding = padding;
328   if(mScrollBar)
329   {
330     mScrollBar.SetProperty(Actor::Property::POSITION, Vector2(mScrollBarPadding.x, -mScrollBarPadding.y));
331   }
332
333   RelayoutRequest();
334 }
335
336 void TextSelectionToolbar::ScrollTo(const Vector2& position)
337 {
338   mFirstScrollEnd = false;
339   mScrollView.SetOvershootEnabled(false);
340   mScrollView.ScrollTo(position, 0.f);
341 }
342
343 void TextSelectionToolbar::ConfigureScrollview(const Property::Map& properties)
344 {
345   // Set any properties specified for the label by iterating through all property key-value pairs.
346   for(unsigned int i = 0, mapCount = properties.Count(); i < mapCount; ++i)
347   {
348     const StringValuePair& propertyPair(properties.GetPair(i));
349
350     // Convert the property string to a property index.
351     Property::Index setPropertyIndex = mScrollView.GetPropertyIndex(propertyPair.first);
352     if(setPropertyIndex != Property::INVALID_INDEX)
353     {
354       // Convert the string representation of a color into a Vector4
355       if(setPropertyIndex == Toolkit::Scrollable::Property::OVERSHOOT_EFFECT_COLOR)
356       {
357         Vector4 color;
358         if(ConvertPropertyToColor(propertyPair.second, color))
359         {
360           mScrollView.SetOvershootEffectColor(color);
361         }
362       }
363       else
364       {
365         // If the conversion worked, we have a valid property index,
366         // Set the property to the new value.
367         mScrollView.SetProperty(setPropertyIndex, propertyPair.second);
368       }
369     }
370   }
371
372   RelayoutRequest();
373 }
374
375 const Vector2& TextSelectionToolbar::GetScrollBarPadding() const
376 {
377   return mScrollBarPadding;
378 }
379
380 TextSelectionToolbar::TextSelectionToolbar()
381 : Control(ControlBehaviour(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT))),
382   mMaxSize(),
383   mScrollBarPadding(DEFAULT_SCROLL_BAR_PADDING),
384   mIndexInTable(0),
385   mDividerIndexes(),
386   mFirstScrollEnd(false)
387 {
388 }
389
390 TextSelectionToolbar::~TextSelectionToolbar()
391 {
392   mRulerX.Reset();
393 }
394
395 } // namespace Internal
396
397 } // namespace Toolkit
398
399 } // namespace Dali