a2191c7ed65cfbec82dfb3c84f0a9cc5705075d2
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / System.Activities.Presentation / System / Activities / Presentation / View / TreeView / TreeViewItemViewModel.cs
1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //----------------------------------------------------------------
4
5 namespace System.Activities.Presentation.View.TreeView
6 {
7     using System;
8     using System.Activities.Presentation.Internal.PropertyEditing;
9     using System.Activities.Presentation.Model;
10     using System.Activities.Presentation.View.OutlineView;
11     using System.Collections.Generic;
12     using System.Collections.ObjectModel;
13     using System.ComponentModel;
14     using System.Globalization;
15     using System.Linq;
16     using System.Runtime;
17     using System.Windows.Media;
18
19     class TreeViewItemViewModel : INotifyPropertyChanged
20     {
21         string nodePrefixText = string.Empty;
22         bool isExpanded = false;
23         bool isHighlighted = false;
24         DrawingBrush icon = null;
25         HashSet<ModelItem> uniqueChildren = null;
26         internal static TreeViewItemViewModel DummyNode = new TreeViewItemViewModel();
27         // the IconCache is a static singleton based on the assumption that the Icon for a particular type is 
28         // the same within an AppDomain
29         internal static Dictionary<Type, DrawingBrush> IconCache = new Dictionary<Type, DrawingBrush>();
30         DesignerPerfEventProvider provider = null;
31
32         public event PropertyChangedEventHandler PropertyChanged;
33         protected TreeViewItemViewModel()
34         {
35             this.IsAlive = true;
36             InternalChildren = new ObservableCollection<TreeViewItemViewModel>();
37             InternalChildren.CollectionChanged += new Collections.Specialized.NotifyCollectionChangedEventHandler(InternalChildren_CollectionChanged);
38             Children = new ReadOnlyObservableCollection<TreeViewItemViewModel>(InternalChildren);
39             ChildrenValueCache = new HashSet<object>();
40             DuplicatedNodeVisible = true;
41
42             this.Trackers = new Dictionary<ModelProperty, ChangeNotificationTracker>();
43         }
44
45         internal ObservableCollection<TreeViewItemViewModel> InternalChildren { get; private set; }
46
47         public ReadOnlyObservableCollection<TreeViewItemViewModel> Children { get; private set; }
48
49         public string NodePrefixText
50         {
51             get
52             {
53                 return nodePrefixText;
54             }
55             set
56             {
57                 nodePrefixText = value;
58                 this.NotifyPropertyChanged("NodePrefixText");
59             }
60         }
61
62         public DrawingBrush Icon
63         {
64             get { return icon; }
65             set
66             {
67                 if (value != this.icon)
68                 {
69                     this.icon = value;
70                     this.NotifyPropertyChanged("Icon");
71                 }
72             }
73         }
74
75         internal bool IsAlive { get; private set; }
76
77         public bool IsExpanded
78         {
79             get
80             {
81                 return isExpanded;
82             }
83             set
84             {
85                 if (value != this.isExpanded)
86                 {
87                     if (this.PerfEventProvider != null)
88                     {
89                         this.PerfEventProvider.DesignerTreeViewExpandStart();
90                     }
91                     this.isExpanded = value;
92                     if (this.isExpanded && this.Children.Count == 1 && this.Children[0] == DummyNode)
93                     {
94                         this.InternalChildren.Remove(DummyNode);
95                         this.LoadChildren();
96                     }
97                     this.NotifyPropertyChanged("IsExpanded");
98                     if (this.PerfEventProvider != null)
99                     {
100                         this.PerfEventProvider.DesignerTreeViewExpandEnd();
101                     }
102                 }
103             }
104         }
105
106         public bool IsHighlighted
107         {
108             get
109             {
110                 return this.isHighlighted;
111             }
112             set
113             {
114                 if (this.isHighlighted != value)
115                 {
116                     this.isHighlighted = value;
117                     this.NotifyPropertyChanged("IsHighlighted");
118                 }
119             }
120         }
121
122         internal bool DuplicatedNodeVisible { get; set; }
123
124         internal TreeViewItemState State { get; set; }
125
126         internal bool HasChildren
127         {
128             get
129             {
130                 return (this.State & TreeViewItemState.HasChildren) == TreeViewItemState.HasChildren;
131             }
132         }
133
134         internal bool HasSibling
135         {
136             get
137             {
138                 return (this.State & TreeViewItemState.HasSibling) == TreeViewItemState.HasSibling;
139             }
140         }
141
142         public TreeViewItemViewModel Parent { get; set; }
143
144         internal ITreeViewItemSelectionHandler TreeViewItem { get; set; }
145
146         protected HashSet<object> ChildrenValueCache { get; set; }
147
148         internal Dictionary<ModelProperty, ChangeNotificationTracker> Trackers { get; private set; }
149
150         protected DesignerPerfEventProvider PerfEventProvider
151         {
152             get
153             {
154                 if (provider == null)
155                 {
156                     EditingContext context = this.GetEditingContext();
157                     if (context != null)
158                     {
159                         provider = context.Services.GetService<DesignerPerfEventProvider>();
160                     }
161                 }
162                 return provider;
163             }
164         }
165
166         protected virtual EditingContext GetEditingContext()
167         {
168             return null;
169         }
170
171         internal virtual object GetValue()
172         {
173             return null;
174         }
175
176         internal virtual void LoadChildren()
177         {
178             foreach (ChangeNotificationTracker t in this.Trackers.Values)
179             {
180                 t.CleanUp();
181             }
182
183             foreach (TreeViewItemViewModel child in InternalChildren)
184             {
185                 child.CleanUp();
186             }
187
188             this.InternalChildren.Clear();
189             this.ChildrenValueCache.Clear();
190         }
191
192         internal virtual void UpdateChildren(ChangeNotificationTracker tracker, EventArgs e)
193         {
194             this.uniqueChildren = null;
195         }
196
197         //if child is null then only add the modelProperty for tracking purpose
198         internal virtual void AddChild(TreeViewItemViewModel child, ModelProperty modelProperty)
199         {
200             //check for duplicate first
201             if (child != null)
202             {
203                 object childValue = child.GetValue();
204                 if (!ChildrenValueCache.Contains(childValue))
205                 {
206                     ChildrenValueCache.Add(childValue);
207                 }
208                 else
209                 {
210                     child.CleanUp();
211                     return;
212                 }
213             }
214
215             ChangeNotificationTracker tracker = GetTracker(modelProperty);
216             if (child != null)
217             {
218                 // May be adding a node before it's expanded; get rid of the dummy
219                 if (this.Children.Count == 1 && this.Children[0] == DummyNode)
220                 {
221                     this.InternalChildren.Remove(DummyNode);
222                 }
223
224                 int insertIndex = this.FindInsertionIndex(tracker);
225                 this.InternalChildren.Insert(insertIndex, child);
226                 tracker.ChildViewModels.Add(child);
227                 if (child.HasSibling)
228                 {
229                     //loading children rather than just add the sibling of the children
230                     //if this turn out to be a big performance impact then we'll need to optimise this
231                     child.LoadChildren();
232                 }
233             }
234         }
235
236         internal static TreeViewItemViewModel CreateViewModel(TreeViewItemViewModel parent, object value)
237         {
238             TreeViewItemViewModel viewModel = null;
239             if (typeof(ModelItem).IsAssignableFrom(value.GetType()))
240             {
241                 viewModel = new TreeViewItemModelItemViewModel(parent, value as ModelItem);
242             }
243             else if (typeof(ModelProperty).IsAssignableFrom(value.GetType()))
244             {
245                 viewModel = new TreeViewItemModelPropertyViewModel(parent, value as ModelProperty);
246             }
247             else if (typeof(KeyValuePair<ModelItem, ModelItem>).IsAssignableFrom(value.GetType()))
248             {
249                 viewModel = new TreeViewItemKeyValuePairModelItemViewModel(parent, (KeyValuePair<ModelItem, ModelItem>)value);
250             }
251             return viewModel;
252         }
253
254         internal static void AddModelItem(TreeViewItemViewModel parent, ModelItem item, ModelProperty trackingProperty)
255         {
256             if (item != null)
257             {
258                 bool updateTrackingProperty = trackingProperty == null;
259
260                 foreach (ModelProperty property in item.Properties)
261                 {
262                     if (updateTrackingProperty)
263                     {
264                         trackingProperty = property;
265                     }
266                     AddModelProperty(parent, item, trackingProperty, property);
267                 }
268             }
269         }
270
271         internal static void AddModelItemCollection(TreeViewItemViewModel parent, ModelItemCollection collection, ModelProperty trackingProperty)
272         {
273             parent.GetTracker(trackingProperty).AddCollection(collection);
274
275             bool duplicatedNodeVisible = true;
276             string childNodePrefix = string.Empty;
277             ShowPropertyInOutlineViewAttribute viewChild = ExtensibilityAccessor.GetAttribute<ShowPropertyInOutlineViewAttribute>(trackingProperty);
278             if (viewChild != null)
279             {
280                 duplicatedNodeVisible = viewChild.DuplicatedChildNodesVisible;
281                 childNodePrefix = viewChild.ChildNodePrefix;
282             }
283
284             foreach (ModelItem item in collection)
285             {
286                 AddChild(parent, item, item, duplicatedNodeVisible, childNodePrefix, trackingProperty);
287             }
288         }
289
290         internal static void AddModelItemDictionary(TreeViewItemViewModel parent, ModelItemDictionary dictionary, ModelProperty trackingProperty)
291         {
292             parent.GetTracker(trackingProperty).AddCollection(dictionary);
293
294             bool duplicatedNodeVisible = true;
295             string childNodePrefix = string.Empty;
296             ShowPropertyInOutlineViewAttribute viewChild = ExtensibilityAccessor.GetAttribute<ShowPropertyInOutlineViewAttribute>(trackingProperty);
297             if (viewChild != null)
298             {
299                 duplicatedNodeVisible = viewChild.DuplicatedChildNodesVisible;
300                 childNodePrefix = viewChild.ChildNodePrefix;
301             }
302
303             foreach (var pair in dictionary)
304             {
305                 ModelItem item = null;
306                 //AddChild(parent, pair.Value, pair, duplicatedNodeVisible, trackingProperty);
307                 AddChild(parent, item, pair, duplicatedNodeVisible, childNodePrefix, trackingProperty);
308             }
309         }
310
311         internal static void AddModelProperty(TreeViewItemViewModel parent, ModelItem item, ModelProperty trackingProperty, ModelProperty property)
312         {
313             //in the case of multiple attributes, they go in this order
314             //HidePropertyInOutlineViewAttribute
315             //[item.ShowInOutlineViewAttribute.PromotedProperty = property.Name]. Set VisualValue by property and ignore itself. Usage ActivityDelegate, FlowStep.
316             //ShowPropertyInOutlineViewAttribute
317             //ShowPropertyInOutlineViewAsSiblingAttribute
318             //ShowInOutlineViewAttribute
319             ShowPropertyInOutlineViewAttribute viewChild = ExtensibilityAccessor.GetAttribute<ShowPropertyInOutlineViewAttribute>(property);
320             if (ExtensibilityAccessor.GetAttribute<HidePropertyInOutlineViewAttribute>(property) != null)
321             {
322                 //ignore
323                 return;
324             }
325             else if (IsPromotedProperty(item, property))
326             {
327                 if (property.IsCollection)
328                 {
329                     ModelItemCollection mc = property.Value as ModelItemCollection;
330                     AddModelItemCollection(parent, mc, trackingProperty);
331                 }
332                 else if (property.IsDictionary)
333                 {
334                     ModelItemDictionary dictionary = property.Dictionary;
335                     AddModelItemDictionary(parent, dictionary, trackingProperty);
336                 }
337                 else
338                 {
339                     parent.GetTracker(trackingProperty).Add(item, property);
340
341                     //if property.Value is null, then this would not add any node
342                     // Use promoted ModelItem's property to track, so pass null to AddModelItem method.
343                     AddModelItem(parent, property.Value, null);
344                 }
345             }
346             else if (viewChild != null)
347             {
348                 if (viewChild.CurrentPropertyVisible) //property node visible
349                 {
350                     if (property.Value != null)
351                     {
352                         TreeViewItemViewModel childModel = TreeViewItemViewModel.CreateViewModel(parent, property);
353                         childModel.DuplicatedNodeVisible = viewChild.DuplicatedChildNodesVisible;
354                         parent.AddChild(childModel, trackingProperty);
355                     }
356                     else
357                     {
358                         //just add the notification tracker without adding the empty child
359                         parent.GetTracker(trackingProperty, true).Add(item, trackingProperty);
360                     }
361                 }
362                 else
363                 {
364                     if (property.IsCollection)
365                     {
366                         ModelItemCollection mc = property.Value as ModelItemCollection;
367                         AddModelItemCollection(parent, mc, trackingProperty);
368                     }
369                     else if (property.IsDictionary)
370                     {
371                         ModelItemDictionary dictionary = property.Dictionary;
372                         AddModelItemDictionary(parent, dictionary, trackingProperty);
373                     }
374                     else
375                     {
376                         if (property.Value != null)
377                         {
378                             TreeViewItemViewModel childModel = TreeViewItemViewModel.CreateViewModel(parent, property.Value);
379                             childModel.DuplicatedNodeVisible = viewChild.DuplicatedChildNodesVisible;
380                             parent.AddChild(childModel, trackingProperty);
381                         }
382                         else
383                         {
384                             parent.GetTracker(trackingProperty).Add(item, property);
385                         }
386
387                     }
388                 }
389             }
390             else if (ExtensibilityAccessor.GetAttribute<ShowPropertyInOutlineViewAsSiblingAttribute>(property) != null)
391             {
392                 //add notification to the tracker that is responsible for this node
393                 ChangeNotificationTracker tracker = parent.Parent.GetTracker(parent);
394                 tracker.Add(item, property);
395                 TreeViewItemViewModel siblingNode = null;
396                 if (property.Value != null)
397                 {
398                     siblingNode = TreeViewItemViewModel.CreateViewModel(parent.Parent, property.Value);
399                 }
400                 parent.Parent.AddChild(siblingNode, tracker.ParentProperty);
401             }
402             else if (ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(property) != null)
403             {
404                 if (property.Value != null)
405                 {
406                     ShowInOutlineViewAttribute outlineView = ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(property);
407                     if (string.IsNullOrWhiteSpace(outlineView.PromotedProperty))
408                     {
409                         parent.AddChild(TreeViewItemViewModel.CreateViewModel(parent, property), trackingProperty);
410                     }
411                     else
412                     {
413                         ModelProperty promotedProperty = property.Value.Properties.Find(outlineView.PromotedProperty);
414                         if (promotedProperty == null)
415                         {
416                             throw FxTrace.Exception.AsError(new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.PromotedPropertyNotFound, outlineView.PromotedProperty, property.Value.Name)));
417                         }
418
419                         // Add promoted ModelItem and property into tracker. So when property got changed, the grandparent of promoted ModelItem will be notified.
420                         ChangeNotificationTracker tracker = parent.GetTracker(trackingProperty, true);
421                         tracker.Add(property.Value, promotedProperty);
422
423                         if (promotedProperty.Value == null)
424                         {
425                             tracker.Add(item, property);
426                         }
427                         else
428                         {
429                             parent.AddChild(TreeViewItemViewModel.CreateViewModel(parent, property), trackingProperty);
430                         }
431                     }
432                 }
433                 else
434                 {
435                     parent.GetTracker(trackingProperty, true).Add(item, property);
436                 }
437
438             }
439             //if the values in the dictionary is viewvisible, note this only works with generic dictionary
440             else if (property.IsDictionary && property.PropertyType.IsGenericType)
441             {
442                 Type[] arguments = property.PropertyType.GetGenericArguments();
443                 if (ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(arguments[1]) != null)
444                 {
445                     if (property.Value != null)
446                     {
447                         parent.AddChild(TreeViewItemViewModel.CreateViewModel(parent, property), trackingProperty);
448                     }
449                     else
450                     {
451                         parent.GetTracker(trackingProperty, true).Add(item, property);
452                     }
453
454                 }
455             }
456         }
457
458         internal static bool IsPromotedProperty(ModelItem modelItem, ModelProperty property)
459         {
460             return IsPromotedProperty(modelItem, property.Name);
461         }
462
463         internal static bool IsPromotedProperty(ModelItem modelItem, string propertyName)
464         {
465             ShowInOutlineViewAttribute attr = ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(modelItem);
466             if (attr != null && !string.IsNullOrEmpty(attr.PromotedProperty))
467             {
468                 return string.Equals(propertyName, attr.PromotedProperty, StringComparison.Ordinal);
469             }
470             return false;
471         }
472
473         internal static void AddChild(TreeViewItemViewModel parent, ModelItem item, object value, bool duplicatedNodeVisible, string childNodePrefix, ModelProperty trackingProperty)
474         {
475             // If necessary, evaluate uniqueness of given item
476             bool isUnique = false;
477             if (!duplicatedNodeVisible)
478             {
479                 // Note: These evaluations expect item to be an immediate child of trackingProperty.Value
480                 // Intermediate nodes would for example undermine simple check just below
481                 //
482                 // Caveat 1: Aim is to greatly reduce, not to eliminate, display of nodes visible elsewhere
483                 // Caveat 1a: Nodes reachable from other isolated nodes are included in the collection
484                 // Caveat 1b: Nodes that are not isolated may be reachable from isolated nodes and thus
485                 // displayed together with the isolated ones; ShowPropertyInOutlineViewAsSiblingAttribute may make this seem normal
486                 // (If complete duplicate elimination were the aim, would likely need a "never expand"
487                 // display mode for duplicateNodeVisible=false children)
488                 // Caveat 2: Use of single uniqueChildren field may cause all children of a second
489                 // duplcatedNodeVisible=false property to be ignored if (fortunately only if) neither
490                 // property uses ShowPropertyInOutlineViewAttribute(true) -- that attribute's default
491                 // Caveat 3-n: Please see caveats described at top of UniqueModelItemHelper
492                 if (1 >= item.Parents.Count())
493                 {
494                     isUnique = true;
495                 }
496                 else
497                 {
498                     // Avoided a thorough evaluation as long as we can
499                     if (null == parent.uniqueChildren)
500                     {
501                         parent.uniqueChildren = UniqueModelItemHelper.FindUniqueChildren(trackingProperty);
502                     }
503
504                     isUnique = parent.uniqueChildren.Contains(item);
505                 }
506             }
507
508             // If displayable now, create the view model node
509             if (duplicatedNodeVisible || isUnique)
510             {
511                 TreeViewItemViewModel child = TreeViewItemViewModel.CreateViewModel(parent, value);
512                 child.NodePrefixText = childNodePrefix;
513                 parent.AddChild(child, trackingProperty);
514             }
515             // Track for potential addition or removal of parents even if not presently visible
516             if (!duplicatedNodeVisible)
517             {
518                 ModelItemImpl itemImpl = item as ModelItemImpl;
519                 if (null != itemImpl)
520                 {
521                     ChangeNotificationTracker tracker = parent.GetTracker(trackingProperty);
522                     tracker.AddCollection(itemImpl.InternalParents);
523                     tracker.AddCollection(itemImpl.InternalSources);
524                 }
525             }
526         }
527
528         internal ChangeNotificationTracker GetTracker(ModelProperty modelProperty)
529         {
530             return GetTracker(modelProperty, true);
531         }
532
533         internal virtual ChangeNotificationTracker GetTracker(ModelProperty modelProperty, bool createNew)
534         {
535             ChangeNotificationTracker tracker = null;
536             if (!this.Trackers.TryGetValue(modelProperty, out tracker) && createNew)
537             {
538                 tracker = new ChangeNotificationTracker(this, modelProperty);
539                 Trackers.Add(modelProperty, tracker);
540             }
541             return tracker;
542         }
543
544         internal ChangeNotificationTracker GetTracker(TreeViewItemViewModel child)
545         {
546             ChangeNotificationTracker trackerForChild = null;
547             foreach (ChangeNotificationTracker tracker in this.Trackers.Values)
548             {
549                 if (tracker.ChildViewModels.Contains(child))
550                 {
551                     trackerForChild = tracker;
552                     break;
553                 }
554             }
555             Fx.Assert(trackerForChild != null, "Tracker should not be null");
556             return trackerForChild;
557         }
558
559         internal virtual int FindInsertionIndex(ChangeNotificationTracker tracker)
560         {
561             int insertIndex = 0;
562             if (tracker != null && tracker.ChildViewModels != null && tracker.ChildViewModels.Count > 0)
563             {
564                 //assume the childViewModels are in order
565                 insertIndex = this.InternalChildren.IndexOf(tracker.ChildViewModels.Last()) + 1;
566             }
567             return insertIndex;
568         }
569
570         internal ModelProperty GetTrackingModelPropertyForChild(TreeViewItemViewModel child)
571         {
572             ModelProperty property = null;
573             foreach (ChangeNotificationTracker tracker in this.Trackers.Values)
574             {
575                 if (tracker.ChildViewModels.Contains(child))
576                 {
577                     property = tracker.ParentProperty;
578                     break;
579                 }
580             }
581             return property;
582         }
583
584         internal virtual void UpdateState()
585         {
586         }
587
588         void InternalChildren_CollectionChanged(object sender, Collections.Specialized.NotifyCollectionChangedEventArgs e)
589         {
590             if (e.Action == Collections.Specialized.NotifyCollectionChangedAction.Remove)
591             {
592                 foreach (TreeViewItemViewModel item in e.OldItems)
593                 {
594                     this.ChildrenValueCache.Remove(item.GetValue());
595                 }
596             }
597         }
598
599         protected void NotifyPropertyChanged(String propertyName)
600         {
601             if (PropertyChanged != null)
602             {
603                 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
604             }
605         }
606
607         [Flags]
608         internal enum TreeViewItemState
609         {
610             Default = 0,
611             HasChildren = 1,
612             HasSibling = 2
613         }
614
615         // <summary>
616         // Disposes this editing context.
617         // </summary>
618         public void CleanUp()
619         {
620             if (this.IsAlive)
621             {
622                 CleanUpCore();
623                 this.IsAlive = false;
624             }
625         }
626
627         protected virtual void CleanUpCore()
628         {
629             foreach (ChangeNotificationTracker t in this.Trackers.Values)
630             {
631                 t.CleanUp();
632             }
633
634             foreach (TreeViewItemViewModel child in InternalChildren)
635             {
636                 child.CleanUp();
637             }
638
639             this.InternalChildren.CollectionChanged -= InternalChildren_CollectionChanged;
640             this.Trackers = null;
641             this.InternalChildren = null;
642             this.ChildrenValueCache = null;
643             this.Children = null;
644             this.icon = null;
645             this.TreeViewItem = null;
646             this.Parent = null;
647         }
648     }
649
650     internal class TreeViewItemViewModel<T> : TreeViewItemViewModel
651     {
652         private T visualValue;
653         //this is the value the UI tree bind to
654         public virtual T VisualValue
655         {
656             get
657             {
658                 return visualValue;
659             }
660             internal set
661             {
662                 if (!Equals(visualValue, value))
663                 {
664                     visualValue = value;
665                     NotifyPropertyChanged("VisualValue");
666                 }
667             }
668         }
669         //this is for the view model processing
670         public T Value { get; protected set; }
671
672         public TreeViewItemViewModel(TreeViewItemViewModel parent)
673         {
674             this.Parent = parent;
675         }
676
677         public override string ToString()
678         {
679             if (Value != null)
680             {
681                 return Value.ToString();
682             }
683
684             return base.ToString();
685         }
686
687         internal override object GetValue()
688         {
689             return this.Value;
690         }
691
692         protected override void CleanUpCore()
693         {
694             visualValue = default(T);
695             Value = default(T);
696             base.CleanUpCore();
697         }
698     }
699 }