Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ObservableNode.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.Collections.ObjectModel;
6 using System.Dynamic;
7 using System.Linq;
8 using System.Linq.Expressions;
9 using System.Windows.Input;
10 
11 using SiliconStudio.Presentation.Collections;
12 using SiliconStudio.Presentation.Core;
13 using SiliconStudio.Presentation.ViewModel;
14 using SiliconStudio.Quantum;
15 using SiliconStudio.Quantum.Contents;
16 
17 namespace SiliconStudio.Presentation.Quantum
18 {
20  {
21  private readonly SortedObservableCollection<IObservableNode> children = new SortedObservableCollection<IObservableNode>(new AnonymousComparer<IObservableNode>(CompareChildren));
22 
23  private readonly ObservableCollection<INodeCommandWrapper> commands = new ObservableCollection<INodeCommandWrapper>();
24  private bool isVisible;
25  private bool isReadOnly;
26  private string displayName;
27 
28  protected ObservableNode(ObservableViewModel ownerViewModel, IObservableNode parentNode, object index = null)
29  : base(ownerViewModel.ServiceProvider)
30  {
31  Owner = ownerViewModel;
32  Parent = parentNode;
33  Index = index;
34  Guid = Guid.NewGuid();
35  IsVisible = true;
36  IsReadOnly = false;
37  }
38 
39  /// <summary>
40  /// Gets the <see cref="ObservableViewModel"/> that owns this node.
41  /// </summary>
42  public ObservableViewModel Owner { get; private set; }
43 
44  /// <summary>
45  /// Gets or sets the name of this node. Note that the name can be used to access this node from its parent using a dynamic object.
46  /// </summary>
47  public string Name { get; protected set; }
48 
49  /// <summary>
50  /// Gets or sets the name used to display the node to the user.
51  /// </summary>
52  public string DisplayName { get { return displayName; } set { SetValue(ref displayName, value); } }
53 
54  /// <summary>
55  /// Gets the path of this node. The path is constructed from the name of all node from the root to this one, separated by periods.
56  /// </summary>
57  public string Path { get { return Parent != null ? Parent.Path + '.' + Name : Name; } }
58 
59  /// <summary>
60  /// Gets the parent of this node.
61  /// </summary>
62  public IObservableNode Parent { get; private set; }
63 
64  /// <summary>
65  /// Gets the root of this node.
66  /// </summary>
67  public IObservableNode Root { get { IObservableNode root = this; while (root.Parent != null) root = root.Parent; return root; } }
68 
69  /// <summary>
70  /// Gets the expected type of <see cref="Value"/>.
71  /// </summary>
72  public abstract Type Type { get; }
73 
74  /// <summary>
75  /// Gets whether this node contains a primitive value. A primitive value has no children node and does not need to refresh its hierarchy when its value is modified.
76  /// </summary>
77  public abstract bool IsPrimitive { get; }
78 
79  /// <summary>
80  /// Gets or sets whether this node should be displayed in the view.
81  /// </summary>
82  public bool IsVisible { get { return isVisible; } set { SetValue(ref isVisible, value); } }
83 
84  /// <summary>
85  /// Gets or sets whether this node can be modified in the view.
86  /// </summary>
87  public bool IsReadOnly { get { return isReadOnly; } set { SetValue(ref isReadOnly, value); } }
88 
89  /// <summary>
90  /// Gets or sets the value.
91  /// </summary>
92  public abstract object Value { get; set; }
93 
94  /// <summary>
95  /// Gets or sets the index of this node, relative to its parent node when its contains a collection. Can be null of this node is not in a collection.
96  /// </summary>
97  public object Index { get; private set; }
98 
99  /// <summary>
100  /// Gets a unique identifier for this observable node.
101  /// </summary>
102  public Guid Guid { get; private set; }
103 
104  /// <summary>
105  /// Gets the list of children nodes.
106  /// </summary>
107  public IReadOnlyCollection<IObservableNode> Children { get { return children; } }
108 
109  /// <summary>
110  /// Gets the list of commands available in this node.
111  /// </summary>
112  public IEnumerable<INodeCommandWrapper> Commands { get { return commands; } }
113 
114  /// <summary>
115  /// Gets additional data associated to this content. This can be used when the content itself does not contain enough information to be used as a view model.
116  /// </summary>
117  public abstract IDictionary<string, object> AssociatedData { get; }
118 
119  /// <summary>
120  /// Gets the order number of this node in its parent.
121  /// </summary>
122  public abstract int? Order { get; }
123 
124  /// <summary>
125  /// Gets whether this node contains a list
126  /// </summary>
127  public abstract bool HasList { get; }
128 
129  /// <summary>
130  /// Gets whether this node contains a dictionary
131  /// </summary>
132  public abstract bool HasDictionary { get; }
133 
134  /// <summary>
135  /// Gets or sets the flags associated to this node.
136  /// </summary>
137  public ViewModelContentFlags Flags { get; set; }
138 
139  /// <summary>
140  /// Gets or sets the serialization flags associated to this node.
141  /// </summary>
142  public ViewModelContentSerializeFlags SerializeFlags { get; set; }
143 
144  /// <summary>
145  /// Gets or sets the state flags associated to this node.
146  /// </summary>
147  public ViewModelContentState LoadState { get; set; }
148 
149  /// <summary>
150  /// Indicates whether this node can be moved.
151  /// </summary>
152  /// <param name="newParent">The new parent of the node once moved.</param>
153  /// <returns><c>true</c> if the node can be moved, <c>fals</c> otherwise.</returns>
154  public bool CanMove(IObservableNode newParent)
155  {
156  if (newParent is CombinedObservableNode)
157  return false;
158 
159  var parent = newParent;
160  while (parent != null)
161  {
162  if (parent == this)
163  return false;
164  parent = parent.Parent;
165  }
166  return true;
167  }
168 
169  /// <summary>
170  /// Moves the node by setting it a new parent.
171  /// </summary>
172  /// <param name="newParent">The new parent of the node once moved.</param>
173  /// <param name="newName">The new name to give to the node once moved. This will modify its path. If <c>null</c>, it does not modify the name.</param>
174  public void Move(IObservableNode newParent, string newName = null)
175  {
176  if (this is CombinedObservableNode)
177  throw new InvalidOperationException("A CombinedObservableNode cannot be moved.");
178  if (newParent is CombinedObservableNode)
179  throw new ArgumentException("The new parent cannot be a CombinedObservableNode");
180 
181  var parent = (ObservableNode)newParent;
182  while (parent != null)
183  {
184  if (parent == this)
185  throw new InvalidOperationException("A node cannot be moved into itself or one of its children.");
186  parent = (ObservableNode)parent.Parent;
187  }
188 
189  if (newParent.Children.Any(x => (newName == null && x.Name == Name) || x.Name == newName))
190  throw new InvalidOperationException("Unable to move this node, a node with the same name already exists.");
191 
192  if (Parent != null)
193  {
194  parent = (ObservableNode)Parent;
195  parent.RemoveChild(this);
196  }
197 
198  if (newName != null)
199  {
200  Name = newName;
201  }
202  Parent = newParent;
203  ((ObservableNode)newParent).AddChild(this);
204  UpdateCommandPath();
205  }
206 
207  /// <summary>
208  /// Returns the child node with the matching name.
209  /// </summary>
210  /// <param name="name">The name of the <see cref="ObservableNode"/> to look for.</param>
211  /// <returns>The corresponding child node, or <c>null</c> if no child with the given name exists.</returns>
212  public ObservableNode GetChild(string name)
213  {
214  return (ObservableNode)Children.FirstOrDefault(x => x.Name == name);
215  }
216 
217  /// <summary>
218  /// Returns the command with the matching name.
219  /// </summary>
220  /// <param name="name">The name of the command to look for.</param>
221  /// <returns>The corresponding command, or <c>null</c> if no command with the given name exists.</returns>
222  public ICommand GetCommand(string name)
223  {
224  return Commands.FirstOrDefault(x => x.Name == name);
225  }
226 
227  /// <summary>
228  /// Returns the additionnal data with the matching name.
229  /// </summary>
230  /// <param name="name">The name of the additionnal data to look for.</param>
231  /// <returns>The corresponding additionnal data, or <c>null</c> if no data with the given name exists.</returns>
232  public object GetAssociatedData(string name)
233  {
234  return AssociatedData.FirstOrDefault(x => x.Key == name).Value;
235  }
236 
237  /// <summary>
238  /// Returns the child node, the command or the additional data with the matching name.
239  /// </summary>
240  /// <param name="name">The name of the object to look for.</param>
241  /// <returns>The corresponding object, or <c>null</c> if no object with the given name exists.</returns>
242  public object GetDynamicObject(string name)
243  {
244  return GetChild(name) ?? GetCommand(name) ?? GetAssociatedData(name);
245  }
246 
247  /// <inheritdoc/>
248  public DynamicMetaObject GetMetaObject(Expression parameter)
249  {
250  return new ObservableNodeDynamicMetaObject(parameter, this);
251  }
252 
253  public void NotifyPropertyChanging(string propertyName)
254  {
255  OnPropertyChanging(propertyName, ObservableViewModel.HasChildPrefix + propertyName);
256  }
257 
258  public void NotifyPropertyChanged(string propertyName)
259  {
260  OnPropertyChanged(propertyName, ObservableViewModel.HasChildPrefix + propertyName);
261  }
262 
263  internal void AddChild(IObservableNode node)
264  {
265  if (node == null) throw new ArgumentNullException("node");
266  if (children.Contains(node)) throw new InvalidOperationException("The node is already in the children list of its parent.");
267  NotifyPropertyChanging(node.Name);
268  children.Add(node);
269  NotifyPropertyChanged(node.Name);
270  }
271 
272  internal void RemoveChild(IObservableNode node)
273  {
274  if (node == null) throw new ArgumentNullException("node");
275  if (!children.Contains(node)) throw new InvalidOperationException("The node is not in the children list of its parent.");
276  NotifyPropertyChanging(node.Name);
277  children.Remove(node);
278  NotifyPropertyChanged(node.Name);
279  }
280 
281  protected void AddCommand(INodeCommandWrapper command)
282  {
283  if (command == null) throw new ArgumentNullException("command");
284  OnPropertyChanging(string.Format("{0}{1}", ObservableViewModel.HasCommandPrefix, command.Name));
285  OnPropertyChanging(command.Name);
286  commands.Add(command);
287  OnPropertyChanged(command.Name);
288  OnPropertyChanged(string.Format("{0}{1}", ObservableViewModel.HasCommandPrefix, command.Name));
289  }
290 
291  protected void ClearCommands()
292  {
293  var commandNames = commands.Select(x => x.Name).ToList();
294  foreach (string commandName in commandNames)
295  {
296  OnPropertyChanging(string.Format("{0}{1}", ObservableViewModel.HasCommandPrefix, commandName));
297  OnPropertyChanging(commandName);
298  }
299  commands.Clear();
300  for (int i = commandNames.Count - 1; i >= 0; --i)
301  {
302  OnPropertyChanged(commandNames[i]);
303  OnPropertyChanged(string.Format("{0}{1}", ObservableViewModel.HasCommandPrefix, commandNames[i]));
304  }
305  }
306 
308  {
309  var memberNames = new HashSet<string>();
310  foreach (var child in Children)
311  {
312  if (string.IsNullOrWhiteSpace(child.Name))
313  throw new InvalidOperationException("This node has a child with a null or blank name");
314 
315  if (child.Name.Contains('.'))
316  throw new InvalidOperationException(string.Format("This node has a child which contains a period (.) in its name: {0}", child.Name));
317 
318  if (memberNames.Contains(child.Name))
319  throw new InvalidOperationException(string.Format("This node contains several members named {0}", child.Name));
320 
321  memberNames.Add(child.Name);
322  }
323 
324  foreach (var command in Commands.OfType<ModelNodeCommandWrapper>())
325  {
326  if (string.IsNullOrWhiteSpace(command.Name))
327  throw new InvalidOperationException("This node has a command with a null or blank name {0}");
328 
329  if (memberNames.Contains(command.Name))
330  throw new InvalidOperationException(string.Format("This node contains several members named {0}", command.Name));
331 
332  memberNames.Add(command.Name);
333  }
334 
335  foreach (var associatedDataKey in AssociatedData.Keys)
336  {
337  if (string.IsNullOrWhiteSpace(associatedDataKey))
338  throw new InvalidOperationException("This node has associated data with a null or blank name {0}");
339 
340  if (memberNames.Contains(associatedDataKey))
341  throw new InvalidOperationException(string.Format("This node contains several members named {0}", associatedDataKey));
342 
343  memberNames.Add(associatedDataKey);
344  }
345  }
346 
347  private void UpdateCommandPath()
348  {
349  foreach (var commandWrapper in Commands.OfType<NodeCommandWrapperBase>())
350  {
351  commandWrapper.ObservableNodePath = Path;
352  }
353  foreach (var child in Children.OfType<ObservableNode>())
354  {
355  child.UpdateCommandPath();
356  }
357  }
358 
359  private static int CompareChildren(IObservableNode a, IObservableNode b)
360  {
361  // Order has the best priority for comparison, if set.
362  if (a.Order != null && b.Order != null)
363  return ((int)a.Order).CompareTo(b.Order);
364 
365  // Then we use index, if they are set and comparable.
366  if (a.Index != null && b.Index != null)
367  {
368  if (a.Index.GetType() == b.Index.GetType() && a.Index is IComparable)
369  {
370  return ((IComparable)a.Index).CompareTo(b.Index);
371  }
372  }
373 
374  // Then we use name, only if both orders are unset.
375  if (a.Order == null && b.Order == null)
376  return string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase);
377 
378  // Otherwise, the first child would be the one who have an order value.
379  return a.Order == null ? 1 : -1;
380  }
381  }
382 }
ObservableNode(ObservableViewModel ownerViewModel, IObservableNode parentNode, object index=null)
function b
object GetDynamicObject(string name)
Returns the child node, the command or the additional data with the matching name.
bool CanMove(IObservableNode newParent)
Indicates whether this node can be moved.
function a
IReadOnlyCollection< IObservableNode > Children
Gets the list of children nodes.
DynamicMetaObject GetMetaObject(Expression parameter)
void Move(IObservableNode newParent, string newName=null)
Moves the node by setting it a new parent.
Flags
Enumeration of the new Assimp's flags.
This abstract class is an implementation of ViewModelBase that uses a dispatcher to invoke OnProperty...
A base class to wrap one or multiple INodeCommand instances into a CancellableCommand.
string Name
Gets or sets the name of this node. Note that the name can be used to access this node from its paren...
ViewModelContentSerializeFlags
Flags applying to IContent.
IObservableNode Parent
Gets or the parent of this node.
ObservableNode GetChild(string name)
Returns the child node with the matching name.
ICommand GetCommand(string name)
Returns the command with the matching name.
object GetAssociatedData(string name)
Returns the additionnal data with the matching name.
void AddCommand(INodeCommandWrapper command)