File: winforms\Managed\System\WinForms\PropertyGridInternal\MergePropertyDescriptor.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="MergePropertyDescriptor.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
/*
 */
namespace System.Windows.Forms.PropertyGridInternal {
    using System.Runtime.Serialization.Formatters;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.ComponentModel;
    using System.Diagnostics;
    using System;
    using System.IO;
    using System.Collections;
    using System.Globalization;
    using System.Reflection;    
    using System.ComponentModel.Design;
    using System.ComponentModel.Design.Serialization;
    using System.Windows.Forms;
    using System.Drawing;
    using Microsoft.Win32;
    
    internal class MergePropertyDescriptor : PropertyDescriptor {
 
        private PropertyDescriptor[] descriptors;
 
        private enum TriState {
                Unknown,
                Yes,
                No
        }
 
        private TriState localizable = TriState.Unknown;
        private TriState readOnly = TriState.Unknown;
        private TriState canReset = TriState.Unknown;
 
        private MultiMergeCollection collection;
 
        
        public MergePropertyDescriptor(PropertyDescriptor[] descriptors) : base(descriptors[0].Name, null)  {
            this.descriptors = descriptors;
        }
 
 
        /// <include file='doc\PropertyDescriptor.uex' path='docs/doc[@for="PropertyDescriptor.ComponentType"]/*' />
        /// <devdoc>
        ///    <para>
        ///       When overridden in a derived class, gets the type of the
        ///       component this property
        ///       is bound to.
        ///    </para>
        /// </devdoc>
        public override Type ComponentType {
                get {
                    return descriptors[0].ComponentType;
                }
        }
 
        /// <include file='doc\PropertyDescriptor.uex' path='docs/doc[@for="PropertyDescriptor.Converter"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets the type converter for this property.
        ///    </para>
        /// </devdoc>
        public override TypeConverter Converter {
            get {
                return descriptors[0].Converter;
            }
        }
 
        public override string DisplayName {
            get {
                return descriptors[0].DisplayName;
            }
        }
 
        /// <include file='doc\PropertyDescriptor.uex' path='docs/doc[@for="PropertyDescriptor.IsLocalizable"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets a value
        ///       indicating whether this property should be localized, as
        ///       specified in the <see cref='System.ComponentModel.LocalizableAttribute'/>.
        ///    </para>
        /// </devdoc>
        public override bool IsLocalizable {
            get {
                if (localizable == TriState.Unknown) {
                    localizable = TriState.Yes;
                    foreach (PropertyDescriptor pd in descriptors) {
                        if (!pd.IsLocalizable) {
                            localizable = TriState.No;
                            break;
                        }
                    }
                }
                return (localizable == TriState.Yes);
            }
        }
 
        /// <include file='doc\PropertyDescriptor.uex' path='docs/doc[@for="PropertyDescriptor.IsReadOnly"]/*' />
        /// <devdoc>
        ///    <para>
        ///       When overridden in
        ///       a derived class, gets a value
        ///       indicating whether this property is read-only.
        ///    </para>
        /// </devdoc>
        public override bool IsReadOnly { 
            get {
                if (readOnly == TriState.Unknown) {
                    readOnly = TriState.No;
                    foreach (PropertyDescriptor pd in descriptors) {
                        if (pd.IsReadOnly) {
                            readOnly = TriState.Yes;
                            break;
                        }
                    }
                }
                return (readOnly == TriState.Yes);
            }
        }
 
   
        /// <include file='doc\PropertyDescriptor.uex' path='docs/doc[@for="PropertyDescriptor.PropertyType"]/*' />
        /// <devdoc>
        ///    <para>
        ///       When overridden in a derived class,
        ///       gets the type of the property.
        ///    </para>
        /// </devdoc>
        public override Type PropertyType { 
            get {
                return descriptors[0].PropertyType;
            }
        }
 
        public PropertyDescriptor this[int index] {
            get {
                return descriptors[index];
            }
        }
 
        /// <include file='doc\PropertyDescriptor.uex' path='docs/doc[@for="PropertyDescriptor.CanResetValue"]/*' />
        /// <devdoc>
        ///    <para>
        ///       When overridden in a derived class, indicates whether
        ///       resetting the <paramref name="component "/>will change the value of the
        ///    <paramref name="component"/>.
        /// </para>
        /// </devdoc>
        public override bool CanResetValue(object component) {
            Debug.Assert(component is Array, "MergePropertyDescriptor::CanResetValue called with non-array value");
            if (canReset == TriState.Unknown) {
                 canReset = TriState.Yes;
                 Array a = (Array)component;
                 for (int i = 0; i < descriptors.Length; i++) {
                     if (!descriptors[i].CanResetValue(GetPropertyOwnerForComponent(a, i))) {
                         canReset = TriState.No;
                         break;
                     }
                 }
                 
             }
             return (canReset == TriState.Yes);
        }
 
        /// <devdoc>
        ///     This method attempts to copy the given value so unique values are
        ///     always passed to each object.  If the object cannot be copied it
        ///     will be returned.
        /// </devdoc>
        private object CopyValue(object value) {
 
            // null is always OK
            if (value == null) {
                return value;
            }
 
            Type type = value.GetType();
 
            // value types are always copies
            if (type.IsValueType) {
                return value;
            }
 
            object clonedValue = null;
 
            // ICloneable is the next easiest thing
            ICloneable clone = value as ICloneable;
            if (clone != null) {
                clonedValue = clone.Clone();
            }
 
            // Next, access the type converter
            if (clonedValue == null) {
                TypeConverter converter = TypeDescriptor.GetConverter(value);
                if (converter.CanConvertTo(typeof(InstanceDescriptor))) {
                    // Instance descriptors provide full fidelity unless
                    // they are marked as incomplete.
                    InstanceDescriptor desc = (InstanceDescriptor)converter.ConvertTo(null, CultureInfo.InvariantCulture, value, typeof(InstanceDescriptor));
                    if (desc != null && desc.IsComplete) {
                        clonedValue = desc.Invoke();
                    }
                }
 
                // If that didn't work, try conversion to/from string
                if (clonedValue == null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string))) {
                    object stringRep = converter.ConvertToInvariantString(value);
                    clonedValue = converter.ConvertFromInvariantString((string)stringRep);
                }
            }
 
            
 
            // How about serialization?
            if (clonedValue == null && type.IsSerializable) {
                BinaryFormatter f = new BinaryFormatter();
                MemoryStream ms = new MemoryStream();
                f.Serialize(ms, value);
                ms.Position = 0;
                clonedValue = f.Deserialize(ms);
            }
 
            if (clonedValue != null) {
                return clonedValue;
            }
 
            // we failed.  This object's reference will be set on each property.
            return value;
        }
 
         /// <include file='doc\MemberDescriptor.uex' path='docs/doc[@for="MemberDescriptor.CreateAttributeCollection"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Creates a collection of attributes using the
        ///       array of attributes that you passed to the constructor.
        ///    </para>
        /// </devdoc>
        protected override AttributeCollection CreateAttributeCollection() {
            return new MergedAttributeCollection(this);
        }
 
 
        private object GetPropertyOwnerForComponent(Array a, int i) {
            object propertyOwner = a.GetValue(i);
            if (propertyOwner is ICustomTypeDescriptor) {
                propertyOwner = ((ICustomTypeDescriptor) propertyOwner).GetPropertyOwner(descriptors[i]);
            }
            return propertyOwner;
        }
 
        /// <include file='doc\PropertyDescriptor.uex' path='docs/doc[@for="PropertyDescriptor.GetEditor"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets an editor of the specified type.
        ///    </para>
        /// </devdoc>
        public override object GetEditor(Type editorBaseType) {
            return descriptors[0].GetEditor(editorBaseType);
        }
 
 
        /// <include file='doc\PropertyDescriptor.uex' path='docs/doc[@for="PropertyDescriptor.GetValue"]/*' />
        /// <devdoc>
        ///    <para>
        ///       When overridden in a derived class, gets the current
        ///       value
        ///       of the
        ///       property on a component.
        ///    </para>
        /// </devdoc>
        public override object GetValue(object component) {
            Debug.Assert(component is Array, "MergePropertyDescriptor::GetValue called with non-array value");
            bool temp;
            return GetValue((Array)component, out temp);
        }
 
        public object GetValue(Array components, out bool allEqual) {
            allEqual = true;
            object obj = descriptors[0].GetValue(GetPropertyOwnerForComponent(components, 0));
                    
            if (obj is ICollection) {
               if (collection == null) {
                   collection = new MultiMergeCollection((ICollection)obj);
               }
               else if (collection.Locked) {
                   return collection;
               }
               else {
                   collection.SetItems((ICollection)obj);
               }
            }
            
            for (int i = 1; i < descriptors.Length; i++) {
                object objCur = descriptors[i].GetValue(GetPropertyOwnerForComponent(components, i));
 
                if (collection != null) {
                   if (!collection.MergeCollection((ICollection)objCur)){
                      allEqual = false;
                      return null;
                   }
                }
                else if ((obj == null && objCur == null) ||
                         (obj != null && obj.Equals(objCur))) {
                
                   continue;
                }
                else {
                    allEqual = false;
                    return null;
                }
            }
            
            if (allEqual && collection != null && collection.Count == 0) {
                return null;
            }
            
            return (collection != null ? collection : obj);
        }
 
        internal object[] GetValues(Array components) {
            object[] values = new object[components.Length];
 
            for (int i = 0; i < components.Length; i++) {
                values[i] = descriptors[i].GetValue(GetPropertyOwnerForComponent(components, i));
            }
            return values;
        }
 
        /// <include file='doc\PropertyDescriptor.uex' path='docs/doc[@for="PropertyDescriptor.ResetValue"]/*' />
        /// <devdoc>
        ///    <para>
        ///       When overridden in a derived class, resets the
        ///       value
        ///       for this property
        ///       of the component.
        ///    </para>
        /// </devdoc>
        public override void ResetValue(object component) {
 
            Debug.Assert(component is Array, "MergePropertyDescriptor::ResetValue called with non-array value");
            Array a = (Array)component;
            for (int i = 0; i < descriptors.Length; i++) {
                descriptors[i].ResetValue(GetPropertyOwnerForComponent(a, i));
            }
        }
 
        private void SetCollectionValues(Array a, IList listValue) {
            
            try {
                if (collection != null) {
                   collection.Locked = true;
                }
 
                // now we have to copy the value into each property.
                object[] values = new object[listValue.Count];
                
                listValue.CopyTo(values, 0);
                
                for (int i = 0; i < descriptors.Length; i++) {
                    IList propList = descriptors[i].GetValue(GetPropertyOwnerForComponent(a, i)) as IList;
 
                    if (propList == null) {
                       continue;
                    }
                    
                    propList.Clear();
                    foreach (object val in values) {
                        propList.Add(val);
                    }
                }
            }
            finally {
               if (collection != null) {
                  collection.Locked = false;
               }
            }
 
        }
 
        /// <include file='doc\PropertyDescriptor.uex' path='docs/doc[@for="PropertyDescriptor.SetValue"]/*' />
        /// <devdoc>
        ///    <para>
        ///       When overridden in a derived class, sets the value of
        ///       the component to a different value.
        ///    </para>
        /// </devdoc>
        public override void SetValue(object component, object value) {
            Debug.Assert(component is Array, "MergePropertyDescriptor::SetValue called with non-array value");
            Array a = (Array)component;
            if (value is IList && typeof(IList).IsAssignableFrom(PropertyType)) {
                SetCollectionValues(a, (IList)value);
            }
            else {
                for (int i = 0; i < descriptors.Length; i++) {
                    object clonedValue = CopyValue(value);
                    descriptors[i].SetValue(GetPropertyOwnerForComponent(a, i), clonedValue);
                }
            }
        }
 
        /// <include file='doc\PropertyDescriptor.uex' path='docs/doc[@for="PropertyDescriptor.ShouldSerializeValue"]/*' />
        /// <devdoc>
        ///    <para>
        ///       When overridden in a derived class, indicates whether the
        ///       value of
        ///       this property needs to be persisted.
        ///    </para>
        /// </devdoc>
 
        public override bool ShouldSerializeValue(object component) {
            Debug.Assert(component is Array, "MergePropertyDescriptor::ShouldSerializeValue called with non-array value");
            Array a = (Array)component;
            for (int i = 0; i < descriptors.Length; i++) {
                if (!descriptors[i].ShouldSerializeValue(GetPropertyOwnerForComponent(a, i))) {
                    return false;
                }
            }
            return true;
        }
 
        private class MultiMergeCollection : ICollection {
        
            private object[] items;
            private bool     locked;
            
            public MultiMergeCollection(ICollection original) {
               SetItems(original);
            }
            
            /// <include file='doc\MultiSelectPropertyGridEntry.uex' path='docs/doc[@for="MultiSelectPropertyGridEntry.MultiMergeCollection.Count"]/*' />
            /// <devdoc>
            ///     Retrieves the number of items.
            /// </devdoc>
            public int Count {
                get {
                    if (items != null) {
                        return items.Length;
                    }
                    else {
                        return 0;
                    }
                }
            }
            
            
            /// <include file='doc\MultiSelectPropertyGridEntry.uex' path='docs/doc[@for="MultiSelectPropertyGridEntry.MultiMergeCollection.Locked"]/*' />
            /// <devdoc>
            ///     Prevents the contents of the collection from being re-initialized;
            /// </devdoc>
            public bool Locked {
               get {
                  return locked;
               }
               set {
                  this.locked = value;
               }
            }
 
            object ICollection.SyncRoot {
                get {
                    return this;
                }
            }
 
            bool ICollection.IsSynchronized {
                get {
                    return false;
                }
            }
            
            public void CopyTo(Array array, int index) {
               if (items == null) return;
               
               Array.Copy(items, 0, array, index, items.Length);
            }
            
            public IEnumerator GetEnumerator(){
               if (items != null) {
                  return items.GetEnumerator();
               }
               else {
                  return new object[0].GetEnumerator();
               }
            }
            
            /// <include file='doc\MultiSelectPropertyGridEntry.uex' path='docs/doc[@for="MultiSelectPropertyGridEntry.MultiMergeCollection.MergeCollection"]/*' />
            /// <devdoc>
            /// Ensures that the new collection equals the exisitng one.
            /// Otherwise, it wipes out the contents of the new collection.
            /// </devdoc>
            public bool MergeCollection(ICollection newCollection) {
                
                if (locked) {
                   return true;
                }
               
                if (items.Length != newCollection.Count) {
                     items = new object[0];
                     return false;
                }
                
                object[] newItems = new object[newCollection.Count];
                newCollection.CopyTo(newItems, 0);
                for (int i = 0;i < newItems.Length; i++) {
                     if (((newItems[i] == null) != (items[i] == null)) ||
                         (items[i] != null && !items[i].Equals(newItems[i]))){
                           items = new object[0];
                           return false;  
                         }
                         
                }
                return true;
            }
            
            public void SetItems(ICollection collection) {
                if (locked) {
                  return;
                }
                items = new object[collection.Count];
                collection.CopyTo(items, 0);
            }
            
        }
 
        private class MergedAttributeCollection : AttributeCollection {
            private MergePropertyDescriptor owner;
 
            private AttributeCollection[] attributeCollections = null;
            private IDictionary             foundAttributes = null;
            
            public MergedAttributeCollection(MergePropertyDescriptor owner) : base((Attribute[])null) {
                this.owner = owner;
            }
 
            public override Attribute this[Type attributeType] {
                get {
                    return GetCommonAttribute(attributeType);
                }
            }
 
            #if false
            private void FullMerge() {
                Attribute[][] collections = new Attribute[owner.descriptors.Length][];
                for (int i = 0; i < owner.descriptors.Length; i++) {
                    AttributeCollection attrCollection = owner.descriptors[i].Attributes;
                    collections[i] = new Attribute[attrCollection.Count];
                    attrCollection.CopyTo(collections[i], 0);
                    Array.Sort(collections[i], GridEntry.AttributeTypeSorter);
                }
                
                ArrayList mergedList = new ArrayList();
    
                // merge the sorted lists -- note that lists aren't fully sorted just by
                // Attribute.TypeId
                //
                int[] posArray = new int[collections.Length];
                for (int i = 0; i < collections[0].Length; i++) {
                    Attribute pivotAttr = collections[0][i];
                    bool match = true;
                    for (int j = 1; j < collections.Length; j++) {
    
                        if (posArray[j] >= collections[j].Length) {
                            match = false;
                            break;
                        }
    
                        // check to see if we're on a match
                        //
                        if (pivotAttr.Equals(collections[j][posArray[j]])) {
                            posArray[j] += 1;
                            continue;
                        }
    
                        int jPos = posArray[j];
                        Attribute jAttr = collections[j][jPos];
    
                        match = false;
    
                        // if we aren't on a match, check all the items until we're past
                        // where the matching item would be
                        while (GridEntry.AttributeTypeSorter.Compare(jAttr, pivotAttr) <= 0) {
                            
                            // got a match!
                            if (pivotAttr.Equals(jAttr)) {
                                posArray[j] = jPos + 1;
                                match = true;
                                break;
                            }
    
                            // try again
                            jPos++;
                            if (jPos < collections[j].Length) {
                                jAttr = collections[j][jPos];
                            }
                            else {
                                break;
                            }
                        }
    
                        // if we got here, there is no match, quit for this guy
                        if (!match) {
                            posArray[j] = jPos;
                            break;
                        }
                    }
    
                    // do we have a match?
                    if (match) {
                        mergedList.Add(pivotAttr);
                    }
                }
    
                // create our merged array
                Attribute[] mergedAttrs = new Attribute[mergedList.Count];
                mergedList.CopyTo(mergedAttrs, 0);
            }
 
            #endif
 
            private Attribute GetCommonAttribute(Type attributeType) {
                if (attributeCollections == null) {
                    attributeCollections = new AttributeCollection[owner.descriptors.Length];
                    for (int i = 0; i < owner.descriptors.Length; i++) {
                        attributeCollections[i] = owner.descriptors[i].Attributes;
                    }
                }
 
                if (attributeCollections.Length == 0) {
                    return GetDefaultAttribute(attributeType);
                }
 
                Attribute value;
                if (foundAttributes != null) {
                    value = foundAttributes[attributeType] as Attribute;
                    if (value != null) {
                        return value;
                    }
                }
 
                value = attributeCollections[0][attributeType];
 
                if (value == null) {
                    return null;
                }
                
                for (int i = 1; i < attributeCollections.Length; i++) {
                    Attribute newValue = attributeCollections[i][attributeType];
                    if (!value.Equals(newValue)) {
                        value = GetDefaultAttribute(attributeType);
                        break;
                    }
                }
 
                if (foundAttributes == null) {
                    foundAttributes = new Hashtable();
                }
                foundAttributes[attributeType] = value;
                return value;
            }
        }
    }
}