PropertyTracker.cs (6820B)
1 // 2 // Copyright 2020 Electronic Arts Inc. 3 // 4 // The Command & Conquer Map Editor and corresponding source code is free 5 // software: you can redistribute it and/or modify it under the terms of 6 // the GNU General Public License as published by the Free Software Foundation, 7 // either version 3 of the License, or (at your option) any later version. 8 9 // The Command & Conquer Map Editor and corresponding source code is distributed 10 // in the hope that it will be useful, but with permitted additional restrictions 11 // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 12 // distributed with this program. You should have received a copy of the 13 // GNU General Public License along with permitted additional restrictions 14 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection 15 using System; 16 using System.Collections.Generic; 17 using System.ComponentModel; 18 using System.Dynamic; 19 using System.Linq; 20 using System.Reflection; 21 22 namespace MobiusEditor.Utility 23 { 24 public class TrackablePropertyDescriptor<T> : PropertyDescriptor 25 { 26 private readonly T obj; 27 private readonly PropertyInfo propertyInfo; 28 private readonly Dictionary<string, object> propertyValues; 29 30 public override Type ComponentType => obj.GetType(); 31 32 public override bool IsReadOnly => false; 33 34 public override Type PropertyType => propertyInfo.PropertyType; 35 36 public TrackablePropertyDescriptor(string name, T obj, PropertyInfo propertyInfo, Dictionary<string, object> propertyValues) 37 : base(name, null) 38 { 39 this.obj = obj; 40 this.propertyInfo = propertyInfo; 41 this.propertyValues = propertyValues; 42 } 43 44 public override bool CanResetValue(object component) 45 { 46 return propertyValues.ContainsKey(Name); 47 } 48 49 public override object GetValue(object component) 50 { 51 if (propertyValues.TryGetValue(Name, out object result)) 52 { 53 return result; 54 } 55 return propertyInfo.GetValue(obj); 56 } 57 58 public override void ResetValue(object component) 59 { 60 propertyValues.Remove(Name); 61 } 62 63 public override void SetValue(object component, object value) 64 { 65 if (Equals(propertyInfo.GetValue(obj), value)) 66 { 67 propertyValues.Remove(Name); 68 } 69 else 70 { 71 propertyValues[Name] = value; 72 } 73 } 74 75 public override bool ShouldSerializeValue(object component) 76 { 77 return false; 78 } 79 } 80 81 public class PropertyTracker<T> : DynamicObject, ICustomTypeDescriptor 82 { 83 private readonly Dictionary<string, PropertyInfo> trackableProperties; 84 private readonly Dictionary<string, object> propertyValues = new Dictionary<string, object>(); 85 86 public T Object { get; private set; } 87 88 public PropertyTracker(T obj) 89 { 90 Object = obj; 91 92 trackableProperties = Object.GetType() 93 .GetProperties(BindingFlags.Public | BindingFlags.Instance) 94 .Where(p => (p.GetGetMethod() != null) && (p.GetSetMethod() != null)) 95 .ToDictionary(k => k.Name, v => v); 96 } 97 98 public void Revert() => propertyValues.Clear(); 99 100 public void Commit() 101 { 102 foreach (var propertyValue in propertyValues) 103 { 104 trackableProperties[propertyValue.Key].SetValue(Object, propertyValue.Value); 105 } 106 propertyValues.Clear(); 107 } 108 109 public IDictionary<string, object> GetUndoValues() => propertyValues.ToDictionary(kv => kv.Key, kv => trackableProperties[kv.Key].GetValue(Object)); 110 111 public IDictionary<string, object> GetRedoValues() => new Dictionary<string, object>(propertyValues); 112 113 public override bool TryGetMember(GetMemberBinder binder, out object result) 114 { 115 if (!trackableProperties.TryGetValue(binder.Name, out PropertyInfo property)) 116 { 117 result = null; 118 return false; 119 } 120 121 if (!propertyValues.TryGetValue(binder.Name, out result)) 122 { 123 result = property.GetValue(Object); 124 } 125 return true; 126 } 127 128 public override bool TrySetMember(SetMemberBinder binder, object value) 129 { 130 if (!trackableProperties.TryGetValue(binder.Name, out PropertyInfo property)) 131 { 132 return false; 133 } 134 135 if (Equals(property.GetValue(Object), value)) 136 { 137 propertyValues.Remove(binder.Name); 138 } 139 else 140 { 141 propertyValues[binder.Name] = value; 142 } 143 return true; 144 } 145 146 public AttributeCollection GetAttributes() 147 { 148 return TypeDescriptor.GetAttributes(Object.GetType()); 149 } 150 151 public string GetClassName() 152 { 153 return TypeDescriptor.GetClassName(Object.GetType()); 154 } 155 156 public string GetComponentName() 157 { 158 return TypeDescriptor.GetComponentName(Object.GetType()); 159 } 160 161 public TypeConverter GetConverter() 162 { 163 return TypeDescriptor.GetConverter(Object.GetType()); 164 } 165 166 public EventDescriptor GetDefaultEvent() 167 { 168 return TypeDescriptor.GetDefaultEvent(Object.GetType()); 169 } 170 171 public PropertyDescriptor GetDefaultProperty() 172 { 173 return TypeDescriptor.GetDefaultProperty(Object.GetType()); 174 } 175 176 public object GetEditor(Type editorBaseType) 177 { 178 return TypeDescriptor.GetEditor(Object.GetType(), editorBaseType); 179 } 180 181 public EventDescriptorCollection GetEvents() 182 { 183 return TypeDescriptor.GetEvents(Object.GetType()); 184 } 185 186 public EventDescriptorCollection GetEvents(Attribute[] attributes) 187 { 188 return TypeDescriptor.GetEvents(Object.GetType(), attributes); 189 } 190 191 public PropertyDescriptorCollection GetProperties() 192 { 193 var propertyDescriptors = trackableProperties.Select(kv => 194 { 195 return new TrackablePropertyDescriptor<T>(kv.Key, Object, kv.Value, propertyValues); 196 }).ToArray(); 197 return new PropertyDescriptorCollection(propertyDescriptors); 198 } 199 200 public PropertyDescriptorCollection GetProperties(Attribute[] attributes) 201 { 202 return GetProperties(); 203 } 204 205 public object GetPropertyOwner(PropertyDescriptor pd) 206 { 207 return Object; 208 } 209 } 210 }