File: System\Windows\Forms\Controls\PropertyGrid\PropertyGridInternal\MultiSelectRootGridEntry.PropertyMerger.cs
Web Access
Project: src\src\System.Windows.Forms\src\System.Windows.Forms.csproj (System.Windows.Forms)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using System.Globalization;
using System.Windows.Forms.Design;
 
namespace System.Windows.Forms.PropertyGridInternal;
 
internal partial class MultiSelectRootGridEntry
{
    internal static class PropertyMerger
    {
        public static MultiPropertyDescriptorGridEntry[]? GetMergedProperties(
            object?[] objects,
            GridEntry parentEntry,
            PropertySort sort,
            PropertyTab? tab)
        {
            if (tab is null)
            {
                return null;
            }
 
            MultiPropertyDescriptorGridEntry[]? result = null;
            try
            {
                if ((sort & PropertySort.Alphabetical) != 0)
                {
                    List<PropertyDescriptor[]>? commonProperties = GetCommonProperties(objects, presort: true, tab, parentEntry);
                    if (commonProperties is not null)
                    {
                        var entries = new MultiPropertyDescriptorGridEntry[commonProperties.Count];
                        for (int i = 0; i < entries.Length; i++)
                        {
                            entries[i] = new MultiPropertyDescriptorGridEntry(
                                parentEntry.OwnerGrid,
                                parentEntry,
                                objects!, // all elements are not null if commonProperties is not null
                                commonProperties[i],
                                hide: false);
                        }
 
                        result = SortParenEntries(entries);
                    }
                }
                else
                {
                    List<PropertyDescriptor[]>? properties = GetCommonProperties(objects.AsSpan(1), presort: true, tab, parentEntry);
 
                    // This will work for just one as well.
                    List<PropertyDescriptor[]>? firstProperties = GetCommonProperties(objects.AsSpan(0, 1), presort: false, tab, parentEntry);
 
                    if (properties is not null && firstProperties is not null)
                    {
                        var firstPropertyDescriptors = new PropertyDescriptor[firstProperties.Count];
                        for (int i = 0; i < firstProperties.Count; i++)
                        {
                            firstPropertyDescriptors[i] = firstProperties[i][0];
                        }
 
                        properties = UnsortedMerge(firstPropertyDescriptors, properties);
 
                        var entries = new MultiPropertyDescriptorGridEntry[properties.Count];
 
                        for (int i = 0; i < entries.Length; i++)
                        {
                            entries[i] = new MultiPropertyDescriptorGridEntry(
                                parentEntry.OwnerGrid,
                                parentEntry,
                                objects!, // all elements are not null if properties and firstProperties are not null
                                properties[i],
                                hide: false);
                        }
 
                        result = SortParenEntries(entries);
                    }
                    else
                    {
                        return null;
                    }
                }
            }
            catch
            {
            }
 
            return result;
        }
 
        /// <summary>
        ///  Returns a list of <see cref="PropertyDescriptor"/> arrays, one for each component.
        /// </summary>
        private static List<PropertyDescriptor[]>? GetCommonProperties(
            ReadOnlySpan<object?> objects,
            bool presort,
            PropertyTab tab,
            GridEntry parentEntry)
        {
            var objectProperties = new PropertyDescriptorCollection[objects.Length];
            Attribute[]? attributes = null;
            if (parentEntry.BrowsableAttributes is not null)
            {
                attributes = new Attribute[parentEntry.BrowsableAttributes.Count];
                parentEntry.BrowsableAttributes.CopyTo(attributes, 0);
            }
 
            for (int i = 0; i < objects.Length; i++)
            {
                object? obj = objects[i];
                if (obj is null)
                {
                    return null;
                }
 
                PropertyDescriptorCollection? properties = tab.GetProperties(parentEntry, obj, attributes);
                if (properties is null)
                {
                    return null;
                }
 
                if (presort)
                {
                    properties = properties.Sort(s_propertyComparer);
                }
 
                objectProperties[i] = properties;
            }
 
            List<PropertyDescriptor[]> mergedList = [];
            var matchArray = new PropertyDescriptor[objects.Length];
 
            //
            // Merge the property descriptors
            //
 
            int[] positions = new int[objectProperties.Length];
 
            // Iterate through the first object's properties to see if it has matches in the other objects.
            for (int i = 0; i < objectProperties[0].Count; i++)
            {
                PropertyDescriptor pivotProperty = objectProperties[0][i];
 
                bool match = pivotProperty.GetAttribute<MergablePropertyAttribute>()?.IsDefaultAttribute() ?? false;
 
                for (int j = 1; match && j < objectProperties.Length; j++)
                {
                    if (positions[j] >= objectProperties[j].Count)
                    {
                        match = false;
                        break;
                    }
 
                    // Check to see if we're on a match.
                    PropertyDescriptor property = objectProperties[j][positions[j]];
                    if (pivotProperty.Equals(property))
                    {
                        positions[j] += 1;
 
                        if (property.GetAttribute<MergablePropertyAttribute>()?.IsDefaultAttribute() ?? false)
                        {
                            matchArray[j] = property;
                            continue;
                        }
 
                        match = false;
                        break;
                    }
 
                    int position = positions[j];
                    property = objectProperties[j][position];
 
                    match = false;
 
                    // If we aren't on a match, check all the items until we're past where the matching item would be.
                    while (s_propertyComparer.Compare(property, pivotProperty) <= 0)
                    {
                        // Got a match!
                        if (pivotProperty.Equals(property))
                        {
                            match = property.GetAttribute<MergablePropertyAttribute>()?.IsDefaultAttribute() ?? false;
                            if (!match)
                            {
                                position++;
                            }
                            else
                            {
                                matchArray[j] = property;
                                positions[j] = position + 1;
                            }
 
                            break;
                        }
 
                        // Try again.
                        position++;
                        if (position < objectProperties[j].Count)
                        {
                            property = objectProperties[j][position];
                        }
                        else
                        {
                            break;
                        }
                    }
 
                    // If we got here, there is no match, quit for this one.
                    if (!match)
                    {
                        positions[j] = position;
                        break;
                    }
                }
 
                // Do we have a match?
                if (match)
                {
                    matchArray[0] = pivotProperty;
                    mergedList.Add((PropertyDescriptor[])matchArray.Clone());
                }
            }
 
            return mergedList;
        }
 
        private static MultiPropertyDescriptorGridEntry[] SortParenEntries(MultiPropertyDescriptorGridEntry[] entries)
        {
            MultiPropertyDescriptorGridEntry[]? newEntries = null;
            int newPosition = 0;
 
            // First scan the list and move any parenthesized properties to the front.
            for (int i = 0; i < entries.Length; i++)
            {
                if (entries[i].ParensAroundName)
                {
                    newEntries ??= new MultiPropertyDescriptorGridEntry[entries.Length];
 
                    newEntries[newPosition++] = entries[i];
                    entries[i] = null!; // we're using this for the second pass below
                }
            }
 
            // Second pass, copy any that didn't have the parens.
            if (newEntries is not null)
            {
                for (int i = 0; i < entries.Length; i++)
                {
                    if (entries[i] is not null)
                    {
                        newEntries[newPosition++] = entries[i];
                    }
                }
 
                return newEntries;
            }
 
            return entries;
        }
 
        /// <summary>
        ///  Merges an unsorted array of grid entries with a sorted array of grid entries that
        ///  have already been merged. The resulting array is the intersection of entries between the two,
        ///  but in the order of <paramref name="baseEntries"/>.
        /// </summary>
        private static List<PropertyDescriptor[]> UnsortedMerge(
            PropertyDescriptor[] baseEntries,
            List<PropertyDescriptor[]> sortedMergedEntries)
        {
            List<PropertyDescriptor[]> mergedEntries = [];
 
            foreach (PropertyDescriptor basePropertyDescriptor in baseEntries)
            {
                // First do a binary search for a matching item.
                string entryName = $"{basePropertyDescriptor.Name} {basePropertyDescriptor.PropertyType.FullName}";
 
                int length = sortedMergedEntries.Count;
 
                // Perform a binary search.
                int offset = length / 2;
                int start = 0;
 
                while (length > 0)
                {
                    var propertyDescriptors = sortedMergedEntries[start + offset];
                    PropertyDescriptor propertyDescriptor = propertyDescriptors[0];
                    string sortString = $"{propertyDescriptor.Name} {propertyDescriptor.PropertyType.FullName}";
                    int result = string.Compare(entryName, sortString, ignoreCase: false, CultureInfo.InvariantCulture);
                    if (result == 0)
                    {
                        mergedEntries.Add([basePropertyDescriptor, .. propertyDescriptors]);
                        break;
                    }
                    else if (result < 0)
                    {
                        length = offset;
                    }
                    else
                    {
                        int delta = offset + 1;
                        start += delta;
                        length -= delta;
                    }
 
                    offset = length / 2;
                }
            }
 
            return mergedEntries;
        }
    }
}