Merge remote-tracking branch 'origin/master' into tizen
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / XamlBinding / Element.cs
1 /*
2  * Copyright(c) 2022 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.Collections.ObjectModel;
21 using System.ComponentModel;
22 using System.Runtime.CompilerServices;
23 using System.Xml;
24 using Tizen.NUI.Binding.Internals;
25
26 namespace Tizen.NUI.Binding
27 {
28     /// <summary>
29     /// Provides the base class for all Tizen.NUI.Binding hierarchal elements. This class contains all the methods and properties required to represent an element in the Tizen.NUI.Binding hierarchy.
30     /// </summary>
31     [EditorBrowsable(EditorBrowsableState.Never)]
32     public abstract partial class Element : BindableObject, IElement, INameScope, IElementController
33     {
34         internal static readonly ReadOnlyCollection<Element> EmptyChildren = new ReadOnlyCollection<Element>(System.Array.Empty<Element>());
35
36         /// <summary>
37         /// Identifies the ClassId bindable property.
38         /// </summary>
39         internal static readonly BindableProperty ClassIdProperty = BindableProperty.Create(nameof(ClassId), typeof(string), typeof(Tizen.NUI.BaseComponents.View), null);
40
41         string automationId;
42
43         IList<BindableObject> bindableResources;
44
45         List<Action<object, ResourcesChangedEventArgs>> changeHandlers;
46
47         Dictionary<BindableProperty, string> dynamicResources;
48
49         Guid? id;
50
51         Element parentOverride;
52
53         string styleId;
54
55         /// <summary>
56         /// Gets or sets a value that allows the automation framework to find and interact with this element.
57         /// </summary>
58         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
59         [EditorBrowsable(EditorBrowsableState.Never)]
60         public string AutomationId
61         {
62             get { return automationId; }
63             set
64             {
65                 if (automationId != null)
66                     throw new InvalidOperationException("AutomationId may only be set one time");
67                 automationId = value;
68             }
69         }
70
71         /// <summary>
72         /// Gets or sets a value used to identify a collection of semantically similar elements.
73         /// </summary>
74         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
75         [EditorBrowsable(EditorBrowsableState.Never)]
76         public string ClassId
77         {
78             get { return (string)GetValue(ClassIdProperty); }
79             set { SetValue(ClassIdProperty, value); }
80         }
81
82         /// <summary>
83         /// Gets a value that can be used to uniquely identify an element through the run of an application.
84         /// </summary>
85         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
86         [EditorBrowsable(EditorBrowsableState.Never)]
87         public Guid Id
88         {
89             get
90             {
91                 if (!id.HasValue)
92                     id = Guid.NewGuid();
93                 return id.Value;
94             }
95         }
96
97         /// <summary>
98         /// Gets the element which is the closest ancestor of this element that is a BaseHandle.
99         /// </summary>
100         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
101         [EditorBrowsable(EditorBrowsableState.Never)]
102         [Obsolete("ParentView is obsolete as of version 2.1.0. Please use Parent instead.")]
103         public BaseHandle ParentView
104         {
105             get
106             {
107                 Element parent = Parent;
108                 while (parent != null)
109                 {
110                     var parentView = parent as BaseHandle;
111                     if (parentView != null)
112                         return parentView;
113                     parent = parent.RealParent;
114                 }
115                 return null;
116             }
117         }
118
119         /// <summary>
120         /// Gets or sets a user defined value to uniquely identify the element.
121         /// </summary>
122         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
123         [EditorBrowsable(EditorBrowsableState.Never)]
124         public string StyleId
125         {
126             get { return styleId; }
127             set
128             {
129                 if (styleId == value)
130                     return;
131
132                 OnPropertyChanging();
133                 styleId = value;
134                 OnPropertyChanged();
135             }
136         }
137
138         internal virtual ReadOnlyCollection<Element> LogicalChildrenInternal => EmptyChildren;
139
140         /// <summary>
141         /// For internal use.
142         /// </summary>
143         [EditorBrowsable(EditorBrowsableState.Never)]
144         public ReadOnlyCollection<Element> LogicalChildren => LogicalChildrenInternal;
145
146         internal bool Owned { get; set; }
147
148         internal Element ParentOverride
149         {
150             get { return parentOverride; }
151             set
152             {
153                 if (parentOverride == value)
154                     return;
155
156                 bool emitChange = Parent != value;
157
158                 if (emitChange)
159                     OnPropertyChanging(nameof(Parent));
160
161                 parentOverride = value;
162
163                 if (emitChange)
164                     OnPropertyChanged(nameof(Parent));
165             }
166         }
167
168         /// <summary>
169         /// For internal use.
170         /// </summary>
171         [EditorBrowsable(EditorBrowsableState.Never)]
172         public Element RealParent { get; private set; }
173
174         Dictionary<BindableProperty, string> DynamicResources
175         {
176             get { return dynamicResources ?? (dynamicResources = new Dictionary<BindableProperty, string>()); }
177         }
178
179         void IElement.AddResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
180         {
181             changeHandlers = changeHandlers ?? new List<Action<object, ResourcesChangedEventArgs>>(2);
182             changeHandlers.Add(onchanged);
183         }
184
185         /// <summary>
186         /// Gets or sets the parent element of the element.
187         /// </summary>
188         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
189         [EditorBrowsable(EditorBrowsableState.Never)]
190         public Element Parent
191         {
192             get { return parentOverride ?? RealParent; }
193             set
194             {
195                 if (RealParent == value)
196                     return;
197
198                 OnPropertyChanging();
199
200                 if (RealParent != null)
201                     ((IElement)RealParent).RemoveResourcesChangedListener(OnParentResourcesChanged);
202                 RealParent = value;
203                 if (RealParent != null)
204                 {
205                     OnParentResourcesChanged(RealParent?.GetMergedResources());
206                     ((IElement)RealParent).AddResourcesChangedListener(OnParentResourcesChanged);
207                 }
208
209                 object context = value != null ? value.BindingContext : null;
210                 if (value != null)
211                 {
212                     value.SetChildInheritedBindingContext(this, context);
213                 }
214                 else
215                 {
216                     SetInheritedBindingContext(this, null);
217                 }
218
219                 OnParentSet();
220
221                 OnPropertyChanged();
222             }
223         }
224
225         /// <summary>
226         /// Gets the x:Name dictionary of the element.
227         /// </summary>
228         [EditorBrowsable(EditorBrowsableState.Never)]
229         public Dictionary<string, object> XNames => (GetNameScope() as NameScope)?.XNames ?? null;
230
231         void IElement.RemoveResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
232         {
233             if (changeHandlers == null)
234                 return;
235             changeHandlers.Remove(onchanged);
236         }
237
238         //void IElementController.SetValueFromRenderer(BindableProperty property, object value) => SetValueFromRenderer(property, value);
239
240         /// <summary>
241         /// Sets the value of the specified property.
242         /// </summary>
243         /// <param name="property">The BindableProperty on which to assign a value.</param>
244         /// <param name="value">The value to set.</param>
245         internal void SetValueFromRenderer(BindableProperty property, object value)
246         {
247             SetValueCore(property, value);
248         }
249
250         /// <summary>
251         /// Sets the value of the propertyKey.
252         /// </summary>
253         /// <param name="property">The BindablePropertyKey on which to assign a value.</param>
254         /// <param name="value">The value to set.</param>
255         internal void SetValueFromRenderer(BindablePropertyKey property, object value)
256         {
257             SetValueCore(property, value);
258         }
259
260         object INameScope.FindByName(string name)
261         {
262             INameScope namescope = GetNameScope();
263             if (namescope == null)
264             {
265                 return null;
266             }
267             else
268             {
269                 return namescope.FindByName(name);
270             }
271         }
272
273         void INameScope.RegisterName(string name, object scopedElement)
274         {
275             INameScope namescope = GetNameScope();
276             if (namescope == null)
277                 throw new InvalidOperationException("this element is not in a namescope");
278             namescope.RegisterName(name, scopedElement);
279         }
280
281         [Obsolete]
282         void INameScope.RegisterName(string name, object scopedElement, IXmlLineInfo xmlLineInfo)
283         {
284             INameScope namescope = GetNameScope();
285             if (namescope == null)
286                 throw new InvalidOperationException("this element is not in a namescope");
287             namescope.RegisterName(name, scopedElement, xmlLineInfo);
288         }
289
290         void INameScope.UnregisterName(string name)
291         {
292             INameScope namescope = GetNameScope();
293             if (namescope == null)
294                 throw new InvalidOperationException("this element is not in a namescope");
295             namescope.UnregisterName(name);
296         }
297
298         internal event EventHandler<ElementEventArgs> ChildAdded;
299
300         internal event EventHandler<ElementEventArgs> ChildRemoved;
301
302         internal event EventHandler<ElementEventArgs> DescendantAdded;
303
304         internal event EventHandler<ElementEventArgs> DescendantRemoved;
305
306         /// <summary>
307         /// Removes a previously set dynamic resource.
308         /// </summary>
309         /// <param name="property">The BindableProperty from which to remove the DynamicResource.</param>
310         internal new void RemoveDynamicResource(BindableProperty property)
311         {
312             base.RemoveDynamicResource(property);
313         }
314
315         /// <summary>
316         /// Sets the BindableProperty property of this element to be updated via the DynamicResource with the provided key.
317         /// </summary>
318         /// <param name="property">The BindableProperty.</param>
319         /// <param name="key">The key of the DynamicResource</param>
320         internal new void SetDynamicResource(BindableProperty property, string key)
321         {
322             base.SetDynamicResource(property, key);
323         }
324
325         /// <summary>
326         /// Invoked whenever the binding context of the element changes. Implement this method to add class handling for this event.
327         /// </summary>
328         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
329         [EditorBrowsable(EditorBrowsableState.Never)]
330         protected override void OnBindingContextChanged()
331         {
332             var gotBindingContext = false;
333             object bc = null;
334
335             for (var index = 0; index < LogicalChildrenInternal.Count; index++)
336             {
337                 Element child = LogicalChildrenInternal[index];
338
339                 if (!gotBindingContext)
340                 {
341                     bc = BindingContext;
342                     gotBindingContext = true;
343                 }
344
345                 SetChildInheritedBindingContext(child, bc);
346             }
347
348             if (bindableResources != null)
349                 foreach (BindableObject item in bindableResources)
350                 {
351                     SetInheritedBindingContext(item, BindingContext);
352                 }
353
354             base.OnBindingContextChanged();
355         }
356
357         /// <summary>
358         /// Invoked whenever the ChildAdded event needs to be emitted.Implement this method to add class handling for this event.
359         /// </summary>
360         /// <param name="child">The element that was added.</param>
361         /// <exception cref="ArgumentNullException"> Thrown when child is null. </exception>
362         /// This will be public opened later after ACR done. Before ACR, need to be hidden as inhouse API.
363         [EditorBrowsable(EditorBrowsableState.Never)]
364         protected virtual void OnChildAdded(Element child)
365         {
366             if (child == null)
367             {
368                 throw new ArgumentNullException(nameof(child));
369             }
370
371             child.Parent = this;
372
373             child.ApplyBindings(skipBindingContext: false, fromBindingContextChanged: true);
374
375             ChildAdded?.Invoke(this, new ElementEventArgs(child));
376
377             OnDescendantAdded(child);
378             foreach (Element element in child.Descendants())
379                 OnDescendantAdded(element);
380         }
381
382         /// <summary>
383         /// Invoked whenever the ChildRemoved event needs to be emitted.Implement this method to add class handling for this event.
384         /// </summary>
385         /// <param name="child">The element that was removed.</param>
386         /// <exception cref="ArgumentNullException"> Thrown when child is null. </exception>
387         /// This will be public opened later after ACR done. Before ACR, need to be hidden as inhouse API.
388         [EditorBrowsable(EditorBrowsableState.Never)]
389         protected virtual void OnChildRemoved(Element child)
390         {
391             if (child == null)
392             {
393                 throw new ArgumentNullException(nameof(child));
394             }
395
396             child.Parent = null;
397
398             ChildRemoved?.Invoke(child, new ElementEventArgs(child));
399
400             OnDescendantRemoved(child);
401             foreach (Element element in child.Descendants())
402                 OnDescendantRemoved(element);
403         }
404
405         /// <summary>
406         /// Invoked whenever the Parent of an element is set.Implement this method in order to add behavior when the element is added to a parent.
407         /// </summary>
408         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
409         [EditorBrowsable(EditorBrowsableState.Never)]
410         protected virtual void OnParentSet()
411         {
412             ParentSet?.Invoke(this, EventArgs.Empty);
413             // ApplyStyleSheetsOnParentSet();
414         }
415
416         /// <summary>
417         /// Method that is called when a bound property is changed.
418         /// </summary>
419         /// <param name="propertyName">The name of the bound property that changed.</param>
420         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
421         [EditorBrowsable(EditorBrowsableState.Never)]
422         protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
423         {
424             base.OnPropertyChanged(propertyName);
425         }
426
427         /// <summary>
428         /// For internal use.
429         /// </summary>
430         /// <returns>the elements</returns>
431         [EditorBrowsable(EditorBrowsableState.Never)]
432         public IEnumerable<Element> Descendants()
433         {
434             var queue = new Queue<Element>(16);
435             queue.Enqueue(this);
436
437             while (queue.Count > 0)
438             {
439                 ReadOnlyCollection<Element> children = queue.Dequeue().LogicalChildrenInternal;
440                 for (var i = 0; i < children.Count; i++)
441                 {
442                     Element child = children[i];
443                     yield return child;
444                     queue.Enqueue(child);
445                 }
446             }
447         }
448
449         internal virtual void OnParentResourcesChanged(object sender, ResourcesChangedEventArgs e)
450         {
451             OnParentResourcesChanged(e.Values);
452         }
453
454         internal virtual void OnParentResourcesChanged(IEnumerable<KeyValuePair<string, object>> values)
455         {
456             OnResourcesChanged(values);
457         }
458
459         internal override void OnRemoveDynamicResource(BindableProperty property)
460         {
461             DynamicResources.Remove(property);
462
463             if (DynamicResources.Count == 0)
464                 dynamicResources = null;
465             base.OnRemoveDynamicResource(property);
466         }
467
468         internal virtual void OnResourcesChanged(object sender, ResourcesChangedEventArgs e)
469         {
470             OnResourcesChanged(e.Values);
471         }
472
473         internal void OnResourcesChanged(IEnumerable<KeyValuePair<string, object>> values)
474         {
475             if (values == null)
476                 return;
477             if (changeHandlers != null)
478                 foreach (Action<object, ResourcesChangedEventArgs> handler in changeHandlers)
479                     handler(this, new ResourcesChangedEventArgs(values));
480             if (dynamicResources == null)
481                 return;
482             if (bindableResources == null)
483                 bindableResources = new List<BindableObject>();
484             foreach (KeyValuePair<string, object> value in values)
485             {
486                 List<BindableProperty> changedResources = null;
487                 foreach (KeyValuePair<BindableProperty, string> dynR in DynamicResources)
488                 {
489                     // when the DynamicResource bound to a BindableProperty is
490                     // changing then the BindableProperty needs to be refreshed;
491                     // The .Value is the name of DynamicResouce to which the BindableProperty is bound.
492                     // The .Key is the name of the DynamicResource whose value is changing.
493                     if (dynR.Value != value.Key)
494                         continue;
495                     changedResources = changedResources ?? new List<BindableProperty>();
496                     changedResources.Add(dynR.Key);
497                 }
498                 if (changedResources == null)
499                     continue;
500                 foreach (BindableProperty changedResource in changedResources)
501                     OnResourceChanged(changedResource, value.Value);
502
503                 var bindableObject = value.Value as BindableObject;
504                 if (bindableObject != null && (bindableObject as Element)?.Parent == null)
505                 {
506                     if (!bindableResources.Contains(bindableObject))
507                         bindableResources.Add(bindableObject);
508                     SetInheritedBindingContext(bindableObject, BindingContext);
509                 }
510             }
511         }
512
513         internal override void OnSetDynamicResource(BindableProperty property, string key)
514         {
515             base.OnSetDynamicResource(property, key);
516             DynamicResources[property] = key;
517             object value;
518             if (this.TryGetResource(key, out value))
519                 OnResourceChanged(property, value);
520         }
521
522         internal event EventHandler ParentSet;
523
524         internal virtual void SetChildInheritedBindingContext(Element child, object context)
525         {
526             SetInheritedBindingContext(child, context);
527         }
528
529         internal IEnumerable<Element> VisibleDescendants()
530         {
531             var queue = new Queue<Element>(16);
532             queue.Enqueue(this);
533
534             while (queue.Count > 0)
535             {
536                 ReadOnlyCollection<Element> children = queue.Dequeue().LogicalChildrenInternal;
537                 for (var i = 0; i < children.Count; i++)
538                 {
539                     var child = children[i] as BaseHandle;
540                     if (child == null)
541                     {
542                         continue;
543                     }
544                     yield return child;
545                     queue.Enqueue(child);
546                 }
547             }
548         }
549
550         INameScope GetNameScope()
551         {
552             INameScope namescope = NameScope.GetNameScope(this);
553             Element p = RealParent;
554             while (namescope == null && p != null)
555             {
556                 namescope = NameScope.GetNameScope(p);
557                 p = p.RealParent;
558             }
559             return namescope;
560         }
561
562         void OnDescendantAdded(Element child)
563         {
564             DescendantAdded?.Invoke(this, new ElementEventArgs(child));
565
566             if (RealParent != null)
567                 RealParent.OnDescendantAdded(child);
568         }
569
570         void OnDescendantRemoved(Element child)
571         {
572             DescendantRemoved?.Invoke(this, new ElementEventArgs(child));
573
574             if (RealParent != null)
575                 RealParent.OnDescendantRemoved(child);
576         }
577
578         void OnResourceChanged(BindableProperty property, object value)
579         {
580             SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearTwoWayBindings);
581         }
582     }
583 }