Release 9.0.0.16429
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / XamlBinding / Element.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.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         void IElement.RemoveResourcesChangedListener(Action<object, ResourcesChangedEventArgs> onchanged)
226         {
227             if (changeHandlers == null)
228                 return;
229             changeHandlers.Remove(onchanged);
230         }
231
232         //void IElementController.SetValueFromRenderer(BindableProperty property, object value) => SetValueFromRenderer(property, value);
233
234         /// <summary>
235         /// Sets the value of the specified property.
236         /// </summary>
237         /// <param name="property">The BindableProperty on which to assign a value.</param>
238         /// <param name="value">The value to set.</param>
239         internal void SetValueFromRenderer(BindableProperty property, object value)
240         {
241             SetValueCore(property, value);
242         }
243
244         /// <summary>
245         /// Sets the value of the propertyKey.
246         /// </summary>
247         /// <param name="property">The BindablePropertyKey on which to assign a value.</param>
248         /// <param name="value">The value to set.</param>
249         internal void SetValueFromRenderer(BindablePropertyKey property, object value)
250         {
251             SetValueCore(property, value);
252         }
253
254         object INameScope.FindByName(string name)
255         {
256             INameScope namescope = GetNameScope();
257             if (namescope == null)
258             {
259                 return null;
260             }
261             else
262             {
263                 return namescope.FindByName(name);
264             }
265         }
266
267         void INameScope.RegisterName(string name, object scopedElement)
268         {
269             INameScope namescope = GetNameScope();
270             if (namescope == null)
271                 throw new InvalidOperationException("this element is not in a namescope");
272             namescope.RegisterName(name, scopedElement);
273         }
274
275         [Obsolete]
276         void INameScope.RegisterName(string name, object scopedElement, IXmlLineInfo xmlLineInfo)
277         {
278             INameScope namescope = GetNameScope();
279             if (namescope == null)
280                 throw new InvalidOperationException("this element is not in a namescope");
281             namescope.RegisterName(name, scopedElement, xmlLineInfo);
282         }
283
284         void INameScope.UnregisterName(string name)
285         {
286             INameScope namescope = GetNameScope();
287             if (namescope == null)
288                 throw new InvalidOperationException("this element is not in a namescope");
289             namescope.UnregisterName(name);
290         }
291
292         internal event EventHandler<ElementEventArgs> ChildAdded;
293
294         internal event EventHandler<ElementEventArgs> ChildRemoved;
295
296         internal event EventHandler<ElementEventArgs> DescendantAdded;
297
298         internal event EventHandler<ElementEventArgs> DescendantRemoved;
299
300         /// <summary>
301         /// Removes a previously set dynamic resource.
302         /// </summary>
303         /// <param name="property">The BindableProperty from which to remove the DynamicResource.</param>
304         internal new void RemoveDynamicResource(BindableProperty property)
305         {
306             base.RemoveDynamicResource(property);
307         }
308
309         /// <summary>
310         /// Sets the BindableProperty property of this element to be updated via the DynamicResource with the provided key.
311         /// </summary>
312         /// <param name="property">The BindableProperty.</param>
313         /// <param name="key">The key of the DynamicResource</param>
314         internal new void SetDynamicResource(BindableProperty property, string key)
315         {
316             base.SetDynamicResource(property, key);
317         }
318
319         /// <summary>
320         /// Invoked whenever the binding context of the element changes. Implement this method to add class handling for this event.
321         /// </summary>
322         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
323         [EditorBrowsable(EditorBrowsableState.Never)]
324         protected override void OnBindingContextChanged()
325         {
326             var gotBindingContext = false;
327             object bc = null;
328
329             for (var index = 0; index < LogicalChildrenInternal.Count; index++)
330             {
331                 Element child = LogicalChildrenInternal[index];
332
333                 if (!gotBindingContext)
334                 {
335                     bc = BindingContext;
336                     gotBindingContext = true;
337                 }
338
339                 SetChildInheritedBindingContext(child, bc);
340             }
341
342             if (bindableResources != null)
343                 foreach (BindableObject item in bindableResources)
344                 {
345                     SetInheritedBindingContext(item, BindingContext);
346                 }
347
348             base.OnBindingContextChanged();
349         }
350
351         /// <summary>
352         /// Invoked whenever the ChildAdded event needs to be emitted.Implement this method to add class handling for this event.
353         /// </summary>
354         /// <param name="child">The element that was added.</param>
355         /// <exception cref="ArgumentNullException"> Thrown when child is null. </exception>
356         /// This will be public opened later after ACR done. Before ACR, need to be hidden as inhouse API.
357         [EditorBrowsable(EditorBrowsableState.Never)]
358         protected virtual void OnChildAdded(Element child)
359         {
360             if (child == null)
361             {
362                 throw new ArgumentNullException(nameof(child));
363             }
364
365             child.Parent = this;
366
367             child.ApplyBindings(skipBindingContext: false, fromBindingContextChanged: true);
368
369             ChildAdded?.Invoke(this, new ElementEventArgs(child));
370
371             OnDescendantAdded(child);
372             foreach (Element element in child.Descendants())
373                 OnDescendantAdded(element);
374         }
375
376         /// <summary>
377         /// Invoked whenever the ChildRemoved event needs to be emitted.Implement this method to add class handling for this event.
378         /// </summary>
379         /// <param name="child">The element that was removed.</param>
380         /// <exception cref="ArgumentNullException"> Thrown when child is null. </exception>
381         /// This will be public opened later after ACR done. Before ACR, need to be hidden as inhouse API.
382         [EditorBrowsable(EditorBrowsableState.Never)]
383         protected virtual void OnChildRemoved(Element child)
384         {
385             if (child == null)
386             {
387                 throw new ArgumentNullException(nameof(child));
388             }
389
390             child.Parent = null;
391
392             ChildRemoved?.Invoke(child, new ElementEventArgs(child));
393
394             OnDescendantRemoved(child);
395             foreach (Element element in child.Descendants())
396                 OnDescendantRemoved(element);
397         }
398
399         /// <summary>
400         /// 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.
401         /// </summary>
402         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
403         [EditorBrowsable(EditorBrowsableState.Never)]
404         protected virtual void OnParentSet()
405         {
406             ParentSet?.Invoke(this, EventArgs.Empty);
407             // ApplyStyleSheetsOnParentSet();
408         }
409
410         /// <summary>
411         /// Method that is called when a bound property is changed.
412         /// </summary>
413         /// <param name="propertyName">The name of the bound property that changed.</param>
414         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
415         [EditorBrowsable(EditorBrowsableState.Never)]
416         protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
417         {
418             base.OnPropertyChanged(propertyName);
419         }
420
421         /// <summary>
422         /// For internal use.
423         /// </summary>
424         /// <returns>the elements</returns>
425         [EditorBrowsable(EditorBrowsableState.Never)]
426         public IEnumerable<Element> Descendants()
427         {
428             var queue = new Queue<Element>(16);
429             queue.Enqueue(this);
430
431             while (queue.Count > 0)
432             {
433                 ReadOnlyCollection<Element> children = queue.Dequeue().LogicalChildrenInternal;
434                 for (var i = 0; i < children.Count; i++)
435                 {
436                     Element child = children[i];
437                     yield return child;
438                     queue.Enqueue(child);
439                 }
440             }
441         }
442
443         internal virtual void OnParentResourcesChanged(object sender, ResourcesChangedEventArgs e)
444         {
445             // if (e == ResourcesChangedEventArgs.StyleSheets)
446             //  // ApplyStyleSheetsOnParentSet();
447             // else
448             //  OnParentResourcesChanged(e.Values);
449         }
450
451         internal virtual void OnParentResourcesChanged(IEnumerable<KeyValuePair<string, object>> values)
452         {
453             OnResourcesChanged(values);
454         }
455
456         internal override void OnRemoveDynamicResource(BindableProperty property)
457         {
458             DynamicResources.Remove(property);
459
460             if (DynamicResources.Count == 0)
461                 dynamicResources = null;
462             base.OnRemoveDynamicResource(property);
463         }
464
465         internal virtual void OnResourcesChanged(object sender, ResourcesChangedEventArgs e)
466         {
467             OnResourcesChanged(e.Values);
468         }
469
470         internal void OnResourcesChanged(IEnumerable<KeyValuePair<string, object>> values)
471         {
472             if (values == null)
473                 return;
474             if (changeHandlers != null)
475                 foreach (Action<object, ResourcesChangedEventArgs> handler in changeHandlers)
476                     handler(this, new ResourcesChangedEventArgs(values));
477             if (dynamicResources == null)
478                 return;
479             if (bindableResources == null)
480                 bindableResources = new List<BindableObject>();
481             foreach (KeyValuePair<string, object> value in values)
482             {
483                 List<BindableProperty> changedResources = null;
484                 foreach (KeyValuePair<BindableProperty, string> dynR in DynamicResources)
485                 {
486                     // when the DynamicResource bound to a BindableProperty is
487                     // changing then the BindableProperty needs to be refreshed;
488                     // The .Value is the name of DynamicResouce to which the BindableProperty is bound.
489                     // The .Key is the name of the DynamicResource whose value is changing.
490                     if (dynR.Value != value.Key)
491                         continue;
492                     changedResources = changedResources ?? new List<BindableProperty>();
493                     changedResources.Add(dynR.Key);
494                 }
495                 if (changedResources == null)
496                     continue;
497                 foreach (BindableProperty changedResource in changedResources)
498                     OnResourceChanged(changedResource, value.Value);
499
500                 var bindableObject = value.Value as BindableObject;
501                 if (bindableObject != null && (bindableObject as Element)?.Parent == null)
502                 {
503                     if (!bindableResources.Contains(bindableObject))
504                         bindableResources.Add(bindableObject);
505                     SetInheritedBindingContext(bindableObject, BindingContext);
506                 }
507             }
508         }
509
510         internal override void OnSetDynamicResource(BindableProperty property, string key)
511         {
512             base.OnSetDynamicResource(property, key);
513             DynamicResources[property] = key;
514             object value;
515             if (this.TryGetResource(key, out value))
516                 OnResourceChanged(property, value);
517
518             Tizen.NUI.Application.AddResourceChangedCallback(this, (this as Element).OnResourcesChanged);
519         }
520
521         internal event EventHandler ParentSet;
522
523         internal virtual void SetChildInheritedBindingContext(Element child, object context)
524         {
525             SetInheritedBindingContext(child, context);
526         }
527
528         internal IEnumerable<Element> VisibleDescendants()
529         {
530             var queue = new Queue<Element>(16);
531             queue.Enqueue(this);
532
533             while (queue.Count > 0)
534             {
535                 ReadOnlyCollection<Element> children = queue.Dequeue().LogicalChildrenInternal;
536                 for (var i = 0; i < children.Count; i++)
537                 {
538                     var child = children[i] as BaseHandle;
539                     if (child == null)
540                     {
541                         continue;
542                     }
543                     yield return child;
544                     queue.Enqueue(child);
545                 }
546             }
547         }
548
549         INameScope GetNameScope()
550         {
551             INameScope namescope = NameScope.GetNameScope(this);
552             Element p = RealParent;
553             while (namescope == null && p != null)
554             {
555                 namescope = NameScope.GetNameScope(p);
556                 p = p.RealParent;
557             }
558             return namescope;
559         }
560
561         void OnDescendantAdded(Element child)
562         {
563             DescendantAdded?.Invoke(this, new ElementEventArgs(child));
564
565             if (RealParent != null)
566                 RealParent.OnDescendantAdded(child);
567         }
568
569         void OnDescendantRemoved(Element child)
570         {
571             DescendantRemoved?.Invoke(this, new ElementEventArgs(child));
572
573             if (RealParent != null)
574                 RealParent.OnDescendantRemoved(child);
575         }
576
577         void OnResourceChanged(BindableProperty property, object value)
578         {
579             SetValueCore(property, value, SetValueFlags.ClearOneWayBindings | SetValueFlags.ClearTwoWayBindings);
580         }
581     }
582 }