// (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). // Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using Avalonia.Threading; using System; using System.Collections.ObjectModel; using System.Threading; namespace Avalonia.Controls.Primitives { public sealed class SelectedDatesCollection : ObservableCollection { /// /// Inherited code: Requires comment. /// private Collection _addedItems; /// /// Inherited code: Requires comment. /// private bool _isCleared; /// /// Inherited code: Requires comment. /// private bool _isRangeAdded; /// /// Inherited code: Requires comment. /// private Calendar _owner; /// /// Initializes a new instance of the /// /// class. /// /// /// The associated /// with this object. /// public SelectedDatesCollection(Calendar owner) { _owner = owner; _addedItems = new Collection(); } private void InvokeCollectionChanged(System.Collections.IList removedItems, System.Collections.IList addedItems) { _owner.OnSelectedDatesCollectionChanged(new SelectionChangedEventArgs(SelectingItemsControl.SelectionChangedEvent, removedItems, addedItems)); } /// /// Adds all the dates in the specified range, which includes the first /// and last dates, to the collection. /// /// The first date to add to the collection. /// The last date to add to the collection. public void AddRange(DateTime start, DateTime end) { DateTime? rangeStart; // increment parameter specifies if the Days were selected in // Descending order or Ascending order based on this value, we add // the days in the range either in Ascending order or in Descending // order int increment = (DateTime.Compare(end, start) >= 0) ? 1 : -1; _addedItems.Clear(); rangeStart = start; _isRangeAdded = true; if (_owner.IsMouseSelection) { // In Mouse Selection we allow the user to be able to add // multiple ranges in one action in MultipleRange Mode. In // SingleRange Mode, we only add the first selected range. while (rangeStart.HasValue && DateTime.Compare(end, rangeStart.Value) != -increment) { if (Calendar.IsValidDateSelection(_owner, rangeStart)) { Add(rangeStart.Value); } else { if (_owner.SelectionMode == CalendarSelectionMode.SingleRange) { _owner.HoverEnd = rangeStart.Value.AddDays(-increment); break; } } rangeStart = DateTimeHelper.AddDays(rangeStart.Value, increment); } } else { // If CalendarSelectionMode.SingleRange and a user // programmatically tries to add multiple ranges, we will throw // away the old range and replace it with the new one. In order // to provide the removed items without an additional event, we // are calling ClearInternal if (_owner.SelectionMode == CalendarSelectionMode.SingleRange && Count > 0) { foreach (DateTime item in this) { _owner.RemovedItems.Add(item); } ClearInternal(); } while (rangeStart.HasValue && DateTime.Compare(end, rangeStart.Value) != -increment) { Add(rangeStart.Value); rangeStart = DateTimeHelper.AddDays(rangeStart.Value, increment); } } _owner.OnSelectedDatesCollectionChanged(new SelectionChangedEventArgs(SelectingItemsControl.SelectionChangedEvent, _owner.RemovedItems, _addedItems)); _owner.RemovedItems.Clear(); _owner.UpdateMonths(); _isRangeAdded = false; } /// /// Removes all items from the collection. /// /// /// This implementation raises the CollectionChanged event. /// protected override void ClearItems() { EnsureValidThread(); Collection addedItems = new Collection(); Collection removedItems = new Collection(); foreach (DateTime item in this) { removedItems.Add(item); } base.ClearItems(); // The event fires after SelectedDate changes if (_owner.SelectionMode != CalendarSelectionMode.None && _owner.SelectedDate != null) { _owner.SelectedDate = null; } if (removedItems.Count != 0) { InvokeCollectionChanged(removedItems, addedItems); } _owner.UpdateMonths(); } /// /// Inserts an item into the collection at the specified index. /// /// /// The zero-based index at which item should be inserted. /// /// The object to insert. /// /// This implementation raises the CollectionChanged event. /// protected override void InsertItem(int index, DateTime item) { EnsureValidThread(); if (!Contains(item)) { Collection addedItems = new Collection(); if (CheckSelectionMode()) { if (Calendar.IsValidDateSelection(_owner, item)) { // If the Collection is cleared since it is SingleRange // and it had another range set the index to 0 if (_isCleared) { index = 0; _isCleared = false; } base.InsertItem(index, item); // The event fires after SelectedDate changes if (index == 0 && !(_owner.SelectedDate.HasValue && DateTime.Compare(_owner.SelectedDate.Value, item) == 0)) { _owner.SelectedDate = item; } if (!_isRangeAdded) { addedItems.Add(item); InvokeCollectionChanged(_owner.RemovedItems, addedItems); _owner.RemovedItems.Clear(); int monthDifference = DateTimeHelper.CompareYearMonth(item, _owner.DisplayDateInternal); if (monthDifference < 2 && monthDifference > -2) { _owner.UpdateMonths(); } } else { _addedItems.Add(item); } } else { throw new ArgumentOutOfRangeException("SelectedDate value is not valid."); } } } } /// /// Removes the item at the specified index of the collection. /// /// /// The zero-based index of the element to remove. /// /// /// This implementation raises the CollectionChanged event. /// protected override void RemoveItem(int index) { EnsureValidThread(); if (index >= Count) { base.RemoveItem(index); } else { Collection addedItems = new Collection(); Collection removedItems = new Collection(); int monthDifference = DateTimeHelper.CompareYearMonth(this[index], _owner.DisplayDateInternal); removedItems.Add(this[index]); base.RemoveItem(index); // The event fires after SelectedDate changes if (index == 0) { if (Count > 0) { _owner.SelectedDate = this[0]; } else { _owner.SelectedDate = null; } } InvokeCollectionChanged(removedItems, addedItems); if (monthDifference < 2 && monthDifference > -2) { _owner.UpdateMonths(); } } } /// /// Replaces the element at the specified index. /// /// /// The zero-based index of the element to replace. /// /// /// The new value for the element at the specified index. /// /// /// This implementation raises the CollectionChanged event. /// protected override void SetItem(int index, DateTime item) { EnsureValidThread(); if (!Contains(item)) { Collection addedItems = new Collection(); Collection removedItems = new Collection(); if (index >= Count) { base.SetItem(index, item); } else { if (DateTime.Compare(this[index], item) != 0 && Calendar.IsValidDateSelection(_owner, item)) { removedItems.Add(this[index]); base.SetItem(index, item); addedItems.Add(item); // The event fires after SelectedDate changes if (index == 0 && !(_owner.SelectedDate.HasValue && DateTime.Compare(_owner.SelectedDate.Value, item) == 0)) { _owner.SelectedDate = item; } InvokeCollectionChanged(removedItems, addedItems); int monthDifference = DateTimeHelper.CompareYearMonth(item, _owner.DisplayDateInternal); if (monthDifference < 2 && monthDifference > -2) { _owner.UpdateMonths(); } } } } } internal void ClearInternal() { base.ClearItems(); } private bool CheckSelectionMode() { if (_owner.SelectionMode == CalendarSelectionMode.None) { throw new InvalidOperationException("The SelectedDate property cannot be set when the selection mode is None."); } if (_owner.SelectionMode == CalendarSelectionMode.SingleDate && Count > 0) { throw new InvalidOperationException("The SelectedDates collection can be changed only in a multiple selection mode. Use the SelectedDate in a single selection mode."); } // if user tries to add an item into the SelectedDates in // SingleRange mode, we throw away the old range and replace it with // the new one in order to provide the removed items without an // additional event, we are calling ClearInternal if (_owner.SelectionMode == CalendarSelectionMode.SingleRange && !_isRangeAdded && Count > 0) { foreach (DateTime item in this) { _owner.RemovedItems.Add(item); } ClearInternal(); _isCleared = true; } return true; } private void EnsureValidThread() { Dispatcher.UIThread.VerifyAccess(); } } }