596 lines
22 KiB
C#
596 lines
22 KiB
C#
#if FRAMEWORK20
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.ComponentModel;
|
|
|
|
namespace DevComponents.Schedule.Model
|
|
{
|
|
/// <summary>
|
|
/// Represents the calendar model control.
|
|
/// </summary>
|
|
public class CalendarModel : INotifyPropertyChanged, INotifySubPropertyChanged
|
|
{
|
|
#region Events
|
|
/// <summary>
|
|
/// Occurs when an appointment has been added to the model.
|
|
/// </summary>
|
|
public event AppointmentEventHandler AppointmentAdded;
|
|
/// <summary>
|
|
/// Occurs when an appointment has been removed from the model.
|
|
/// </summary>
|
|
public event AppointmentEventHandler AppointmentRemoved;
|
|
/// <summary>
|
|
/// Occurs when AppointmentStartTime has been reached. This event can be used to trigger appointment reminders. Note that event handler will be called on the thread of System.Timer which is different
|
|
/// than UI thread. You should use BeginInvoke calls to marshal the calls to your UI thread.
|
|
/// </summary>
|
|
public event AppointmentEventHandler AppointmentStartTimeReached;
|
|
/// <summary>
|
|
/// Occurs when Reminder's ReminderTime has been reached. Note that event handler will be called on the thread of System.Timer which is different
|
|
/// than UI thread. You should use BeginInvoke calls to marshal the calls to your UI thread.
|
|
/// </summary>
|
|
[Description("Occurs when Reminder's ReminderTime has been reached.")]
|
|
public event ReminderEventHandler ReminderNotification;
|
|
/// <summary>
|
|
/// Occurs when Appointments collection has been cleared.
|
|
/// </summary>
|
|
public event EventHandler AppointmentsCleared;
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the CalendarModel class.
|
|
/// </summary>
|
|
public CalendarModel()
|
|
{
|
|
_Appointments = new AppointmentCollection(this);
|
|
_Owners = new OwnerCollection(this);
|
|
_WorkDays = new WorkDayCollection(this);
|
|
_CalendarWorkDays = new CalendarWorkDayCollection(this);
|
|
// Initialize default work-days
|
|
_WorkDays.Add(new WorkDay(DayOfWeek.Monday));
|
|
_WorkDays.Add(new WorkDay(DayOfWeek.Tuesday));
|
|
_WorkDays.Add(new WorkDay(DayOfWeek.Wednesday));
|
|
_WorkDays.Add(new WorkDay(DayOfWeek.Thursday));
|
|
_WorkDays.Add(new WorkDay(DayOfWeek.Friday));
|
|
}
|
|
#endregion
|
|
|
|
#region Internal Implementation
|
|
private AppointmentCollection _Appointments;
|
|
/// <summary>
|
|
/// Gets appointments associated with this calendar.
|
|
/// </summary>
|
|
public AppointmentCollection Appointments
|
|
{
|
|
get { return _Appointments; }
|
|
}
|
|
|
|
private OwnerCollection _Owners;
|
|
/// <summary>
|
|
/// Gets owners of appointments associated with this calendar.
|
|
/// </summary>
|
|
public OwnerCollection Owners
|
|
{
|
|
get { return _Owners; }
|
|
}
|
|
|
|
private WorkDayCollection _WorkDays;
|
|
/// <summary>
|
|
/// Gets working days associated with this calendar.
|
|
/// </summary>
|
|
public WorkDayCollection WorkDays
|
|
{
|
|
get { return _WorkDays; }
|
|
}
|
|
|
|
private CalendarWorkDayCollection _CalendarWorkDays = null;
|
|
/// <summary>
|
|
/// Gets the calendar/date based working days collection. This collection allows you to specify working time for specific dates. Values specified here take precedence over working hours set through WorkDays collection.
|
|
/// </summary>
|
|
public CalendarWorkDayCollection CalendarWorkDays
|
|
{
|
|
get { return _CalendarWorkDays; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets reference to the Day object which represents day in calendar.
|
|
/// </summary>
|
|
/// <param name="date">Date to retrieve day for.</param>
|
|
/// <returns>Returns reference to Day object.</returns>
|
|
public Day GetDay(DateTime date)
|
|
{
|
|
Year year = null;
|
|
if(!_Years.TryGetValue(date.Year, out year))
|
|
year = CreateYear(date.Year);
|
|
|
|
return year.Months[date.Month - 1].Days[date.Day - 1];
|
|
//return new Day(date, this);
|
|
}
|
|
|
|
private Year CreateYear(int y)
|
|
{
|
|
Year year = new Year(y, this);
|
|
_Years.Add(y, year);
|
|
return year;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if appointment overlapps with one or more of the appointments in the model.
|
|
/// </summary>
|
|
/// <param name="app">Appointment to check overlap for.</param>
|
|
/// <returns>true if there are appointments overlapping appointment otherwise false.</returns>
|
|
public bool ContainsOverlappingAppointments(Appointment app)
|
|
{
|
|
int duration = (int)Math.Max(1, app.EndTime.Subtract(app.StartTime).TotalDays);
|
|
for (int i = 0; i < duration; i++)
|
|
{
|
|
DateTime date = app.StartTime.Date.AddDays(i);
|
|
Day day = GetDay(date);
|
|
foreach (Appointment item in day.Appointments)
|
|
{
|
|
if (item != app && DateTimeHelper.TimePeriodsOverlap(item.StartTime, item.EndTime, app.StartTime, app.EndTime))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds appointments that overlap with the parameter appointment.
|
|
/// </summary>
|
|
/// <param name="app">Appointment to use to find overlapps</param>
|
|
/// <returns>Array of appointments that overlap parameter.</returns>
|
|
public Appointment[] FindOverlappingAppointments(Appointment app)
|
|
{
|
|
List<Appointment> overlaps = new List<Appointment>();
|
|
int duration = (int)Math.Max(1, app.EndTime.Subtract(app.StartTime).TotalDays);
|
|
for (int i = 0; i < duration; i++)
|
|
{
|
|
DateTime date = app.StartTime.Date.AddDays(i);
|
|
Day day = GetDay(date);
|
|
foreach (Appointment item in day.Appointments)
|
|
{
|
|
if (item != app && DateTimeHelper.TimePeriodsOverlap(item.StartTime, item.EndTime, app.StartTime, app.EndTime))
|
|
overlaps.Add(item);
|
|
}
|
|
}
|
|
|
|
return overlaps.ToArray();
|
|
}
|
|
|
|
//public Month GetMonth(int year, int month)
|
|
//{
|
|
// return null;
|
|
//}
|
|
|
|
//private HolidaysCollection _Holidays;
|
|
///// <summary>
|
|
///// Gets the collection of holidays associated with calendar.
|
|
///// </summary>
|
|
//public HolidaysCollection Holidays
|
|
//{
|
|
// get { return _Holidays; }
|
|
//}
|
|
|
|
/// <summary>
|
|
/// Returns the calendar date time which has seconds part set to 0.
|
|
/// </summary>
|
|
/// <param name="dt"></param>
|
|
/// <returns></returns>
|
|
public static DateTime GetCalendarDateTime(DateTime dt)
|
|
{
|
|
if (_PreciseDateTimeValues)
|
|
return dt;
|
|
return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0);
|
|
}
|
|
|
|
private static bool _PreciseDateTimeValues = false;
|
|
/// <summary>
|
|
/// Gets or sets whether Appointment StartTime and EndTime values retain seconds and milliseconds. When
|
|
/// set to false seconds and milliseconds are discarded. When set to true the DateTime set to appointment
|
|
/// StartTime and EndTime is used as is including seconds and milliseconds. Default value is false.
|
|
/// </summary>
|
|
public static bool PreciseDateTimeValues
|
|
{
|
|
get
|
|
{
|
|
return _PreciseDateTimeValues;
|
|
}
|
|
set
|
|
{
|
|
_PreciseDateTimeValues = value;
|
|
}
|
|
}
|
|
|
|
public static DateTime CurrentDateTime
|
|
{
|
|
get
|
|
{
|
|
DateTime dt = DateTime.Now;
|
|
return dt;
|
|
}
|
|
}
|
|
|
|
internal System.Globalization.Calendar GetCalendar()
|
|
{
|
|
return CultureInfo.CurrentCulture.Calendar;
|
|
}
|
|
|
|
private Dictionary<int, Year> _Years = new Dictionary<int, Year>();
|
|
|
|
internal void InternalAppointmentRemoved(Appointment item, bool isClearing)
|
|
{
|
|
item.SubPropertyChanged -= this.ChildPropertyChangedEventHandler;
|
|
if (!item.IsRecurringInstance && !isClearing)
|
|
InvalidateAppointmentCache(item);
|
|
if (!isClearing)
|
|
OnAppointmentRemoved(new AppointmentEventArgs(item));
|
|
}
|
|
|
|
internal void InternalAppointmentsCleared()
|
|
{
|
|
InvalidateAppointmentCache();
|
|
OnAppointmentsCleared(EventArgs.Empty);
|
|
}
|
|
|
|
protected virtual void OnAppointmentsCleared(EventArgs e)
|
|
{
|
|
EventHandler handler = AppointmentsCleared;
|
|
if (handler != null) handler(this, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the AppointmentRemoved event.
|
|
/// </summary>
|
|
/// <param name="appointmentEventArgs">Event arguments</param>
|
|
protected virtual void OnAppointmentRemoved(AppointmentEventArgs appointmentEventArgs)
|
|
{
|
|
AppointmentEventHandler handler = AppointmentRemoved;
|
|
if (handler != null) handler(this, appointmentEventArgs);
|
|
}
|
|
|
|
internal void InternalAppointmentAdded(Appointment item)
|
|
{
|
|
item.SubPropertyChanged += this.ChildPropertyChangedEventHandler;
|
|
if (!item.IsRecurringInstance)
|
|
InvalidateAppointmentCache(item);
|
|
OnAppointmentAdded(new AppointmentEventArgs(item));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the AppointmentAdded event.
|
|
/// </summary>
|
|
/// <param name="appointmentEventArgs">Event arguments</param>
|
|
protected virtual void OnAppointmentAdded(AppointmentEventArgs appointmentEventArgs)
|
|
{
|
|
AppointmentEventHandler handler = AppointmentAdded;
|
|
if (handler != null) handler(this, appointmentEventArgs);
|
|
}
|
|
|
|
internal void OwnerRemoved(Owner item)
|
|
{
|
|
item.SubPropertyChanged -= this.ChildPropertyChangedEventHandler;
|
|
OnPropertyChanged(new PropertyChangedEventArgs("Owners"));
|
|
}
|
|
|
|
internal void OwnerAdded(Owner item)
|
|
{
|
|
item.SubPropertyChanged += this.ChildPropertyChangedEventHandler;
|
|
OnPropertyChanged(new PropertyChangedEventArgs("Owners"));
|
|
}
|
|
|
|
internal void WorkDayRemoved(WorkDay item)
|
|
{
|
|
item.SubPropertyChanged -= this.ChildPropertyChangedEventHandler;
|
|
OnPropertyChanged(new PropertyChangedEventArgs("WorkDays"));
|
|
}
|
|
|
|
internal void WorkDayAdded(WorkDay item)
|
|
{
|
|
item.SubPropertyChanged += this.ChildPropertyChangedEventHandler;
|
|
OnPropertyChanged(new PropertyChangedEventArgs("WorkDays"));
|
|
}
|
|
|
|
internal void CalendarWorkDateRemoved(CalendarWorkDay item)
|
|
{
|
|
item.SubPropertyChanged -= this.ChildPropertyChangedEventHandler;
|
|
OnPropertyChanged(new PropertyChangedEventArgs("CalendarWorkDays"));
|
|
}
|
|
|
|
internal void CalendarWorkDateAdded(CalendarWorkDay item)
|
|
{
|
|
item.SubPropertyChanged += this.ChildPropertyChangedEventHandler;
|
|
OnPropertyChanged(new PropertyChangedEventArgs("CalendarWorkDays"));
|
|
}
|
|
|
|
private SubPropertyChangedEventHandler _ChildPropertyChangedEventHandler = null;
|
|
private SubPropertyChangedEventHandler ChildPropertyChangedEventHandler
|
|
{
|
|
get
|
|
{
|
|
if (_ChildPropertyChangedEventHandler == null) _ChildPropertyChangedEventHandler = new SubPropertyChangedEventHandler(ChildPropertyChanged);
|
|
return _ChildPropertyChangedEventHandler;
|
|
}
|
|
}
|
|
|
|
private void ChildPropertyChanged(object sender, SubPropertyChangedEventArgs e)
|
|
{
|
|
Appointment app = sender as Appointment;
|
|
OnSubPropertyChanged(e);
|
|
|
|
if (app != null)
|
|
{
|
|
if (IsNonTimeProperty(e.PropertyChangedArgs.PropertyName)) return;
|
|
if (app.InMoveTo && e.PropertyChangedArgs.PropertyName != "EndTime") return;
|
|
if (IsRecurranceProperty(e.PropertyChangedArgs.PropertyName) ||
|
|
e.Source is DailyRecurrenceSettings ||
|
|
e.Source is WeeklyRecurrenceSettings ||
|
|
e.Source is YearlyRecurrenceSettings ||
|
|
e.Source is MonthlyRecurrenceSettings)
|
|
InvalidateAppointmentCache();
|
|
else
|
|
InvalidateAppointmentCache(app);
|
|
}
|
|
}
|
|
|
|
private bool IsRecurranceProperty(string propertyName)
|
|
{
|
|
return propertyName == Appointment.RecurrencePropertyName;
|
|
}
|
|
|
|
private void InvalidateAppointmentCache(Appointment app)
|
|
{
|
|
if (IsUpdateSuspended) return;
|
|
if (app.Recurrence != null)
|
|
{
|
|
// Invalidate all
|
|
InvalidateAppointmentCache();
|
|
}
|
|
else if (_Years.ContainsKey(app.LocalStartTime.Year))
|
|
{
|
|
DateTime d = DateTimeHelper.BeginningOfDay(app.LocalStartTime);
|
|
DateTime end = app.LocalEndTime;
|
|
int year = d.Year;
|
|
while (d < end)
|
|
{
|
|
_Years[d.Year].InvalidateAppointments(d.Month, d.Day);
|
|
d = d.AddDays(1);
|
|
if (d.Year != year && !_Years.ContainsKey(d.Year))
|
|
break;
|
|
}
|
|
}
|
|
AccessToday();
|
|
OnPropertyChanged(new PropertyChangedEventArgs(AppointmentsPropertyName));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invalidates appointments cache store and causes recurrences to be regenerated when requested.
|
|
/// </summary>
|
|
public void InvalidateAppointmentCache()
|
|
{
|
|
if (IsUpdateSuspended) return;
|
|
|
|
// Invalidate all
|
|
foreach (Year year in _Years.Values)
|
|
{
|
|
year.InvalidateAppointments();
|
|
}
|
|
AccessToday();
|
|
OnPropertyChanged(new PropertyChangedEventArgs(AppointmentsPropertyName));
|
|
}
|
|
|
|
private void AccessToday()
|
|
{
|
|
DateTime today = DateTime.Today;
|
|
Day day = this.GetDay(today);
|
|
int appCount = day.Appointments.Count;
|
|
}
|
|
private bool IsNonTimeProperty(string propertyName)
|
|
{
|
|
if (propertyName == "Description" || propertyName == "IsRecurringInstance" || propertyName == "Tag"
|
|
|| propertyName == "OwnerKey" || propertyName == "IsSelected" || propertyName == "Locked" || propertyName=="LocalStartTime" || propertyName=="LocalEndTime")
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
private int _UpdatesCount = 0;
|
|
/// <summary>
|
|
/// Suspends internal control updates to the cache structures etc. When making changes on multiple appointments
|
|
/// time related properties or when adding multiple appointments before doing so call BeginUpdate and after
|
|
/// updates are done call EndUpdate method to optimize performance.
|
|
/// <remarks>Calls to BeginUpdate method can be nested and only last outer most EndUpdate call will resume internal control updates.</remarks>
|
|
/// </summary>
|
|
public void BeginUpdate()
|
|
{
|
|
_UpdatesCount++;
|
|
}
|
|
/// <summary>
|
|
/// Resumes internal control updates that were suspended using BeginUpdate call and invalidates internal cache.
|
|
/// </summary>
|
|
public void EndUpdate()
|
|
{
|
|
if (_UpdatesCount == 0)
|
|
throw new InvalidOperationException("EndUpdate must be called AFTER BeginUpdate");
|
|
_UpdatesCount--;
|
|
if (_UpdatesCount == 0)
|
|
InvalidateAppointmentCache();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether internal control update is suspended due to the call to BeginUpdate method.
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
public bool IsUpdateSuspended
|
|
{
|
|
get
|
|
{
|
|
return _UpdatesCount > 0;
|
|
}
|
|
}
|
|
|
|
internal static string AppointmentsPropertyName
|
|
{
|
|
get { return "Appointments"; }
|
|
}
|
|
internal static string WorkDaysPropertyName
|
|
{
|
|
get { return "WorkDays"; }
|
|
}
|
|
internal static string CalendarWorkDaysPropertyName
|
|
{
|
|
get { return "CalendarWorkDays"; }
|
|
}
|
|
|
|
private TimeZoneInfo _DisplayTimeZone = null;
|
|
/// <summary>
|
|
/// Gets or sets the default display time zone used for the appointments. Default value is null which indicates that system time-zone is used.
|
|
/// Display Time zone can also be set for each Owner on Owner object. Value set here is used if specific display time-zone is not set on user.
|
|
/// </summary>
|
|
[DefaultValue(null)]
|
|
public TimeZoneInfo DisplayTimeZone
|
|
{
|
|
get { return _DisplayTimeZone; }
|
|
set
|
|
{
|
|
if (value != _DisplayTimeZone)
|
|
{
|
|
TimeZoneInfo oldValue = _DisplayTimeZone;
|
|
_DisplayTimeZone = value;
|
|
InvalidateAppointmentTimes();
|
|
InvalidateAppointmentCache();
|
|
OnDisplayTimeZoneChanged(oldValue, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InvalidateAppointmentTimes()
|
|
{
|
|
foreach (Appointment item in _Appointments)
|
|
{
|
|
item.InvokeLocalTimePropertyChange();
|
|
}
|
|
}
|
|
|
|
private void OnDisplayTimeZoneChanged(TimeZoneInfo oldValue, TimeZoneInfo newValue)
|
|
{
|
|
OnPropertyChanged(new PropertyChangedEventArgs("DisplayTimeZone"));
|
|
|
|
}
|
|
|
|
|
|
internal void InvokeAppointmentStartTimeReached(Appointment appointment)
|
|
{
|
|
OnAppointmentStartTimeReached(new AppointmentEventArgs(appointment));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises AppointmentStartTimeReached event.
|
|
/// </summary>
|
|
/// <param name="appointmentEventArgs">Event arguments</param>
|
|
protected virtual void OnAppointmentStartTimeReached(AppointmentEventArgs appointmentEventArgs)
|
|
{
|
|
AppointmentEventHandler handler = AppointmentStartTimeReached;
|
|
if (handler != null)
|
|
handler(this, appointmentEventArgs);
|
|
}
|
|
|
|
internal void InvokeReminderNotification(Reminder reminder)
|
|
{
|
|
OnReminderNotification(new ReminderEventArgs(reminder));
|
|
}
|
|
/// <summary>
|
|
/// Raises ReminderNotification event.
|
|
/// </summary>
|
|
/// <param name="e">Event arguments</param>
|
|
protected virtual void OnReminderNotification(ReminderEventArgs e)
|
|
{
|
|
ReminderEventHandler h = ReminderNotification;
|
|
if (h != null)
|
|
h(this, e);
|
|
}
|
|
#endregion
|
|
|
|
#region INotifyPropertyChanged Members
|
|
/// <summary>
|
|
/// Occurs when property value has changed.
|
|
/// </summary>
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
/// <summary>
|
|
/// Raises the PropertyChanged event.
|
|
/// </summary>
|
|
/// <param name="e">Event arguments</param>
|
|
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
|
|
{
|
|
PropertyChangedEventHandler eh = PropertyChanged;
|
|
|
|
if (eh != null)
|
|
eh(this, e);
|
|
|
|
OnSubPropertyChanged(new SubPropertyChangedEventArgs(this, e));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region INotifySubPropertyChanged Members
|
|
|
|
/// <summary>
|
|
/// Occurs when property or property of child objects has changed. This event is similar to PropertyChanged event with key
|
|
/// difference that it occurs for the property changed of child objects as well.
|
|
/// </summary>
|
|
public event SubPropertyChangedEventHandler SubPropertyChanged;
|
|
/// <summary>
|
|
/// Raises the SubPropertyChanged event.
|
|
/// </summary>
|
|
/// <param name="e">Event arguments</param>
|
|
protected virtual void OnSubPropertyChanged(SubPropertyChangedEventArgs e)
|
|
{
|
|
SubPropertyChangedEventHandler eh = SubPropertyChanged;
|
|
if (eh != null) eh(this, e);
|
|
}
|
|
#endregion
|
|
|
|
#region CustomReminders
|
|
private ReminderCollection _CustomReminders = null;
|
|
/// <summary>
|
|
/// Gets the collection of custom reminders that are not associated with appointments.
|
|
/// </summary>
|
|
public ReminderCollection CustomReminders
|
|
{
|
|
get
|
|
{
|
|
if (_CustomReminders == null) _CustomReminders = new ReminderCollection(this);
|
|
return _CustomReminders;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
#region Events Support
|
|
public delegate void AppointmentEventHandler(object sender, AppointmentEventArgs e);
|
|
/// <summary>
|
|
/// Defines arguments for appointment related events.
|
|
/// </summary>
|
|
public class AppointmentEventArgs : EventArgs
|
|
{
|
|
/// <summary>
|
|
/// Gets the appointment referenced by this event.
|
|
/// </summary>
|
|
public Appointment Appointment;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the AppointmentEventArgs class.
|
|
/// </summary>
|
|
/// <param name="appointment"></param>
|
|
public AppointmentEventArgs(Appointment appointment)
|
|
{
|
|
Appointment = appointment;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
}
|
|
#endif
|
|
|