// 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; } } } |