#if FRAMEWORK20 using System; using System.Collections.Generic; using System.Globalization; using System.ComponentModel; namespace DevComponents.Schedule.Model { /// /// Represents the calendar model control. /// public class CalendarModel : INotifyPropertyChanged, INotifySubPropertyChanged { #region Events /// /// Occurs when an appointment has been added to the model. /// public event AppointmentEventHandler AppointmentAdded; /// /// Occurs when an appointment has been removed from the model. /// public event AppointmentEventHandler AppointmentRemoved; /// /// 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. /// public event AppointmentEventHandler AppointmentStartTimeReached; /// /// 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. /// [Description("Occurs when Reminder's ReminderTime has been reached.")] public event ReminderEventHandler ReminderNotification; /// /// Occurs when Appointments collection has been cleared. /// public event EventHandler AppointmentsCleared; #endregion #region Constructor /// /// Initializes a new instance of the CalendarModel class. /// 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; /// /// Gets appointments associated with this calendar. /// public AppointmentCollection Appointments { get { return _Appointments; } } private OwnerCollection _Owners; /// /// Gets owners of appointments associated with this calendar. /// public OwnerCollection Owners { get { return _Owners; } } private WorkDayCollection _WorkDays; /// /// Gets working days associated with this calendar. /// public WorkDayCollection WorkDays { get { return _WorkDays; } } private CalendarWorkDayCollection _CalendarWorkDays = null; /// /// 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. /// public CalendarWorkDayCollection CalendarWorkDays { get { return _CalendarWorkDays; } } /// /// Gets reference to the Day object which represents day in calendar. /// /// Date to retrieve day for. /// Returns reference to Day object. 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; } /// /// Returns true if appointment overlapps with one or more of the appointments in the model. /// /// Appointment to check overlap for. /// true if there are appointments overlapping appointment otherwise false. 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; } /// /// Finds appointments that overlap with the parameter appointment. /// /// Appointment to use to find overlapps /// Array of appointments that overlap parameter. public Appointment[] FindOverlappingAppointments(Appointment app) { List overlaps = new List(); 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; ///// ///// Gets the collection of holidays associated with calendar. ///// //public HolidaysCollection Holidays //{ // get { return _Holidays; } //} /// /// Returns the calendar date time which has seconds part set to 0. /// /// /// 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; /// /// 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. /// 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 _Years = new Dictionary(); 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); } /// /// Raises the AppointmentRemoved event. /// /// Event arguments 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)); } /// /// Raises the AppointmentAdded event. /// /// Event arguments 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)); } /// /// Invalidates appointments cache store and causes recurrences to be regenerated when requested. /// 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; /// /// 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. /// Calls to BeginUpdate method can be nested and only last outer most EndUpdate call will resume internal control updates. /// public void BeginUpdate() { _UpdatesCount++; } /// /// Resumes internal control updates that were suspended using BeginUpdate call and invalidates internal cache. /// public void EndUpdate() { if (_UpdatesCount == 0) throw new InvalidOperationException("EndUpdate must be called AFTER BeginUpdate"); _UpdatesCount--; if (_UpdatesCount == 0) InvalidateAppointmentCache(); } /// /// Gets whether internal control update is suspended due to the call to BeginUpdate method. /// [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; /// /// 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. /// [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)); } /// /// Raises AppointmentStartTimeReached event. /// /// Event arguments protected virtual void OnAppointmentStartTimeReached(AppointmentEventArgs appointmentEventArgs) { AppointmentEventHandler handler = AppointmentStartTimeReached; if (handler != null) handler(this, appointmentEventArgs); } internal void InvokeReminderNotification(Reminder reminder) { OnReminderNotification(new ReminderEventArgs(reminder)); } /// /// Raises ReminderNotification event. /// /// Event arguments protected virtual void OnReminderNotification(ReminderEventArgs e) { ReminderEventHandler h = ReminderNotification; if (h != null) h(this, e); } #endregion #region INotifyPropertyChanged Members /// /// Occurs when property value has changed. /// public event PropertyChangedEventHandler PropertyChanged; /// /// Raises the PropertyChanged event. /// /// Event arguments protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { PropertyChangedEventHandler eh = PropertyChanged; if (eh != null) eh(this, e); OnSubPropertyChanged(new SubPropertyChangedEventArgs(this, e)); } #endregion #region INotifySubPropertyChanged Members /// /// 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. /// public event SubPropertyChangedEventHandler SubPropertyChanged; /// /// Raises the SubPropertyChanged event. /// /// Event arguments protected virtual void OnSubPropertyChanged(SubPropertyChangedEventArgs e) { SubPropertyChangedEventHandler eh = SubPropertyChanged; if (eh != null) eh(this, e); } #endregion #region CustomReminders private ReminderCollection _CustomReminders = null; /// /// Gets the collection of custom reminders that are not associated with appointments. /// 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); /// /// Defines arguments for appointment related events. /// public class AppointmentEventArgs : EventArgs { /// /// Gets the appointment referenced by this event. /// public Appointment Appointment; /// /// Initializes a new instance of the AppointmentEventArgs class. /// /// public AppointmentEventArgs(Appointment appointment) { Appointment = appointment; } } #endregion } #endif