// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; namespace System.Windows.Forms { internal class ItemArray : IComparer { private static int lastMask = 1; private readonly ListControl listControl; private Entry[] entries; private int count; private int version; public ItemArray(ListControl listControl) { this.listControl = listControl; } internal IReadOnlyList Entries => entries; /// /// The version of this array. This number changes with each /// change to the item list. /// public int Version { get { return version; } } /// /// Adds the given item to the array. The state is initially /// zero. /// public object Add(object item) { EnsureSpace(1); version++; entries[count] = new Entry(item); return entries[count++]; } /// /// Adds the given collection of items to the array. /// public void AddRange(ICollection items) { if (items == null) { throw new ArgumentNullException(nameof(items)); } EnsureSpace(items.Count); foreach (object i in items) { entries[count++] = new Entry(i); } version++; } /// /// Clears this array. /// public void Clear() { if (count > 0) { Array.Clear(entries, 0, count); } count = 0; version++; } /// /// Allocates a new bitmask for use. /// public static int CreateMask() { int mask = lastMask; lastMask <<= 1; return mask; } /// /// Ensures that our internal array has space for /// the requested # of elements. /// private void EnsureSpace(int elements) { if (entries == null) { entries = new Entry[Math.Max(elements, 4)]; } else if (count + elements >= entries.Length) { int newLength = Math.Max(entries.Length * 2, entries.Length + elements); Entry[] newEntries = new Entry[newLength]; entries.CopyTo(newEntries, 0); entries = newEntries; } } /// /// Turns a virtual index into an actual index. /// public int GetActualIndex(int virtualIndex, int stateMask) { if (stateMask == 0) { return virtualIndex; } // More complex; we must compute this index. int calcIndex = -1; for (int i = 0; i < count; i++) { if ((entries[i].state & stateMask) != 0) { calcIndex++; if (calcIndex == virtualIndex) { return i; } } } return -1; } /// /// Gets the count of items matching the given mask. /// public int GetCount(int stateMask) { // If mask is zero, then just give the main count if (stateMask == 0) { return count; } // more complex: must provide a count of items // based on a mask. int filteredCount = 0; for (int i = 0; i < count; i++) { if ((entries[i].state & stateMask) != 0) { filteredCount++; } } return filteredCount; } /// /// Retrieves an enumerator that will enumerate based on /// the given mask. /// public IEnumerator GetEnumerator(int stateMask) { return GetEnumerator(stateMask, false); } /// /// Retrieves an enumerator that will enumerate based on /// the given mask. /// public IEnumerator GetEnumerator(int stateMask, bool anyBit) { return new EntryEnumerator(this, stateMask, anyBit); } /// /// Gets the item at the given index. The index is /// virtualized against the given mask value. /// public object GetItem(int virtualIndex, int stateMask) { int actualIndex = GetActualIndex(virtualIndex, stateMask); if (actualIndex == -1) { throw new IndexOutOfRangeException(); } return entries[actualIndex].item; } /// /// Gets the item at the given index. The index is /// virtualized against the given mask value. /// internal object GetEntryObject(int virtualIndex, int stateMask) { int actualIndex = GetActualIndex(virtualIndex, stateMask); if (actualIndex == -1) { throw new IndexOutOfRangeException(); } return entries[actualIndex]; } /// /// Returns true if the requested state mask is set. /// The index is the actual index to the array. /// public bool GetState(int index, int stateMask) { return ((entries[index].state & stateMask) == stateMask); } /// /// Returns the virtual index of the item based on the /// state mask. /// public int IndexOf(object item, int stateMask) { int virtualIndex = -1; for (int i = 0; i < count; i++) { if (stateMask == 0 || (entries[i].state & stateMask) != 0) { virtualIndex++; if (entries[i].item.Equals(item)) { return virtualIndex; } } } return -1; } /// /// Returns the virtual index of the item based on the /// state mask. Uses reference equality to identify the /// given object in the list. /// public int IndexOfIdentifier(object identifier, int stateMask) { int virtualIndex = -1; for (int i = 0; i < count; i++) { if (stateMask == 0 || (entries[i].state & stateMask) != 0) { virtualIndex++; if (entries[i] == identifier) { return virtualIndex; } } } return -1; } /// /// Inserts item at the given index. The index /// is not virtualized. /// public void Insert(int index, object item) { EnsureSpace(1); if (index < count) { System.Array.Copy(entries, index, entries, index + 1, count - index); } entries[index] = new Entry(item); count++; version++; } /// /// Removes the given item from the array. If /// the item is not in the array, this does nothing. /// public void Remove(object item) { int index = IndexOf(item, 0); if (index != -1) { RemoveAt(index); } } /// /// Removes the item at the given index. /// public void RemoveAt(int index) { count--; for (int i = index; i < count; i++) { entries[i] = entries[i + 1]; } entries[count] = null; version++; } /// /// Sets the item at the given index to a new value. /// public void SetItem(int index, object item) { entries[index].item = item; } /// /// Sets the state data for the given index. /// public void SetState(int index, int stateMask, bool value) { if (value) { entries[index].state |= stateMask; } else { entries[index].state &= ~stateMask; } version++; } /// /// Find element in sorted array. If element is not found returns a binary complement of index for inserting /// public int BinarySearch(object element) { return Array.BinarySearch(entries, 0, count, element, this); } /// /// Sorts our array. /// public void Sort() { Array.Sort(entries, 0, count, this); } public void Sort(Array externalArray) { Array.Sort(externalArray, this); } int IComparer.Compare(object item1, object item2) { if (item1 == null) { if (item2 == null) { return 0; //both null, then they are equal } return -1; //item1 is null, but item2 is valid (greater) } if (item2 == null) { return 1; //item2 is null, so item 1 is greater } if (item1 is Entry) { item1 = ((Entry)item1).item; } if (item2 is Entry) { item2 = ((Entry)item2).item; } string itemName1 = listControl.GetItemText(item1); string itemName2 = listControl.GetItemText(item2); CompareInfo compInfo = CultureInfo.CurrentCulture.CompareInfo; return compInfo.Compare(itemName1, itemName2, CompareOptions.StringSort); } /// /// This is a single entry in our item array. /// internal class Entry { public object item; public int state; public Entry(object item) { this.item = item; state = 0; } } /// /// EntryEnumerator is an enumerator that will enumerate over /// a given state mask. /// private class EntryEnumerator : IEnumerator { private readonly ItemArray items; private readonly bool anyBit; private readonly int state; private int current; private readonly int version; /// /// Creates a new enumerator that will enumerate over the given state. /// public EntryEnumerator(ItemArray items, int state, bool anyBit) { this.items = items; this.state = state; this.anyBit = anyBit; version = items.version; current = -1; } /// /// Moves to the next element, or returns false if at the end. /// bool IEnumerator.MoveNext() { if (version != items.version) { throw new InvalidOperationException(); } while (true) { if (current < items.count - 1) { current++; if (anyBit) { if ((items.entries[current].state & state) != 0) { return true; } } else { if ((items.entries[current].state & state) == state) { return true; } } } else { current = items.count; return false; } } } /// /// Resets the enumeration back to the beginning. /// void IEnumerator.Reset() { if (version != items.version) { throw new InvalidOperationException(); } current = -1; } /// /// Retrieves the current value in the enumerator. /// object IEnumerator.Current { get { if (current == -1 || current == items.count) { throw new InvalidOperationException(); } return items.entries[current].item; } } } } }