Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ObservableModelNode.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.Linq;
6 
7 using SiliconStudio.Core.Reflection;
8 using SiliconStudio.Quantum;
9 using SiliconStudio.Quantum.Contents;
10 using SiliconStudio.Quantum.References;
11 
12 namespace SiliconStudio.Presentation.Quantum
13 {
14  public abstract class ObservableModelNode : SingleObservableNode
15  {
16  private readonly bool isPrimitive;
17  private readonly IModelNode sourceNode;
18  private IModelNode targetNode;
19  private IDictionary<string, object> associatedData;
20  private bool isInitialized;
21 
22  /// <summary>
23  /// Initializes a new instance of the <see cref="ObservableModelNode"/> class.
24  /// </summary>
25  /// <param name="ownerViewModel">The <see cref="ObservableViewModel"/> that owns the new <see cref="ObservableModelNode"/>.</param>
26  /// <param name="baseName">The base name of this node. Can be null if <see cref="index"/> is not. If so a name will be automatically generated from the index.</param>
27  /// <param name="isPrimitive">Indicate whether this node should be considered as a primitive node.</param>
28  /// <param name="parentNode">The parent node of the new <see cref="ObservableModelNode"/>, or <c>null</c> if the node being created is the root node of the view model.</param>
29  /// <param name="modelNode">The model node bound to the new <see cref="ObservableModelNode"/>.</param>
30  /// <param name="index">The index of this content in the model node, when this node represent an item of a collection. <c>null</c> must be passed otherwise</param>
31  protected ObservableModelNode(ObservableViewModel ownerViewModel, string baseName, bool isPrimitive, SingleObservableNode parentNode, IModelNode modelNode, object index = null)
32  : base(ownerViewModel, baseName, parentNode, index)
33  {
34  if (modelNode == null) throw new ArgumentNullException("modelNode");
35  if (baseName == null && index == null)
36  throw new ArgumentException("baseName and index can't be both null.");
37 
38  this.isPrimitive = isPrimitive;
39  sourceNode = modelNode;
40  // By default we will always combine items of list of primitive items.
41  CombineMode = index != null && isPrimitive ? CombineMode.AlwaysCombine : CombineMode.CombineOnlyForAll;
42  targetNode = GetTargetNode(modelNode, index);
43  }
44 
45  /// <summary>
46  /// Create an <see cref="ObservableModelNode{T}"/> that matches the given content type.
47  /// </summary>
48  /// <param name="ownerViewModel">The <see cref="ObservableViewModel"/> that owns the new <see cref="ObservableModelNode"/>.</param>
49  /// <param name="baseName">The base name of this node. Can be null if <see cref="index"/> is not. If so a name will be automatically generated from the index.</param>
50  /// <param name="isPrimitive">Indicate whether this node should be considered as a primitive node.</param>
51  /// <param name="parentNode">The parent node of the new <see cref="ObservableModelNode"/>, or <c>null</c> if the node being created is the root node of the view model.</param>
52  /// <param name="modelNode">The model node bound to the new <see cref="ObservableModelNode"/>.</param>
53  /// <param name="contentType">The type of content contained by the new <see cref="ObservableModelNode"/>.</param>
54  /// <param name="index">The index of this content in the model node, when this node represent an item of a collection. <c>null</c> must be passed otherwise</param>
55  /// <returns>A new instance of <see cref="ObservableModelNode{T}"/> instanced with the given content type as generic argument.</returns>
56  internal static ObservableModelNode Create(ObservableViewModel ownerViewModel, string baseName, bool isPrimitive, SingleObservableNode parentNode, IModelNode modelNode, Type contentType, object index)
57  {
58  var node = (ObservableModelNode)Activator.CreateInstance(typeof(ObservableModelNode<>).MakeGenericType(contentType), ownerViewModel, baseName, isPrimitive, parentNode, modelNode, index);
59  return node;
60  }
61 
62  internal void Initialize()
63  {
64  Initialize(false);
65  }
66 
67  private void Initialize(bool isUpdating)
68  {
69  var path = ModelNodePath.GetPath(((ObservableModelNode)Root).sourceNode, targetNode);
70  if (!path.IsValid)
71  throw new InvalidOperationException("Unable to retrieve the path of the given model node.");
72 
73  foreach (var command in targetNode.Commands)
74  {
75  var commandWrapper = new ModelNodeCommandWrapper(ServiceProvider, command, Path, Owner.Identifier, path, Owner.ModelContainer, Owner.GetDirtiableViewModels(this));
76  AddCommand(commandWrapper);
77  }
78 
79  if (!isPrimitive)
80  GenerateChildren(targetNode, isUpdating);
81 
82  isInitialized = true;
83 
84  if (Owner.ObservableViewModelService != null)
85  {
86  var data = Owner.ObservableViewModelService.RequestAssociatedData(this, isUpdating);
87  SetValue(ref associatedData, data, "AssociatedData");
88  }
89 
90  CheckDynamicMemberConsistency();
91  }
92 
93  /// <inheritdoc/>
94  public override int? Order { get { return sourceNode.Content is MemberContent && (!(sourceNode.Content.Reference is ReferenceEnumerable) && Index == null) ? ((MemberContent)sourceNode.Content).Member.Order : null; } }
95 
96  /// <inheritdoc/>
97  public sealed override bool IsPrimitive { get { AssertInit(); return isPrimitive; } }
98 
99  // To distinguish between lists and items of a list (which have the same TargetNode if the items are primitive types), we check whether the TargetNode is
100  // the same of the one of its parent. If so, we're likely in an item of a list of primitive objects.
101  /// <inheritdoc/>
102  public sealed override bool HasList { get { AssertInit(); return (targetNode.Content.Descriptor is CollectionDescriptor && (Parent == null || (ModelNodeParent != null && ModelNodeParent.targetNode.Content.Value != targetNode.Content.Value))) || targetNode.Content.Reference is ReferenceEnumerable; } }
103 
104  // To distinguish between dictionaries and items of a dictionary (which have the same TargetNode if the value type is a primitive type), we check whether the TargetNode is
105  // the same of the one of its parent. If so, we're likely in an item of a dictionary of primitive objects.
106  /// <inheritdoc/>
107  public sealed override bool HasDictionary { get { AssertInit(); return (targetNode.Content.Descriptor is DictionaryDescriptor && (Parent == null || (ModelNodeParent != null && ModelNodeParent.targetNode.Content.Value != targetNode.Content.Value))) || (targetNode.Content.Reference is ReferenceEnumerable && ((ReferenceEnumerable)targetNode.Content.Reference).IsDictionary); } }
108 
109  /// <inheritdoc/>
110  public sealed override IDictionary<string, object> AssociatedData { get { return associatedData; } }
111 
112  internal Guid ModelGuid { get { return targetNode.Guid; } }
113 
114  private ObservableModelNode ModelNodeParent { get { AssertInit(); for (var p = Parent; p != null; p = p.Parent) { var mp = p as ObservableModelNode; if (mp != null) return mp; } return null; } }
115 
116  /// <summary>
117  /// Retrieves a <see cref="ModelNodePath"/> object corresponding to the path of the model node contained in this <see cref="ObservableModelNode"/>.
118  /// </summary>
119  /// <returns>A <see cref="ModelNodePath"/> object corresponding to the path of the model node contained in this <see cref="ObservableModelNode"/>.</returns>
121  {
122  return ModelNodePath.GetPath(((ObservableModelNode)Root).sourceNode, sourceNode);
123  }
124 
125  /// <summary>
126  /// Indicates whether this <see cref="ObservableModelNode"/> instance corresponds to the given <see cref="IModelNode"/>.
127  /// </summary>
128  /// <param name="node">The node to match.</param>
129  /// <returns><c>true</c> if the node matches, <c>false</c> otherwise.</returns>
130  public bool MatchNode(IModelNode node)
131  {
132  return sourceNode == node;
133  }
134 
136  {
137  var memberContent = sourceNode.Content as MemberContent;
138  return memberContent != null ? memberContent.Member : null;
139  }
140 
141  internal void CheckConsistency()
142  {
143 #if DEBUG
144  if (sourceNode != targetNode)
145  {
146  var objectReference = sourceNode.Content.Reference as ObjectReference;
147  var referenceEnumerable = sourceNode.Content.Reference as ReferenceEnumerable;
148  if (objectReference != null && targetNode != objectReference.TargetNode)
149  {
150  throw new ObservableViewModelConsistencyException(this, "The target node does not match the target of the source node object reference.");
151  }
152  if (referenceEnumerable != null && Index != null)
153  {
154  if (!referenceEnumerable.ContainsIndex(Index))
155  throw new ObservableViewModelConsistencyException(this, "The Index of this node does not exist in the reference of its source node.");
156 
157  if (targetNode != referenceEnumerable[Index].TargetNode)
158  {
159  throw new ObservableViewModelConsistencyException(this, "The target node does not match the target of the source node object reference.");
160  }
161  }
162  }
163 
164  var modelContentValue = GetModelContentValue();
165  if (!Equals(modelContentValue, Value))
166  {
167  // TODO: I had this exception with a property that is returning a new IEnumerable each time - we should have a way to notice this, maybe by correctly transfering and checking the IsReadOnly property
168  //throw new ObservableViewModelConsistencyException(this, "The value of this node does not match the value of its source node content.");
169  }
170 
171  foreach (var child in Children.OfType<ObservableModelNode>())
172  {
173  if (targetNode.Content.IsReference)
174  {
175  var objectReference = targetNode.Content.Reference as ObjectReference;
176  if (objectReference != null)
177  {
178  throw new ObservableViewModelConsistencyException(this, "The target node does not match the target of the source node object reference.");
179  }
180  }
181  else if (!targetNode.Children.Contains(child.sourceNode))
182  {
183  if (child.Index == null || !child.IsPrimitive)
184  {
185  throw new ObservableViewModelConsistencyException(child, "The source node of this node is not a child of the target node of its parent.");
186  }
187  }
188  child.CheckConsistency();
189  }
190 #endif
191  }
192 
193  public new void ClearCommands()
194  {
195  base.ClearCommands();
196  }
197 
198  protected void AssertInit()
199  {
200  if (!isInitialized)
201  {
202  throw new InvalidOperationException("Accessing a property of a non-initialized ObservableNode.");
203  }
204  }
205 
206  /// <summary>
207  /// Retrieve the value of the model content associated to this <see cref="ObservableModelNode"/>.
208  /// </summary>
209  /// <returns>The value of the model content associated to this <see cref="ObservableModelNode"/>.</returns>
210  protected object GetModelContentValue()
211  {
212  var dictionary = sourceNode.Content.Descriptor as DictionaryDescriptor;
213  var list = sourceNode.Content.Descriptor as CollectionDescriptor;
214 
215  if (Index != null && dictionary != null)
216  return dictionary.GetValue(sourceNode.Content.Value, Index);
217 
218  if (Index != null && list != null)
219  return list.GetValue(sourceNode.Content.Value, Index);
220 
221  return sourceNode.Content.Value;
222  }
223 
224  /// <summary>
225  /// Sets the value of the model content associated to this <see cref="ObservableModelNode"/>. The value is actually modified only if the new value is different from the previous value.
226  /// </summary>
227  /// <returns><c>True</c> if the value has been modified, <c>false</c> otherwise.</returns>
228  protected bool SetModelContentValue(object newValue)
229  {
230  var dictionary = sourceNode.Content.Descriptor as DictionaryDescriptor;
231  var list = sourceNode.Content.Descriptor as CollectionDescriptor;
232  bool result = false;
233  if (Index != null && dictionary != null)
234  {
235  if (!Equals(dictionary.GetValue(sourceNode.Content.Value, Index), newValue))
236  {
237  result = true;
238  dictionary.SetValue(sourceNode.Content.Value, Index, newValue);
239  }
240  }
241  else if (Index != null && list != null)
242  {
243  if (!Equals(list.GetValue(sourceNode.Content.Value, Index), newValue))
244  {
245  result = true;
246  list.SetValue(sourceNode.Content.Value, Index, newValue);
247  }
248  }
249  else
250  {
251  if (!Equals(sourceNode.Content.Value, newValue))
252  {
253  result = true;
254  sourceNode.Content.Value = newValue;
255  }
256  }
257 
258  if (!IsPrimitive)
259  {
260  Owner.ModelContainer.UpdateReferences(sourceNode);
261  Refresh();
262  }
263  return result;
264  }
265 
266  private void GenerateChildren(IModelNode modelNode, bool isUpdating)
267  {
268  if (modelNode.Content.IsReference)
269  {
270  var referenceEnumerable = modelNode.Content.Reference as ReferenceEnumerable;
271  if (referenceEnumerable != null)
272  {
273  foreach (var reference in referenceEnumerable)
274  {
275  // The type might be a boxed primitive type, such as float, if the collection has object as generic argument.
276  // In this case, we must set the actual type to have type converter working, since they usually can't convert
277  // a boxed float to double for example. Otherwise, we want to have to have a node type that is value-dependent.
278  var type = reference.TargetNode != null && reference.TargetNode.Content.IsPrimitive ? reference.TargetNode.Content.Type : reference.Type;
279  var observableNode = Create(Owner, null, false, this, modelNode, type, reference.Index);
280  observableNode.Initialize(isUpdating);
281  AddChild(observableNode);
282  }
283  }
284  else
285  {
286  var targetViewModelNode = ((ObjectReference)modelNode.Content.Reference).TargetNode;
287  GenerateChildren(targetViewModelNode, isUpdating);
288  }
289  }
290  else
291  {
292  var dictionary = modelNode.Content.Descriptor as DictionaryDescriptor;
293  var list = modelNode.Content.Descriptor as CollectionDescriptor;
294  if (dictionary != null && modelNode.Content.Value != null)
295  {
296  // Dictionary of primitive objects
297  foreach (var key in dictionary.GetKeys(modelNode.Content.Value))
298  {
299  var observableChild = Create(Owner, null, true, this, modelNode, dictionary.ValueType, key);
300  observableChild.Initialize(isUpdating);
301  AddChild(observableChild);
302  }
303  }
304  else if (list != null && modelNode.Content.Value != null)
305  {
306  // List of primitive objects
307  for (int i = 0; i < list.GetCollectionCount(modelNode.Content.Value); ++i)
308  {
309  var observableChild = Create(Owner, null, true, this, modelNode, list.ElementType, i);
310  observableChild.Initialize(isUpdating);
311  AddChild(observableChild);
312  }
313  }
314  else
315  {
316  // Single non-reference primitive object
317  foreach (var child in modelNode.Children)
318  {
319  var observableChild = Create(Owner, child.Name, child.Content.IsPrimitive, this, child, child.Content.Type, null);
320  observableChild.Initialize(isUpdating);
321  AddChild(observableChild);
322  }
323  }
324  }
325  }
326 
327  internal void Refresh()
328  {
329  if (Parent == null) throw new InvalidOperationException("The node to refresh can't be a root node.");
330 
331  OnPropertyChanging("IsPrimitive", "HasList", "HasDictionary");
332 
333  targetNode = GetTargetNode(sourceNode, Index);
334 
335  // Clean the current node so it can be re-initialized (associatedData are overwritten in Initialize)
336  ClearCommands();
337  foreach (var child in Children.ToList())
338  RemoveChild(child);
339 
340  Initialize(true);
341 
342  OnPropertyChanged("IsPrimitive", "HasList", "HasDictionary");
343  }
344 
345  private static IModelNode GetTargetNode(IModelNode sourceNode, object index)
346  {
347  var objectReference = sourceNode.Content.Reference as ObjectReference;
348  var referenceEnumerable = sourceNode.Content.Reference as ReferenceEnumerable;
349  if (objectReference != null)
350  {
351  return objectReference.TargetNode;
352  }
353  if (referenceEnumerable != null && index != null)
354  {
355  return referenceEnumerable[index].TargetNode;
356  }
357  return sourceNode;
358  }
359  }
360 
362  {
363  /// <summary>
364  /// Construct a new <see cref="ObservableModelNode"/>.
365  /// </summary>
366  /// <param name="ownerViewModel">The <see cref="ObservableViewModel"/> that owns the new <see cref="ObservableModelNode"/>.</param>
367  /// <param name="baseName">The base name of this node. Can be null if <see cref="index"/> is not. If so a name will be automatically generated from the index.</param>
368  /// <param name="isPrimitive">Indicate whether this node should be considered as a primitive node.</param>
369  /// <param name="parentNode">The parent node of the new <see cref="ObservableModelNode"/>, or <c>null</c> if the node being created is the root node of the view model.</param>
370  /// <param name="modelNode">The model node bound to the new <see cref="ObservableModelNode"/>.</param>
371  /// <param name="index">The index of this content in the model node, when this node represent an item of a collection. <c>null</c> must be passed otherwise</param>
372  public ObservableModelNode(ObservableViewModel ownerViewModel, string baseName, bool isPrimitive, SingleObservableNode parentNode, IModelNode modelNode, object index)
373  : base(ownerViewModel, baseName, isPrimitive, parentNode, modelNode, index)
374  {
375  DependentProperties.Add(Tuple.Create("TypedValue", "Value"));
376  }
377 
378  /// <summary>
379  /// Gets or sets the value of this node through a correctly typed property, which is more adapted to binding.
380  /// </summary>
381  public T TypedValue
382  {
383  get
384  {
385  AssertInit();
386  return (T)GetModelContentValue();
387  }
388  set
389  {
390  AssertInit();
391  var previousValue = (T)GetModelContentValue();
392  bool hasChanged = !Equals(previousValue, value);
393  if (hasChanged)
394  {
395  OnPropertyChanging("TypedValue");
396  }
397 
398  // We set the value even if it has not changed in case it's a reference value and a refresh might be required (new node in a list, etc.)
399  SetModelContentValue(value);
400 
401  if (hasChanged)
402  {
403  OnPropertyChanged("TypedValue");
404  string displayName = Owner.FormatSingleUpdateMessage(this, value);
405  var dirtiables = Owner.GetDirtiableViewModels(this);
406  var nodePath = GetModelNodePath();
407  // This is just a sanity check
408  var checkNode = nodePath.GetNode();
409  if (!MatchNode(checkNode))
410  throw new InvalidOperationException("An internal error occured while building the node path.");
411 
412  Owner.RegisterAction(displayName, nodePath, Path, Index, dirtiables, value, previousValue);
413  }
414  }
415  }
416 
417  /// <inheritdoc/>
418  public override Type Type { get { return typeof(T); } }
419 
420  /// <inheritdoc/>
421  public override sealed object Value { get { return TypedValue; } set { TypedValue = (T)value; } }
422  }
423 }
Provides a descriptor for a System.Collections.ICollection.
IReference Reference
Gets or sets the reference hold by this content, if applicable.
Definition: IContent.cs:44
ObservableModelNode(ObservableViewModel ownerViewModel, string baseName, bool isPrimitive, SingleObservableNode parentNode, IModelNode modelNode, object index)
Construct a new ObservableModelNode.
ObservableModelNode(ObservableViewModel ownerViewModel, string baseName, bool isPrimitive, SingleObservableNode parentNode, IModelNode modelNode, object index=null)
Initializes a new instance of the ObservableModelNode class.
object GetModelContentValue()
Retrieve the value of the model content associated to this ObservableModelNode.
IContent Content
Gets the content of the IModelNode.
Definition: IModelNode.cs:41
A class representing an enumeration of references to multiple objects.
bool SetModelContentValue(object newValue)
Sets the value of the model content associated to this ObservableModelNode. The value is actually mod...
Provides a descriptor for a System.Collections.IDictionary.
An implementation of IContent that gives access to a member of an object.
CombineMode
An enum that describes what to do with a node or a command when combining view models.
Definition: CombineMode.cs:8
bool MatchNode(IModelNode node)
Indicates whether this ObservableModelNode instance corresponds to the given IModelNode.
Creates a new file, always. If a file exists, the function overwrites the file, clears the existing a...
bool IsReference
Gets wheither this content holds a reference or is a direct value.
Definition: IContent.cs:39
ModelNodePath GetModelNodePath()
Retrieves a ModelNodePath object corresponding to the path of the model node contained in this Observ...
A class representing a reference to another object that has a different model.
The IModelNode interface represents a node in a model object. A model object is represented by a grap...
Definition: IModelNode.cs:16
object Value
Gets or sets the value.
Definition: IContent.cs:23
IReadOnlyCollection< IModelNode > Children
Gets the children collection.
Definition: IModelNode.cs:36
A class describing the path of a node, relative to a root node. The path can cross references...
An exception that occurs during consistency checks of ObservableViewModel nodes, indicating that an O...