File: System.Activities.Presentation\System\Activities\Presentation\Base\Core\Internal\PropertyEditing\Model\ModelCategoryEntry.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.Model 
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Diagnostics;
    using System.Activities.Presentation.PropertyEditing;
    using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework.Data;
    using System.Activities.Presentation.Internal.PropertyEditing.FromExpression.Framework.PropertyInspector;
    using System.Activities.Presentation;
 
    // <summary>
    // Cider's concrete implementation of CategoryEntry (CategoryBase comes from Sparkle
    // and it has a few extra goodies that we want to reuse).  This class implements
    // INotifyCollectionChanged.  We need to push this implementation to the base class
    // in v2.
    // </summary>
    internal class ModelCategoryEntry : CategoryBase, INotifyCollectionChanged 
    {
 
        private ObservableCollectionWorkaround<PropertyEntry> _basicProperties;
        private ObservableCollectionWorkaround<PropertyEntry> _advancedProperties;
 
        // <summary>
        // Basic ctor
        // </summary>
        // <param name="categoryName">Localized name for this category</param>
        public ModelCategoryEntry(string categoryName) : base(categoryName) 
        {
            _basicProperties = new ObservableCollectionWorkaround<PropertyEntry>();
            _advancedProperties = new ObservableCollectionWorkaround<PropertyEntry>();
        }
 
        public event NotifyCollectionChangedEventHandler CollectionChanged;
 
        // <summary>
        // Gets the advanced properties contained in this category
        // </summary>
        public override ObservableCollection<PropertyEntry> AdvancedProperties 
        {
            get {
                return _advancedProperties;
            }
        }
 
        // <summary>
        // Gets the basic properties contained in this category
        // </summary>
        public override ObservableCollection<PropertyEntry> BasicProperties 
        {
            get {
                return _basicProperties;
            }
        }
 
        // <summary>
        // Gets a flag indicating whether this category contains any properties
        // </summary>
        internal bool IsEmpty 
        {
            get {
                return _advancedProperties.Count + _basicProperties.Count == 0;
            }
        }
 
        // <summary>
        // Returns either the basic or the advanced bucket based on the IsAdvanced flag
        // set in the PropertyEntry itself
        // </summary>
        // <param name="property">Property to examine</param>
        // <returns>The corresponding basic or advanced bucket</returns>
        internal ObservableCollectionWorkaround<PropertyEntry> GetBucket(PropertyEntry property) 
        {
            if (property == null) 
            {
                throw FxTrace.Exception.ArgumentNull("property");
            }
            return property.IsAdvanced ? _advancedProperties : _basicProperties;
        }
 
        // <summary>
        // Adds the given property to the specified property bucket (use
        // ModelCategoryEntry.BasicProperties, ModelCategoryEntry.AdvancedProperties, or
        // ModelCategoryEntry.GetBucket()) sorted using the specified comparer.
        // </summary>
        // <param name="property">Property to add</param>
        // <param name="bucket">Property bucket to populate</param>
        // <param name="comparer">Sort algorithm to use</param>
        // <param name="fireCollectionChangedEvent">If set to true, NotifyCollectionChanged event is fired</param>
        internal void Add(
            PropertyEntry property,
            ObservableCollection<PropertyEntry> bucket,
            IComparer<PropertyEntry> comparer) 
        {
            Add(property, bucket, comparer, true);
        }
 
        //
        // Adds the given property to the specified property bucket (use
        // ModelCategoryEntry.BasicProperties, ModelCategoryEntry.AdvancedProperties, or
        // ModelCategoryEntry.GetBucket()) sorted using the specified comparer.
        //
        private void Add(
            PropertyEntry property,
            ObservableCollection<PropertyEntry> bucket,
            IComparer<PropertyEntry> comparer,
            bool fireCollectionChangedEvent) 
        {
 
            if (property == null) 
            {
                throw FxTrace.Exception.ArgumentNull("property");
            }
            if (bucket == null) 
            {
                throw FxTrace.Exception.ArgumentNull("bucket");
            }
            if (comparer == null) 
            {
                throw FxTrace.Exception.ArgumentNull("comparer");
            }
 
            ObservableCollectionWorkaround<PropertyEntry> castBucket = bucket as ObservableCollectionWorkaround<PropertyEntry>;
            int insertionIndex = 0;
 
            if (castBucket == null) 
            {
                Debug.Fail("Invalid property bucket.  The property sort order will be broken.");
            }
            else 
            {
                insertionIndex = castBucket.BinarySearch(property, comparer);
                if (insertionIndex < 0) 
                {
                    insertionIndex = ~insertionIndex;
                }
            }
 
            bucket.Insert(insertionIndex, property);
 
            if (fireCollectionChangedEvent)
            {
                FirePropertiesChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, property));
            }
        }
 
        // <summary>
        // Removes and re-adds the specified property from this category, if it existed
        // there to begin with.  Noop otherwise.
        //
        // Use this method to refresh the cate----zation of a property if it suddenly
        // becomes Advanced if it was Basic before, or if its IsBrowsable status changes.
        // </summary>
        // <param name="property">Property to refresh</param>
        // <param name="bucket">Property bucket to repopulate</param>
        // <param name="sortComparer">Comparer to use to reinsert the given property in its new place</param>
        internal void Refresh(ModelPropertyEntry property, ObservableCollection<PropertyEntry> bucket, IComparer<PropertyEntry> sortComparer) 
        {
            if (property == null) 
            {
                throw FxTrace.Exception.ArgumentNull("property");
            }
            if (bucket != _basicProperties && bucket != _advancedProperties) 
            {
                Debug.Fail("Invalid bucket specified.  Property was not refreshed.");
                return;
            }
 
            // Let's see if we know about this property
            ObservableCollectionWorkaround<PropertyEntry> collection;
            collection = _advancedProperties;
 
            int index = collection.BinarySearch(property, null);
            if (index < 0) 
            {
                collection = _basicProperties;
                index = collection.BinarySearch(property, null);
            }
 
            // If not, noop
            if (index < 0)
            {
                return;
            }
 
            // We know about this property, so refresh it.  It may have changed
            // somehow (eg. switched from basic to advanced, become hidden, etc.)
            // so make sure it's thrown into the right bucket.
            collection.RemoveAt(index);
            Add(property, bucket, sortComparer, false);
        }
 
        // <summary>
        // This is a work-around fix because Blend's CategoryBase does not handle null filters (valid value)
        // correctly.  We need to ask Blend to eventually fix this issue.
        // </summary>
        // <param name="filter">Filter to apply, can be null</param>
        public override void ApplyFilter(PropertyFilter filter) 
        {
            if (filter == null) 
            {
                this.MatchesFilter = true;
                this.BasicPropertyMatchesFilter = true;
                this.AdvancedPropertyMatchesFilter = true;
 
                foreach (PropertyEntry property in this.BasicProperties)
                {
                    property.ApplyFilter(filter);
                }
 
                foreach (PropertyEntry property in this.AdvancedProperties)
                {
                    property.ApplyFilter(filter);
                }
            }
            else 
            {
                base.ApplyFilter(filter);
            }
        }
 
        // Another Blend work-around - we expose all properties through the OM, not just the
        // Browsable ones.  However, as a result, we need to cull the non-browsable ones from
        // consideration.  Otherwise, empty categories may appear.
        protected override bool DoesPropertyMatchFilter(PropertyFilter filter, PropertyEntry property) 
        {
            property.ApplyFilter(filter);
 
            bool isBrowsable = true;
            ModelPropertyEntry modelPropertyEntry = property as ModelPropertyEntry;
            if (modelPropertyEntry != null)
            {
                //display given property if it is browsable or
                isBrowsable = modelPropertyEntry.IsBrowsable || 
                    // it may not be browsable, but if there is a category editor associated - display it anyway
                    (this.CategoryEditors != null && this.CategoryEditors.Count != 0);
            }
 
            return isBrowsable && property.MatchesFilter;
        }
 
        // <summary>
        // Sets the Disassociated flag on all contained properties to True
        // </summary>
        internal void MarkAllPropertiesDisassociated() 
        {
            MarkAllPropertiesDisassociated(_basicProperties);
            MarkAllPropertiesDisassociated(_advancedProperties);
        }
 
        // <summary>
        // Sets the Disassociated flag on all contained attached properties to True
        // </summary>
        internal void MarkAttachedPropertiesDisassociated() 
        {
            MarkAttachedPropertiesDisassociated(_basicProperties);
            MarkAttachedPropertiesDisassociated(_advancedProperties);
        }
 
        // <summary>
        // Removes all properties from this category whose Disassociated flag is set to True
        // </summary>
        internal void CullDisassociatedProperties() 
        {
            bool propertiesCulled = false;
            propertiesCulled |= CullDisassociatedProperties(_basicProperties);
            propertiesCulled |= CullDisassociatedProperties(_advancedProperties);
 
            if (propertiesCulled)
            {
                FirePropertiesChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }
        }
 
        private static void MarkAllPropertiesDisassociated(ObservableCollectionWorkaround<PropertyEntry> propertyList) 
        {
            foreach (ModelPropertyEntry property in propertyList)
            {
                property.Disassociated = true;
            }
        }
 
        private static void MarkAttachedPropertiesDisassociated(ObservableCollectionWorkaround<PropertyEntry> propertyList) 
        {
            foreach (ModelPropertyEntry property in propertyList)
            {
                if (property.IsAttached)
                {
                    property.Disassociated = true;
                }
            }
        }
 
        private static bool CullDisassociatedProperties(ObservableCollectionWorkaround<PropertyEntry> propertyList) 
        {
            bool propertiesCulled = false;
            for (int i = propertyList.Count - 1; i >= 0; i--) 
            {
                ModelPropertyEntry property = (ModelPropertyEntry)propertyList[i];
                if (property.Disassociated) 
                {
                    property.Disconnect();
                    propertyList.RemoveAt(i);
                    propertiesCulled = true;
                }
            }
 
            return propertiesCulled;
        }
 
        // INotifyCollectionChanged Members
 
        private void FirePropertiesChanged(NotifyCollectionChangedEventArgs collectionChangedEventArgs) 
        {
            // Fire both "Properties" changed events
            OnPropertyChanged("Properties");
            OnPropertyChanged("Item[]");
 
            // as well as the appropriate collection-changed event
            if (CollectionChanged != null)
            {
                CollectionChanged(this, collectionChangedEventArgs);
            }
        }
 
    }
}