[AT-SPI] Support reading states and tooltips of ToggleButton
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / buttons / toggle-button-impl.cpp
1 /*
2  * Copyright (c) 2020 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 "toggle-button-impl.h"
20
21 // EXTERNAL INCLUDES
22 #include <dali/public-api/object/type-registry.h>
23 #include <dali/public-api/object/type-registry-helper.h>
24 #include <dali/devel-api/scripting/scripting.h>
25 #include <dali/public-api/object/property-array.h>
26 #include <dali/integration-api/debug.h>
27
28 // INTERNAL INCLUDES
29 #include <dali-toolkit/public-api/controls/image-view/image-view.h>
30 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
31 #include <dali-toolkit/devel-api/controls/control-devel.h>
32 #include <dali-toolkit/devel-api/visual-factory/visual-factory.h>
33 #include <dali-toolkit/public-api/visuals/visual-properties.h>
34 #include <dali-toolkit/public-api/align-enumerations.h>
35 #include <dali-toolkit/devel-api/controls/tooltip/tooltip-properties.h>
36 #include <dali-toolkit/public-api/visuals/text-visual-properties.h>
37 #include <dali-toolkit/devel-api/controls/control-devel.h>
38
39 #if defined(DEBUG_ENABLED)
40   extern Debug::Filter* gLogButtonFilter;
41 #endif
42
43 namespace Dali
44 {
45
46 namespace Toolkit
47 {
48
49 namespace Internal
50 {
51
52 namespace
53 {
54
55 BaseHandle Create()
56 {
57   return Toolkit::ToggleButton::New();
58 }
59
60 // Properties
61 DALI_TYPE_REGISTRATION_BEGIN( Toolkit::ToggleButton, Toolkit::Button, Create )
62
63 DALI_PROPERTY_REGISTRATION( Toolkit, ToggleButton, "stateVisuals",       ARRAY,     STATE_VISUALS        )
64 DALI_PROPERTY_REGISTRATION( Toolkit, ToggleButton, "tooltips",           ARRAY,     TOOLTIPS             )
65 DALI_PROPERTY_REGISTRATION( Toolkit, ToggleButton, "currentStateIndex",  INTEGER,   CURRENT_STATE_INDEX  )
66
67 DALI_TYPE_REGISTRATION_END()
68
69 } // unnamed namespace
70
71 Dali::Toolkit::ToggleButton ToggleButton::New()
72 {
73   DALI_LOG_INFO( gLogButtonFilter, Debug::General, "ToggleButton::New\n" );
74   // Create the implementation, temporarily owned on stack
75   IntrusivePtr< ToggleButton > internalToggleButton = new ToggleButton();
76
77   // Pass ownership to CustomActor
78   Dali::Toolkit::ToggleButton toggleButton( *internalToggleButton );
79
80   // Second-phase init of the implementation
81   // This can only be done after the CustomActor connection has been made...
82   internalToggleButton->Initialize();
83
84   return toggleButton;
85 }
86
87 ToggleButton::ToggleButton()
88 : Button(),
89   mToggleStates(),
90   mToggleVisuals(),
91   mToggleSelectedVisuals(),
92   mToggleDisabledVisuals(),
93   mToggleDisabledSelectedVisuals(),
94   mToggleTooltips(),
95   mCurrentToggleIndex(0)
96 {
97   DALI_LOG_INFO( gLogButtonFilter, Debug::General, "ToggleButton::Constructor\n" );
98   SetTogglableButton( false );
99 }
100
101 ToggleButton::~ToggleButton()
102 {
103 }
104
105 void ToggleButton::OnInitialize()
106 {
107   DALI_LOG_INFO( gLogButtonFilter, Debug::General, "ToggleButton::OnInitialize\n" );
108   Button::OnInitialize();
109
110   // Toggle button requires the Leave event.
111   Actor self = Self();
112   self.SetProperty( Actor::Property::LEAVE_REQUIRED, true );
113
114   DevelControl::SetAccessibilityConstructor( Self(), []( Dali::Actor actor ) {
115     return std::unique_ptr< Dali::Accessibility::Accessible >(
116       new AccessibleImpl( actor, Dali::Accessibility::Role::TOGGLE_BUTTON ) );
117   } );
118 }
119
120 void ToggleButton::SetProperty( BaseObject* object, Property::Index propertyIndex, const Property::Value& value )
121 {
122   Toolkit::ToggleButton toggleButton = Toolkit::ToggleButton::DownCast( Dali::BaseHandle( object ) );
123
124   DALI_LOG_INFO( gLogButtonFilter, Debug::Verbose, "ToggleButton::SetProperty index[%d]\n", propertyIndex );
125
126   if ( toggleButton )
127   {
128     ToggleButton& toggleButtonImpl( GetImplementation( toggleButton ) );
129
130     switch ( propertyIndex )
131     {
132       case Toolkit::ToggleButton::Property::STATE_VISUALS:
133       {
134         Property::Array stateArray;
135         if( value.Get( stateArray ) )
136         {
137           toggleButtonImpl.SetToggleStates( stateArray );
138         }
139
140         break;
141       }
142       case Toolkit::ToggleButton::Property::TOOLTIPS:
143       {
144         const Property::Array* tipArray = value.GetArray();
145         if( tipArray )
146         {
147           std::vector<std::string> tips;
148           size_t tipsCount = tipArray->Count();
149           tips.resize( tipsCount );
150           for( size_t i = 0; i != tipsCount; ++i )
151           {
152             tipArray->GetElementAt( i ).Get( tips[i] );
153           }
154           toggleButtonImpl.SetToggleTooltips(tips);
155         }
156         break;
157       }
158       default :
159       {
160         break;
161       }
162     } // end of switch
163   }
164 }
165
166 Property::Value ToggleButton::GetProperty( BaseObject* object, Property::Index propertyIndex )
167 {
168   Property::Value value;
169
170   Toolkit::ToggleButton toggleButton = Toolkit::ToggleButton::DownCast( Dali::BaseHandle( object ) );
171
172   DALI_LOG_INFO( gLogButtonFilter, Debug::Verbose, "ToggleButton::GetProperty index[%d]\n", propertyIndex );
173
174   if ( toggleButton )
175   {
176     ToggleButton& toggleButtonImpl( GetImplementation( toggleButton ) );
177
178     switch ( propertyIndex )
179     {
180       case Toolkit::ToggleButton::Property::STATE_VISUALS:
181       {
182         Property::Array array = toggleButtonImpl.GetToggleStates();
183         value = Property::Value( array );
184         break;
185       }
186       case Toolkit::ToggleButton::Property::TOOLTIPS:
187       {
188         Property::Value value1( Property::ARRAY );
189         Property::Array* tipArray = value1.GetArray();
190
191         if( tipArray )
192         {
193           std::vector<std::string> tips = toggleButtonImpl.GetToggleTooltips();
194           size_t tipsCount( tips.size() );
195           for( size_t i( 0 ); i != tipsCount; ++i )
196           {
197             tipArray->PushBack( tips[i] );
198           }
199         }
200         value = value1;
201         break;
202       }
203       case Toolkit::ToggleButton::Property::CURRENT_STATE_INDEX:
204       {
205         value = static_cast<int>(toggleButtonImpl.mCurrentToggleIndex);
206         break;
207       }
208     } // end of switch
209   }
210
211   return value;
212 }
213
214 void ToggleButton::CreateVisualsForAllStates( const Property::Array& states, std::vector<Toolkit::Visual::Base>& visuals )
215 {
216   DALI_LOG_INFO( gLogButtonFilter, Debug::General, "ToggleButton::CreateVisualsForAllStates\n" );
217
218   visuals.clear();
219
220   for ( unsigned int i = 0; i < states.Count(); i++ )
221   {
222     Property::Value value(  states[i] );
223
224     Toolkit::VisualFactory visualFactory = Toolkit::VisualFactory::Get();
225     Toolkit::Visual::Base stateVisual;
226
227     if ( value.GetType() == Property::MAP )
228     {
229       Property::Map *map = value.GetMap();
230       if( map && !map->Empty() ) // Empty map results in current visual removal.
231       {
232         DALI_LOG_INFO( gLogButtonFilter, Debug::Verbose, "ToggleButton::CreateVisuals Using Map\n" );
233         stateVisual = visualFactory.CreateVisual( *map );
234       }
235     }
236     else if ( value.GetType() ==  Property::STRING )
237     {
238       std::string imageUrl = value.Get<std::string>();
239       DALI_LOG_INFO( gLogButtonFilter, Debug::Verbose, "ToggleButton::CreateVisuals Using image URL\n" );
240       if ( !imageUrl.empty() )
241       {
242         stateVisual = visualFactory.CreateVisual( imageUrl, ImageDimensions() );
243       }
244     }
245
246     if ( stateVisual )
247     {
248       stateVisual.SetDepthIndex( DepthIndex::CONTENT );
249       visuals.push_back( stateVisual );
250     }
251   } // end of for
252   DALI_LOG_INFO( gLogButtonFilter, Debug::Verbose, "ToggleButton::CreateVisuals mToggleVisuals:%d\n", mToggleVisuals.size() );
253 }
254
255 void ToggleButton::SetToggleStates( const Property::Array& states )
256 { //this should really be generalized to be either string or maps so that any visual can be created.
257   DALI_LOG_INFO( gLogButtonFilter, Debug::General, "ToggleButton::SetToggleStates\n" );
258   if ( !states.Empty() )
259   {
260     mToggleStates.Clear();
261     mToggleStates = states;
262     /* New toggle button index from 0. */
263     mCurrentToggleIndex = 0;
264
265     // Create all visuals, save to mToggleVisuals.
266     CreateVisualsForAllStates( states, mToggleVisuals );
267     CreateVisualsForAllStates( states, mToggleSelectedVisuals );
268     CreateVisualsForAllStates( states, mToggleDisabledVisuals );
269     CreateVisualsForAllStates( states, mToggleDisabledSelectedVisuals );
270
271     DALI_LOG_INFO( gLogButtonFilter, Debug::General, "ToggleButton::Began to register visual.\n" );
272
273     PrepareVisual( Toolkit::Button::Property::UNSELECTED_VISUAL, mToggleVisuals[mCurrentToggleIndex] );
274     PrepareVisual( Toolkit::Button::Property::SELECTED_VISUAL, mToggleSelectedVisuals[mCurrentToggleIndex] );
275     PrepareVisual( Toolkit::Button::Property::DISABLED_UNSELECTED_VISUAL, mToggleDisabledVisuals[mCurrentToggleIndex] );
276     PrepareVisual( Toolkit::Button::Property::DISABLED_SELECTED_VISUAL, mToggleDisabledSelectedVisuals[mCurrentToggleIndex] );
277
278     RelayoutRequest();
279   }
280 }
281
282 Property::Array ToggleButton::GetToggleStates() const
283 {
284   return mToggleStates;
285 }
286
287 void ToggleButton::SetToggleTooltips( std::vector<std::string>& tips )
288 {
289   DALI_LOG_INFO( gLogButtonFilter, Debug::General, "ToggleButton::SetToggleTooltips\n" );
290   if ( !tips.empty() )
291   {
292     mToggleTooltips.clear();
293     mToggleTooltips.swap( tips );
294   }
295
296   if ( !mToggleTooltips.empty() && ( mCurrentToggleIndex < mToggleTooltips.size() ) )
297   {
298     Self().SetProperty( Toolkit::DevelControl::Property::TOOLTIP, mToggleTooltips[mCurrentToggleIndex] );
299   }
300
301   RelayoutRequest();
302 }
303
304 const std::vector<std::string>& ToggleButton::GetToggleTooltips() const
305 {
306   return mToggleTooltips;
307 }
308
309 void ToggleButton::PrepareVisual(Property::Index index, Toolkit::Visual::Base& visual)
310 {
311   bool enabled = false; // Disabled by default
312
313   // Unregister the visual with the given index if registered previously
314   if( DevelControl::GetVisual( *this, index ) )
315   {
316     // Check whether it was enabled to ensure we do the same with the new visual we're registering
317     enabled = DevelControl::IsVisualEnabled( *this, index );
318     DevelControl::UnregisterVisual( *this, index );
319   }
320
321   DevelControl::RegisterVisual( *this, index, visual, enabled );
322 }
323
324 void ToggleButton::RelayoutVisual( Property::Index index, const Vector2& size )
325 {
326   Toolkit::Visual::Base visual = DevelControl::GetVisual( *this, index );
327   if ( visual )
328   {
329     Size visualSize = Size::ZERO;
330     Vector2 visualPosition = Vector2::ZERO;
331
332     visual.GetNaturalSize( visualSize );
333
334     DALI_LOG_INFO( gLogButtonFilter, Debug::General, "ToggleButton::OnRelayout Setting visual size to(%f,%f)\n", visualSize.width, visualSize.height );
335     DALI_LOG_INFO( gLogButtonFilter, Debug::General, "ToggleButton::OnRelayout Setting visual position to(%f,%f)\n", visualPosition.x, visualPosition.y );
336
337     Property::Map visualTransform;
338     visualTransform.Add( Toolkit::Visual::Transform::Property::SIZE, visualSize )
339                    .Add( Toolkit::Visual::Transform::Property::OFFSET, visualPosition )
340                    .Add( Toolkit::Visual::Transform::Property::OFFSET_POLICY, Vector2( Toolkit::Visual::Transform::Policy::ABSOLUTE, Toolkit::Visual::Transform::Policy::ABSOLUTE ) )
341                    .Add( Toolkit::Visual::Transform::Property::SIZE_POLICY, Vector2( Toolkit::Visual::Transform::Policy::ABSOLUTE, Toolkit::Visual::Transform::Policy::ABSOLUTE ) )
342                    .Add( Toolkit::Visual::Transform::Property::ORIGIN, Toolkit::Align::CENTER )
343                    .Add( Toolkit::Visual::Transform::Property::ANCHOR_POINT, Toolkit::Align::CENTER );
344
345     visual.SetTransformAndSize( visualTransform, size );
346   }
347 }
348
349 void ToggleButton::OnRelayout( const Vector2& size, RelayoutContainer& container )
350 {
351   DALI_LOG_INFO( gLogButtonFilter, Debug::General, "ToggleButton::OnRelayout targetSize(%f,%f) ptr(%p)\n", size.width, size.height, this );
352
353   RelayoutVisual( Toolkit::Button::Property::UNSELECTED_VISUAL, size );
354   RelayoutVisual( Toolkit::Button::Property::SELECTED_VISUAL, size );
355   RelayoutVisual( Toolkit::Button::Property::DISABLED_UNSELECTED_VISUAL, size );
356   RelayoutVisual( Toolkit::Button::Property::DISABLED_SELECTED_VISUAL, size );
357 }
358
359 void ToggleButton::OnPressed()
360 {
361   DALI_LOG_INFO( gLogButtonFilter, Debug::General, "ToggleButton::OnPressed\n" );
362   // State index will add 1 only when button is pressed.
363   mCurrentToggleIndex = ( mCurrentToggleIndex + 1 ) % mToggleVisuals.size();
364
365   // Both create SelectedVisual and UnselectedVisual
366   PrepareVisual( Toolkit::Button::Property::UNSELECTED_VISUAL, mToggleVisuals[mCurrentToggleIndex] );
367   PrepareVisual( Toolkit::Button::Property::SELECTED_VISUAL, mToggleSelectedVisuals[mCurrentToggleIndex] );
368   PrepareVisual( Toolkit::Button::Property::DISABLED_UNSELECTED_VISUAL, mToggleDisabledVisuals[mCurrentToggleIndex] );
369   PrepareVisual( Toolkit::Button::Property::DISABLED_SELECTED_VISUAL, mToggleDisabledSelectedVisuals[mCurrentToggleIndex] );
370
371   //Need to check mCurrentToggleIndex, it must less than the size of mToggleTooltips.
372   if ( !mToggleTooltips.empty() && ( mCurrentToggleIndex < mToggleTooltips.size() ) )
373   {
374     Self().SetProperty( Toolkit::DevelControl::Property::TOOLTIP, mToggleTooltips[mCurrentToggleIndex] );
375   }
376
377   RelayoutRequest();
378 }
379
380 Dali::Accessibility::States ToggleButton::AccessibleImpl::CalculateStates()
381 {
382   auto states = Button::AccessibleImpl::CalculateStates();
383   auto button = Toolkit::ToggleButton::DownCast( self );
384   if( button.GetProperty<int>( Toolkit::ToggleButton::Property::CURRENT_STATE_INDEX ) )
385     states[Dali::Accessibility::State::CHECKED] = true;
386   return states;
387 }
388
389 std::string ToggleButton::AccessibleImpl::GetDescriptionRaw()
390 {
391   auto button = Toolkit::ToggleButton::DownCast( self );
392   auto index = button.GetProperty<int>( Toolkit::ToggleButton::Property::CURRENT_STATE_INDEX );
393   auto tooltips = button.GetProperty<Property::Array>( Toolkit::ToggleButton::Property::TOOLTIPS );
394
395   return tooltips[index].Get<std::string>();
396 }
397
398 void ToggleButton::OnStateChange( State newState )
399 {
400   // TODO: replace it with OnPropertySet hook once Button::Property::SELECTED will be consistently used
401   if (Dali::Accessibility::IsUp() && (newState == SELECTED_STATE || newState == UNSELECTED_STATE))
402   {
403     Dali::Accessibility::Accessible::Get(Self())->EmitStateChanged(
404       Dali::Accessibility::State::CHECKED, mCurrentToggleIndex ? 1 : 0, 0
405     );
406
407     if(Self() == Dali::Accessibility::Accessible::GetCurrentlyHighlightedActor())
408     {
409       Dali::Accessibility::Accessible::Get(Self())->Emit(Dali::Accessibility::ObjectPropertyChangeEvent::DESCRIPTION);
410     }
411   }
412 }
413
414 } // namespace Internal
415
416 } // namespace Toolkit
417
418 } // namespace Dali