Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
CombinedObservableNode.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.ComponentModel;
6 using System.Linq;
7 
8 using SiliconStudio.Core.Extensions;
10 using SiliconStudio.Quantum;
11 
12 namespace SiliconStudio.Presentation.Quantum
13 {
14  public abstract class CombinedObservableNode : ObservableNode
15  {
16  private readonly List<SingleObservableNode> combinedNodes;
17  private readonly List<object> combinedNodeInitialValues;
18  private readonly HashSet<object> distinctCombinedNodeInitialValues;
19  private readonly int? order;
20 
21  protected static readonly HashSet<CombinedObservableNode> ChangedNodes = new HashSet<CombinedObservableNode>();
22 
23  protected static bool ChangeInProgress;
24 
25  private IDictionary<string, object> associatedData;
26 
27  protected CombinedObservableNode(ObservableViewModel ownerViewModel, string name, CombinedObservableNode parentNode, IEnumerable<SingleObservableNode> combinedNodes, object index)
28  : base(ownerViewModel, parentNode, index)
29  {
30  this.combinedNodes = new List<SingleObservableNode>(combinedNodes);
31  Name = name;
32  DisplayName = this.combinedNodes.First().DisplayName;
33 
34  combinedNodeInitialValues = new List<object>();
35  distinctCombinedNodeInitialValues = new HashSet<object>();
36 
37  bool isReadOnly = false;
38  bool isVisible = false;
39  bool nullOrder = false;
40 
41  foreach (var node in this.combinedNodes)
42  {
43  if (node.IsReadOnly)
44  isReadOnly = true;
45 
46  if (node.IsVisible)
47  isVisible = true;
48 
49  if (node.Order == null)
50  nullOrder = true;
51 
52  if (order == node.Order || (!nullOrder && order == null))
53  order = node.Order;
54 
55  combinedNodeInitialValues.Add(node.Value);
56  distinctCombinedNodeInitialValues.Add(node.Value);
57  node.PropertyChanged += NodePropertyChanged;
58  }
59  IsReadOnly = isReadOnly;
60  IsVisible = isVisible;
61 
62  ResetInitialValues = new AnonymousCommand(ServiceProvider, () => { Owner.BeginCombinedAction(); CombinedNodes.Zip(combinedNodeInitialValues).ForEach(x => x.Item1.Value = x.Item2); Refresh(); Owner.EndCombinedAction(Owner.FormatCombinedUpdateMessage(this, null), Path, null); });
63  }
64 
65 
66  internal void Initialize()
67  {
68  Initialize(false);
69  }
70 
71  private void Initialize(bool isUpdating)
72  {
73  var commandGroups = new Dictionary<string, List<ModelNodeCommandWrapper>>();
74  foreach (var node in combinedNodes)
75  {
76  foreach (var command in node.Commands)
77  {
78  var list = commandGroups.GetOrCreateValue(command.Name);
79  list.Add((ModelNodeCommandWrapper)command);
80  }
81  }
82 
83  foreach (var commandGroup in commandGroups)
84  {
85  var mode = commandGroup.Value.First().CombineMode;
86  if (commandGroup.Value.Any(x => x.CombineMode != mode))
87  throw new InvalidOperationException(string.Format("Inconsistent combine mode among command {0}", commandGroup.Key));
88 
89  var shouldCombine = mode != CombineMode.DoNotCombine && (mode == CombineMode.AlwaysCombine || commandGroup.Value.Count == combinedNodes.Count);
90 
91  if (shouldCombine)
92  {
93  var command = new CombinedNodeCommandWrapper(ServiceProvider, commandGroup.Key, Path, Owner.Identifier, commandGroup.Value);
94  AddCommand(command);
95  }
96  }
97 
98  if (!HasList || HasDictionary)
99  {
100  var commonChildren = GetCommonChildren();
101  GenerateChildren(commonChildren, isUpdating);
102  }
103  else
104  {
105  var allChildren = GetAllChildrenByValue();
106  if (allChildren != null)
107  {
108  GenerateListChildren(allChildren, isUpdating);
109  }
110  }
111 
112  if (Owner.ObservableViewModelService != null)
113  {
114  var data = Owner.ObservableViewModelService.RequestAssociatedData(this, isUpdating);
115  SetValue(ref associatedData, data, "AssociatedData");
116  }
117  CheckDynamicMemberConsistency();
118  }
119 
120  private void NodePropertyChanged(object sender, PropertyChangedEventArgs e)
121  {
122  if (ChangeInProgress)
123  {
124  ChangedNodes.Add(this);
125  }
126  }
127 
128  internal static CombinedObservableNode Create(ObservableViewModel ownerViewModel, string name, CombinedObservableNode parent, Type contentType, IEnumerable<SingleObservableNode> combinedNodes, object index)
129  {
130  var node = (CombinedObservableNode)Activator.CreateInstance(typeof(CombinedObservableNode<>).MakeGenericType(contentType), ownerViewModel, name, parent, combinedNodes, index);
131  return node;
132  }
133 
134  /// <inheritdoc/>
135  public override sealed bool IsPrimitive { get { return CombinedNodes.All(x => x.IsPrimitive); } }
136 
137  public IReadOnlyCollection<SingleObservableNode> CombinedNodes { get { return combinedNodes; } }
138 
139  public bool HasMultipleValues { get { if (Type.IsValueType || Type == typeof(string)) return CombinedNodes.Any(x => !Equals(x.Value, CombinedNodes.First().Value)); return Children.Any(x => ((CombinedObservableNode)x).HasMultipleValues); } }
140 
141  public bool HasMultipleInitialValues { get { if (Type.IsValueType || Type == typeof(string)) return distinctCombinedNodeInitialValues.Count > 1; return Children.Any(x => ((CombinedObservableNode)x).HasMultipleInitialValues); } }
142 
143  public ICommandBase ResetInitialValues { get; private set; }
144 
145  public IEnumerable<object> DistinctInitialValues { get { return distinctCombinedNodeInitialValues; } }
146 
147  public override int? Order { get { return order; } }
148 
149  /// <inheritdoc/>
150  public override sealed bool HasList { get { return CombinedNodes.First().HasList; } }
151 
152  /// <inheritdoc/>
153  public override sealed bool HasDictionary { get { return CombinedNodes.First().HasDictionary; } }
154 
155  // TODO: we shall find a better way to handle combined associated data...
156  /// <inheritdoc/>
157  public override IDictionary<string, object> AssociatedData { get { return associatedData; } }
158 
159  // TODO: do not remove from parent if we can avoid it
160  public void Refresh()
161  {
162  if (Parent == null) throw new InvalidOperationException("The node to refresh can be a root node.");
163 
164  OnPropertyChanging("TypedValue", "HasMultipleValues", "IsPrimitive", "HasList", "HasDictionary");
165  if (CombinedNodes.Any(x => x != null))
166  {
167  var parent = (CombinedObservableNode)Parent;
168  parent.RemoveChild(this);
169 
170  if (AreCombinable(CombinedNodes))
171  {
172  ClearCommands();
173 
174  foreach (var child in Children.ToList())
175  RemoveChild(child);
176 
177  foreach (var modelNode in CombinedNodes.OfType<ObservableModelNode>())
178  modelNode.Refresh();
179 
180  Initialize(true);
181  }
182  parent.AddChild(this);
183  }
184  OnPropertyChanged("TypedValue", "HasMultipleValues", "IsPrimitive", "HasList", "HasDictionary");
185  }
186 
187  public static bool AreCombinable(IEnumerable<SingleObservableNode> nodes, bool ignoreNameConstraint = false)
188  {
189  bool firstNode = true;
190 
191  Type type = null;
192  string name = null;
193  object index = null;
194  foreach (var node in nodes)
195  {
196  if (firstNode)
197  {
198  type = node.Type;
199  name = node.Name;
200  index = node.Index;
201  firstNode = false;
202  }
203  else
204  {
205  if (node.Type != type)
206  return false;
207  if (!ignoreNameConstraint && node.Name != name)
208  return false;
209  if (!Equals(node.Index, index))
210  return false;
211  }
212  }
213  return true;
214  }
215 
216  private void GenerateChildren(IEnumerable<KeyValuePair<string, List<SingleObservableNode>>> commonChildren, bool isUpdating)
217  {
218  foreach (var children in commonChildren)
219  {
220  var contentType = children.Value.First().Type;
221  var index = children.Value.First().Index;
222  CombinedObservableNode child = Create(Owner, children.Key, this, contentType, children.Value, index);
223  child.Initialize(isUpdating);
224  AddChild(child);
225  }
226  }
227 
228  private void GenerateListChildren(IEnumerable<KeyValuePair<object, List<SingleObservableNode>>> allChildren, bool isUpdating)
229  {
230  int currentIndex = 0;
231  foreach (var children in allChildren)
232  {
233  if (!ShouldCombine(children.Value, CombinedNodes.Count, "(ListItem)", true))
234  continue;
235 
236  var contentType = children.Value.First().Type;
237  var name = string.Format("Item {0}", currentIndex);
238  CombinedObservableNode child = Create(Owner, name, this, contentType, children.Value, currentIndex);
239  child.Initialize(isUpdating);
240  child.DisplayName = name;
241  ++currentIndex;
242  AddChild(child);
243  }
244  }
245 
247  {
248  var allChildNodes = new Dictionary<string, List<SingleObservableNode>>();
249  foreach (var singleNode in CombinedNodes)
250  {
251  foreach (var observableNode in singleNode.Children)
252  {
253  var child = (SingleObservableNode)observableNode;
254  var list = allChildNodes.GetOrCreateValue(child.Name);
255  list.Add(child);
256  }
257  }
258 
259  return allChildNodes.Where(x => ShouldCombine(x.Value, CombinedNodes.Count, x.Key));
260  }
261 
262  private static bool ShouldCombine(List<SingleObservableNode> nodes, int combineCount, string name, bool ignoreNameConstraint = false)
263  {
264  CombineMode? combineMode = null;
265 
266  if (!AreCombinable(nodes, ignoreNameConstraint))
267  return false;
268 
269  foreach (var node in nodes)
270  {
271  if (combineMode == null)
272  combineMode = node.CombineMode;
273 
274  if (combineMode != node.CombineMode)
275  throw new InvalidOperationException(string.Format("Inconsistent values of CombineMode in single nodes for child '{0}'", name));
276  }
277 
278  if (combineMode == CombineMode.DoNotCombine)
279  return false;
280 
281  return combineMode == CombineMode.AlwaysCombine || nodes.Count == combineCount;
282  }
283 
284  private IEnumerable<KeyValuePair<object, List<SingleObservableNode>>> GetAllChildrenByValue()
285  {
286  var allChildNodes = new List<KeyValuePair<object, List<SingleObservableNode>>>();
287  foreach (var singleNode in CombinedNodes)
288  {
289  var usedSlots = new List<List<SingleObservableNode>>();
290  foreach (var observableNode in singleNode.Children)
291  {
292  var child = (SingleObservableNode)observableNode;
293  if (!child.Type.IsValueType && child.Type != typeof(string))
294  return null;
295 
296  var list = allChildNodes.FirstOrDefault(x => Equals(x.Key, child.Value) && !usedSlots.Contains(x.Value)).Value;
297  if (list == null)
298  {
299  list = new List<SingleObservableNode>();
300  allChildNodes.Add(new KeyValuePair<object, List<SingleObservableNode>>(child.Value, list));
301  }
302  list.Add(child);
303  usedSlots.Add(list);
304  }
305  }
306 
307  return allChildNodes;
308  }
309  }
310 
312  {
313  public CombinedObservableNode(ObservableViewModel ownerViewModel, string name, CombinedObservableNode parentNode, IEnumerable<SingleObservableNode> combinedNodes, object index)
314  : base(ownerViewModel, name, parentNode, combinedNodes, index)
315  {
316  DependentProperties.Add(Tuple.Create("TypedValue", "Value"));
317  }
318 
319  /// <summary>
320  /// Gets or sets the value of this node through a correctly typed property, which is more adapted to binding.
321  /// </summary>
322  public T TypedValue
323  {
324  get
325  {
326  return HasMultipleValues ? default(T) : (T)CombinedNodes.First().Value;
327  }
328  set
329  {
330  Owner.BeginCombinedAction();
331  ChangeInProgress = true;
332  CombinedNodes.Where(x => x.IsVisible).ForEach(x => x.Value = value);
333  var changedNodes = ChangedNodes.Where(x => x != this).ToList();
334  ChangedNodes.Clear();
335  ChangeInProgress = false;
336  Refresh();
337  changedNodes.ForEach(x => x.Refresh());
338  string displayName = Owner.FormatCombinedUpdateMessage(this, value);
339  Owner.EndCombinedAction(displayName, Path, value);
340  }
341  }
342 
343  /// <inheritdoc/>
344  public override Type Type { get { return typeof(T); } }
345 
346  /// <inheritdoc/>
347  public override sealed object Value { get { return TypedValue; } set { TypedValue = (T)value; } }
348  }
349 }
An implementation of CommandBase that route Execute calls to a given anonymous method.
An interface representing an implementation of ICommand with additional properties.
Definition: ICommandBase.cs:10
CombineMode
An enum that describes what to do with a node or a command when combining view models.
Definition: CombineMode.cs:8
Creates a new file, always. If a file exists, the function overwrites the file, clears the existing a...
static bool AreCombinable(IEnumerable< SingleObservableNode > nodes, bool ignoreNameConstraint=false)
CombinedObservableNode(ObservableViewModel ownerViewModel, string name, CombinedObservableNode parentNode, IEnumerable< SingleObservableNode > combinedNodes, object index)
CombinedObservableNode(ObservableViewModel ownerViewModel, string name, CombinedObservableNode parentNode, IEnumerable< SingleObservableNode > combinedNodes, object index)