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