/* Copyright 2012 Justin LeCheminant This file is part of WindowsFormsCalendar. indowsFormsCalendar is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. indowsFormsCalendar is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with indowsFormsCalendar. If not, see . */ using System; using System.Collections.Generic; using System.Text; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; namespace WindowsFormsCalendar { /// /// Base class that renders visual elements of Calendar control /// public class CalendarRenderer { #region Static /// /// Comparison delegate to sort items /// /// /// /// private static int CompareItems(CalendarItem item1, CalendarItem item2) { return item1.StartDate.CompareTo(item2.StartDate) * -1; } /// /// Comparison delegate to sort units /// /// /// /// private static int CompareUnits(CalendarTimeScaleUnit item1, CalendarTimeScaleUnit item2) { return item1.Date.CompareTo(item2.Date); } /// /// Compares both items by Date /// /// /// /// private static int CompareTops(CalendarDayTop top1, CalendarDayTop top2) { return top1.Date.CompareTo(top2.Date); } /// /// Creates a rectangle with rounded corners /// /// /// /// public static GraphicsPath RoundRectangle(Rectangle r, int radius) { return RoundRectangle(r, radius, Corners.All); } /// /// Creates a rectangle with the specified corners rounded /// /// /// /// /// public static GraphicsPath RoundRectangle(Rectangle r, int radius, Corners corners) { GraphicsPath path = new GraphicsPath(); if (r.Width <= 0 || r.Height <= 0) return path; int d = radius * 2; int nw = (corners & Corners.NorthWest) == Corners.NorthWest ? d : 0; int ne = (corners & Corners.NorthEast) == Corners.NorthEast ? d : 0; int se = (corners & Corners.SouthEast) == Corners.SouthEast ? d : 0; int sw = (corners & Corners.SouthWest) == Corners.SouthWest ? d : 0; path.AddLine(r.Left + nw, r.Top, r.Right - ne, r.Top); if (ne > 0) { path.AddArc(Rectangle.FromLTRB(r.Right - ne, r.Top, r.Right, r.Top + ne), -90, 90); } path.AddLine(r.Right, r.Top + ne, r.Right, r.Bottom - se); if (se > 0) { path.AddArc(Rectangle.FromLTRB(r.Right - se, r.Bottom - se, r.Right, r.Bottom), 0, 90); } path.AddLine(r.Right - se, r.Bottom, r.Left + sw, r.Bottom); if (sw > 0) { path.AddArc(Rectangle.FromLTRB(r.Left, r.Bottom - sw, r.Left + sw, r.Bottom), 90, 90); } path.AddLine(r.Left, r.Bottom - sw, r.Left, r.Top + nw); if (nw > 0) { path.AddArc(Rectangle.FromLTRB(r.Left, r.Top, r.Left + nw, r.Top + nw), 180, 90); } path.CloseFigure(); return path; } #endregion #region Events #endregion #region Fields private int _allDayItemsPadding; private int _standardItemHeight; private int _dayTopHeight; private int _dayTopMinHeight; private Calendar _calendar; private Rectangle[] _dayNameHeaderColumns; private int _dayHeaderHeight; private int _dayNameHeadersHeight; private int _itemInvalidateMargin; private int _itemsPadding; private Padding _itemTextMargin; private int _itemShadowPadding; private int _itemRoundness; private Rectangle _timeScaleBounds; private int _timeScaleUnitHeight; private int _timeScaleWidth; private int _weekHeaderWidth; #endregion #region Properties /// /// Gets or sets the padding of the items that goes on the top part of the days, /// when in /// /// /// The day top items padding. /// public int DayTopItemsPadding { get { return _allDayItemsPadding; } set { _allDayItemsPadding = value; } } /// /// Gets the current height of the all day items area /// /// /// The height of the day top. /// public virtual int DayTopHeight { get { if( _dayTopHeight == 0 ) { _dayTopHeight = DayTopMinHeight; } return _dayTopHeight; } set { _dayTopHeight = value; } } /// /// Gets the height of items on day tops /// /// /// The height of the standard item. /// public virtual int StandardItemHeight { get { if( _standardItemHeight == 0 ) { _standardItemHeight = TextRenderer.MeasureText( "Ag", Calendar.Font ).Height; } return _standardItemHeight + ItemTextMargin.Vertical; } } /// /// Gets the minimum height for day tops /// /// /// The height of the day top min. /// public virtual int DayTopMinHeight { get { if( _dayTopMinHeight == 0 ) { _dayTopMinHeight = TextRenderer.MeasureText( "Ag", Calendar.Font ).Height + 16; } return _dayTopMinHeight; } } /// /// Gets the this renderer belongs to /// public Calendar Calendar { get { return _calendar; } } /// /// Gets the bounds for day name headers /// public Rectangle[] DayNameHeaderColumns { get { return _dayNameHeaderColumns; } } /// /// Gets the height of the header of days /// /// /// The height of the day header. /// public virtual int DayHeaderHeight { get { if( _dayHeaderHeight == 0 ) { _dayHeaderHeight = TextRenderer.MeasureText( "Ag", Calendar.Font ).Height + 4; } return _dayHeaderHeight; } } /// /// Gets a value indicating if the day names headers are visible (e.g. Monday, Tuesday, Wednesday ...) /// /// /// true if [day name headers visible]; otherwise, false. /// public bool DayNameHeadersVisible { get { return Calendar.DaysMode == CalendarDaysMode.Short; } } /// /// Gets the height of the day name headers /// /// /// The height of the day name headers. /// public virtual int DayNameHeadersHeight { get { if( _dayNameHeadersHeight == 0 ) { _dayNameHeadersHeight = DayHeaderHeight; } return _dayNameHeadersHeight; } } /// /// Gets the margin of the text in the items /// /// /// The item text margin. /// public virtual Padding ItemTextMargin { get { return _itemTextMargin; } set { _itemTextMargin = value; } } /// /// Gets or sets the amount of pixels that the item's shadow is dropped /// /// /// The item shadow padding. /// public virtual int ItemShadowPadding { get { return _itemShadowPadding; } set { _itemShadowPadding = value; } } /// /// Gets or sets the extra margin for invalidating and redrawing items /// /// /// The item invalidate margin. /// public int ItemInvalidateMargin { get { return _itemInvalidateMargin; } set { _itemInvalidateMargin = value; } } /// /// Gets or sets the padding of items on expanded mode /// /// /// The items padding. /// public int ItemsPadding { get { return _itemsPadding; } set { _itemsPadding = value; } } /// /// Gets or sets the roundness of the item /// /// /// The item roundness. /// public int ItemRoundness { get { return _itemRoundness; } set { _itemRoundness = value; } } /// /// Gets or sets the bounds of the timescale /// /// /// The time scale bounds. /// public Rectangle TimeScaleBounds { get { return _timeScaleBounds; } set { _timeScaleBounds = value; } } /// /// Gets the height of the time scale unit. /// /// /// The height of the time scale unit. /// public virtual int TimeScaleUnitHeight { get { if( _timeScaleUnitHeight == 0 ) { _timeScaleUnitHeight = TextRenderer.MeasureText( "Ag", Calendar.Font ).Height + 10; } return _timeScaleUnitHeight; } } /// /// Gets a value indicating whether [time scale visible]. /// /// /// true if [time scale visible]; otherwise, false. /// public bool TimeScaleVisible { get { return Calendar.DaysMode == CalendarDaysMode.Expanded; } } /// /// Gets the width of the timescale /// /// /// The width of the time scale. /// public virtual int TimeScaleWidth { get { if( _timeScaleWidth == 0 ) { _timeScaleWidth = 60; } return _timeScaleWidth; } } /// /// Gets the width of the week header. /// /// /// The width of the week header. /// public virtual int WeekHeaderWidth { get { if( _weekHeaderWidth == 0 ) { _weekHeaderWidth = TextRenderer.MeasureText( "Ag", Calendar.Font ).Height + 4; } return _weekHeaderWidth; } } #endregion /// /// Creates a new renderer for the specified calendar /// /// public CalendarRenderer(Calendar calendar) { if (calendar == null) { throw new ArgumentNullException("calendar"); } _calendar = calendar; _allDayItemsPadding = 5; _itemsPadding = 5; _itemTextMargin = new Padding(3); _itemShadowPadding = 4; _itemInvalidateMargin = 0; } #region Public Methods /// /// Gets the exact Y coordinate that corresponds to the specified time. /// This only works when in Expanded mode. /// /// Time of day to get Y coordinate /// /// Y coordinate corresponding to the specified time /// /// When calendar is not in Expaned mode. public int GetTimeY(TimeSpan time) { if (Calendar.DaysMode != CalendarDaysMode.Expanded) throw new InvalidOperationException("Can't measure Time's Y when calendar isn't in Expanded mode"); //If no days, no Y if (Calendar.Days == null || Calendar.Days.Length == 0) return 0; CalendarDay fisrtDay = Calendar.Days[0]; CalendarTimeScaleUnit firstUnit = fisrtDay.TimeUnits[0]; double duration = Convert.ToDouble(firstUnit.Duration.TotalMinutes); double totalmins = time.TotalMinutes; int unitIndex = Convert.ToInt32(Math.Floor(totalmins / duration)); double module = Convert.ToInt32(Math.Floor(totalmins % duration)); CalendarTimeScaleUnit unit = Calendar.Days[0].TimeUnits[unitIndex]; int minuteHeight = Convert.ToInt32(Convert.ToDouble(unit.Bounds.Height) / duration); return unit.Bounds.Top + minuteHeight * Convert.ToInt32(module); } /// /// Creates a rectangle with item roundess /// /// The instance containing the event data. /// The bounds. /// public GraphicsPath ItemRectangle(CalendarRendererItemBoundsEventArgs evtData, Rectangle bounds) { int pointerPadding = 5; if ((evtData.Item.Bounds.Top != evtData.Item.MinuteStartTop || evtData.Item.Bounds.Bottom != evtData.Item.MinuteEndTop) && (evtData.Item.MinuteEndTop != 0 && evtData.Item.MinuteStartTop != 0) && ! evtData.Item.IsOnDayTop && evtData.Calendar.DaysMode == CalendarDaysMode.Expanded) { /* * Trace pointed item * * C--------------------D * | | * A---B | * | | * H---G | * | | * F--------------------E */ int sq = ItemRoundness * 2; Point a = new Point(bounds.Left, evtData.Item.MinuteStartTop); Point b = new Point(a.X + pointerPadding, a.Y); Point c = new Point(b.X, bounds.Top); Point d = new Point(bounds.Right, c.Y); Point e = new Point(d.X, bounds.Bottom); Point f = new Point(b.X, e.Y); Point g = new Point(b.X, evtData.Item.MinuteEndTop); Point h = new Point(a.X, g.Y); GraphicsPath path = new GraphicsPath(); path.AddLine(a, b); path.AddLine(b, c); path.AddLine(c, new Point(d.X - sq, d.Y)); path.AddArc(new Rectangle(d.X - sq, d.Y, sq, sq), -90, 90); path.AddLine(new Point(d.X, d.Y + sq), new Point(d.X, e.Y - sq)); path.AddArc(new Rectangle(e.X - sq, e.Y - sq, sq, sq), 0, 90); path.AddLine(new Point(e.X - sq, e.Y), f); path.AddLine(f, g); path.AddLine(g, h); path.AddLine(h, a); path.CloseFigure(); return path; } else { Corners crns = Corners.None; if (evtData.IsFirst) { crns |= Corners.West; } if (evtData.IsLast) { crns |= Corners.East; } return RoundRectangle(bounds, ItemRoundness, crns); } } /// /// Fills the specified rectangle with item border roundness /// /// The instance containing the event data. /// The bounds. /// The north. /// The south. public void ItemFill(CalendarRendererItemBoundsEventArgs e, Rectangle bounds, Color north, Color south) { if (bounds.Width <= 0 || bounds.Height <= 0) { return; } using (GraphicsPath r = ItemRectangle(e, bounds)) { using (LinearGradientBrush b = new LinearGradientBrush(bounds, north, south, 90)) { e.Graphics.FillPath(b, r); } } } /// /// Items the pattern. /// /// The instance containing the event data. /// The bounds. /// Color of the pattern. public void ItemPattern(CalendarRendererItemBoundsEventArgs e, Rectangle bounds, Color patternColor) { if (bounds.Width <= 0 || bounds.Height <= 0) { return; } using (GraphicsPath r = ItemRectangle(e, bounds)) { using (Brush b = new HatchBrush(e.Item.Pattern, patternColor, Color.Transparent)) { e.Graphics.FillPath(b, r); } } } /// /// Draws the specified rectangle with item border roundnesss /// /// The instance containing the event data. /// The bounds. /// The color. /// The width. public void ItemBorder(CalendarRendererItemBoundsEventArgs e, Rectangle bounds, Color color, float width) { using (GraphicsPath r = ItemRectangle(e, bounds)) { using (Pen p = new Pen(color, width)) { e.Graphics.DrawPath(p, r); } } } /// /// Peform layout of elements and items of the calendar /// public void PerformLayout() { PerformLayout(true); } /// /// Updates the bounds of graphical elements. /// Optionally calls to update bounds of items. /// /// /// This method is called every time the control is resized. /// public void PerformLayout(bool performItemsLayout) { if (Calendar.Days == null) return; int leftStart = 0; int curLeft = 0; int curTop = 0; int dayWidth = 0; int dayHeight = 0; int scrollBarWidth = 20; TimeScaleBounds = Rectangle.Empty; if (Calendar.DaysMode == CalendarDaysMode.Expanded) { #region Expanded days TimeScaleBounds = new Rectangle(0, 0, TimeScaleWidth, Calendar.ClientRectangle.Height); curLeft = TimeScaleBounds.Right; dayHeight = Calendar.ClientSize.Height - 1; dayWidth = (Calendar.ClientSize.Width - TimeScaleBounds.Width - scrollBarWidth) / Calendar.Days.Length; for (int i = 0; i < Calendar.Days.Length; i++) { CalendarDay day = Calendar.Days[i]; day.SetBounds(new Rectangle(curLeft, curTop, dayWidth, dayHeight)); day.DayTop.SetBounds(new Rectangle(curLeft, day.HeaderBounds.Bottom, dayWidth, DayTopHeight)); curLeft += dayWidth + 1; //int k = 0; int utop = day.BodyBounds.Top + Calendar.TimeUnitsOffset * TimeScaleUnitHeight; for (int j = 0; j < day.TimeUnits.Length; j++) { CalendarTimeScaleUnit unit = day.TimeUnits[j]; if (Calendar.TimeUnitsOffset * -1 >= (j + 1)) { unit.SetVisible(false); } else { unit.SetVisible(true); //unit.SetBounds(new Rectangle(day.Bounds.Left, day.BodyBounds.Top + k++ * TimeScaleUnitHeight, day.Bounds.Width, TimeScaleUnitHeight)); } unit.SetBounds(new Rectangle(day.Bounds.Left, utop, day.Bounds.Width, TimeScaleUnitHeight)); utop += TimeScaleUnitHeight; } } #endregion } else { #region Short days (Calendar View) leftStart = WeekHeaderWidth; curLeft = leftStart; curTop = DayNameHeadersHeight; dayWidth = (Calendar.ClientSize.Width - leftStart - scrollBarWidth) / 7; dayHeight = (Calendar.ClientSize.Height - curTop) / (Calendar.Days.Length / 7) - 1; _dayNameHeaderColumns = new Rectangle[7]; int j = 0; for (int i = 0; i < Calendar.Days.Length; i++) { Calendar.Days[i].SetBounds(new Rectangle(curLeft, curTop, dayWidth, dayHeight)); curLeft += dayWidth + 1; if ((i + 1) % 7 == 0) { curTop += dayHeight + 1; curLeft = leftStart; } if (i < _dayNameHeaderColumns.Length) { _dayNameHeaderColumns[i] = new Rectangle(curLeft, 0, dayWidth, DayNameHeadersHeight); } if (Calendar.Days[i].Date.DayOfWeek == Calendar.FirstDayOfWeek) { Calendar.Weeks[j++].SetBounds(new Rectangle(0, curTop, Calendar.ClientSize.Width, dayHeight)); } } #endregion } if(performItemsLayout) PerformItemsLayout(); } /// /// Updates the bounds of CalendarItems /// public void PerformItemsLayout() { if (Calendar.Days == null || Calendar.Items.Count == 0) return; bool alldaychanged = false; int offset = Math.Abs(Calendar.TimeUnitsOffset); List itemsOnScene = new List(); foreach (CalendarDay day in Calendar.Days) day.ContainedItems.Clear(); if (Calendar.DaysMode == CalendarDaysMode.Expanded) { #region Expanded mode algorithm #region Assign units and initial coords int maxItemsOnDayTop = 0; foreach (CalendarItem item in Calendar.Items) { item.ClearBounds(); item.ClearPassings(); if (item.IsOnDayTop) { #region Among day tops CalendarDay dayStart = item.DayStart; CalendarDay dayEnd = item.DayEnd; if (dayStart == null) dayStart = Calendar.Days[0]; if (dayEnd == null) dayEnd = Calendar.Days[Calendar.Days.Length - 1]; for (int i = dayStart.Index; i <= dayEnd.Index; i++) { item.AddTopPassing(Calendar.Days[i].DayTop); Calendar.Days[i].DayTop.AddPassingItem(item); } item.SetBounds(Rectangle.FromLTRB(dayStart.DayTop.Bounds.Left, 0, dayEnd.DayTop.Bounds.Right, 1)); #endregion } else { #region Among time units CalendarDay day = item.DayStart; if (day == null) continue; double unitDurationMinutes = Convert.ToDouble((int)Calendar.TimeScale); DateTime date1 = item.StartDate; DateTime date2 = item.EndDate; int indexStart = Convert.ToInt32(Math.Floor(date1.TimeOfDay.TotalMinutes / unitDurationMinutes)); int indexEnd = Convert.ToInt32(Math.Ceiling(date2.TimeOfDay.TotalMinutes / unitDurationMinutes)); for (int i = 0; i < day.TimeUnits.Length; i++) { if (i >= indexStart && i < indexEnd) { day.TimeUnits[i].AddPassingItem(item); item.AddUnitPassing(day.TimeUnits[i]); } } item.SetBounds(Rectangle.Empty); itemsOnScene.Add(item); #endregion } } //Calendar.Items.Sort(CompareItems); #endregion #region Items on DayTops foreach (CalendarDay day in Calendar.Days) { maxItemsOnDayTop = Math.Max(maxItemsOnDayTop, day.DayTop.PassingItems.Count); } int[,] tmatix = new int[Calendar.Days.Length, maxItemsOnDayTop]; if (tmatix.GetLength(1) > 0) { foreach (CalendarItem item in Calendar.Items) { if (!item.IsOnDayTop) continue; item.TopsPassing.Sort(CompareTops); int topStart = item.TopsPassing[0].Day.Index; int topEnd = item.TopsPassing[item.TopsPassing.Count - 1].Day.Index; PlaceInMatrix(ref tmatix, Calendar.Items.IndexOf(item) + 1, topStart, topEnd); } int dayTopsHeight = tmatix.GetLength(1) * StandardItemHeight + DayTopMinHeight; if (DayTopHeight != dayTopsHeight) { DayTopHeight = dayTopsHeight; alldaychanged = true; } int itemHeight = StandardItemHeight;//Convert.ToInt32(Math.Floor(Convert.ToSingle(DayTopHeight) / Convert.ToSingle(tmatix.GetLength(1)))); foreach (CalendarItem item in Calendar.Items) { if (!item.IsOnDayTop) continue; int index = Calendar.Items.IndexOf(item); int top, left; FindInMatrix(tmatix, index + 1, out left, out top); Rectangle r = item.Bounds; r.Y = Calendar.Days[0].DayTop.Bounds.Top + top * itemHeight; r.Height = itemHeight; item.SetBounds(r); item.FirstAndLastRectangleGapping(); } } if (alldaychanged) PerformLayout(false); #endregion foreach (CalendarDay day in Calendar.Days) { #region Create groups maxItemsOnDayTop = Math.Max(maxItemsOnDayTop, day.DayTop.PassingItems.Count); List> groups = new List>(); List items = new List(day.ContainedItems); while (items.Count > 0) { List group = new List(); CollectIntersectingGroup(items[0], items, group); groups.Add(group); foreach (CalendarItem item in group) items.Remove(item); } #endregion foreach (List group in groups) { #region Create group matrix int maxConcurrent = 0; int startIndex, endIndex; GetGroupBoundUnits(group, out startIndex, out endIndex); //Get the maximum concurrent items for (int i = startIndex; i <= endIndex; i++) maxConcurrent = Math.Max(day.TimeUnits[i].PassingItems.Count, maxConcurrent); int[,] matix = new int[maxConcurrent, endIndex - startIndex + 1]; foreach (CalendarItem item in group) { int x = 0; item.UnitsPassing.Sort(CompareUnits); int unitStart = item.UnitsPassing[0].Index - startIndex; int unitEnd = unitStart + item.UnitsPassing.Count - 1; bool xFound = false; //if (startIndex + unitEnd < offset) //{ // item.SetIsOnView(false); // continue; //} //else //{ // item.SetIsOnView(true); //} while (!xFound) { xFound = true; for (int i = unitStart; i <= unitEnd; i++) { if (matix[x, i] != 0) { xFound = false; break; } } if (!xFound) x++; } for (int i = unitStart; i <= unitEnd; i++) { matix[x, i] = group.IndexOf(item) + 1; } } #endregion #region Expand Items foreach (CalendarItem item in group) { int index = group.IndexOf(item); int left, top; int height = item.UnitsPassing.Count; int width = 1; FindInMatrix(matix, index + 1, out left, out top); bool canExpand = left >= 0 && top >= 0; while (canExpand) { for (int i = top; i < top + height; i++) { if (matix.GetLength(0) <= left + width || matix[left + width, i] != 0) { canExpand = false; break; } } if (canExpand) { for (int i = top; i < top + height; i++) { matix[left + width, i] = index + 1; } width++; } } } #endregion #region Matrix to rectangles int itemWidth = Convert.ToInt32(Math.Floor(Convert.ToSingle(day.Bounds.Width - ItemsPadding) / Convert.ToSingle(matix.GetLength(0)))); foreach (CalendarItem item in group) { int index = group.IndexOf(item); int top, left; int width = 1; FindInMatrix(matix, index + 1, out left, out top); if (left >= 0 && top >= 0) { for (int i = left + 1; i < matix.GetLength(0); i++) { if (matix[i, top] == index + 1) { width++; } else { break; } } } int rtop = day.TimeUnits[item.UnitsPassing[0].Index].Bounds.Top; int bottom = day.TimeUnits[item.UnitsPassing[item.UnitsPassing.Count - 1].Index].Bounds.Bottom; int rleft = day.Bounds.Left + left * itemWidth; int right = rleft + itemWidth * width; item.SetBounds(Rectangle.FromLTRB(rleft, rtop, right, bottom)); item.SetMinuteStartTop(GetTimeY(item.StartDate.TimeOfDay)); item.SetMinuteEndTop(GetTimeY(item.EndDate.TimeOfDay)); } #endregion } } #endregion } else if (Calendar.DaysMode == CalendarDaysMode.Short) { #region Short mode algorithm Calendar.Items.Reverse(); for (int i = 0; i < Calendar.Days.Length; i++) { Calendar.Days[i].ContainedItems.Clear(); Calendar.Days[i].SetOverflowEnd(false); Calendar.Days[i].SetOverflowStart(false); } int maxItems = 0; foreach (CalendarItem item in Calendar.Items) { CalendarDay dayStart = item.DayStart; CalendarDay dayEnd = item.DayEnd; item.ClearBounds(); for (int i = dayStart.Index; i <= dayEnd.Index; i++) { Calendar.Days[i].AddContainedItem(item); maxItems = Math.Max(maxItems, Calendar.Days[i].ContainedItems.Count); } } int[,] matix = new int[Calendar.Days.Length, maxItems]; int curIndex = 0; foreach (CalendarItem item in Calendar.Items) { CalendarDay dayStart = item.DayStart; CalendarDay dayEnd = item.DayEnd; PlaceInMatrix(ref matix, curIndex + 1, dayStart.Index, dayEnd.Index); curIndex++; } for (int week = 0; week < Calendar.Weeks.Length; week++) { int xStart = week * 7; int xEnd = xStart + 6; int index = 0; int[,] wmatix = new int[7, matix.GetLength(1)]; CalendarDay sunday = Calendar.FindDay(Calendar.Weeks[week].StartDate); #region Fill week matrix for (int i = 0; i < wmatix.GetLength(1); i++) for (int j = 0; j < wmatix.GetLength(0); j++) wmatix[j, i] = matix[j + xStart, i]; #endregion foreach (CalendarItem item in Calendar.Items) { int left, top, width = 0; FindInMatrix(wmatix, ++index, out left, out top); if (left < 0 || top < 0) continue; for (int i = left; i < wmatix.GetLength(0); i++) if (wmatix[i, top] == index) width++; else break; CalendarDay dayStart = Calendar.Days[xStart + left]; CalendarDay dayEnd = Calendar.Days[xStart + left + width - 1]; Rectangle rStart = dayStart.Bounds; Rectangle rEnd = dayEnd.Bounds; int rtop = rStart.Top + DayHeaderHeight + top * StandardItemHeight; Rectangle r = Rectangle.FromLTRB(rStart.Left, rtop, rEnd.Right, rtop + StandardItemHeight); if (r.Bottom <= sunday.Bounds.Bottom) item.AddBounds(r); else for (int i = dayStart.Index; i <= dayEnd.Index; i++) Calendar.Days[i].SetOverflowEnd(true); } } foreach (CalendarItem item in Calendar.Items) item.FirstAndLastRectangleGapping(); Calendar.Items.Reverse(); #endregion } Calendar.RaiseItemsPositioned(); } /// /// Places the specified item in the matrix for the layout engine purposes /// /// /// /// /// private void PlaceInMatrix(ref int[,] m, int index, int startX, int endX) { int y = 0; bool yFound = false; while (!yFound && y < m.GetLength(1)) //HACK: && y < m.GetLength(1) //This is Because of some bug, possible item not showing { yFound = true; for (int i = startX; i <= endX; i++) { if (i >= 0 && i < m.GetLength(0) && m[i, y] != 0) { yFound = false; break; } } if (!yFound) y++; } if (y < m.GetLength(1)) //HACK: This if is because of same bug { for (int i = startX; i <= endX; i++) { m[i, y] = index; } } } #endregion #region Private Methods /// /// Gets the amout of units that can be displayed on the calendar viewport /// internal int GetVisibleTimeUnits() { if (Calendar.DaysMode == CalendarDaysMode.Short) { return 0; } else if (Calendar.Days != null && Calendar.Days.Length > 0) { return Convert.ToInt32(Math.Floor( Convert.ToSingle(Calendar.Days[0].BodyBounds.Height) / Convert.ToSingle(TimeScaleUnitHeight) )); } else { return 0; } } /// /// Sets the value of the property /// /// Height of all elements protected void SetDayTopHeight(int height) { _dayTopHeight = height; } /// /// Sets the value of the property /// /// Height of the day header protected void SetDayHeaderHeight(int height) { _dayHeaderHeight = height; } /// /// Sets the value of the property /// /// Height of the day name headers protected void SetDayNameHeadersHeight(int height) { _dayNameHeadersHeight = height; } /// /// Sets the value of the property /// /// Height of the time scale unit protected void SetTimeScaleUnitHeight(int height) { _timeScaleUnitHeight = height; } /// /// Sets the value of the property /// /// New width for the time scale protected void SetTimeScaleWidth(int width) { _timeScaleWidth = width; } /// /// Draws text using the information of the /// /// protected virtual void DrawStandarBoxText(CalendarRendererBoxEventArgs e) { TextRenderer.DrawText(e.Graphics, e.Text, e.Font, e.Bounds, e.TextColor, e.Format); } /// /// Outs the location of the specified number in the matrix /// /// Matrix to search in /// Number to find /// Result left /// Result top private void FindInMatrix(int[,] m, int number, out int left, out int top) { for (int i = 0; i < m.GetLength(1); i++) { for (int j = 0; j < m.GetLength(0); j++) { if (m[j, i] == number) { left = j; top = i; return; } } } left = top = -1; } /// /// Outs the startIndex and the endIndex of units in the group /// /// /// /// private void GetGroupBoundUnits(List group, out int startIndex, out int endIndex) { startIndex = int.MaxValue; endIndex = int.MinValue; foreach (CalendarItem item in group) { foreach (CalendarTimeScaleUnit unit in item.UnitsPassing) { startIndex = Math.Min(startIndex, unit.Index); endIndex = Math.Max(endIndex, unit.Index); } } } /// /// Recursive method that collects items intersecting on time, to graphically represent-them on the layout /// /// /// /// private void CollectIntersectingGroup(CalendarItem calendarItem, List items, List grouped) { if (!grouped.Contains(calendarItem)) grouped.Add(calendarItem); foreach (CalendarItem item in items) { if (!grouped.Contains(item) && calendarItem.IntersectsWith(item.StartDate.TimeOfDay, item.EndDate.TimeOfDay)) { grouped.Add(item); CollectIntersectingGroup(item, items, grouped); } } } /// /// Prints the specified matrix on debug /// /// private void PrintMatrix(int[,] m) { //return; Console.WriteLine("--------------------------------"); for (int i = 0; i < m.GetLength(1); i++) { for (int j = 0; j < m.GetLength(0); j++) { Console.Write(string.Format(" {0}", m[j, i])); } Console.WriteLine(" "); } Console.WriteLine("--------------------------------"); } #endregion #region Render Methods /// /// Initializes the Calendar /// /// public virtual void OnInitialize(CalendarRendererEventArgs e) { } /// /// Paints the background of the calendar /// /// Paint info public virtual void OnDrawBackground(CalendarRendererEventArgs e) { } /// /// Paints the timescale of the calendar /// /// Paint info public virtual void OnDrawTimeScale(CalendarRendererEventArgs e) { if (e.Calendar.DaysMode == CalendarDaysMode.Short || e.Calendar.Days == null || e.Calendar.Days.Length == 0 || e.Calendar.Days[0].TimeUnits == null ) return; Font hourFont = new Font(e.Calendar.Font.FontFamily, e.Calendar.Font.Size * (e.Calendar.TimeScale == CalendarTimeScale.SixtyMinutes ? 1f : 1.5f)); Font minuteFont = e.Calendar.Font; int hourLeft = TimeScaleBounds.Left; int hourWidth = TimeScaleBounds.Left + TimeScaleBounds.Width / 2; int minuteLeft = hourLeft + hourWidth; int minuteWidth = hourWidth; int k = 0; for (int i = 0; i < e.Calendar.Days[0].TimeUnits.Length; i++) { CalendarTimeScaleUnit unit = e.Calendar.Days[0].TimeUnits[i]; if (!unit.Visible) continue; string hours = unit.Hours.ToString("00"); string minutes = unit.Minutes == 0 ? "00" : string.Empty; if (!string.IsNullOrEmpty(minutes)) { switch( _calendar.CalendarTimeFormat ) { case CalendarTimeFormat.TwelveHour: if( Convert.ToInt32( hours ) > 12 ) hours = ( Convert.ToInt32( hours ) - 12 ).ToString(); break; case CalendarTimeFormat.TwentyFourHour: if( hours == "00" ) hours = "12"; break; } CalendarRendererBoxEventArgs hevt = new CalendarRendererBoxEventArgs(e, new Rectangle(hourLeft, unit.Bounds.Top, hourWidth, unit.Bounds.Height), hours, TextFormatFlags.Right); hevt.Font = hourFont; OnDrawTimeScaleHour(hevt); if (k++ == 0 || unit.Hours == 0 || unit.Hours == 12 ) minutes = unit.Date.ToString("tt"); CalendarRendererBoxEventArgs mevt = new CalendarRendererBoxEventArgs(e, new Rectangle(minuteLeft, unit.Bounds.Top, minuteWidth, unit.Bounds.Height), minutes, TextFormatFlags.Top | TextFormatFlags.Left); mevt.Font = minuteFont; OnDrawTimeScaleMinutes(mevt); } } } /// /// Paints an hour of a timescale unit /// /// Paint Info public virtual void OnDrawTimeScaleHour(CalendarRendererBoxEventArgs e) { DrawStandarBoxText(e); } /// /// Paints minutes of a timescale unit /// /// Paint Info public virtual void OnDrawTimeScaleMinutes(CalendarRendererBoxEventArgs e) { DrawStandarBoxText(e); } /// /// Paints the days on the current calendar view /// /// Paint Info public virtual void OnDrawDays(CalendarRendererEventArgs e) { for (int i = 0; i < e.Calendar.Days.Length; i++) { CalendarDay day = e.Calendar.Days[i]; e.Tag = day; OnDrawDay(new CalendarRendererDayEventArgs(e, day)); } } /// /// Paints the specified day on the calendar /// /// Paint info public virtual void OnDrawDay(CalendarRendererDayEventArgs e) { CalendarDay day = e.Day; CalendarRendererBoxEventArgs hevt = new CalendarRendererBoxEventArgs(e, day.HeaderBounds, day.Date.Day.ToString(), TextFormatFlags.VerticalCenter); hevt.Font = new Font(Calendar.Font, FontStyle.Bold); CalendarRendererBoxEventArgs devt = new CalendarRendererBoxEventArgs(e, day.HeaderBounds, day.Date.ToString("dddd"), TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter); OnDrawDayHeaderBackground(e); if (Calendar.DaysMode == CalendarDaysMode.Short && (day.Index == 0 || day.Date.Day == 1)) { hevt.Text = day.Date.ToString("dd MMM"); } OnDrawDayHeaderText(hevt); if (devt.TextSize.Width < day.HeaderBounds.Width - hevt.TextSize.Width * 2 && e.Calendar.DaysMode == CalendarDaysMode.Expanded) { OnDrawDayHeaderText(devt); } OnDrawDayTimeUnits(e); OnDrawDayTop(e); OnDrawDayBorder(e); } /// /// Paints the border of the specified day /// /// public virtual void OnDrawDayBorder(CalendarRendererDayEventArgs e) { } /// /// Draws the all day items area /// /// Paint Info public virtual void OnDrawDayTop(CalendarRendererDayEventArgs e) { } /// /// Paints the background of the specified day's header /// /// public virtual void OnDrawDayHeaderBackground(CalendarRendererDayEventArgs e) { } /// /// Paints the header of the specified day /// /// Paint info public virtual void OnDrawDayHeaderText(CalendarRendererBoxEventArgs e) { DrawStandarBoxText(e); } /// /// Raises the event. /// /// The instance containing the event data. public virtual void OnDrawDayTimeUnits(CalendarRendererDayEventArgs e) { for (int i = 0; i < e.Day.TimeUnits.Length; i++) { CalendarTimeScaleUnit unit = e.Day.TimeUnits[i]; if(unit.Visible) OnDrawDayTimeUnit(new CalendarRendererTimeUnitEventArgs(e, unit)); } } /// /// Draws a time unit of a day /// /// public virtual void OnDrawDayTimeUnit(CalendarRendererTimeUnitEventArgs e) { } /// /// Raises the event. /// /// The instance containing the event data. public virtual void OnDrawDayNameHeaders(CalendarRendererEventArgs e) { DateTime startDate = DateTime.Now.AddDays(-((int)DateTime.Now.DayOfWeek % 7) + 1 + (int)Calendar.FirstDayOfWeek); for (int i = 0; i < DayNameHeaderColumns.Length; i++) { OnDrawDayNameHeader(new CalendarRendererBoxEventArgs(e, DayNameHeaderColumns[i], startDate.AddDays(i).ToString("dddd"), TextFormatFlags.VerticalCenter | TextFormatFlags.HorizontalCenter)); } } /// /// Raises the event. /// /// The instance containing the event data. public virtual void OnDrawDayNameHeader(CalendarRendererBoxEventArgs e) { DrawStandarBoxText(e); } /// /// Draws the items of the calendar /// /// Event info public virtual void OnDrawItems(CalendarRendererEventArgs e) { Rectangle days = e.Calendar.DaysBodyRectangle; days.Inflate(-1, -1); Region oldclip = e.Graphics.Clip; bool doClip = e.Calendar.DaysMode == CalendarDaysMode.Expanded; bool clipped = false; #region Shadows foreach (CalendarItem item in e.Calendar.Items) { clipped = false; if (doClip && !item.IsOnDayTop && item.Bounds.Top < days.Top) { e.Graphics.SetClip(days, CombineMode.Intersect); clipped = true; } List rects = new List(item.GetAllBounds()); for (int i = 0; i < rects.Count; i++) { CalendarRendererItemBoundsEventArgs evt = new CalendarRendererItemBoundsEventArgs( new CalendarRendererItemEventArgs(e, item), rects[i], i == 0 && !item.IsOpenStart, (i == rects.Count - 1) && !item.IsOpenEnd); OnDrawItemShadow(evt); } if (clipped) e.Graphics.SetClip(oldclip, CombineMode.Replace); } #endregion #region Items foreach (CalendarItem item in e.Calendar.Items) { clipped = false; if( doClip && !item.IsOnDayTop && item.Bounds.Top < days.Top ) { e.Graphics.SetClip( days, CombineMode.Intersect ); clipped = true; } OnDrawItem(new CalendarRendererItemEventArgs(e, item)); if( clipped ) { e.Graphics.SetClip( oldclip, CombineMode.Replace ); } } #endregion #region Borders of selected items foreach (CalendarItem item in e.Calendar.Items) { if (!item.Selected) continue; List rects = new List(item.GetAllBounds()); for (int i = 0; i < rects.Count; i++) { CalendarRendererItemBoundsEventArgs evt = new CalendarRendererItemBoundsEventArgs( new CalendarRendererItemEventArgs(e, item), rects[i], i == 0 && !item.IsOpenStart, (i == rects.Count - 1) && !item.IsOpenEnd); SmoothingMode smbuff = e.Graphics.SmoothingMode; e.Graphics.SmoothingMode = SmoothingMode.HighQuality; OnDrawItemBorder(evt); e.Graphics.SmoothingMode = smbuff; } } #endregion } /// /// Draws an item of the calendar /// /// Event Info public virtual void OnDrawItem( CalendarRendererItemEventArgs e ) { List rects = new List( e.Item.GetAllBounds() ); for( int i = 0; i < rects.Count; i++ ) { CalendarRendererItemBoundsEventArgs evt = new CalendarRendererItemBoundsEventArgs( e, rects[i], i == 0 && !e.Item.IsOpenStart, ( i == rects.Count - 1 ) && !e.Item.IsOpenEnd ); OnDrawItemBackground( evt ); if( !evt.Item.PatternColor.IsEmpty ) { OnDrawItemPattern( evt ); } if( !e.Item.IsEditing ) { OnDrawItemContent( evt ); } OnDrawItemBorder( evt ); } } /// /// Draws the background of the specified item /// /// Event Info public virtual void OnDrawItemBackground(CalendarRendererItemBoundsEventArgs e) { } /// /// Raises the event. /// /// The instance containing the event data. public virtual void OnDrawItemPattern(CalendarRendererItemBoundsEventArgs e) { foreach (Rectangle bounds in e.Item.GetAllBounds()) { ItemPattern(e, bounds, e.Item.PatternColor); } } /// /// Draws the strings of an item. Strings inlude StartTime, EndTime and Text /// /// Event Info public virtual void OnDrawItemContent(CalendarRendererItemBoundsEventArgs e) { if (e.Item == e.Calendar.EditModeItem) return; List rectangles = new List(e.Item.GetAllBounds()); for (int i = 0; i < rectangles.Count; i++) { Rectangle bounds = rectangles[i]; Rectangle imageBounds = Rectangle.Empty; Rectangle rStartTime = new Rectangle(); Rectangle rEndTime = new Rectangle(); string endTime = string.Empty; string startTime = string.Empty; Color secondaryForecolor = e.Item.ForeColor; if (e.Item.ShowEndTime && i == rectangles.Count - 1) { endTime = e.Item.EndDateText; rEndTime = new Rectangle(Point.Empty, TextRenderer.MeasureText(endTime, e.Calendar.Font)); rEndTime = Rectangle.FromLTRB(bounds.Right - rEndTime.Width - ItemTextMargin.Right, bounds.Top + ItemTextMargin.Top, bounds.Right - ItemTextMargin.Right, bounds.Bottom - ItemTextMargin.Bottom); OnDrawItemEndTime(new CalendarRendererBoxEventArgs(e, rEndTime, endTime, secondaryForecolor)); } if (e.Item.ShowStartTime && i == 0) { startTime = e.Item.StartDateText; rStartTime = new Rectangle(Point.Empty, TextRenderer.MeasureText(startTime, e.Calendar.Font)); rStartTime.X = bounds.Left + ItemTextMargin.Left; rStartTime.Y = bounds.Top + ItemTextMargin.Top; rStartTime.Height = bounds.Height - ItemTextMargin.Vertical; OnDrawItemStartTime(new CalendarRendererBoxEventArgs(e, rStartTime, startTime, secondaryForecolor)); } Rectangle r = Rectangle.FromLTRB( bounds.Left + ItemTextMargin.Left + rStartTime.Width, bounds.Top + ItemTextMargin.Top, bounds.Right - ItemTextMargin.Right - rEndTime.Width, bounds.Bottom - ItemTextMargin.Bottom); CalendarRendererBoxEventArgs evt = new CalendarRendererBoxEventArgs(e, r, e.Item.Text, TextFormatFlags.Left | TextFormatFlags.Top); evt.Font = e.Item.Font; if( e.Item.ShowStartTime || e.Item.ShowEndTime ) { evt.Font = new Font( evt.Font, FontStyle.Bold ); } if( e.Item.IsOnDayTop || Calendar.DaysMode == CalendarDaysMode.Short ) { evt.Format |= TextFormatFlags.HorizontalCenter; } if (!e.Item.ForeColor.IsEmpty) { evt.TextColor = e.Item.ForeColor; } evt.Tag = e.Item; #region Image if (e.Item.Image != null) { Rectangle tBounds = e.Item.Bounds; imageBounds.Size = e.Item.Image.Size; switch (e.Item.ImageAlign) { case CalendarItemImageAlign.North: tBounds.Height -= imageBounds.Height; tBounds.Y += imageBounds.Height; imageBounds.Y = tBounds.Y - imageBounds.Height; break; case CalendarItemImageAlign.South: tBounds.Height -= imageBounds.Height; imageBounds.Y = tBounds.Bottom; break; case CalendarItemImageAlign.East: tBounds.Width -= imageBounds.Width; imageBounds.X = tBounds.Right; break; case CalendarItemImageAlign.West: tBounds.Width -= imageBounds.Width; tBounds.X += imageBounds.Width; imageBounds.X = tBounds.Left - imageBounds.Width; break; } switch (e.Item.ImageAlign) { case CalendarItemImageAlign.North: case CalendarItemImageAlign.South: imageBounds.X = e.Item.Bounds.X + ( ( e.Item.Bounds.Width - imageBounds.Width ) / 2); break; case CalendarItemImageAlign.East: case CalendarItemImageAlign.West: imageBounds.Y = e.Item.Bounds.Y + ((e.Item.Bounds.Height - imageBounds.Height) / 2); break; } evt.Bounds = tBounds; OnDrawItemImage(new CalendarRendererItemBoundsEventArgs(e, imageBounds, false, false)); } #endregion OnDrawItemText(evt); } } /// /// Draws the text of an item /// /// public virtual void OnDrawItemText(CalendarRendererBoxEventArgs e) { DrawStandarBoxText(e); } /// /// Draws the image of an item /// /// public virtual void OnDrawItemImage(CalendarRendererItemBoundsEventArgs e) { if (e.Item.Image != null) { e.Graphics.DrawImage(e.Item.Image, e.Bounds); } } /// /// Draws the starttime of the item if applicable /// /// Event data public virtual void OnDrawItemStartTime(CalendarRendererBoxEventArgs e) { DrawStandarBoxText(e); } /// /// Draws the end time of the item if applicable /// /// Event data public virtual void OnDrawItemEndTime(CalendarRendererBoxEventArgs e) { DrawStandarBoxText(e); } /// /// Draws the border of the specified item /// /// Event Info public virtual void OnDrawItemBorder(CalendarRendererItemBoundsEventArgs e) { } /// /// Draws the shadow of the specified item /// /// public virtual void OnDrawItemShadow(CalendarRendererItemBoundsEventArgs e) { } /// /// Draws the overflows of days /// /// public virtual void OnDrawOverflows(CalendarRendererEventArgs e) { for (int i = 0; i < e.Calendar.Days.Length; i++) { CalendarDay day = e.Calendar.Days[i]; if (day.OverflowStart) { OnDrawDayOverflowStart(new CalendarRendererDayEventArgs(e, day)); } if(day.OverflowEnd) { OnDrawDayOverflowEnd(new CalendarRendererDayEventArgs(e, day)); } } } /// /// Draws the overflow to start of specified day /// /// Event data public virtual void OnDrawDayOverflowStart(CalendarRendererDayEventArgs e) { } /// /// Draws the overflow to end of specified day /// /// public virtual void OnDrawDayOverflowEnd(CalendarRendererDayEventArgs e) { //e.Graphics.FillRectangle(Brushes.Red, e.Day.OverflowEndBounds); } /// /// Raises the event. /// /// The instance containing the event data. public virtual void OnDrawWeekHeaders(CalendarRendererEventArgs e) { if (Calendar.Weeks == null) return; for (int i = 0; i < Calendar.Weeks.Length; i++) { string str = Calendar.Weeks[i].ToStringLarge(); SizeF sz = e.Graphics.MeasureString(str, e.Calendar.Font); if (sz.Width > Calendar.Weeks[i].HeaderBounds.Height) { str = Calendar.Weeks[i].ToStringShort(); } OnDrawWeekHeader(new CalendarRendererBoxEventArgs(e, Calendar.Weeks[i].HeaderBounds, str, TextFormatFlags.Default)); } } /// /// Raises the event. /// /// The instance containing the event data. public virtual void OnDrawWeekHeader(CalendarRendererBoxEventArgs e) { StringFormat sf = new StringFormat(); sf.FormatFlags = StringFormatFlags.DirectionVertical | StringFormatFlags.DirectionRightToLeft | StringFormatFlags.NoWrap; sf.LineAlignment = StringAlignment.Center; sf.Alignment = StringAlignment.Center; using (SolidBrush b = new SolidBrush(e.TextColor)) { e.Graphics.DrawString(e.Text, e.Font, b, e.Bounds, sf); } e.Graphics.ResetTransform(); sf.Dispose(); } #endregion } }