using Outlook2013TodoAddIn.Forms; using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Windows.Forms; using Outlook = Microsoft.Office.Interop.Outlook; namespace Outlook2013TodoAddIn { /// User control to hold the calendar, etc... /// public partial class AppointmentsControl : UserControl { #region "Properties" /// /// Number of days (including today) to retrieve appointments from in the future /// public decimal NumDays { get; set; } /// /// Gets/sets whether mail notifications are enabled or not /// public bool MailAlertsEnabled { get; set; } /// /// Gets/sets whether to show past appointments in the current day or not /// public bool ShowPastAppointments { get; set; } /// /// Gets/sets a list of all stores/accounts to retrieve information from /// public StringCollection Accounts { get; set; } /// /// Gets/sets whether to show friendly group headers (yesterday, today, tomorrow) /// public bool ShowFriendlyGroupHeaders { get; set; } /// /// Gets/sets whether to show localized day names next to the days /// public bool ShowDayNames { get; set; } /// /// Gets/sets whether to show week numbers /// public bool ShowWeekNumbers { get; set; } /// /// Gets/sets the selected calendar date /// public DateTime SelectedDate { get { return this.apptCalendar.SelectedDate; } set { this.apptCalendar.SelectedDate = value; } } /// /// Gets/sets whether to show the tasks list /// public bool ShowTasks { get; set; } /// /// Gets/sets whether to show the completed tasks in the list /// public bool ShowCompletedTasks { get; set; } /// /// Gets/sets the first day of the week for the calendar /// public System.DayOfWeek FirstDayOfWeek { get; set; } #endregion "Properties" #region "Methods" /// /// Default constructor /// public AppointmentsControl() { InitializeComponent(); if (Properties.Settings.Default.SplitterDistance >= this.splitContainer1.Panel1MinSize && Properties.Settings.Default.SplitterDistance <= this.splitContainer1.Height - this.splitContainer1.Panel2MinSize) { // This is to avoid the bug "SplitterDistance must be between Panel1MinSize and Width - Panel2MinSize." this.splitContainer1.SplitterDistance = Properties.Settings.Default.SplitterDistance; // TODO: This doesn't work, need to fix (race condition?) // TODO: Another event is fired after controls are added... } } /// /// Respond to calendar changes /// /// Sender /// DateRangeEventArgs private void apptCalendar_SelectedDateChanged(object sender, EventArgs e) { this.RetrieveData(); } /// /// Retrieves appointments and tasks if configured /// public void RetrieveData() { this.apptCalendar.FirstDayOfWeek = this.FirstDayOfWeek; this.RetrieveAppointments(); if (this.ShowTasks) { this.RetrieveTasks(); this.splitContainer1.Panel2Collapsed = false; } else { this.splitContainer1.Panel2Collapsed = true; } } /// /// Retrieves tasks for all selected stores /// private void RetrieveTasks() { List tasks = new List(); foreach (Outlook.Store store in Globals.ThisAddIn.Application.Session.Stores) { if (Properties.Settings.Default.Accounts != null && Properties.Settings.Default.Accounts.Contains(store.DisplayName)) { Outlook.Folder todoFolder = store.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderToDo) as Outlook.Folder; tasks.AddRange(this.RetrieveTasksForFolder(todoFolder)); // TODO: Shared calendars? } } if (!this.ShowCompletedTasks) { tasks = tasks.Where(t => !t.Completed).ToList(); } // We need to sort them because they may come from different accounts already ordered // Applies sorting by due date tasks.Sort(CompareTasks); //Outlook.Folder todoFolder = // Globals.ThisAddIn.Application.Session.GetDefaultFolder( // Outlook.OlDefaultFolders.olFolderToDo) // as Outlook.Folder; //this.RetrieveTasksForFolder(todoFolder); int sameDay = -1; // startRange.Day; List lstCol = new List(); ListViewGroup grp = null; tasks.ForEach(t => { if (t.DueDate.Day != sameDay) { string groupHeaderText = t.DueDate.ToShortDateString(); if (t.DueDate.Year == Constants.NullYear) groupHeaderText = "No due date"; if (this.ShowFriendlyGroupHeaders) { int daysDiff = (int)(t.DueDate.Date - DateTime.Today).TotalDays; switch (daysDiff) { case -1: groupHeaderText = Constants.Yesterday + ": " + groupHeaderText; break; case 0: groupHeaderText = Constants.Today + ": " + groupHeaderText; break; case 1: groupHeaderText = Constants.Tomorrow + ": " + groupHeaderText; break; default: break; } } if (this.ShowDayNames && t.DueDate.Year != Constants.NullYear) { groupHeaderText += " (" + t.DueDate.ToString("dddd") + ")"; } grp = new ListViewGroup(groupHeaderText, HorizontalAlignment.Left); this.lstTasks.Groups.Add(grp); sameDay = t.DueDate.Day; }; ListViewItem current = new ListViewItem(new string[] { String.Format("{0} {1}", t.DueDate.ToShortTimeString(), t.TaskSubject), "***" }); current.SubItems.Add(t.TaskSubject); // current.Font = new Font(this.Font, FontStyle.Bold); // current.UseItemStyleForSubItems = false; current.ToolTipText = String.Format("Task Subject: {0}", t.TaskSubject); if (t.StartDate.Year != Constants.NullYear) { current.ToolTipText += Environment.NewLine + String.Format("Start Date: {0}", t.StartDate.ToShortDateString()); } if (t.Reminder.Year != Constants.NullYear) { current.ToolTipText += Environment.NewLine + String.Format("Reminder Time: {0}", t.Reminder.ToString()); } if (t.DueDate.Year != Constants.NullYear) { current.ToolTipText += Environment.NewLine + String.Format("Due Date: {0}", t.DueDate.ToShortDateString()); } current.ToolTipText += Environment.NewLine + String.Format("In Folder: {0}", t.FolderName); t.Categories.ForEach(cat => { Outlook.Category c = Globals.ThisAddIn.Application.Session.Categories[cat] as Outlook.Category; if (c != null) { current.ToolTipText += Environment.NewLine + " - " + c.Name; } }); current.Tag = t; current.Group = grp; lstCol.Add(current); }); this.lstTasks.Items.Clear(); this.lstTasks.Items.AddRange(lstCol.ToArray()); } /// /// Retrieves to-do tasks for the folder on the specified store /// /// Outlook folder private List RetrieveTasksForFolder(Outlook.Folder todoFolder) { List tasks = new List(); foreach (object item in todoFolder.Items) { try { tasks.Add(new OLTaskItem(item)); } catch { // Do nothing, probably the task had already been deleted } } return tasks.Where(t => t.ValidTaskItem).ToList(); // Filter out invalid ones } /// /// Retrieve all appointments for the current configurations for all selected stores /// private void RetrieveAppointments() { List appts = new List(); foreach (Outlook.Store store in Globals.ThisAddIn.Application.Session.Stores) { if (Properties.Settings.Default.Accounts != null && Properties.Settings.Default.Accounts.Contains(store.DisplayName)) { Outlook.Folders rootFolders = store.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar).Parent.Folders as Outlook.Folders; foreach (Outlook.Folder folder in rootFolders) { appts.AddRange(this.RetrieveAppointmentsForFolder(folder)); } // TODO: Shared calendars? } } // We need to sort them because they may come from different accounts already ordered appts.Sort(CompareAppointments); // Get the Outlook folder for the calendar to retrieve the appointments //Outlook.Folder calFolder = // Globals.ThisAddIn.Application.Session.GetDefaultFolder( // Outlook.OlDefaultFolders.olFolderCalendar) // as Outlook.Folder; //List appts = this.RetrieveAppointmentsForFolder(calFolder); // Highlight dates with appointments in the current calendar this.apptCalendar.BoldedDates = appts.Select(a => a.Start.Date).Distinct().ToArray(); // Now display the actual appointments below the calendar DateTime startRange = this.apptCalendar.SelectedDate; if (!this.ShowPastAppointments && startRange.Date == DateTime.Today) { startRange = startRange.Add(DateTime.Now.TimeOfDay); } DateTime endRange = startRange.AddDays((int)this.NumDays); // Get items in range var lstItems = appts.Where(a => a.Start >= startRange && a.Start <= endRange); int sameDay = -1; // startRange.Day; List lstCol = new List(); ListViewGroup grp = null; lstItems.ToList().ForEach(i => { if (i.Start.Day != sameDay) { string groupHeaderText = i.Start.ToShortDateString(); if (this.ShowFriendlyGroupHeaders) { int daysDiff = (int)(i.Start.Date - DateTime.Today).TotalDays; switch (daysDiff) { case -1: groupHeaderText = Constants.Yesterday + ": " + groupHeaderText; break; case 0: groupHeaderText = Constants.Today + ": " + groupHeaderText; break; case 1: groupHeaderText = Constants.Tomorrow + ": " + groupHeaderText; break; default: break; } } if (this.ShowDayNames) { groupHeaderText += " (" + i.Start.ToString("dddd") + ")"; } grp = new ListViewGroup(groupHeaderText, HorizontalAlignment.Left); this.lstAppointments.Groups.Add(grp); sameDay = i.Start.Day; }; string loc = "-"; // TODO: If no second line is specified, the tile is stretched to only one line if (!String.IsNullOrEmpty(i.Location)) loc = i.Location; ListViewItem current = new ListViewItem(new string[] { String.Format("{0} {1}", i.Start.ToShortTimeString(), i.Subject), loc }); current.SubItems.Add(i.Subject); // current.Font = new Font(this.Font, FontStyle.Bold); // current.UseItemStyleForSubItems = false; current.ToolTipText = String.Format("{0} - {1} {2}", i.Start.ToShortTimeString(), i.End.ToShortTimeString(), i.Subject); if (!String.IsNullOrEmpty(i.Categories)) { string[] allCats = i.Categories.Split(new char[] { ',' }); if (allCats != null && allCats.Length != 0) { List cs = allCats.Select(cat => cat.Trim()).ToList(); cs.ForEach(cat => { Outlook.Category c = Globals.ThisAddIn.Application.Session.Categories[cat] as Outlook.Category; if (c != null) { current.ToolTipText += Environment.NewLine + " - " + c.Name; } }); } } current.Tag = i; current.Group = grp; switch (i.BusyStatus) { case Outlook.OlBusyStatus.olBusy: current.ForeColor = Color.Purple; break; case Outlook.OlBusyStatus.olFree: break; case Outlook.OlBusyStatus.olOutOfOffice: current.ForeColor = Color.Brown; break; case Outlook.OlBusyStatus.olTentative: break; case Outlook.OlBusyStatus.olWorkingElsewhere: break; default: break; } lstCol.Add(current); }); this.lstAppointments.Items.Clear(); this.lstAppointments.Items.AddRange(lstCol.ToArray()); this.apptCalendar.ShowWeekNumbers = this.ShowWeekNumbers; this.apptCalendar.UpdateCalendar(); appts.ToList().ForEach(i => { i.Close(Outlook.OlInspectorClose.olDiscard); }); } /// /// Comparer method to sort tasks based on due date/time /// /// First task /// Second task /// private static int CompareTasks(OLTaskItem x, OLTaskItem y) { return x.DueDate.CompareTo(y.DueDate); } /// /// Comparer method to sort appointments based on start date/time /// /// First appointment /// Second appointment /// private static int CompareAppointments(Outlook.AppointmentItem x, Outlook.AppointmentItem y) { return x.Start.CompareTo(y.Start); } /// /// Retrieve all appointments for the current configurations for a specific folder /// /// Outlook folder /// List of appointments private List RetrieveAppointmentsForFolder(Outlook.Folder calFolder) { int selectedMonth = this.apptCalendar.SelectedDate.Month; int selectedYear = this.apptCalendar.SelectedDate.Year; // To get all the appointments for the current month (so it displays nicely bolded even for past events) DateTime start = new DateTime(selectedYear, selectedMonth, 1); // MM-01-YYYY DateTime end = start.AddMonths(1).AddDays(-1); // Last day of the month end = end.AddDays((int)this.NumDays); // So we get appointments for the "possible" first days of the next month // Get all the appointments Outlook.Items rangeAppts = GetAppointmentsInRange(calFolder, start, end); // Get a more manageable list List appts = new List(); if (rangeAppts != null) { foreach (Outlook.AppointmentItem appt in rangeAppts) { appts.Add(appt); } } return appts; } /// /// Get recurring appointments in a date range. /// /// Outlook folder /// Start time /// End time /// Outlook.Items private Outlook.Items GetAppointmentsInRange(Outlook.Folder folder, DateTime startTime, DateTime endTime) { string filter = "[Start] >= '" + startTime.ToString("g") + "' AND [End] <= '" + endTime.ToString("g") + "'"; try { Outlook.Items calItems = folder.Items; calItems.IncludeRecurrences = true; calItems.Sort("[Start]", Type.Missing); Outlook.Items restrictItems = calItems.Restrict(filter); if (restrictItems.Count > 0) { return restrictItems; } else { return null; } } catch { return null; } } /// /// Open the task /// /// Sender /// EventArgs private void lstTasks_DoubleClick(object sender, EventArgs e) { if (this.lstTasks.SelectedIndices.Count != 0) { OLTaskItem task = this.lstTasks.SelectedItems[0].Tag as OLTaskItem; if (task != null) { if (task.OriginalItem is Outlook.MailItem) { Outlook.MailItem mail = task.OriginalItem as Outlook.MailItem; mail.Display(true); } else if (task.OriginalItem is Outlook.ContactItem) { Outlook.ContactItem contact = task.OriginalItem as Outlook.ContactItem; contact.Display(true); } else if (task.OriginalItem is Outlook.TaskItem) { Outlook.TaskItem t = task.OriginalItem as Outlook.TaskItem; t.Display(true); } else { // Do nothing } // At the end, synchronously "refresh" tasks in case they have changed this.RetrieveTasks(); } } } /// /// Open the appointment, having in mind it might be a recurring event /// /// Sender /// EventArgs private void lstAppointments_DoubleClick(object sender, EventArgs e) { if (this.lstAppointments.SelectedIndices.Count != 0) { Outlook.AppointmentItem appt = this.lstAppointments.SelectedItems[0].Tag as Outlook.AppointmentItem; if (appt != null) { if (appt.IsRecurring) { FormRecurringOpen f = new FormRecurringOpen(); f.Title = "Open Recurring Item"; f.Message = "This is one appointment in a series. What do you want to open?"; if (f.ShowDialog() == DialogResult.OK) { if (f.OpenRecurring) { // Open up the master appointment in a new window // If we open the current instance then there is an error: "This item is no longer valid because it has been closed" // One workaround is to refresh the appointments list to get new instances... Outlook.AppointmentItem masterAppt = appt.Parent; // Get the master appointment item masterAppt.Display(true); // Will modify ALL instances } else { // Open up the appointment in a new window appt.Display(true); // Modal yes/no } } } else { // Open up the appointment in a new window appt.Display(true); // Modal yes/no } // At the end, synchronously "refresh" appointments in case they have changed this.RetrieveAppointments(); } } } /// /// Creates a new mail item to reply all recipients of an appointment (except the current user) /// /// Sender /// EventArgs private void mnuItemReplyAllEmail_Click(object sender, EventArgs e) { if (this.lstAppointments.SelectedIndices.Count != 0) { Outlook.AppointmentItem appt = this.lstAppointments.SelectedItems[0].Tag as Outlook.AppointmentItem; if (appt != null) { Outlook.MailItem mail = Globals.ThisAddIn.Application.CreateItem(Outlook.OlItemType.olMailItem) as Outlook.MailItem; string curUserAddress = OutlookHelper.GetEmailAddress(Globals.ThisAddIn.Application.Session.CurrentUser); foreach (Outlook.Recipient rcpt in appt.Recipients) { string smtpAddress = OutlookHelper.GetEmailAddress(rcpt); if (curUserAddress != smtpAddress) { mail.Recipients.Add(smtpAddress); } } mail.Body = Environment.NewLine + Environment.NewLine + appt.Body; mail.Subject = Constants.SubjectRE + ": " + appt.Subject; mail.Display(); } } } /// /// Allows to delete the selected appointment (whether it's a recurring one or not) /// /// Sender /// EventArgs private void mnuItemDeleteAppointment_Click(object sender, EventArgs e) { if (this.lstAppointments.SelectedIndices.Count != 0) { Outlook.AppointmentItem appt = this.lstAppointments.SelectedItems[0].Tag as Outlook.AppointmentItem; if (appt != null) { if (appt.IsRecurring) { FormRecurringOpen f = new FormRecurringOpen(); f.Title = "Warning: Delete Recurring Item"; f.Message = "This is one appointment in a series. What do you want to delete?"; if (f.ShowDialog() == DialogResult.OK) { if (f.OpenRecurring) { Outlook.AppointmentItem masterAppt = appt.Parent; // Get the master appointment item masterAppt.Delete(); // Will delete ALL instances } else { appt.Delete(); // Delete just this instance } } } else { if (MessageBox.Show("Are you sure you want to delete this appointment?", "Delete appointment", MessageBoxButtons.YesNo) == DialogResult.Yes) { appt.Delete(); // Delete just this instance } } // At the end, synchronously "refresh" appointments in case they have changed this.RetrieveAppointments(); } } } /// /// Allows to delete the selected task /// /// Sender /// EventArgs private void mnuItemDeleteTask_Click(object sender, EventArgs e) { if (this.lstTasks.SelectedIndices.Count != 0) { OLTaskItem task = this.lstTasks.SelectedItems[0].Tag as OLTaskItem; if (task != null) { if (MessageBox.Show("Are you sure you want to delete this task?", "Delete task", MessageBoxButtons.YesNo) == DialogResult.Yes) { if (task.OriginalItem is Outlook.MailItem) { Outlook.MailItem mail = task.OriginalItem as Outlook.MailItem; mail.Delete(); } else if (task.OriginalItem is Outlook.ContactItem) { Outlook.ContactItem contact = task.OriginalItem as Outlook.ContactItem; contact.Delete(); } else if (task.OriginalItem is Outlook.TaskItem) { Outlook.TaskItem t = task.OriginalItem as Outlook.TaskItem; t.Delete(); } else { // Do nothing } } // At the end, synchronously "refresh" tasks in case they have changed this.RetrieveTasks(); } } } /// /// Allows to mark the selected task as completed (clear task flag for regular items) /// /// Sender /// EventArgs private void mnuItemMarkComplete_Click(object sender, EventArgs e) { if (this.lstTasks.SelectedIndices.Count != 0) { OLTaskItem task = this.lstTasks.SelectedItems[0].Tag as OLTaskItem; if (task != null && !task.Completed) // Attempting to complete an already completed task throws an exception { if (MessageBox.Show("Are you sure you want to complete this task?", "Mark task as completed/Clear task flag", MessageBoxButtons.YesNo) == DialogResult.Yes) { if (task.OriginalItem is Outlook.MailItem) { Outlook.MailItem mail = task.OriginalItem as Outlook.MailItem; mail.ClearTaskFlag(); } else if (task.OriginalItem is Outlook.ContactItem) { Outlook.ContactItem contact = task.OriginalItem as Outlook.ContactItem; contact.ClearTaskFlag(); } else if (task.OriginalItem is Outlook.TaskItem) { Outlook.TaskItem t = task.OriginalItem as Outlook.TaskItem; t.MarkComplete(); } else { // Do nothing } } // At the end, synchronously "refresh" tasks in case they have changed this.RetrieveTasks(); } } } /// /// Switch to the calendar view when double-clicking a date /// /// Sender /// EventArgs private void apptCalendar_CellDoubleClick(object sender, EventArgs e) { // Clicking in days outside of the current month will cause the calendar to refresh to that day // reposition all days and select the wrong one Outlook.Folder f = Globals.ThisAddIn.Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar) as Outlook.Folder; Globals.ThisAddIn.Application.ActiveExplorer().CurrentFolder = f; Outlook.CalendarView cv = (Outlook.CalendarView)(Globals.ThisAddIn.Application.ActiveExplorer().CurrentView); cv.CalendarViewMode = Outlook.OlCalendarViewMode.olCalendarViewDay; cv.GoToDate(this.apptCalendar.SelectedDate); } /// /// New method to show the configuration form /// /// Sender /// EventArgs private void apptCalendar_ConfigurationButtonClicked(object sender, EventArgs e) { using (FormConfiguration cfg = new FormConfiguration()) { if (cfg.ShowDialog() == DialogResult.OK) { this.NumDays = cfg.NumDays; this.MailAlertsEnabled = cfg.MailAlertsEnabled; this.ShowPastAppointments = cfg.ShowPastAppointments; this.Accounts = cfg.Accounts; this.ShowFriendlyGroupHeaders = cfg.ShowFriendlyGroupHeaders; this.ShowDayNames = cfg.ShowDayNames; this.ShowWeekNumbers = cfg.ShowWeekNumbers; this.ShowTasks = cfg.ShowTasks; this.ShowCompletedTasks = cfg.ShowCompletedTasks; this.FirstDayOfWeek = cfg.FirstDayOfWeek; this.RetrieveData(); } } } private void lstTasks_DrawItem(object sender, DrawListViewItemEventArgs e) { e.DrawBackground(); // To avoid repainting (making font "grow") OLTaskItem task = e.Item.Tag as OLTaskItem; // Color catColor = Color.Empty; List catColors = new List(); Font itemFont = this.Font; task.Categories.ForEach(cat => { Outlook.Category c = Globals.ThisAddIn.Application.Session.Categories[cat] as Outlook.Category; if (c != null) { catColors.Add(TranslateCategoryColor(c.Color)); } }); int categoriesRectangleWidth = 30; int horizontalSpacing = 2; Rectangle totalRectangle = e.Bounds; Rectangle subjectRectangle = totalRectangle; subjectRectangle.Height = this.FontHeight; Rectangle categoriesRectangle = totalRectangle; categoriesRectangle.Width = categoriesRectangleWidth; categoriesRectangle.Height = this.FontHeight; categoriesRectangle.Offset(10, this.FontHeight); Rectangle reminderRectangle = totalRectangle; reminderRectangle.Height = this.FontHeight; reminderRectangle.Offset(0, this.FontHeight); if (task.Categories.Count != 0) reminderRectangle.Offset(categoriesRectangleWidth + horizontalSpacing * 4, 0); bool selected = e.State.HasFlag(ListViewItemStates.Selected); Color back = Color.Empty; if (selected) back = Color.LightCyan; using (Brush br = new SolidBrush(back)) e.Graphics.FillRectangle(br, totalRectangle); StringFormat rightFormat = new StringFormat(); rightFormat.Alignment = StringAlignment.Far; rightFormat.LineAlignment = StringAlignment.Near; StringFormat leftFormat = new StringFormat(); leftFormat.Alignment = StringAlignment.Near; leftFormat.LineAlignment = StringAlignment.Near; Brush colorBrush = new SolidBrush(this.ForeColor); if (catColors.Count != 0) { int catWidth = categoriesRectangle.Width / catColors.Count; // int catWidth = categoriesRectangle.Width / 3; // TODO: This looks nicer, but more than 3 won't fit Rectangle catRect = categoriesRectangle; catRect.Width = catWidth; // catColors.ForEach(cc => catColors.Take(3).ToList().ForEach(cc => { // e.Graphics.FillEllipse(new SolidBrush(cc), catRect); e.Graphics.FillRectangle(new SolidBrush(cc), catRect); // e.Graphics.FillRectangle(new SolidBrush(cc), catRect.Left, catRect.Top, catWidth, this.FontHeight); catRect.Offset(catWidth, 0); }); } Font mainFont = new Font(this.Font, FontStyle.Bold); Font subFont = this.Font; if (task.Completed) { mainFont = new Font(this.Font, FontStyle.Bold | FontStyle.Strikeout); subFont = new Font(this.Font, FontStyle.Strikeout); } e.Graphics.DrawString(task.TaskSubject, mainFont, colorBrush, subjectRectangle, leftFormat); if (task.Reminder.Year != Constants.NullYear) { e.Graphics.DrawImage(Properties.Resources.Alert_16xSM, reminderRectangle.Left, reminderRectangle.Top); e.Graphics.DrawString(task.Reminder.ToString(), subFont, colorBrush, reminderRectangle.Left + Properties.Resources.Alert_16xSM.Width + horizontalSpacing, reminderRectangle.Top, leftFormat); } else if (task.StartDate.Year != Constants.NullYear) { e.Graphics.DrawImage(Properties.Resources.CurrentRow_15x14, reminderRectangle.Left, reminderRectangle.Top); e.Graphics.DrawString(task.StartDate.ToShortDateString(), subFont, colorBrush, reminderRectangle.Left + Properties.Resources.CurrentRow_15x14.Width + horizontalSpacing, reminderRectangle.Top, leftFormat); } } /// /// Method to custom draw the list items /// /// Sender /// DrawListViewItemEventArgs private void lstAppointments_DrawItem(object sender, DrawListViewItemEventArgs e) { e.DrawBackground(); // To avoid repainting (making font "grow") Outlook.AppointmentItem appt = e.Item.Tag as Outlook.AppointmentItem; // Color catColor = Color.Empty; List catColors = new List(); Font itemFont = this.Font; if (!String.IsNullOrEmpty(appt.Categories)) { string[] allCats = appt.Categories.Split(new char[] { ',' }); if (allCats != null && allCats.Length != 0) { List cs = allCats.Select(cat => cat.Trim()).ToList(); cs.ForEach(cat => { Outlook.Category c = Globals.ThisAddIn.Application.Session.Categories[cat] as Outlook.Category; if (c != null) { catColors.Add(TranslateCategoryColor(c.Color)); } }); } } int startRectangleWidth = 65; int categoriesRectangleWidth = 55; int horizontalSpacing = 5; Rectangle totalRectangle = e.Bounds; Rectangle startRectangle = totalRectangle; startRectangle.Width = startRectangleWidth; Rectangle statusRectangle = totalRectangle; statusRectangle.Width = horizontalSpacing * 2; statusRectangle.Offset(startRectangleWidth + horizontalSpacing, 0); Rectangle subjectRectangle = totalRectangle; subjectRectangle.Height = this.FontHeight; subjectRectangle.Offset(startRectangleWidth + horizontalSpacing * 4, 0); Rectangle categoriesRectangle = totalRectangle; categoriesRectangle.Width = categoriesRectangleWidth; categoriesRectangle.Height = this.FontHeight; categoriesRectangle.Offset(10, this.FontHeight); Rectangle locationRectangle = totalRectangle; locationRectangle.Height = this.FontHeight; locationRectangle.Offset(startRectangleWidth + horizontalSpacing * 4, this.FontHeight); bool selected = e.State.HasFlag(ListViewItemStates.Selected); Color back = Color.Empty; if (selected) back = Color.LightCyan; using (Brush br = new SolidBrush(back)) e.Graphics.FillRectangle(br, totalRectangle); StringFormat rightFormat = new StringFormat(); rightFormat.Alignment = StringAlignment.Far; rightFormat.LineAlignment = StringAlignment.Near; StringFormat leftFormat = new StringFormat(); leftFormat.Alignment = StringAlignment.Near; leftFormat.LineAlignment = StringAlignment.Near; Brush colorBrush = new SolidBrush(this.ForeColor); e.Graphics.DrawString(appt.Start.ToShortTimeString(), this.Font, colorBrush, startRectangle, rightFormat); Color statusColor = Color.LightBlue; Brush statusBrush = new SolidBrush(Color.Transparent); switch (appt.BusyStatus) { case Outlook.OlBusyStatus.olBusy: statusBrush = new SolidBrush(statusColor); break; case Outlook.OlBusyStatus.olFree: break; case Outlook.OlBusyStatus.olOutOfOffice: statusBrush = new SolidBrush(Color.Purple); // TODO: Figure this out break; case Outlook.OlBusyStatus.olTentative: statusBrush = new HatchBrush(HatchStyle.BackwardDiagonal, statusColor, this.BackColor); break; case Outlook.OlBusyStatus.olWorkingElsewhere: statusBrush = new HatchBrush(HatchStyle.DottedDiamond, statusColor, this.BackColor); break; default: break; } // Let's draw the status with a custom brush e.Graphics.FillRectangle(statusBrush, statusRectangle); e.Graphics.DrawRectangle(new Pen(statusColor), statusRectangle); if (catColors.Count != 0) { // int catWidth = categoriesRectangle.Width / catColors.Count; int catWidth = categoriesRectangle.Width / 3; // TODO: This looks nicer, but more than 3 won't fit Rectangle catRect = categoriesRectangle; catRect.Width = catWidth; // catColors.ForEach(cc => catColors.Take(3).ToList().ForEach(cc => { e.Graphics.FillEllipse(new SolidBrush(cc), catRect); catRect.Offset(catWidth, 0); }); } //e.Graphics.FillRectangle(new SolidBrush(catColor), subjectRectangle); e.Graphics.DrawString(appt.Subject, new Font(this.Font, FontStyle.Bold), colorBrush, subjectRectangle, leftFormat); //e.Graphics.FillRectangle(new SolidBrush(catColor), locationRectangle); e.Graphics.DrawString(appt.Location, this.Font, colorBrush, locationRectangle, leftFormat); } /// /// The color scheme for Outlook is not the same as of Windows Forms, so this method tries to do a best effor in matching them /// /// Color of the category /// Resulting color to be displayed private Color TranslateCategoryColor(Outlook.OlCategoryColor col) { Color result = Color.Black; switch (col) { case Outlook.OlCategoryColor.olCategoryColorNone: // Nothing break; case Outlook.OlCategoryColor.olCategoryColorRed: result = Color.Red; break; case Outlook.OlCategoryColor.olCategoryColorOrange: result = Color.Orange; break; case Outlook.OlCategoryColor.olCategoryColorPeach: result = Color.PeachPuff; break; case Outlook.OlCategoryColor.olCategoryColorYellow: result = Color.Yellow; break; case Outlook.OlCategoryColor.olCategoryColorGreen: result = Color.Green; break; case Outlook.OlCategoryColor.olCategoryColorTeal: result = Color.Teal; break; case Outlook.OlCategoryColor.olCategoryColorOlive: result = Color.Olive; break; case Outlook.OlCategoryColor.olCategoryColorBlue: result = Color.Blue; break; case Outlook.OlCategoryColor.olCategoryColorPurple: result = Color.Purple; break; case Outlook.OlCategoryColor.olCategoryColorMaroon: result = Color.Maroon; break; case Outlook.OlCategoryColor.olCategoryColorSteel: result = Color.LightSteelBlue; break; case Outlook.OlCategoryColor.olCategoryColorDarkSteel: result = Color.SteelBlue; break; case Outlook.OlCategoryColor.olCategoryColorGray: result = Color.Gray; break; case Outlook.OlCategoryColor.olCategoryColorDarkGray: result = Color.DarkGray; break; case Outlook.OlCategoryColor.olCategoryColorBlack: result = Color.Black; break; case Outlook.OlCategoryColor.olCategoryColorDarkRed: result = Color.DarkRed; break; case Outlook.OlCategoryColor.olCategoryColorDarkOrange: result = Color.DarkOrange; break; case Outlook.OlCategoryColor.olCategoryColorDarkPeach: result = Color.DarkSalmon; break; case Outlook.OlCategoryColor.olCategoryColorDarkYellow: result = Color.DarkGoldenrod; break; case Outlook.OlCategoryColor.olCategoryColorDarkGreen: result = Color.DarkGreen; break; case Outlook.OlCategoryColor.olCategoryColorDarkTeal: result = Color.DarkCyan; break; case Outlook.OlCategoryColor.olCategoryColorDarkOlive: result = Color.DarkOliveGreen; break; case Outlook.OlCategoryColor.olCategoryColorDarkBlue: result = Color.DarkBlue; break; case Outlook.OlCategoryColor.olCategoryColorDarkPurple: result = Color.DarkViolet; break; case Outlook.OlCategoryColor.olCategoryColorDarkMaroon: result = Color.DarkKhaki; break; default: break; } return result; } /// /// Save the splitter distance to restore upon reloading the plugin /// /// Sender /// SplitterEventArgs private void splitContainer1_SplitterMoved(object sender, SplitterEventArgs e) { Properties.Settings.Default.SplitterDistance = this.splitContainer1.SplitterDistance; } #endregion "Methods" } }