File: System.Activities.Presentation\System\Activities\Presentation\Base\Core\Internal\PropertyEditing\Editors\SubPropertyEditor.cs
Project: ndp\cdf\src\NetFx40\Tools\System.Activities.Presentation.csproj (System.Activities.Presentation)
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//----------------------------------------------------------------
namespace System.Activities.Presentation.Internal.PropertyEditing.Editors 
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Globalization;
    using System.Windows;
    using System.Windows.Automation.Peers;
    using System.Windows.Controls;
    using System.Windows.Data;
 
    using System.Runtime;
    using System.Activities.Presentation.Model;
    using System.Activities.Presentation.PropertyEditing;
 
    using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework.PropertyInspector;
 
    using System.Activities.Presentation.Internal.PropertyEditing.Automation;
    using System.Activities.Presentation.Internal.PropertyEditing.Model;
    using ModelUtilities = System.Activities.Presentation.Internal.PropertyEditing.Model.ModelUtilities;
    using System.Activities.Presentation.Internal.PropertyEditing.Resources;
    using System.Activities.Presentation.Internal.PropertyEditing.Selection;
    using System.Activities.Presentation.Internal.PropertyEditing.State;
 
    // <summary>
    // We use the SubPropertyEditor to replace the entire property row
    // when the property exposes its subproperties.  This control is _not_
    // just used within the value-editing portion of a property row.
    // We cheat because we can.
    // </summary>
    internal class SubPropertyEditor : Control, INotifyPropertyChanged, ISelectionStop 
    {
 
        // <summary>
        // PropertyEntry is used to store the currently displayed PropertyEntry
        // </summary>
        public static readonly DependencyProperty PropertyEntryProperty = DependencyProperty.Register(
            "PropertyEntry",
            typeof(PropertyEntry),
            typeof(SubPropertyEditor),
            new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyEntryChanged)));
 
        // <summary>
        // Boolean used to indicate whether the sub-properties are being shown or not.  As an optimization,
        // we don't actually expose the PropertyValue's sub-properties through SelectiveSubProperties until
        // the sub-property expando-pane has been open at least once.
        // </summary>
        public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(
            "IsExpanded",
            typeof(bool),
            typeof(SubPropertyEditor),
            new PropertyMetadata(false, new PropertyChangedCallback(OnIsExpandedChanged)));
 
        // <summary>
        // Exposes the currently selected QuickType in the QuickType drop-down.  Essentially,
        // the value of this DP is plumbed through to reflect the value of _quickTypeView.CurrentItem
        // </summary>
        public static readonly DependencyProperty CurrentQuickTypeProperty = DependencyProperty.Register(
            "CurrentQuickType",
            typeof(NewItemFactoryTypeModel),
            typeof(SubPropertyEditor),
            new PropertyMetadata(null, new PropertyChangedCallback(OnCurrentQuickTypeChanged)));
 
        private ICollectionView _quickTypeView;
        private ObservableCollection<NewItemFactoryTypeModel> _quickTypeCollection;
        private bool _ignoreInternalChanges;
        private bool _exposedSubProperties;
 
        private ItemsControl _subPropertyListControl;
 
        // <summary>
        // Basic ctor
        // </summary>
        public SubPropertyEditor() 
        {
            _quickTypeCollection = new ObservableCollection<NewItemFactoryTypeModel>();
 
            _quickTypeView = CollectionViewSource.GetDefaultView(_quickTypeCollection);
            _quickTypeView.CurrentChanged += new EventHandler(OnCurrentQuickTypeChanged);
        }
 
        // Automation
 
        public event PropertyChangedEventHandler PropertyChanged;
 
        // Internal event we fire for the sake of SubPropertyEditorAutomationPeer that
        // causes it to refresh its offered set of children
        internal event EventHandler VisualsChanged;
 
        public PropertyEntry PropertyEntry 
        {
            get { return (PropertyEntry)this.GetValue(PropertyEntryProperty); }
            set { this.SetValue(PropertyEntryProperty, value); }
        }
 
        public bool IsExpanded 
        {
            get { return (bool)this.GetValue(IsExpandedProperty); }
            set { this.SetValue(IsExpandedProperty, value); }
        }
 
        public NewItemFactoryTypeModel CurrentQuickType 
        {
            get { return (NewItemFactoryTypeModel)this.GetValue(CurrentQuickTypeProperty); }
            set { this.SetValue(CurrentQuickTypeProperty, value); }
        }
 
        // <summary>
        // Gets a flag indicating whether QuickTypes exist
        // </summary>
        public bool HasQuickTypes 
        {
            get {
                return _quickTypeCollection.Count > 0;
            }
        }
 
        // <summary>
        // Returns a list of available QuickTypes (collection of NewItemFactoryTypeModel instances)
        // </summary>
        public ICollectionView QuickTypes 
        {
            get {
                return _quickTypeView;
            }
        }
 
        // <summary>
        // Exposes PropertyValue.SubProperties when the IsExpanded flag first gets set to true
        // and forever thereafter (or at least until the current PropertyValue changes and we
        // collapse the sub-properties)
        // </summary>
        public IEnumerable<PropertyEntry> SelectiveSubProperties 
        {
            get {
                if (!_exposedSubProperties) 
                {
                    if (!this.IsExpanded)
                    {
                        yield break;
                    }
 
                    _exposedSubProperties = true;
                }
 
                PropertyEntry parent = this.PropertyEntry;
                if (parent == null)
                {
                    yield break;
                }
 
                foreach (ModelPropertyEntry subProperty in parent.PropertyValue.SubProperties)
                {
                    if (subProperty.IsBrowsable)
                    {
                        yield return subProperty;
                    }
                }
            }
        }
 
        // <summary>
        // Gets a flag indicating whether the sub-property editor can be expanded or not.
        // </summary>
        public bool IsExpandable 
        {
            get { return this.HasQuickTypes && this.CurrentQuickType != null; }
        }
 
        // <summary>
        // Gets a SelectionPath to itself.
        // </summary>
        public SelectionPath Path 
        {
            get { return PropertySelectionPathInterpreter.Instance.ConstructSelectionPath(this.PropertyEntry); }
        }
 
        // <summary>
        // Gets a description of the contained property
        // to expose through automation
        // </summary>
        public string Description 
        {
            get {
                PropertyEntry property = this.PropertyEntry;
                if (property != null) 
                {
                    return string.Format(
                        CultureInfo.CurrentCulture,
                        Properties.Resources.PropertyEditing_SelectionStatus_Property,
                        this.PropertyEntry.PropertyName);
                }
 
                return string.Empty;
            }
        }
 
 
        // <summary>
        // Exposes the ItemsControl used to display the list of sub-properties.  UI-specific
        // </summary>
        private ItemsControl SubPropertyListControl 
        {
            get {
                if (_subPropertyListControl == null) 
                {
                    _subPropertyListControl = VisualTreeUtils.GetNamedChild<ItemsControl>(this, "PART_SubPropertyList");
                    Fx.Assert(_subPropertyListControl != null, "UI for SubPropertyEditor changed.  Need to update SubPropertyEditor class logic.");
                }
 
                return _subPropertyListControl;
            }
        }
 
 
        // Keyboard Navigation
 
        protected override AutomationPeer OnCreateAutomationPeer() 
        {
            return new SubPropertyEditorAutomationPeer(this);
        }
 
 
        // Properties
 
        // PropertyEntry DP
 
        // When the displayed PropertyEntry changes, make sure we update the UI and hook into the
        // new PropertyEntry's notification mechanism
        private static void OnPropertyEntryChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
        {
            SubPropertyEditor theThis = obj as SubPropertyEditor;
            if (theThis == null)
            {
                return;
            }
 
            PropertyEntry oldValue = e.OldValue as PropertyEntry;
            if (oldValue != null) 
            {
                oldValue.PropertyValue.RootValueChanged -= new EventHandler(theThis.OnPropertyValueRootValueChanged);
            }
 
            PropertyEntry newValue = e.NewValue as PropertyEntry;
            if (newValue != null) 
            {
                newValue.PropertyValue.RootValueChanged += new EventHandler(theThis.OnPropertyValueRootValueChanged);
            }
 
            theThis.RefreshVisuals();
        }
 
        private void OnPropertyValueRootValueChanged(object sender, EventArgs e) 
        {
            if (_ignoreInternalChanges)
            {
                return;
            }
 
            RefreshVisuals();
        }
 
 
        // IsExpanded DP
 
        private static void OnIsExpandedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
        {
            SubPropertyEditor theThis = obj as SubPropertyEditor;
            if (theThis == null)
            {
                return;
            }
 
            bool newIsExpanded = (bool)e.NewValue;
            PropertyEntry containedProperty = theThis.PropertyEntry;
 
            // Store the new expansion state
            if (containedProperty != null) 
            {
                PropertyState state = PropertyStateContainer.Instance.GetPropertyState(
                    ModelUtilities.GetCachedSubPropertyHierarchyPath(containedProperty));
                state.SubPropertiesExpanded = newIsExpanded;
            }
 
            // If we are expanded but we never exposed the sub-properties to anyone before,
            // fire a signal saying that a list of sub-properties may be now available, so that
            // UI DataBindings refresh themselves
            if (newIsExpanded == true &&
                theThis._exposedSubProperties == false) 
            {
                theThis.FireSubPropertiesListChangedEvents();
            }
        }
 
        // CurrentQuickType DP
 
        // This method gets called when the DP changes
        private static void OnCurrentQuickTypeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
        {
            SubPropertyEditor theThis = obj as SubPropertyEditor;
            if (theThis == null)
            {
                return;
            }
 
            if (theThis._ignoreInternalChanges)
            {
                return;
            }
 
            theThis._quickTypeView.MoveCurrentTo(e.NewValue);
 
            theThis.ExpandSubProperties();
            theThis.FireSubPropertiesListChangedEvents();
        }
 
        // This method gets called when the CurrentItem on _quickTypeView changes
        private void OnCurrentQuickTypeChanged(object sender, EventArgs e) 
        {
            if (_ignoreInternalChanges)
            {
                return;
            }
 
            NewItemFactoryTypeModel selectedTypeModel = _quickTypeView.CurrentItem as NewItemFactoryTypeModel;
 
            if (selectedTypeModel == null)
            {
                return;
            }
 
            Fx.Assert(this.PropertyEntry != null, "PropertyEntry should not be null");
            if (this.PropertyEntry == null)
            {
                return;
            }
 
            bool previousValue = IgnoreInternalChanges();
            try 
            {
                this.PropertyEntry.PropertyValue.Value = selectedTypeModel.CreateInstance();
            }
            finally 
            {
                NoticeInternalChanges(previousValue);
            }
        }
 
 
        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) 
        {
            if (e.Property == PropertyContainer.OwningPropertyContainerProperty) 
            {
 
                // A quick and dirty way to register this instance as the implementation of 
                // ISelectionBranchPoint that controls the expansion / collapse of this control
                //
                OnOwningPropertyContainerChanged((PropertyContainer)e.OldValue, (PropertyContainer)e.NewValue);
 
            }
 
            base.OnPropertyChanged(e);
        }
 
        private void OnOwningPropertyContainerChanged(PropertyContainer oldValue, PropertyContainer newValue) 
        {
            if (oldValue != null) 
            {
                PropertySelection.ClearSelectionStop(oldValue);
                PropertySelection.ClearIsSelectionStopDoubleClickTarget(oldValue);
            }
 
            if (newValue != null) 
            {
                PropertySelection.SetSelectionStop(newValue, this);
                PropertySelection.SetIsSelectionStopDoubleClickTarget(newValue, true);
            }
        }
 
 
        // Visual Lookup Helpers
 
        // <summary>
        // Looks for and returns the specified sub-property
        // </summary>
        // <param name="propertyName">Sub-property to look up</param>
        // <returns>Corresponding PropertyEntry if found, null otherwise.</returns>
        internal PropertyEntry FindSubPropertyEntry(string propertyName) 
        {
            if (string.IsNullOrEmpty(propertyName)) 
            {
                return null;
            }
 
            foreach (PropertyEntry property in SelectiveSubProperties)
            {
                if (property.PropertyName.Equals(propertyName))
                {
                    return property;
                }
            }
 
            return null;
        }
 
        // <summary>
        // Looks for and returns the PropertyContainer used to display
        // the specified PropertyEntry
        // </summary>
        // <param name="property">Property to look for</param>
        // <returns>Corresponding PropertyContainer if found, null otherwise.</returns>
        internal PropertyContainer FindSubPropertyEntryVisual(PropertyEntry property) 
        {
            if (property == null) 
            {
                return null;
            }
 
            ItemsControl subPropertyListControl = this.SubPropertyListControl;
            if (subPropertyListControl == null)
            {
                return null;
            }
 
            return subPropertyListControl.ItemContainerGenerator.ContainerFromItem(property) as PropertyContainer;
        }
 
 
        // Helpers
 
        private void RefreshVisuals() 
        {
            RefreshQuickTypes();
            RestoreIsExpandedState();
            FireVisualsChangedEvents();
        }
 
        private void RefreshQuickTypes() 
        {
            bool previousValue = IgnoreInternalChanges();
            try 
            {
                _quickTypeCollection.Clear();
 
                PropertyEntry containedProperty = this.PropertyEntry;
                if (containedProperty == null)
                {
                    return;
                }
 
                ModelProperty property = ((ModelPropertyEntry)containedProperty).FirstModelProperty;
                Type containerValueType = ((ModelPropertyEntryBase)containedProperty).CommonValueType;
                NewItemFactoryTypeModel selectedFactoryModel = null;
                Type defaultItemType = GetDefaultItemType(property);
 
                // Find all elligible NewItemFactoryTypes declared through metadata
                IEnumerable<NewItemFactoryTypeModel> factoryModels =
                    ExtensibilityAccessor.GetNewItemFactoryTypeModels(
                    property,
                    ResourceUtilities.GetDesiredTypeIconSize(this));
 
                if (factoryModels != null) 
                {
                    foreach (NewItemFactoryTypeModel factoryModel in factoryModels) 
                    {
                        _quickTypeCollection.Add(factoryModel);
 
                        if (selectedFactoryModel == null) 
                        {
                            if (object.Equals(containerValueType, factoryModel.Type)) 
                            {
                                selectedFactoryModel = factoryModel;
                            }
                        }
 
                        if (defaultItemType != null &&
                            object.Equals(defaultItemType, factoryModel.Type)) 
                        {
                            defaultItemType = null;
                        }
                    }
                }
 
                //add a null value - user should always have an option to clear property value
                NewItemFactoryTypeModel nullTypeFactoryTypeModel =
                    new NewItemFactoryTypeModel(null, new NullItemFactory());
 
                // Add a default item type based on the property type (if it wasn't also added through
                // metadata)
                if (defaultItemType != null) 
                {
                    NewItemFactoryTypeModel defaultItemFactoryTypeModel = new NewItemFactoryTypeModel(defaultItemType, new NewItemFactory());
                    _quickTypeCollection.Add(defaultItemFactoryTypeModel);
 
                    if (selectedFactoryModel == null) 
                    {
                        if (object.Equals(containerValueType, defaultItemFactoryTypeModel.Type)) 
                        {
                            selectedFactoryModel = defaultItemFactoryTypeModel;
                        }
                        else if (containerValueType == null)
                        {
                            selectedFactoryModel = nullTypeFactoryTypeModel;
                        }
                    }
                }
                
                _quickTypeCollection.Add(nullTypeFactoryTypeModel);
 
                // Make sure the currently selected value on the CollectionView reflects the
                // actual value of the property
                _quickTypeView.MoveCurrentTo(selectedFactoryModel);
                this.CurrentQuickType = selectedFactoryModel;
            }
            finally 
            {
                NoticeInternalChanges(previousValue);
            }
        }
 
        private static Type GetDefaultItemType(ModelProperty property) 
        {
            if (property == null)
            {
                return null;
            }
 
            Type propertyType = property.PropertyType;
            if (EditorUtilities.IsConcreteWithDefaultCtor(propertyType))
            {
                return propertyType;
            }
 
            return null;
        }
 
        private void RestoreIsExpandedState() 
        {
            bool newIsExpanded = false;
            PropertyEntry property = this.PropertyEntry;
 
            if (property != null) 
            {
                PropertyState state = PropertyStateContainer.Instance.GetPropertyState(
                    ModelUtilities.GetCachedSubPropertyHierarchyPath(property));
                newIsExpanded = state.SubPropertiesExpanded;
            }
 
            this.IsExpanded = newIsExpanded;
            _exposedSubProperties = false;
        }
 
        private void ExpandSubProperties() 
        {
            this.IsExpanded = true;
        }
 
 
        // Change Notification Helpers
 
        private bool IgnoreInternalChanges() 
        {
            bool previousValue = _ignoreInternalChanges;
            _ignoreInternalChanges = true;
            return previousValue;
        }
 
        private void NoticeInternalChanges(bool previousValue) 
        {
            _ignoreInternalChanges = previousValue;
        }
 
        private void FireVisualsChangedEvents() 
        {
            // Fire updated events
            OnPropertyChanged("HasQuickTypes");
            OnPropertyChanged("QuickTypes");
            FireSubPropertiesListChangedEvents();
        }
 
        private void FireSubPropertiesListChangedEvents() 
        {
            OnPropertyChanged("IsExpandable");
            OnPropertyChanged("SelectiveSubProperties");
 
            if (VisualsChanged != null)
            {
                VisualsChanged(this, EventArgs.Empty);
            }
        }
 
 
        // INotifyPropertyChanged Members
 
        private void OnPropertyChanged(string propertyName) 
        {
            Fx.Assert(!string.IsNullOrEmpty(propertyName), "Can't raise OnPropertyChanged event without a valid property name.");
 
            if (PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
 
        // ISelectionStop Members
 
        //NullItemFactory - this class is used to provide a null entry in quick types list - it is required to allow user 
        //to clear value of an object.
        sealed class NullItemFactory : NewItemFactory
        {
            public override object CreateInstance(Type type)
            {
                //no input type is allowed - we never create instance of anything
                Fx.Assert(type == null, "NullItemFactory supports only null as type parameter");
                return null;
            }
 
            public override string GetDisplayName(Type type)
            {
                //no input type is allowed - we always return (null) string
                Fx.Assert(type == null, "NullItemFactory supports only null as type parameter");
                return "(null)";
            }
        }
    }
}