b59c50a01958ca9ecf80829e4b31fd48cec1990f
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / BaseComponents / ViewAccessibility.cs
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 using System;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using System.Runtime.InteropServices;
22 using Tizen.NUI;
23
24 namespace Tizen.NUI.BaseComponents
25 {
26     /// <summary>
27     /// AccessibilityRange is used to store data related with Text.
28     /// </summary>
29     [EditorBrowsable(EditorBrowsableState.Never)]
30     public class AccessibilityRange
31     {
32         /// <summary>
33         /// Start position in stored text.
34         /// </summary>
35         public int StartOffset { get; set; } = 0;
36
37         /// <summary>
38         /// End position in stored text.
39         /// </summary>
40         public int EndOffset { get; set; } = 0;
41
42         /// <summary>
43         /// Text content in stored text.
44         /// </summary>
45         public string Content { get; set; } = "";
46     }
47
48     /// <summary>
49     /// View is the base class for all views.
50     /// </summary>
51     /// <since_tizen> 3 </since_tizen>
52     public partial class View
53     {
54         ///////////////////////////////////////////////////////////////////
55         // ****************** Accessibility Attributes ****************** //
56         ///////////////////////////////////////////////////////////////////
57
58         /// <summary>
59         /// Dictionary of accessibility attributes (key-value pairs of strings).
60         /// </summary>
61         [EditorBrowsable(EditorBrowsableState.Never)]
62         public Dictionary<string, string> AccessibilityAttributes { get; } = new Dictionary<string, string>();
63
64         /// <summary>
65         /// Dictionary of dynamically-evaluated accessibility attributes (key-value pairs of strings).
66         /// </summary>
67         [EditorBrowsable(EditorBrowsableState.Never)]
68         public Dictionary<string, Func<string>> AccessibilityDynamicAttributes { get; } = new Dictionary<string, Func<string>>();
69
70         ///////////////////////////////////////////////////////////////////
71         // ************************** Highlight ************************ //
72         ///////////////////////////////////////////////////////////////////
73
74         /// <summary>
75         /// Clears accessibility highlight.
76         /// </summary>
77         /// <returns>True if cleared, otherwise false when it is not possible</returns>
78         [EditorBrowsable(EditorBrowsableState.Never)]
79         public bool ClearAccessibilityHighlight()
80         {
81             bool result = Interop.ControlDevel.DaliToolkitDevelControlClearAccessibilityHighlight(SwigCPtr);
82             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
83             return result;
84         }
85
86         /// <summary>
87         /// Grabs accessibility highlight.
88         /// </summary>
89         /// <returns>True if cleared, otherwise false when it is not possible</returns>
90         [EditorBrowsable(EditorBrowsableState.Never)]
91         public bool GrabAccessibilityHighlight()
92         {
93             bool result = Interop.ControlDevel.DaliToolkitDevelControlGrabAccessibilityHighlight(SwigCPtr);
94             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
95             return result;
96         }
97
98         /// <summary>
99         /// Flag to check whether this view is highlighted or not.
100         /// </summary>
101         [EditorBrowsable(EditorBrowsableState.Never)]
102         protected bool IsHighlighted
103         {
104             get
105             {
106                 return (this == Accessibility.Accessibility.GetCurrentlyHighlightedView());
107             }
108         }
109
110         ///////////////////////////////////////////////////////////////////
111         // ****************** Accessibility Relations ******************* //
112         ///////////////////////////////////////////////////////////////////
113
114         /// <summary>
115         /// Creates relation between objects.
116         /// </summary>
117         /// <param name="second">Object which will be in relation.</param>
118         /// <param name="relation">Relation type.</param>
119         /// <exception cref="ArgumentNullException">You must pass valid object. NULL could not be in relation.</exception>
120         [EditorBrowsable(EditorBrowsableState.Never)]
121         public void AppendAccessibilityRelation(View second, AccessibilityRelationType relation)
122         {
123             if (second is null)
124             {
125                 throw new ArgumentNullException(nameof(second));
126             }
127
128             Interop.ControlDevel.DaliToolkitDevelControlAppendAccessibilityRelation(SwigCPtr, second.SwigCPtr, (int)relation);
129             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
130         }
131
132         /// <summary>
133         /// Removes accessibility relation.
134         /// </summary>
135         /// <param name="second">Object which will be removed in relation</param>
136         /// <param name="relation">Relation type</param>
137         [EditorBrowsable(EditorBrowsableState.Never)]
138         public void RemoveAccessibilityRelation(View second, AccessibilityRelationType relation)
139         {
140             if (second is null)
141             {
142                 throw new ArgumentNullException(nameof(second));
143             }
144
145             Interop.ControlDevel.DaliToolkitDevelControlRemoveAccessibilityRelation(SwigCPtr, second.SwigCPtr, (int)relation);
146             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
147         }
148
149         /// <summary>
150         /// Removes all previously appended relations.
151         /// </summary>
152         [EditorBrowsable(EditorBrowsableState.Never)]
153         public void ClearAccessibilityRelations()
154         {
155             Interop.ControlDevel.DaliToolkitDevelControlClearAccessibilityRelations(SwigCPtr);
156             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
157         }
158
159         /// <summary>
160         /// Gets accessibility collection connected with the current object.
161         /// </summary>
162         /// <returns>A dictionary mapping a relation type to a set of objects in that relation</returns>
163         [EditorBrowsable(EditorBrowsableState.Never)]
164         public Dictionary<AccessibilityRelationType, List<View>> GetAccessibilityRelations()
165         {
166             var list = new List<KeyValuePair<int, IntPtr>>();
167             var listHandle = GCHandle.Alloc(list);
168             var callback = new Interop.ControlDevel.GetAccessibilityRelationsCallback(GetAccessibilityRelationsCallback);
169
170             Interop.ControlDevel.DaliToolkitDevelControlGetAccessibilityRelations(SwigCPtr, callback, GCHandle.ToIntPtr(listHandle));
171             listHandle.Free();
172             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
173
174             var result = new Dictionary<AccessibilityRelationType, List<View>>();
175
176             foreach (var pair in list)
177             {
178                 var key = (AccessibilityRelationType)pair.Key;
179                 var value = this.GetInstanceSafely<View>(pair.Value);
180
181                 if (!result.ContainsKey(key))
182                 {
183                     result[key] = new List<View>();
184                 }
185
186                 result[key].Add(value);
187             }
188
189             return result;
190         }
191
192         private static void GetAccessibilityRelationsCallback(int relationType, IntPtr relationTarget, IntPtr userData)
193         {
194             var handle = GCHandle.FromIntPtr(userData);
195             var list = (List<KeyValuePair<int, IntPtr>>)handle.Target;
196
197             list.Add(new KeyValuePair<int, IntPtr>(relationType, relationTarget));
198         }
199
200         ///////////////////////////////////////////////////////////////////
201         // ********************* ReadingInfoType *********************** //
202         ///////////////////////////////////////////////////////////////////
203
204         /// <summary>
205         /// Sets accessibility reading information.
206         /// </summary>
207         /// <param name="type">Reading information type</param>
208         [EditorBrowsable(EditorBrowsableState.Never)]
209         public void SetAccessibilityReadingInfoTypes(AccessibilityReadingInfoTypes type)
210         {
211             Interop.ControlDevel.DaliToolkitDevelControlSetAccessibilityReadingInfoTypes(SwigCPtr, (int)type);
212             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
213         }
214
215         /// <summary>
216         /// Gets accessibility reading information.
217         /// </summary>
218         /// <returns>Reading information type</returns>
219         [EditorBrowsable(EditorBrowsableState.Never)]
220         public AccessibilityReadingInfoTypes GetAccessibilityReadingInfoTypes()
221         {
222             AccessibilityReadingInfoTypes result = (AccessibilityReadingInfoTypes)Interop.ControlDevel.DaliToolkitDevelControlGetAccessibilityReadingInfoTypes(SwigCPtr);
223             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
224             return result;
225         }
226
227         ///////////////////////////////////////////////////////////////////
228         // ******************** Accessibility States ******************* //
229         ///////////////////////////////////////////////////////////////////
230
231         /// <summary>
232         /// Notifies sending notifications about the current states to accessibility clients.
233         /// </summary>
234         /// <remarks>
235         /// In essence, this is equivalent to calling EmitAccessibilityStateChangedEvent in a loop for all specified states.
236         /// If recursive mode is specified, all children of the Accessibility object will also re-emit the states.
237         /// </remarks>
238         /// <param name="states">Accessibility States</param>
239         /// <param name="notifyMode">Controls the notification strategy</param>
240         [EditorBrowsable(EditorBrowsableState.Never)]
241         public void NotifyAccessibilityStatesChange(AccessibilityStates states, AccessibilityStatesNotifyMode notifyMode)
242         {
243             if (states is null)
244             {
245                 throw new ArgumentNullException(nameof(states));
246             }
247
248             Interop.ControlDevel.DaliToolkitDevelControlNotifyAccessibilityStateChange(SwigCPtr, states.BitMask, (int)notifyMode);
249             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
250         }
251
252         /// <summary>
253         /// Gets Accessibility States.
254         /// </summary>
255         /// <returns>Accessibility States</returns>
256         [EditorBrowsable(EditorBrowsableState.Never)]
257         public AccessibilityStates GetAccessibilityStates()
258         {
259             var result = new AccessibilityStates { BitMask = Interop.ControlDevel.DaliToolkitDevelControlGetAccessibilityStates(SwigCPtr) };
260             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
261             return result;
262         }
263
264         ///////////////////////////////////////////////////////////////////
265         // ************************ Accessible ************************* //
266         ///////////////////////////////////////////////////////////////////
267
268         /// <summary>
269         /// Emits accessibility property changed event.
270         /// </summary>
271         /// <param name="changeEvent">Property changed event</param>
272         [EditorBrowsable(EditorBrowsableState.Never)]
273         public void EmitAccessibilityEvent(AccessibilityPropertyChangeEvent changeEvent)
274         {
275             Interop.ControlDevel.DaliAccessibilityEmitAccessibilityEvent(SwigCPtr, Convert.ToInt32(changeEvent));
276             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
277         }
278
279         /// <summary>
280         /// Emits accessibility states changed event.
281         /// </summary>
282         /// <param name="state">Accessibility state</param>
283         /// <param name="equal">True if the state is set or enabled, otherwise false</param>
284         [EditorBrowsable(EditorBrowsableState.Never)]
285         public void EmitAccessibilityStateChangedEvent(AccessibilityState state, bool equal)
286         {
287             Interop.ControlDevel.DaliAccessibilityEmitAccessibilityStateChangedEvent(SwigCPtr, (int)state, Convert.ToInt32(equal));
288             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
289         }
290
291         /// <summary>
292         /// Emits accessibility text inserted event.
293         /// </summary>
294         /// <param name="cursorPosition">Text cursor position</param>
295         /// <param name="length">Text length</param>
296         /// <param name="content">Inserted text content</param>
297         [EditorBrowsable(EditorBrowsableState.Never)]
298         public void EmitTextInsertedEvent(int cursorPosition, int length, string content)
299         {
300             Interop.ControlDevel.DaliAccessibilityEmitTextInsertedEvent(SwigCPtr, cursorPosition, length, content);
301             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
302         }
303
304         /// <summary>
305         /// Emits accessibility text deleted event.
306         /// </summary>
307         /// <param name="cursorPosition">Text cursor position</param>
308         /// <param name="length">Text length</param>
309         /// <param name="content">Inserted text content</param>
310         [EditorBrowsable(EditorBrowsableState.Never)]
311         public void EmitTextDeletedEvent(int cursorPosition, int length, string content)
312         {
313             Interop.ControlDevel.DaliAccessibilityEmitTextDeletedEvent(SwigCPtr, cursorPosition, length, content);
314             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
315         }
316
317         /// <summary>
318         /// Emits accessibility text cursor moved event.
319         /// </summary>
320         /// <param name="cursorPosition">The new cursor position</param>
321         [EditorBrowsable(EditorBrowsableState.Never)]
322         public void EmitTextCursorMovedEvent(int cursorPosition)
323         {
324             Interop.ControlDevel.DaliAccessibilityEmitTextCursorMovedEvent(SwigCPtr, cursorPosition);
325             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
326         }
327
328         /// <summary>
329         /// Modifiable collection of suppressed AT-SPI events (D-Bus signals).
330         /// </summary>
331         [EditorBrowsable(EditorBrowsableState.Never)]
332         public AccessibilityEvents AccessibilitySuppressedEvents
333         {
334             get
335             {
336                 return new AccessibilityEvents { Owner = this };
337             }
338         }
339
340         ///////////////////////////////////////////////////////////////////
341         // ************************** Bridge *************************** //
342         ///////////////////////////////////////////////////////////////////
343
344         /// <summary>
345         /// Registers component as a source of an accessibility "default label".
346         /// The "Default label" is a text that could be read by screen-reader immediately
347         /// after the navigation context has changed (window activates, popup shows up, tab changes)
348         /// and before first UI element is highlighted.
349         /// </summary>
350         [EditorBrowsable(EditorBrowsableState.Never)]
351         public void RegisterDefaultLabel()
352         {
353             Interop.ControlDevel.DaliAccessibilityBridgeRegisterDefaultLabel(SwigCPtr);
354             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
355         }
356
357         /// <summary>
358         /// Unregisters component that has been registered previously as a source of an accessibility "default label".
359         /// The "Default label" is a text that could be read by screen-reader immediately
360         /// after the navigation context has changed (window activates, popup shows up, tab changes)
361         /// and before first UI element is highlighted.
362         /// </summary>
363         [EditorBrowsable(EditorBrowsableState.Never)]
364         public void UnregisterDefaultLabel()
365         {
366             Interop.ControlDevel.DaliAccessibilityBridgeUnregisterDefaultLabel(SwigCPtr);
367             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
368         }
369
370         [EditorBrowsable(EditorBrowsableState.Never)]
371         protected override void Dispose(bool disposing)
372         {
373             if (disposed)
374             {
375                 return;
376             }
377
378             internalName = null;
379
380             if (SwigCPtr.Handle != IntPtr.Zero && global::System.Threading.Thread.CurrentThread.ManagedThreadId == Registry.Instance.SavedApplicationThread.ManagedThreadId)
381             {
382                 Interop.ControlDevel.DaliAccessibilityDetachAccessibleObject(SwigCPtr);
383                 if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
384             }
385
386             if (disposing == false)
387             {
388                 if (IsNativeHandleInvalid() || SwigCMemOwn == false)
389                 {
390                     // at this case, implicit nor explicit dispose is not required. No native object is made.
391                     disposed = true;
392                     return;
393                 }
394             }
395
396             if (disposing)
397             {
398                 Unparent();
399             }
400
401             base.Dispose(disposing);
402         }
403
404         [EditorBrowsable(EditorBrowsableState.Never)]
405         protected static readonly string AccessibilityActivateAction = "activate";
406         [EditorBrowsable(EditorBrowsableState.Never)]
407         protected static readonly string AccessibilityReadingSkippedAction = "ReadingSkipped";
408         [EditorBrowsable(EditorBrowsableState.Never)]
409         protected static readonly string AccessibilityReadingCancelledAction = "ReadingCancelled";
410         [EditorBrowsable(EditorBrowsableState.Never)]
411         protected static readonly string AccessibilityReadingStoppedAction = "ReadingStopped";
412         [EditorBrowsable(EditorBrowsableState.Never)]
413         protected static readonly string AccessibilityReadingPausedAction = "ReadingPaused";
414         [EditorBrowsable(EditorBrowsableState.Never)]
415         protected static readonly string AccessibilityReadingResumedAction = "ReadingResumed";
416
417         [EditorBrowsable(EditorBrowsableState.Never)]
418         private static readonly string[] AccessibilityActions = {
419             AccessibilityActivateAction,
420             AccessibilityReadingSkippedAction,
421             AccessibilityReadingCancelledAction,
422             AccessibilityReadingStoppedAction,
423             AccessibilityReadingPausedAction,
424             AccessibilityReadingResumedAction,
425         };
426
427         [EditorBrowsable(EditorBrowsableState.Never)]
428         protected virtual string AccessibilityGetName()
429         {
430             return "";
431         }
432
433         [EditorBrowsable(EditorBrowsableState.Never)]
434         protected virtual string AccessibilityGetDescription()
435         {
436             return "";
437         }
438
439         [EditorBrowsable(EditorBrowsableState.Never)]
440         protected virtual bool AccessibilityDoAction(string name)
441         {
442             if (name == AccessibilityActivateAction)
443             {
444                 if (ActivateSignal?.Empty() == false)
445                 {
446                     ActivateSignal?.Emit();
447                     return true;
448                 }
449                 else
450                 {
451                     return OnAccessibilityActivated();
452                 }
453             }
454             else if (name == AccessibilityReadingSkippedAction)
455             {
456                 if (ReadingSkippedSignal?.Empty() == false)
457                 {
458                     ReadingSkippedSignal?.Emit();
459                     return true;
460                 }
461                 else
462                 {
463                     return OnAccessibilityReadingSkipped();
464                 }
465             }
466             else if (name == AccessibilityReadingCancelledAction)
467             {
468                 if (ReadingCancelledSignal?.Empty() == false)
469                 {
470                     ReadingCancelledSignal?.Emit();
471                     return true;
472                 }
473                 else
474                 {
475                     return OnAccessibilityReadingCancelled();
476                 }
477             }
478             else if (name == AccessibilityReadingStoppedAction)
479             {
480                 if (ReadingStoppedSignal?.Empty() == false)
481                 {
482                     ReadingStoppedSignal?.Emit();
483                     return true;
484                 }
485                 else
486                 {
487                     return OnAccessibilityReadingStopped();
488                 }
489             }
490             else if (name == AccessibilityReadingPausedAction)
491             {
492                 if (ReadingPausedSignal?.Empty() == false)
493                 {
494                     ReadingPausedSignal?.Emit();
495                     return true;
496                 }
497                 else
498                 {
499                     return OnAccessibilityReadingPaused();
500                 }
501             }
502             else if (name == AccessibilityReadingResumedAction)
503             {
504                 if (ReadingResumedSignal?.Empty() == false)
505                 {
506                     ReadingResumedSignal?.Emit();
507                     return true;
508                 }
509                 else
510                 {
511                     return OnAccessibilityReadingResumed();
512                 }
513             }
514             else
515             {
516                 return false;
517             }
518         }
519
520         [EditorBrowsable(EditorBrowsableState.Never)]
521         protected virtual AccessibilityStates AccessibilityCalculateStates()
522         {
523             var states = AccessibilityInitialStates;
524
525             states[AccessibilityState.Focused] = this.State == States.Focused;
526             states[AccessibilityState.Enabled] = this.State != States.Disabled;
527             states[AccessibilityState.Sensitive] = this.Sensitive;
528
529             return states;
530         }
531
532         [EditorBrowsable(EditorBrowsableState.Never)]
533         protected virtual int AccessibilityGetActionCount()
534         {
535             return AccessibilityActions.Length;
536         }
537
538         [EditorBrowsable(EditorBrowsableState.Never)]
539         protected virtual string AccessibilityGetActionName(int index)
540         {
541             if (index >= 0 && index < AccessibilityActions.Length)
542             {
543                 return AccessibilityActions[index];
544             }
545             else
546             {
547                 return "";
548             }
549         }
550
551         [EditorBrowsable(EditorBrowsableState.Never)]
552         protected virtual bool AccessibilityIsScrollable()
553         {
554             return false;
555         }
556
557         [EditorBrowsable(EditorBrowsableState.Never)]
558         protected virtual bool AccessibilityScrollToChild(View child)
559         {
560             return false;
561         }
562
563         /// <summary>
564         /// This method is called when the control accessibility is activated.<br />
565         /// Derived classes should override this to perform custom accessibility activation.<br />
566         /// </summary>
567         /// <returns>True if this control can perform accessibility activation.</returns>
568         [EditorBrowsable(EditorBrowsableState.Never)]
569         protected virtual bool OnAccessibilityActivated()
570         {
571             return FocusManager.Instance.SetCurrentFocusView(this);
572         }
573
574         /// <summary>
575         /// This method is called when reading is skipped.
576         /// </summary>
577         /// <returns>True if information was served.</returns>
578         [EditorBrowsable(EditorBrowsableState.Never)]
579         protected virtual bool OnAccessibilityReadingSkipped()
580         {
581             return false;
582         }
583
584         /// <summary>
585         /// This method is called when reading is cancelled.
586         /// </summary>
587         /// <returns>True if information was served.</returns>
588         [EditorBrowsable(EditorBrowsableState.Never)]
589         protected virtual bool OnAccessibilityReadingCancelled()
590         {
591             return false;
592         }
593
594         /// <summary>
595         /// This method is called when reading is stopped.
596         /// </summary>
597         /// <returns>True if information was served.</returns>
598         [EditorBrowsable(EditorBrowsableState.Never)]
599         protected virtual bool OnAccessibilityReadingStopped()
600         {
601             return false;
602         }
603
604         /// <summary>
605         /// This method is called when reading was paused.
606         /// </summary>
607         /// <returns>True if information was served.</returns>
608         [EditorBrowsable(EditorBrowsableState.Never)]
609         protected virtual bool OnAccessibilityReadingPaused()
610         {
611             return false;
612         }
613
614         /// <summary>
615         /// This method is called when reading is resumed.
616         /// </summary>
617         /// <returns>True if information was served.</returns>
618         [EditorBrowsable(EditorBrowsableState.Never)]
619         protected virtual bool OnAccessibilityReadingResumed()
620         {
621             return false;
622         }
623     }
624 }