[NUI] remove unused Xamarin style classes (#2184)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / XamlBinding / ResourceDictionary.cs
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Collections.ObjectModel;
5 using System.Collections.Specialized;
6 using System.ComponentModel;
7 using System.Globalization;
8 using System.Linq;
9 using System.Reflection;
10 using System.Runtime.CompilerServices;
11
12 using Tizen.NUI.Binding.Internals;
13 using Tizen.NUI.Xaml;
14
15 namespace Tizen.NUI.Binding
16 {
17     /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
18     [EditorBrowsable(EditorBrowsableState.Never)]
19     public class ResourceDictionary : IResourceDictionary, IDictionary<string, object>
20     {
21         static ConditionalWeakTable<Type, ResourceDictionary> s_instances = new ConditionalWeakTable<Type, ResourceDictionary>();
22         readonly Dictionary<string, object> _innerDictionary = new Dictionary<string, object>();
23         ResourceDictionary _mergedInstance;
24         Type _mergedWith;
25         Uri _source;
26
27         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
28         [EditorBrowsable(EditorBrowsableState.Never)]
29         public ResourceDictionary()
30         {
31             DependencyService.Register<IResourcesLoader, ResourcesLoader>();
32         }
33
34         /// <summary>
35         /// Gets or sets the type of object with which the resource dictionary is merged.
36         /// </summary>
37         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
38         [EditorBrowsable(EditorBrowsableState.Never)]
39         [TypeConverter(typeof(TypeTypeConverter))]
40         [Obsolete("Use Source")]
41         public Type MergedWith
42         {
43             get { return _mergedWith; }
44             set
45             {
46                 if (_mergedWith == value)
47                     return;
48
49                 if (_source != null)
50                     throw new ArgumentException("MergedWith can not be used with Source");
51
52                 if (!typeof(ResourceDictionary).GetTypeInfo().IsAssignableFrom(value.GetTypeInfo()))
53                     throw new ArgumentException("MergedWith should inherit from ResourceDictionary");
54
55                 _mergedWith = value;
56                 if (_mergedWith == null)
57                     return;
58
59                 _mergedInstance = s_instances.GetValue(_mergedWith, (key) => (ResourceDictionary)Activator.CreateInstance(key));
60                 OnValuesChanged(_mergedInstance.ToArray());
61             }
62         }
63
64         /// <summary>
65         /// Gets or sets the URI of the merged resource dictionary.
66         /// </summary>
67         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
68         [EditorBrowsable(EditorBrowsableState.Never)]
69         [TypeConverter(typeof(RDSourceTypeConverter))]
70         public Uri Source
71         {
72             get { return _source; }
73             set
74             {
75                 if (_source == value)
76                     return;
77                 throw new InvalidOperationException("Source can only be set from XAML."); //through the RDSourceTypeConverter
78             }
79         }
80
81         /// <summary>
82         /// To set and load source.
83         /// </summary>
84         /// <param name="value">The source.</param>
85         /// <param name="resourcePath">The resource path.</param>
86         /// <param name="assembly">The assembly.</param>
87         /// <param name="lineInfo">The xml line info.</param>
88         /// Used by the XamlC compiled converter.
89         [EditorBrowsable(EditorBrowsableState.Never)]
90         public void SetAndLoadSource(Uri value, string resourcePath, Assembly assembly, System.Xml.IXmlLineInfo lineInfo)
91         {
92             _source = value;
93             if (_mergedWith != null)
94                 throw new ArgumentException("Source can not be used with MergedWith");
95
96             //this will return a type if the RD as an x:Class element, and codebehind
97             var type = XamlResourceIdAttribute.GetTypeForPath(assembly, resourcePath);
98             if (type != null)
99                 _mergedInstance = s_instances.GetValue(type, (key) => (ResourceDictionary)Activator.CreateInstance(key));
100             else
101                 _mergedInstance = DependencyService.Get<IResourcesLoader>()?.CreateFromResource<ResourceDictionary>(resourcePath, assembly, lineInfo);
102             OnValuesChanged(_mergedInstance.ToArray());
103         }
104
105         ICollection<ResourceDictionary> _mergedDictionaries;
106
107         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
108         [EditorBrowsable(EditorBrowsableState.Never)]
109         public ICollection<ResourceDictionary> MergedDictionaries
110         {
111             get
112             {
113                 if (_mergedDictionaries == null)
114                 {
115                     var col = new ObservableCollection<ResourceDictionary>();
116                     col.CollectionChanged += MergedDictionaries_CollectionChanged;
117                     _mergedDictionaries = col;
118                 }
119                 return _mergedDictionaries;
120             }
121         }
122
123         IList<ResourceDictionary> _collectionTrack;
124
125         void MergedDictionaries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
126         {
127             // Move() isn't exposed by ICollection
128             if (e.Action == NotifyCollectionChangedAction.Move)
129                 return;
130
131             _collectionTrack = _collectionTrack ?? new List<ResourceDictionary>();
132             // Collection has been cleared
133             if (e.Action == NotifyCollectionChangedAction.Reset)
134             {
135                 foreach (var dictionary in _collectionTrack)
136                     dictionary.ValuesChanged -= Item_ValuesChanged;
137
138                 _collectionTrack.Clear();
139                 return;
140             }
141
142             // New Items
143             if (e.NewItems != null)
144             {
145                 foreach (var item in e.NewItems)
146                 {
147                     var rd = (ResourceDictionary)item;
148                     _collectionTrack.Add(rd);
149                     rd.ValuesChanged += Item_ValuesChanged;
150                     OnValuesChanged(rd.ToArray());
151                 }
152             }
153
154             // Old Items
155             if (e.OldItems != null)
156             {
157                 foreach (var item in e.OldItems)
158                 {
159                     var rd = (ResourceDictionary)item;
160                     rd.ValuesChanged -= Item_ValuesChanged;
161                     _collectionTrack.Remove(rd);
162                 }
163             }
164         }
165
166         void Item_ValuesChanged(object sender, ResourcesChangedEventArgs e)
167         {
168             OnValuesChanged(e.Values.ToArray());
169         }
170
171         void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
172         {
173             ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Add(item);
174             OnValuesChanged(item);
175         }
176
177         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
178         [EditorBrowsable(EditorBrowsableState.Never)]
179         public void Clear()
180         {
181             _innerDictionary.Clear();
182         }
183
184         bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
185         {
186             return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Contains(item)
187                 || (_mergedInstance != null && _mergedInstance.Contains(item));
188         }
189
190         void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
191         {
192             ((ICollection<KeyValuePair<string, object>>)_innerDictionary).CopyTo(array, arrayIndex);
193         }
194
195         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
196         [EditorBrowsable(EditorBrowsableState.Never)]
197         public int Count
198         {
199             get { return _innerDictionary.Count; }
200         }
201
202         bool ICollection<KeyValuePair<string, object>>.IsReadOnly
203         {
204             get { return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).IsReadOnly; }
205         }
206
207         bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
208         {
209             return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Remove(item);
210         }
211
212         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
213         [EditorBrowsable(EditorBrowsableState.Never)]
214         public void Add(string key, object value)
215         {
216             if (ContainsKey(key))
217                 throw new ArgumentException($"A resource with the key '{key}' is already present in the ResourceDictionary.");
218             _innerDictionary.Add(key, value);
219             OnValueChanged(key, value);
220         }
221
222         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
223         [EditorBrowsable(EditorBrowsableState.Never)]
224         public bool ContainsKey(string key)
225         {
226             return _innerDictionary.ContainsKey(key);
227         }
228
229         /// <summary>
230         /// Gets or sets the value according to index.
231         /// </summary>
232         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
233         [EditorBrowsable(EditorBrowsableState.Never)]
234         [IndexerName("Item")]
235         public object this[string index]
236         {
237             get
238             {
239                 if (_innerDictionary.ContainsKey(index))
240                     return _innerDictionary[index];
241                 if (_mergedInstance != null && _mergedInstance.ContainsKey(index))
242                     return _mergedInstance[index];
243                 if (MergedDictionaries != null)
244                     foreach (var dict in MergedDictionaries.Reverse())
245                         if (dict.ContainsKey(index))
246                             return dict[index];
247                 throw new KeyNotFoundException($"The resource '{index}' is not present in the dictionary.");
248             }
249             set
250             {
251                 _innerDictionary[index] = value;
252                 OnValueChanged(index, value);
253             }
254         }
255
256         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
257         [EditorBrowsable(EditorBrowsableState.Never)]
258         public ICollection<string> Keys
259         {
260             get { return _innerDictionary.Keys; }
261         }
262
263         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
264         [EditorBrowsable(EditorBrowsableState.Never)]
265         public bool Remove(string key)
266         {
267             return _innerDictionary.Remove(key);
268         }
269
270         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
271         [EditorBrowsable(EditorBrowsableState.Never)]
272         public ICollection<object> Values
273         {
274             get { return _innerDictionary.Values; }
275         }
276
277         IEnumerator IEnumerable.GetEnumerator()
278         {
279             return GetEnumerator();
280         }
281
282         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
283         [EditorBrowsable(EditorBrowsableState.Never)]
284         public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
285         {
286             return _innerDictionary.GetEnumerator();
287         }
288
289         internal IEnumerable<KeyValuePair<string, object>> MergedResources
290         {
291             get
292             {
293                 if (MergedDictionaries != null)
294                     foreach (var r in MergedDictionaries.Reverse().SelectMany(x => x.MergedResources))
295                         yield return r;
296                 if (_mergedInstance != null)
297                     foreach (var r in _mergedInstance.MergedResources)
298                         yield return r;
299                 foreach (var r in _innerDictionary)
300                     yield return r;
301             }
302         }
303
304         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
305         [EditorBrowsable(EditorBrowsableState.Never)]
306         public bool TryGetValue(string key, out object value)
307         {
308             return _innerDictionary.TryGetValue(key, out value)
309                 || (_mergedInstance != null && _mergedInstance.TryGetValue(key, out value))
310                 || (MergedDictionaries != null && TryGetMergedDictionaryValue(key, out value));
311         }
312
313         bool TryGetMergedDictionaryValue(string key, out object value)
314         {
315             foreach (var dictionary in MergedDictionaries.Reverse())
316                 if (dictionary.TryGetValue(key, out value))
317                     return true;
318
319             value = null;
320             return false;
321         }
322
323         event EventHandler<ResourcesChangedEventArgs> IResourceDictionary.ValuesChanged
324         {
325             add { ValuesChanged += value; }
326             remove { ValuesChanged -= value; }
327         }
328
329         /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
330         [EditorBrowsable(EditorBrowsableState.Never)]
331         public void Add(ResourceDictionary mergedResourceDictionary)
332         {
333             MergedDictionaries.Add(mergedResourceDictionary);
334         }
335
336         void OnValueChanged(string key, object value)
337         {
338             OnValuesChanged(new KeyValuePair<string, object>(key, value));
339         }
340
341         void OnValuesChanged(params KeyValuePair<string, object>[] values)
342         {
343             if (values == null || values.Length == 0)
344                 return;
345             ValuesChanged?.Invoke(this, new ResourcesChangedEventArgs(values));
346         }
347
348         event EventHandler<ResourcesChangedEventArgs> ValuesChanged;
349
350         [Xaml.ProvideCompiled("Tizen.NUI.Xaml.Core.XamlC.RDSourceTypeConverter")]
351         internal class RDSourceTypeConverter : TypeConverter, IExtendedTypeConverter
352         {
353             object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
354             {
355                 if (serviceProvider == null)
356                     throw new ArgumentNullException(nameof(serviceProvider));
357
358                 var targetRD = (serviceProvider.GetService(typeof(Xaml.IProvideValueTarget)) as Xaml.IProvideValueTarget)?.TargetObject as ResourceDictionary;
359                 if (targetRD == null)
360                     return null;
361
362                 var rootObjectType = (serviceProvider.GetService(typeof(Xaml.IRootObjectProvider)) as Xaml.IRootObjectProvider)?.RootObject.GetType();
363                 if (rootObjectType == null)
364                     return null;
365
366                 var lineInfo = (serviceProvider.GetService(typeof(Xaml.IXmlLineInfoProvider)) as Xaml.IXmlLineInfoProvider)?.XmlLineInfo;
367                 var rootTargetPath = XamlResourceIdAttribute.GetPathForType(rootObjectType);
368                 var uri = new Uri(value, UriKind.Relative); //we don't want file:// uris, even if they start with '/'
369                 var resourcePath = GetResourcePath(uri, rootTargetPath);
370
371                 targetRD.SetAndLoadSource(uri, resourcePath, rootObjectType.GetTypeInfo().Assembly, lineInfo);
372                 return uri;
373             }
374
375             internal static string GetResourcePath(Uri uri, string rootTargetPath)
376             {
377                 //need a fake scheme so it's not seen as file:// uri, and the forward slashes are valid on all plats
378                 var resourceUri = uri.OriginalString.StartsWith("/", StringComparison.Ordinal)
379                                      ? new Uri($"pack://{uri.OriginalString}", UriKind.Absolute)
380                                      : new Uri($"pack:///{rootTargetPath}/../{uri.OriginalString}", UriKind.Absolute);
381
382                 //drop the leading '/'
383                 return resourceUri.AbsolutePath.Substring(1);
384             }
385
386             object IExtendedTypeConverter.ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider)
387             {
388                 throw new NotImplementedException();
389             }
390
391             public override object ConvertFromInvariantString(string value)
392             {
393                 throw new NotImplementedException();
394             }
395         }
396     }
397 }