[NUI] Change GetDefaultWindow() to static func (#900)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / XamlBinding / Internals / TypedBinding.cs
1 #define DO_NOT_CHECK_FOR_BINDING_REUSE
2
3 using System;
4 using System.ComponentModel;
5 using System.Globalization;
6 using System.Collections.Generic;
7 using Tizen.NUI.Binding;
8
9 namespace Tizen.NUI.Binding.Internals
10 {
11     //FIXME: need a better name for this, and share with Binding, so we can share more unittests
12     internal abstract class TypedBindingBase : BindingBase
13     {
14         IValueConverter _converter;
15         object _converterParameter;
16         object _source;
17         string _updateSourceEventName;
18
19         public IValueConverter Converter {
20             get { return _converter; }
21             set {
22                 ThrowIfApplied();
23                 _converter = value;
24             }
25         }
26
27         public object ConverterParameter {
28             get { return _converterParameter; }
29             set {
30                 ThrowIfApplied();
31                 _converterParameter = value;
32             }
33         }
34
35         public object Source {
36             get { return _source; }
37             set {
38                 ThrowIfApplied();
39                 _source = value;
40             }
41         }
42
43         internal string UpdateSourceEventName {
44             get { return _updateSourceEventName; }
45             set {
46                 ThrowIfApplied();
47                 _updateSourceEventName = value;
48             }
49         }
50
51         internal TypedBindingBase()
52         {
53         }
54     }
55
56     internal sealed class TypedBinding<TSource, TProperty> : TypedBindingBase
57     {
58         readonly Func<TSource, TProperty> _getter;
59         readonly Action<TSource, TProperty> _setter;
60         readonly PropertyChangedProxy [] _handlers;
61
62         public TypedBinding(Func<TSource, TProperty> getter, Action<TSource, TProperty> setter, Tuple<Func<TSource, object>, string> [] handlers)
63         {
64             if (getter == null)
65                 throw new ArgumentNullException(nameof(getter));
66
67             _getter = getter;
68             _setter = setter;
69
70             if (handlers == null)
71                 return;
72
73             _handlers = new PropertyChangedProxy [handlers.Length];
74             for (var i = 0; i < handlers.Length; i++)
75                 _handlers [i] = new PropertyChangedProxy(handlers [i].Item1, handlers [i].Item2, this);
76         }
77
78         readonly WeakReference<object> _weakSource = new WeakReference<object>(null);
79         readonly WeakReference<BindableObject> _weakTarget = new WeakReference<BindableObject>(null);
80         BindableProperty _targetProperty;
81
82         // Applies the binding to a previously set source and target.
83         internal override void Apply(bool fromTarget = false)
84         {
85             base.Apply(fromTarget);
86
87             BindableObject target;
88 #if DO_NOT_CHECK_FOR_BINDING_REUSE
89             if (!_weakTarget.TryGetTarget(out target))
90                 throw new InvalidOperationException();
91 #else
92             if (!_weakTarget.TryGetTarget(out target) || target == null) {
93                 Unapply();
94                 return;
95             }
96 #endif
97             object source;
98             if (_weakSource.TryGetTarget(out source) && source != null)
99                 ApplyCore(source, target, _targetProperty, fromTarget);
100         }
101
102         // Applies the binding to a new source or target.
103         internal override void Apply(object context, BindableObject bindObj, BindableProperty targetProperty, bool fromBindingContextChanged = false)
104         {
105             _targetProperty = targetProperty;
106             var source = Source ?? Context ?? context;
107             var isApplied = IsApplied;
108
109             if (Source != null && isApplied && fromBindingContextChanged)
110                 return;
111
112             base.Apply(source, bindObj, targetProperty, fromBindingContextChanged);
113             
114 #if (!DO_NOT_CHECK_FOR_BINDING_REUSE)
115             BindableObject prevTarget;
116             if (_weakTarget.TryGetTarget(out prevTarget) && !ReferenceEquals(prevTarget, bindObj))
117                 throw new InvalidOperationException("Binding instances can not be reused");
118
119             object previousSource;
120             if (_weakSource.TryGetTarget(out previousSource) && !ReferenceEquals(previousSource, source))
121                 throw new InvalidOperationException("Binding instances can not be reused");
122 #endif
123             _weakSource.SetTarget(source);
124             _weakTarget.SetTarget(bindObj);
125
126             ApplyCore(source, bindObj, targetProperty);
127         }
128
129         internal override BindingBase Clone()
130         {
131             Tuple<Func<TSource, object>, string> [] handlers = _handlers == null ? null : new Tuple<Func<TSource, object>, string> [_handlers.Length];
132             if (handlers != null) {
133                 for (var i = 0; i < _handlers.Length; i++)
134                     handlers [i] = new Tuple<Func<TSource, object>, string>(_handlers [i].PartGetter, _handlers [i].PropertyName);
135             }
136             return new TypedBinding<TSource, TProperty>(_getter, _setter, handlers) {
137                 Mode = Mode,
138                 Converter = Converter,
139                 ConverterParameter = ConverterParameter,
140                 StringFormat = StringFormat,
141                 Source = Source,
142                 UpdateSourceEventName = UpdateSourceEventName,
143             };
144         }
145
146         internal override object GetSourceValue(object value, Type targetPropertyType)
147         {
148             if (Converter != null)
149                 value = Converter.Convert(value, targetPropertyType, ConverterParameter, CultureInfo.CurrentUICulture);
150
151             //return base.GetSourceValue(value, targetPropertyType);
152             if (StringFormat != null)
153                 return string.Format(StringFormat, value);
154
155             return value;
156         }
157
158         internal override object GetTargetValue(object value, Type sourcePropertyType)
159         {
160             if (Converter != null)
161                 value = Converter.ConvertBack(value, sourcePropertyType, ConverterParameter, CultureInfo.CurrentUICulture);
162
163             //return base.GetTargetValue(value, sourcePropertyType);
164             return value;
165         }
166
167         internal override void Unapply(bool fromBindingContextChanged = false)
168         {
169             if (Source != null && fromBindingContextChanged && IsApplied)
170                 return;
171
172 #if (!DO_NOT_CHECK_FOR_BINDING_REUSE)
173             base.Unapply(fromBindingContextChanged:fromBindingContextChanged);
174 #endif
175             if (_handlers != null)
176                 Unsubscribe();
177             
178 #if (!DO_NOT_CHECK_FOR_BINDING_REUSE)
179             _weakSource.SetTarget(null);
180             _weakTarget.SetTarget(null);
181 #endif
182         }
183
184         // ApplyCore is as slim as it should be:
185         // Setting  100000 values                                               : 17ms.
186         // ApplyCore  100000 (w/o INPC, w/o unnapply)   : 20ms.
187         internal void ApplyCore(object sourceObject, BindableObject target, BindableProperty property, bool fromTarget = false)
188         {
189             var isTSource = sourceObject != null && sourceObject is TSource;
190             var mode = this.GetRealizedMode(property);
191             if ((mode == BindingMode.OneWay || mode == BindingMode.OneTime) && fromTarget)
192                 return;
193
194             var needsGetter = (mode == BindingMode.TwoWay && !fromTarget) || mode == BindingMode.OneWay || mode == BindingMode.OneTime;
195
196             if (isTSource && (mode == BindingMode.OneWay || mode == BindingMode.TwoWay) && _handlers != null)
197                 Subscribe((TSource)sourceObject);
198
199             if (needsGetter) {
200                 var value = property.DefaultValue;
201                 if (isTSource) {
202                     try {
203                         value = GetSourceValue(_getter((TSource)sourceObject), property.ReturnType);
204                     } catch (Exception ex) when (ex is NullReferenceException || ex is KeyNotFoundException) {
205                     }
206                 }
207                 if (!TryConvert(ref value, property, property.ReturnType, true)) {
208                     // Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, property.ReturnType);
209                     return;
210                 }
211                 target.SetValueCore(property, value, SetValueFlags.ClearDynamicResource, BindableObject.SetValuePrivateFlags.Default | BindableObject.SetValuePrivateFlags.Converted, false);
212                 return;
213             }
214
215             var needsSetter = (mode == BindingMode.TwoWay && fromTarget) || mode == BindingMode.OneWayToSource;
216             if (needsSetter && _setter != null && isTSource) {
217                 var value = GetTargetValue(target.GetValue(property), typeof(TProperty));
218                 if (!TryConvert(ref value, property, typeof(TProperty), false)) {
219                     // Log.Warning("Binding", "{0} can not be converted to type '{1}'", value, typeof(TProperty));
220                     return;
221                 }
222                 _setter((TSource)sourceObject, (TProperty)value);
223             }
224         }
225
226         static bool TryConvert(ref object value, BindableProperty targetProperty, Type convertTo, bool toTarget)
227         {
228             if (value == null)
229                 return true;
230             if ((toTarget && targetProperty.TryConvert(ref value)) || (!toTarget && convertTo.IsInstanceOfType(value)))
231                 return true;
232
233             object original = value;
234             try {
235                 value = Convert.ChangeType(value, convertTo, CultureInfo.InvariantCulture);
236                 return true;
237             } catch (Exception ex ) when (ex is InvalidCastException || ex is FormatException||ex is OverflowException) {
238                 value = original;
239                 return false;
240             }
241         }
242
243         class PropertyChangedProxy
244         {
245             public Func<TSource, object> PartGetter { get; }
246             public string PropertyName { get; }
247             public BindingExpression.WeakPropertyChangedProxy Listener { get; }
248             WeakReference<INotifyPropertyChanged> _weakPart = new WeakReference<INotifyPropertyChanged>(null);
249             readonly BindingBase _binding;
250
251             public INotifyPropertyChanged Part {
252                 get {
253                     INotifyPropertyChanged target;
254                     if (_weakPart.TryGetTarget(out target))
255                         return target;
256                     return null;
257                 } 
258                 set {
259                     _weakPart.SetTarget(value);
260                     Listener.SubscribeTo(value, OnPropertyChanged);
261                 }
262             }
263
264             public PropertyChangedProxy(Func<TSource, object> partGetter, string propertyName, BindingBase binding)
265             {
266                 PartGetter = partGetter;
267                 PropertyName = propertyName;
268                 _binding = binding;
269                 Listener = new BindingExpression.WeakPropertyChangedProxy();
270             }
271
272             void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
273             {
274                 if (!string.IsNullOrEmpty(e.PropertyName) && string.CompareOrdinal(e.PropertyName, PropertyName) != 0)
275                     return;
276                 Device.BeginInvokeOnMainThread(() => _binding.Apply(false));
277             }
278         }
279
280         void Subscribe(TSource sourceObject)
281         {
282             for (var i = 0; i < _handlers.Length; i++) {
283                 var part = _handlers [i].PartGetter(sourceObject);
284                 if (part == null)
285                     break;
286                 var inpc = part as INotifyPropertyChanged;
287                 if (inpc == null)
288                     continue;
289                 _handlers [i].Part = (inpc);
290             }
291         }
292
293         void Unsubscribe()
294         {
295             for (var i = 0; i < _handlers.Length; i++)
296                 _handlers [i].Listener.Unsubscribe();
297         }
298     }
299 }