[corlib] Avoid unnecessary ephemeron array resizes
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / System.Activities.Presentation / System / Activities / Presentation / View / TreeView / TreeViewItemModelItemViewModel.cs
1 //----------------------------------------------------------------
2 // <copyright company="Microsoft Corporation">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //----------------------------------------------------------------
6
7 namespace System.Activities.Presentation.View.TreeView
8 {
9     using System;
10     using System.Activities.Presentation.Internal.PropertyEditing;
11     using System.Activities.Presentation.Model;
12     using System.Activities.Presentation.Services;
13     using System.Activities.Presentation.Utility;
14     using System.Activities.Presentation.View;
15     using System.Activities.Presentation.View.OutlineView;
16     using System.Activities.Statements;
17     using System.ComponentModel;
18     using System.Diagnostics.CodeAnalysis;
19     using System.Runtime;
20     using System.Windows;
21     using System.Windows.Media;
22
23     internal sealed class TreeViewItemModelItemViewModel : TreeViewItemViewModel<ModelItem>
24     {
25         private ModelProperty promotedProperty;
26
27         public TreeViewItemModelItemViewModel(TreeViewItemViewModel parent, ModelItem modelItem, bool lazyLoad)
28             : base(parent)
29         {
30             this.Value = modelItem;
31             ShowInOutlineViewAttribute attr = ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(modelItem);
32             if (attr != null && !string.IsNullOrEmpty(attr.PromotedProperty))
33             {
34                 // Only consider one level of promoted property.
35                 this.promotedProperty = modelItem.Properties[attr.PromotedProperty];
36                 if (this.promotedProperty != null)
37                 {
38                     this.VisualValue = this.promotedProperty.Value;
39                 }
40                 else
41                 {
42                     // is this what we really want?
43                     Fx.Assert(attr.PromotedProperty + " not found on " + modelItem.Name);
44                     this.VisualValue = null;
45                 }
46             }
47             else
48             {
49                 this.VisualValue = modelItem;
50             }
51
52             IsHighlighted = Selection.IsSelection(this.VisualValue);
53
54             if (lazyLoad)
55             {
56                 this.UpdateState();
57                 if (this.HasChildren)
58                 {
59                     this.InternalChildren.Add(TreeViewItemViewModel.DummyNode);
60                 }
61             }
62             else
63             {
64                 this.LoadChildren();
65             }
66         }
67
68         public TreeViewItemModelItemViewModel(TreeViewItemViewModel parent, ModelItem modelItem)
69             : this(parent, modelItem, true)
70         {
71         }
72
73         /// <summary>
74         /// Set VisualValue will update Icon and binding to designer.
75         /// </summary>
76         public override ModelItem VisualValue
77         {
78             get
79             {
80                 return base.VisualValue;
81             }
82
83             internal set
84             {
85                 if (base.VisualValue != value)
86                 {
87                     // Remove old event chain
88                     if (base.VisualValue != null)
89                     {
90                         base.VisualValue.PropertyChanged -= this.VisualValue_PropertyChanged;
91                     }
92
93                     base.VisualValue = value;
94
95                     if (ModelItemHasDesigner(base.VisualValue))
96                     {
97                         base.VisualValue.PropertyChanged += this.VisualValue_PropertyChanged;
98                     }
99
100                     this.Icon = this.GetIconByVisualValue();
101                     NotifyPropertyChanged("VisualValue");
102                 }
103             }
104         }
105
106         internal bool HasDesigner
107         {
108             get
109             {
110                 return ModelItemHasDesigner(this.VisualValue);
111             }
112         }
113
114         internal override void LoadChildren()
115         {
116             if (this.PerfEventProvider != null)
117             {
118                 this.PerfEventProvider.DesignerTreeViewLoadChildrenStart();
119             }
120
121             base.LoadChildren();
122             TreeViewItemViewModel.AddModelItem(this, this.Value, null);
123             if (this.PerfEventProvider != null)
124             {
125                 this.PerfEventProvider.DesignerTreeViewLoadChildrenEnd();
126             }
127         }
128
129         internal override void UpdateChildren(ChangeNotificationTracker tracker, EventArgs e)
130         {
131             if (this.PerfEventProvider != null)
132             {
133                 this.PerfEventProvider.DesignerTreeViewUpdateStart();
134             }
135
136             // Update VisualValue when promotedProperty's got changed.
137             if (this.promotedProperty != null && this.promotedProperty == tracker.ParentProperty)
138             {
139                 this.VisualValue = this.promotedProperty.Value;
140             }
141
142             if (this.Children.Count == 1 && this.Children[0] == DummyNode)
143             {
144                 // If the node never expanded before, LoadChildren instead of UpdateChildren.
145                 // Otherwise, when expanding node, the LoadChildren method won't invoke 
146                 // Then other tracking properties cannot be setup correctly.
147                 this.InternalChildren.Remove(DummyNode);
148                 this.LoadChildren();
149             }
150             else
151             {
152                 // If requireUpdateChildren = false, the related TreeViewItemModelPropertyViewModel take care of updating child nodes.
153                 bool requireUpdateChildren = true;
154                 if (e is PropertyChangedEventArgs && this.IsModelPropertyNodeExisted(tracker.ParentProperty))
155                 {
156                     ModelProperty modelProperty = tracker.ParentProperty;
157                     if (modelProperty.Value != null)
158                     {
159                         string changedPropertyName = ((PropertyChangedEventArgs)e).PropertyName;
160                         bool isPromotedPropertyChanged = TreeViewItemViewModel.IsPromotedProperty(modelProperty.Value, changedPropertyName);
161                         if (isPromotedPropertyChanged)
162                         {
163                             if (modelProperty.Value.Properties[changedPropertyName].Value != null)
164                             {
165                                 requireUpdateChildren = false;
166                             }
167                         }
168                         else
169                         {
170                             requireUpdateChildren = false;
171                         }
172                     }
173                 }
174
175                 if (requireUpdateChildren)
176                 {
177                     base.UpdateChildren(tracker, e);
178                     tracker.CleanUp();
179                     TreeViewItemViewModel.AddModelProperty(this, this.Value, tracker.ParentProperty, tracker.ParentProperty);
180                 }
181             }
182
183             if (this.PerfEventProvider != null)
184             {
185                 this.PerfEventProvider.DesignerTreeViewUpdateEnd();
186             }
187         }
188
189         internal override void UpdateState()
190         {
191             base.UpdateState();
192             if (this.Value != null)
193             {
194                 this.State |= this.UpdateModelItemState(this.Value);
195             }
196         }
197
198         internal override int FindInsertionIndex(ChangeNotificationTracker tracker)
199         {
200             int insertionIndex = 0;
201             if (tracker != null && (tracker.ChildViewModels == null || tracker.ChildViewModels.Count < 1))
202             {
203                 foreach (ModelProperty property in this.Value.Properties)
204                 {
205                     if (property != tracker.ParentProperty)
206                     {
207                         // assume this would increament
208                         ChangeNotificationTracker propertyTracker = this.GetTracker(property, false);
209                         if (propertyTracker != null)
210                         {
211                             insertionIndex = base.FindInsertionIndex(propertyTracker);
212                         }
213                     }
214                     else
215                     {
216                         // we've reach the property and hence the last of the previous property
217                         break;
218                     }
219                 }
220             }
221             else
222             {
223                 insertionIndex = base.FindInsertionIndex(tracker);
224             }
225
226             return insertionIndex;
227         }
228
229         internal override ChangeNotificationTracker GetTracker(ModelProperty modelProperty, bool createNew)
230         {
231             ChangeNotificationTracker tracker = base.GetTracker(modelProperty, createNew);
232             if (createNew)
233             {
234                 if (this.VisualValue == modelProperty.Parent)
235                 {
236                     // If this TreeViewModelItem use Promopted property and the property belongs to Promoted activity
237                     // add the tracked property by default.
238                     tracker.Add(this.VisualValue, modelProperty);
239                 }
240                 else
241                 {
242                     // If it's an model item, add the tracked property by default
243                     tracker.Add(this.Value, modelProperty);
244                 }
245             }
246
247             return tracker;
248         }
249
250         protected override void CleanUpCore()
251         {
252             if (this.VisualValue != null)
253             {
254                 this.VisualValue.PropertyChanged -= this.VisualValue_PropertyChanged;
255             }
256
257             this.promotedProperty = null;
258
259             base.CleanUpCore();
260         }
261
262         protected override EditingContext GetEditingContext()
263         {
264             if (this.Value != null)
265             {
266                 return this.Value.GetEditingContext();
267             }
268             else
269             {
270                 return base.GetEditingContext();
271             }
272         }
273
274         private static bool ModelItemHasDesigner(ModelItem modelItem)
275         {
276             if (modelItem != null)
277             {
278                 DesignerAttribute attribute = WorkflowViewService.GetAttribute<DesignerAttribute>(modelItem.ItemType);
279                 if (attribute != null && !string.IsNullOrEmpty(attribute.DesignerTypeName))
280                 {
281                     return true;
282                 }
283             }
284
285             return false;
286         }
287
288         private static DrawingBrush GetIconFromUnInitializedDesigner(ActivityDesigner designer)
289         {
290             DrawingBrush icon = null;
291             if (designer != null)
292             {
293                 // force the designer to load
294                 designer.BeginInit();
295
296                 // An exception will be thrown, if BeginInit is called more than once on 
297                 // the same activity designer prior to EndInit being called.  So we call
298                 // EndInit to avoid that, note this will cause an Initialized event.
299                 designer.EndInit();
300
301                 if (designer.Icon == null)
302                 {
303                     // the loading of the default icon depends on Activity.Loaded event.
304                     // however the designer might not be loaded unless it is added to the
305                     // designer surface.  So we load the default icon manually here.
306                     icon = designer.GetDefaultIcon();
307                 }
308                 else
309                 {
310                     icon = designer.Icon;
311                 }
312             }
313
314             return icon;
315         }
316
317         private void ExpandToNode()
318         {
319             TreeViewItemViewModel viewModel = this.Parent;
320             while (viewModel != null)
321             {
322                 viewModel.IsExpanded = true;
323                 viewModel = viewModel.Parent;
324             }
325         }
326
327         private DrawingBrush GetIconByVisualValue()
328         {
329             if (this.VisualValue != null)
330             {
331                 DrawingBrush icon = null;
332                 Type modelItemType = this.VisualValue.ItemType;
333                 if (modelItemType.IsGenericType)
334                 {
335                     // If Type is generic type, whatever T, it should display same icon, so use generic type instead.
336                     modelItemType = this.VisualValue.ItemType.GetGenericTypeDefinition();
337                 }
338
339                 // If the user specifies the attribute, then the Designer would be providing the icon,
340                 // bypassing the pipeline of retrieving the icons via reflection and attached properties.
341                 ActivityDesignerOptionsAttribute attr = ExtensibilityAccessor.GetAttribute<ActivityDesignerOptionsAttribute>(modelItemType);
342                 if (attr != null && attr.OutlineViewIconProvider != null)
343                 {
344                     icon = attr.OutlineViewIconProvider(this.VisualValue);
345                 }
346
347                 if (icon == null && !TreeViewItemViewModel.IconCache.TryGetValue(modelItemType, out icon))
348                 {
349                     EditingContext context = this.VisualValue.GetEditingContext();
350                     ViewService service = context.Services.GetService<ViewService>();
351                     WorkflowViewService workflowViewService = service as WorkflowViewService;
352                     ActivityDesigner designer = null;
353
354                     // first try to create an detached view element that won't participate in the designer,
355                     // if the view service is WorkflowViewService
356                     if (workflowViewService != null)
357                     {
358                         designer = workflowViewService.CreateDetachedViewElement(this.VisualValue) as ActivityDesigner;
359                         icon = GetIconFromUnInitializedDesigner(designer);
360                     }
361                     else
362                     {
363                         // fall back if the view service is not the default implementation
364                         // We only need to get the icon from the designer, so we don't need to make sure the view is parented.
365                         designer = this.VisualValue.View as ActivityDesigner;
366                         if (designer == null && service != null)
367                         {
368                             designer = service.GetView(this.VisualValue) as ActivityDesigner;
369                         }
370
371                         if (designer != null)
372                         {
373                             if (designer.Icon != null || designer.IsLoaded)
374                             {
375                                 icon = designer.Icon;
376                             }
377                             else
378                             {
379                                 icon = GetIconFromUnInitializedDesigner(designer);
380                             }
381                         }
382                     }
383
384                     // Cache even a null icon since answers found above won't change within this AppDomain
385                     TreeViewItemViewModel.IconCache.Add(modelItemType, icon);
386                 }
387
388                 return icon;
389             }
390             else
391             {
392                 return null;
393             }
394         }
395
396         private bool IsModelPropertyNodeExisted(ModelProperty property)
397         {
398             bool isModelPropertyNodeExisted = false;
399
400             foreach (TreeViewItemViewModel viewModel in Children)
401             {
402                 TreeViewItemModelPropertyViewModel modelPropertyViewModel = viewModel as TreeViewItemModelPropertyViewModel;
403                 if (modelPropertyViewModel != null)
404                 {
405                     if (modelPropertyViewModel.Value == property)
406                     {
407                         isModelPropertyNodeExisted = true;
408                         break;
409                     }
410                 }
411             }
412
413             return isModelPropertyNodeExisted;
414         }
415
416         private TreeViewItemState UpdateModelItemState(ModelItem modelItem)
417         {
418             TreeViewItemState state = TreeViewItemState.Default;
419
420             foreach (ModelProperty property in modelItem.Properties)
421             {
422                 if (ExtensibilityAccessor.GetAttribute<HidePropertyInOutlineViewAttribute>(property) != null)
423                 {
424                     continue;
425                 }
426                 else if (ExtensibilityAccessor.GetAttribute<ShowPropertyInOutlineViewAttribute>(property) != null)
427                 {
428                     if (property.Value != null)
429                     {
430                         state |= TreeViewItemState.HasChildren;
431                     }
432
433                     // create the property change notification tracker
434                     this.GetTracker(property, true);
435                 }
436                 else if (ExtensibilityAccessor.GetAttribute<ShowPropertyInOutlineViewAsSiblingAttribute>(property) != null)
437                 {
438                     // First of all, ShowPropertyInOutlineViewAsSiblingAttribute property's tracker will be setup at the LoadChildren() time.
439                     // The reason we cannot do it here is because during the constructor, this.Parent is null.
440                     // If all other properties don't flag HasChildren, the current node won't be able to expand.
441                     // So we cannot rely on expand operation to invoke LoadChildren() to setup tracker.
442                     // TreeViewItemViewModel.AddChild(TreeViewItemViewModel, ModelProperty) will by default invoke LoadChildren() if the node HasSibling.
443                     // So even if property's value == null, we should flag it HasSibling, and let LoadChildren invoked by default.
444                     state |= TreeViewItemState.HasSibling;
445                 }
446                 else if (ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(property) != null)
447                 {
448                     if (property.Value != null)
449                     {
450                         // Only consider one level of PromotedProperty.
451                         if (property != this.promotedProperty)
452                         {
453                             state |= TreeViewItemState.HasChildren;
454                         }
455                         else
456                         {
457                             // Since the property has been promoted, need to check whether this property has children.
458                             // If this promoted property.Value has children, those children should belong to this node.
459                             state |= this.UpdateModelItemState(property.Value);
460                         }
461                     }
462
463                     this.GetTracker(property, true);
464                 }
465                 else if (property.IsDictionary && property.PropertyType.IsGenericType)
466                 {
467                     // if the values in the dictionary is viewvisible, note this only works with generic dictionary
468                     Type[] arguments = property.PropertyType.GetGenericArguments();
469                     if (ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(arguments[1]) != null)
470                     {
471                         if (property.Value != null)
472                         {
473                             state |= TreeViewItemState.HasChildren;
474                         }
475
476                         this.GetTracker(property, true);
477                     }
478                 }
479             }
480
481             return state;
482         }
483
484         private void VisualValue_PropertyChanged(object sender, ComponentModel.PropertyChangedEventArgs e)
485         {
486             if (e.PropertyName.Equals("IsSelection"))
487             {
488                 this.IsHighlighted = Selection.IsSelection(this.VisualValue);
489                 if (this.IsHighlighted)
490                 {
491                     this.ExpandToNode();
492                 }
493             }
494             else if (e.PropertyName.Equals("IsPrimarySelection"))
495             {
496                 if (TreeViewItem == null)
497                 {
498                     // TreeViewItem is not initialized at the first time of SelectionChanged by WorkflowViewElement.OnGotFocusEvent.
499                     return;
500                 }
501
502                 bool isPrimarySelection = Selection.IsPrimarySelection(this.VisualValue);
503                 if (isPrimarySelection)
504                 {
505                     TreeViewItem.Select();
506                 }
507                 else
508                 {
509                     TreeViewItem.Unselect();
510                 }
511             }
512         }
513     }
514 }