233 lines
9.2 KiB
C#
233 lines
9.2 KiB
C#
#if FRAMEWORK20
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Collections.ObjectModel;
|
|
using DevComponents.Schedule.Model.Primitives;
|
|
|
|
namespace DevComponents.Schedule.Model
|
|
{
|
|
/// <summary>
|
|
/// Represents subset of appointments collection.
|
|
/// </summary>
|
|
public class AppointmentSubsetCollection : CustomCollection<Appointment>
|
|
{
|
|
#region Private Variables
|
|
private DateTime _StartDate = DateTime.MinValue;
|
|
private DateTime _EndDate = DateTime.MinValue;
|
|
#endregion
|
|
|
|
#region Constructor
|
|
/// <summary>
|
|
/// Initializes a new instance of the AppointmentSubsetCollection class with appointments between given start and end date.
|
|
/// </summary>
|
|
/// <param name="calendar"></param>
|
|
/// <param name="start"></param>
|
|
/// <param name="end"></param>
|
|
public AppointmentSubsetCollection(CalendarModel calendar, DateTime start, DateTime end)
|
|
{
|
|
_Calendar = calendar;
|
|
_StartDate = start;
|
|
_EndDate = end;
|
|
PopulateCollection();
|
|
}
|
|
#endregion
|
|
|
|
#region Internal Implementation
|
|
|
|
private CalendarModel _Calendar = null;
|
|
/// <summary>
|
|
/// Gets the calendar collection is associated with this collection.
|
|
/// </summary>
|
|
public CalendarModel Calendar
|
|
{
|
|
get { return _Calendar; }
|
|
internal set { _Calendar = value; }
|
|
}
|
|
|
|
protected override void InsertItem(int index, Appointment item)
|
|
{
|
|
OnBeforeInsert(index, item);
|
|
base.InsertItem(index, item);
|
|
}
|
|
|
|
private void OnBeforeInsert(int index, Appointment item)
|
|
{
|
|
item.SubPropertyChanged += this.ChildPropertyChangedEventHandler;
|
|
item.Calendar = _Calendar;
|
|
if (item.IsRecurringInstance && _Calendar != null)
|
|
_Calendar.InternalAppointmentAdded(item);
|
|
|
|
}
|
|
|
|
protected override void SetItem(int index, Appointment item)
|
|
{
|
|
OnBeforeSetItem(index, item);
|
|
base.SetItem(index, item);
|
|
}
|
|
|
|
private void OnBeforeSetItem(int index, Appointment item)
|
|
{
|
|
Appointment app = this[index];
|
|
app.SubPropertyChanged -= this.ChildPropertyChangedEventHandler;
|
|
app.Calendar = null;
|
|
item.SubPropertyChanged += this.ChildPropertyChangedEventHandler;
|
|
app.Calendar = _Calendar;
|
|
}
|
|
|
|
protected override void RemoveItem(int index)
|
|
{
|
|
OnBeforeRemove(index);
|
|
base.RemoveItem(index);
|
|
}
|
|
|
|
private void OnBeforeRemove(int index)
|
|
{
|
|
Appointment item = this[index];
|
|
OnAppointmentRemoved(item);
|
|
}
|
|
|
|
private void OnAppointmentRemoved(Appointment item)
|
|
{
|
|
item.SubPropertyChanged -= this.ChildPropertyChangedEventHandler;
|
|
if (item.IsRecurringInstance && _Calendar != null)
|
|
_Calendar.InternalAppointmentRemoved(item, false);
|
|
if (item.IsRecurringInstance)
|
|
item.Calendar = null;
|
|
}
|
|
protected override void ClearItems()
|
|
{
|
|
foreach (Appointment item in this.GetItemsDirect())
|
|
{
|
|
OnAppointmentRemoved(item);
|
|
}
|
|
base.ClearItems();
|
|
}
|
|
|
|
private void PopulateCollection()
|
|
{
|
|
this.Clear();
|
|
|
|
foreach (Appointment app in _Calendar.Appointments)
|
|
{
|
|
if (app.LocalStartTime >= _StartDate && app.LocalStartTime <= _EndDate || app.LocalEndTime > _StartDate && (app.LocalEndTime <= _EndDate || app.LocalStartTime < _StartDate))
|
|
{
|
|
this.Add(app);
|
|
}
|
|
if (app.Recurrence != null && IsRecurrenceInRange(app, _StartDate, _EndDate))
|
|
{
|
|
int count = this.GetItemsDirect().Count;
|
|
app.Recurrence.GenerateSubset(this, _StartDate, _EndDate);
|
|
if (count == this.GetItemsDirect().Count && TotalDaysDuration(app.StartTime, app.EndTime) >= 1 &&
|
|
app.LocalEndTime < _StartDate)
|
|
{
|
|
// Nothing generated lets wind back and look to see is there an recurrence that needs to be captured in this view
|
|
int daysLookBack = TotalDaysDuration(app.StartTime, app.EndTime);
|
|
DateTime start = _StartDate;
|
|
for (int i = 0; i < daysLookBack; i++)
|
|
{
|
|
start = start.AddDays(-1);
|
|
AppointmentSubsetCollection dayCollection = _Calendar.GetDay(start).Appointments;
|
|
foreach (Appointment subAppointment in dayCollection)
|
|
{
|
|
if (subAppointment.IsRecurringInstance && subAppointment.RootAppointment == app &&
|
|
(subAppointment.LocalStartTime >= _StartDate && subAppointment.LocalStartTime <= _EndDate ||
|
|
subAppointment.LocalEndTime > _StartDate && (subAppointment.LocalEndTime <= _EndDate ||
|
|
subAppointment.LocalStartTime < _StartDate)))
|
|
{
|
|
this.Add(subAppointment);
|
|
daysLookBack = -1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_IsCollectionUpToDate = true;
|
|
}
|
|
|
|
private int TotalDaysDuration(DateTime startTime, DateTime endTime)
|
|
{
|
|
if (endTime.Hour == 0 && endTime.Minute == 0 && endTime.Second == 0)
|
|
endTime = endTime.AddMinutes(-1);
|
|
return (int)Math.Max(0, Math.Ceiling(endTime.Date.Subtract(startTime.Date).TotalDays));
|
|
}
|
|
|
|
private bool IsRecurrenceInRange(Appointment app, DateTime startDate, DateTime endDate)
|
|
{
|
|
if (app.Recurrence.RecurrenceType == eRecurrencePatternType.Yearly)
|
|
{
|
|
// Simple range check on number of occurrences
|
|
if (app.Recurrence.RangeLimitType == eRecurrenceRangeLimitType.RangeNumberOfOccurrences && app.LocalEndTime.Subtract(endDate).TotalDays / 365 > app.Recurrence.RangeNumberOfOccurrences)
|
|
return false;
|
|
// Date check based on next expected recurrence date
|
|
if (app.Recurrence.Yearly.RepeatInterval > 1)
|
|
{
|
|
DateTime nextRecurrence = DateTimeHelper.MaxDate(app.Recurrence.RecurrenceStartDate, app.EndTime.AddDays(1).Date);
|
|
while (nextRecurrence < startDate)
|
|
nextRecurrence = RecurrenceGenerator.GetNextYearlyRecurrence(app.Recurrence, nextRecurrence);
|
|
if (nextRecurrence > endDate || nextRecurrence.Add(endDate.Subtract(startDate)) < startDate)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
|
|
DateTime nextRecurrence = RecurrenceGenerator.GetNextYearlyRecurrence(app.Recurrence, startDate.Date.AddDays(-startDate.DayOfYear));
|
|
if (nextRecurrence > endDate || nextRecurrence.Add(endDate.Subtract(startDate)) < startDate)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (app.Recurrence.RangeEndDate == DateTime.MinValue || app.Recurrence.RangeEndDate >= _EndDate || app.Recurrence.RangeEndDate < _EndDate && app.Recurrence.RangeEndDate > _StartDate)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
private SubPropertyChangedEventHandler _ChildPropertyChangedEventHandler = null;
|
|
private SubPropertyChangedEventHandler ChildPropertyChangedEventHandler
|
|
{
|
|
get
|
|
{
|
|
if (_ChildPropertyChangedEventHandler == null) _ChildPropertyChangedEventHandler = new SubPropertyChangedEventHandler(ChildPropertyChanged);
|
|
return _ChildPropertyChangedEventHandler;
|
|
}
|
|
}
|
|
|
|
private void ChildPropertyChanged(object sender, SubPropertyChangedEventArgs e)
|
|
{
|
|
InvalidateCollection();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invalidates collection content due to the change to appointments or some other condition. Invalidating collection
|
|
/// content causes the collection elements to be re-generated on next collection read access.
|
|
/// </summary>
|
|
public virtual void InvalidateCollection()
|
|
{
|
|
_IsCollectionUpToDate = false;
|
|
}
|
|
|
|
protected override void OnCollectionReadAccess()
|
|
{
|
|
if (!_IsCollectionUpToDate) PopulateCollection();
|
|
base.OnCollectionReadAccess();
|
|
}
|
|
|
|
private bool _IsCollectionUpToDate = false;
|
|
internal bool IsCollectionUpToDate
|
|
{
|
|
get { return _IsCollectionUpToDate; }
|
|
set
|
|
{
|
|
_IsCollectionUpToDate = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
#endif
|
|
|