File: cdf\src\NetFx40\Tools\System.Activities.Presentation\System\Activities\Presentation\View\TreeView\TreeViewItemModelItemViewModel.cs
Project: ndp\System.Data.csproj (System.Data)
//----------------------------------------------------------------
// <copyright company="Microsoft Corporation">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//----------------------------------------------------------------
 
namespace System.Activities.Presentation.View.TreeView
{
    using System;
    using System.Activities.Presentation.Internal.PropertyEditing;
    using System.Activities.Presentation.Model;
    using System.Activities.Presentation.Services;
    using System.Activities.Presentation.Utility;
    using System.Activities.Presentation.View;
    using System.Activities.Presentation.View.OutlineView;
    using System.Activities.Statements;
    using System.ComponentModel;
    using System.Diagnostics.CodeAnalysis;
    using System.Runtime;
    using System.Windows;
    using System.Windows.Media;
 
    internal sealed class TreeViewItemModelItemViewModel : TreeViewItemViewModel<ModelItem>
    {
        private ModelProperty promotedProperty;
 
        public TreeViewItemModelItemViewModel(TreeViewItemViewModel parent, ModelItem modelItem, bool lazyLoad)
            : base(parent)
        {
            this.Value = modelItem;
            ShowInOutlineViewAttribute attr = ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(modelItem);
            if (attr != null && !string.IsNullOrEmpty(attr.PromotedProperty))
            {
                // Only consider one level of promoted property.
                this.promotedProperty = modelItem.Properties[attr.PromotedProperty];
                if (this.promotedProperty != null)
                {
                    this.VisualValue = this.promotedProperty.Value;
                }
                else
                {
                    // is this what we really want?
                    Fx.Assert(attr.PromotedProperty + " not found on " + modelItem.Name);
                    this.VisualValue = null;
                }
            }
            else
            {
                this.VisualValue = modelItem;
            }
 
            IsHighlighted = Selection.IsSelection(this.VisualValue);
 
            if (lazyLoad)
            {
                this.UpdateState();
                if (this.HasChildren)
                {
                    this.InternalChildren.Add(TreeViewItemViewModel.DummyNode);
                }
            }
            else
            {
                this.LoadChildren();
            }
        }
 
        public TreeViewItemModelItemViewModel(TreeViewItemViewModel parent, ModelItem modelItem)
            : this(parent, modelItem, true)
        {
        }
 
        /// <summary>
        /// Set VisualValue will update Icon and binding to designer.
        /// </summary>
        public override ModelItem VisualValue
        {
            get
            {
                return base.VisualValue;
            }
 
            internal set
            {
                if (base.VisualValue != value)
                {
                    // Remove old event chain
                    if (base.VisualValue != null)
                    {
                        base.VisualValue.PropertyChanged -= this.VisualValue_PropertyChanged;
                    }
 
                    base.VisualValue = value;
 
                    if (ModelItemHasDesigner(base.VisualValue))
                    {
                        base.VisualValue.PropertyChanged += this.VisualValue_PropertyChanged;
                    }
 
                    this.Icon = this.GetIconByVisualValue();
                    NotifyPropertyChanged("VisualValue");
                }
            }
        }
 
        internal bool HasDesigner
        {
            get
            {
                return ModelItemHasDesigner(this.VisualValue);
            }
        }
 
        internal override void LoadChildren()
        {
            if (this.PerfEventProvider != null)
            {
                this.PerfEventProvider.DesignerTreeViewLoadChildrenStart();
            }
 
            base.LoadChildren();
            TreeViewItemViewModel.AddModelItem(this, this.Value, null);
            if (this.PerfEventProvider != null)
            {
                this.PerfEventProvider.DesignerTreeViewLoadChildrenEnd();
            }
        }
 
        internal override void UpdateChildren(ChangeNotificationTracker tracker, EventArgs e)
        {
            if (this.PerfEventProvider != null)
            {
                this.PerfEventProvider.DesignerTreeViewUpdateStart();
            }
 
            // Update VisualValue when promotedProperty's got changed.
            if (this.promotedProperty != null && this.promotedProperty == tracker.ParentProperty)
            {
                this.VisualValue = this.promotedProperty.Value;
            }
 
            if (this.Children.Count == 1 && this.Children[0] == DummyNode)
            {
                // If the node never expanded before, LoadChildren instead of UpdateChildren.
                // Otherwise, when expanding node, the LoadChildren method won't invoke 
                // Then other tracking properties cannot be setup correctly.
                this.InternalChildren.Remove(DummyNode);
                this.LoadChildren();
            }
            else
            {
                // If requireUpdateChildren = false, the related TreeViewItemModelPropertyViewModel take care of updating child nodes.
                bool requireUpdateChildren = true;
                if (e is PropertyChangedEventArgs && this.IsModelPropertyNodeExisted(tracker.ParentProperty))
                {
                    ModelProperty modelProperty = tracker.ParentProperty;
                    if (modelProperty.Value != null)
                    {
                        string changedPropertyName = ((PropertyChangedEventArgs)e).PropertyName;
                        bool isPromotedPropertyChanged = TreeViewItemViewModel.IsPromotedProperty(modelProperty.Value, changedPropertyName);
                        if (isPromotedPropertyChanged)
                        {
                            if (modelProperty.Value.Properties[changedPropertyName].Value != null)
                            {
                                requireUpdateChildren = false;
                            }
                        }
                        else
                        {
                            requireUpdateChildren = false;
                        }
                    }
                }
 
                if (requireUpdateChildren)
                {
                    base.UpdateChildren(tracker, e);
                    tracker.CleanUp();
                    TreeViewItemViewModel.AddModelProperty(this, this.Value, tracker.ParentProperty, tracker.ParentProperty);
                }
            }
 
            if (this.PerfEventProvider != null)
            {
                this.PerfEventProvider.DesignerTreeViewUpdateEnd();
            }
        }
 
        internal override void UpdateState()
        {
            base.UpdateState();
            if (this.Value != null)
            {
                this.State |= this.UpdateModelItemState(this.Value);
            }
        }
 
        internal override int FindInsertionIndex(ChangeNotificationTracker tracker)
        {
            int insertionIndex = 0;
            if (tracker != null && (tracker.ChildViewModels == null || tracker.ChildViewModels.Count < 1))
            {
                foreach (ModelProperty property in this.Value.Properties)
                {
                    if (property != tracker.ParentProperty)
                    {
                        // assume this would increament
                        ChangeNotificationTracker propertyTracker = this.GetTracker(property, false);
                        if (propertyTracker != null)
                        {
                            insertionIndex = base.FindInsertionIndex(propertyTracker);
                        }
                    }
                    else
                    {
                        // we've reach the property and hence the last of the previous property
                        break;
                    }
                }
            }
            else
            {
                insertionIndex = base.FindInsertionIndex(tracker);
            }
 
            return insertionIndex;
        }
 
        internal override ChangeNotificationTracker GetTracker(ModelProperty modelProperty, bool createNew)
        {
            ChangeNotificationTracker tracker = base.GetTracker(modelProperty, createNew);
            if (createNew)
            {
                if (this.VisualValue == modelProperty.Parent)
                {
                    // If this TreeViewModelItem use Promopted property and the property belongs to Promoted activity
                    // add the tracked property by default.
                    tracker.Add(this.VisualValue, modelProperty);
                }
                else
                {
                    // If it's an model item, add the tracked property by default
                    tracker.Add(this.Value, modelProperty);
                }
            }
 
            return tracker;
        }
 
        protected override void CleanUpCore()
        {
            if (this.VisualValue != null)
            {
                this.VisualValue.PropertyChanged -= this.VisualValue_PropertyChanged;
            }
 
            this.promotedProperty = null;
 
            base.CleanUpCore();
        }
 
        protected override EditingContext GetEditingContext()
        {
            if (this.Value != null)
            {
                return this.Value.GetEditingContext();
            }
            else
            {
                return base.GetEditingContext();
            }
        }
 
        private static bool ModelItemHasDesigner(ModelItem modelItem)
        {
            if (modelItem != null)
            {
                DesignerAttribute attribute = WorkflowViewService.GetAttribute<DesignerAttribute>(modelItem.ItemType);
                if (attribute != null && !string.IsNullOrEmpty(attribute.DesignerTypeName))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        private static DrawingBrush GetIconFromUnInitializedDesigner(ActivityDesigner designer)
        {
            DrawingBrush icon = null;
            if (designer != null)
            {
                // force the designer to load
                designer.BeginInit();
 
                // An exception will be thrown, if BeginInit is called more than once on 
                // the same activity designer prior to EndInit being called.  So we call
                // EndInit to avoid that, note this will cause an Initialized event.
                designer.EndInit();
 
                if (designer.Icon == null)
                {
                    // the loading of the default icon depends on Activity.Loaded event.
                    // however the designer might not be loaded unless it is added to the
                    // designer surface.  So we load the default icon manually here.
                    icon = designer.GetDefaultIcon();
                }
                else
                {
                    icon = designer.Icon;
                }
            }
 
            return icon;
        }
 
        private void ExpandToNode()
        {
            TreeViewItemViewModel viewModel = this.Parent;
            while (viewModel != null)
            {
                viewModel.IsExpanded = true;
                viewModel = viewModel.Parent;
            }
        }
 
        private DrawingBrush GetIconByVisualValue()
        {
            if (this.VisualValue != null)
            {
                DrawingBrush icon = null;
                Type modelItemType = this.VisualValue.ItemType;
                if (modelItemType.IsGenericType)
                {
                    // If Type is generic type, whatever T, it should display same icon, so use generic type instead.
                    modelItemType = this.VisualValue.ItemType.GetGenericTypeDefinition();
                }
 
                // If the user specifies the attribute, then the Designer would be providing the icon,
                // bypassing the pipeline of retrieving the icons via reflection and attached properties.
                ActivityDesignerOptionsAttribute attr = ExtensibilityAccessor.GetAttribute<ActivityDesignerOptionsAttribute>(modelItemType);
                if (attr != null && attr.OutlineViewIconProvider != null)
                {
                    icon = attr.OutlineViewIconProvider(this.VisualValue);
                }
 
                if (icon == null && !TreeViewItemViewModel.IconCache.TryGetValue(modelItemType, out icon))
                {
                    EditingContext context = this.VisualValue.GetEditingContext();
                    ViewService service = context.Services.GetService<ViewService>();
                    WorkflowViewService workflowViewService = service as WorkflowViewService;
                    ActivityDesigner designer = null;
 
                    // first try to create an detached view element that won't participate in the designer,
                    // if the view service is WorkflowViewService
                    if (workflowViewService != null)
                    {
                        designer = workflowViewService.CreateDetachedViewElement(this.VisualValue) as ActivityDesigner;
                        icon = GetIconFromUnInitializedDesigner(designer);
                    }
                    else
                    {
                        // fall back if the view service is not the default implementation
                        // We only need to get the icon from the designer, so we don't need to make sure the view is parented.
                        designer = this.VisualValue.View as ActivityDesigner;
                        if (designer == null && service != null)
                        {
                            designer = service.GetView(this.VisualValue) as ActivityDesigner;
                        }
 
                        if (designer != null)
                        {
                            if (designer.Icon != null || designer.IsLoaded)
                            {
                                icon = designer.Icon;
                            }
                            else
                            {
                                icon = GetIconFromUnInitializedDesigner(designer);
                            }
                        }
                    }
 
                    // Cache even a null icon since answers found above won't change within this AppDomain
                    TreeViewItemViewModel.IconCache.Add(modelItemType, icon);
                }
 
                return icon;
            }
            else
            {
                return null;
            }
        }
 
        private bool IsModelPropertyNodeExisted(ModelProperty property)
        {
            bool isModelPropertyNodeExisted = false;
 
            foreach (TreeViewItemViewModel viewModel in Children)
            {
                TreeViewItemModelPropertyViewModel modelPropertyViewModel = viewModel as TreeViewItemModelPropertyViewModel;
                if (modelPropertyViewModel != null)
                {
                    if (modelPropertyViewModel.Value == property)
                    {
                        isModelPropertyNodeExisted = true;
                        break;
                    }
                }
            }
 
            return isModelPropertyNodeExisted;
        }
 
        private TreeViewItemState UpdateModelItemState(ModelItem modelItem)
        {
            TreeViewItemState state = TreeViewItemState.Default;
 
            foreach (ModelProperty property in modelItem.Properties)
            {
                if (ExtensibilityAccessor.GetAttribute<HidePropertyInOutlineViewAttribute>(property) != null)
                {
                    continue;
                }
                else if (ExtensibilityAccessor.GetAttribute<ShowPropertyInOutlineViewAttribute>(property) != null)
                {
                    if (property.Value != null)
                    {
                        state |= TreeViewItemState.HasChildren;
                    }
 
                    // create the property change notification tracker
                    this.GetTracker(property, true);
                }
                else if (ExtensibilityAccessor.GetAttribute<ShowPropertyInOutlineViewAsSiblingAttribute>(property) != null)
                {
                    // First of all, ShowPropertyInOutlineViewAsSiblingAttribute property's tracker will be setup at the LoadChildren() time.
                    // The reason we cannot do it here is because during the constructor, this.Parent is null.
                    // If all other properties don't flag HasChildren, the current node won't be able to expand.
                    // So we cannot rely on expand operation to invoke LoadChildren() to setup tracker.
                    // TreeViewItemViewModel.AddChild(TreeViewItemViewModel, ModelProperty) will by default invoke LoadChildren() if the node HasSibling.
                    // So even if property's value == null, we should flag it HasSibling, and let LoadChildren invoked by default.
                    state |= TreeViewItemState.HasSibling;
                }
                else if (ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(property) != null)
                {
                    if (property.Value != null)
                    {
                        // Only consider one level of PromotedProperty.
                        if (property != this.promotedProperty)
                        {
                            state |= TreeViewItemState.HasChildren;
                        }
                        else
                        {
                            // Since the property has been promoted, need to check whether this property has children.
                            // If this promoted property.Value has children, those children should belong to this node.
                            state |= this.UpdateModelItemState(property.Value);
                        }
                    }
 
                    this.GetTracker(property, true);
                }
                else if (property.IsDictionary && property.PropertyType.IsGenericType)
                {
                    // if the values in the dictionary is viewvisible, note this only works with generic dictionary
                    Type[] arguments = property.PropertyType.GetGenericArguments();
                    if (ExtensibilityAccessor.GetAttribute<ShowInOutlineViewAttribute>(arguments[1]) != null)
                    {
                        if (property.Value != null)
                        {
                            state |= TreeViewItemState.HasChildren;
                        }
 
                        this.GetTracker(property, true);
                    }
                }
            }
 
            return state;
        }
 
        private void VisualValue_PropertyChanged(object sender, ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName.Equals("IsSelection"))
            {
                this.IsHighlighted = Selection.IsSelection(this.VisualValue);
                if (this.IsHighlighted)
                {
                    this.ExpandToNode();
                }
            }
            else if (e.PropertyName.Equals("IsPrimarySelection"))
            {
                if (TreeViewItem == null)
                {
                    // TreeViewItem is not initialized at the first time of SelectionChanged by WorkflowViewElement.OnGotFocusEvent.
                    return;
                }
 
                bool isPrimarySelection = Selection.IsPrimarySelection(this.VisualValue);
                if (isPrimarySelection)
                {
                    TreeViewItem.Select();
                }
                else
                {
                    TreeViewItem.Unselect();
                }
            }
        }
    }
}