Files
2019-04-23 12:29:22 -07:00

891 lines
28 KiB
C#

/*
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 <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace WindowsFormsCalendar
{
/// <summary>
/// Represents an item of the calendar with a date and timespan
/// </summary>
/// <remarks>
/// <para>CalendarItem provides a graphical representation of tasks within a date range.</para>
/// </remarks>
public class CalendarItem
: CalendarSelectableElement
{
#region Static
/// <summary>
/// Compares the bounds.
/// </summary>
/// <param name="r1">The r1.</param>
/// <param name="r2">The r2.</param>
/// <returns></returns>
private static int CompareBounds( Rectangle r1, Rectangle r2 )
{
return r1.Top.CompareTo( r2.Top );
}
#endregion
#region Events
#endregion
#region Fields
private Rectangle[] _additionalBounds;
private Color _backgroundColor;
private Color _backgroundColorLighter;
private Color _borderColor;
private DateTime _startDate;
private DateTime _endDate;
private Color _foreColor;
private bool _locked;
private TimeSpan _duration;
private Image _image;
private CalendarItemImageAlign _imageAlign;
private bool _isDragging;
private bool _isEditing;
private bool _isResizingStartDate;
private bool _isResizingEndDate;
private bool _isOnView;
private int _minuteStartTop;
private int _minuteEndTop;
private HatchStyle _pattern;
private Color _patternColor;
private List<CalendarTimeScaleUnit> _unitsPassing;
private List<CalendarDayTop> _topsPassing;
private object _tag;
private string _text;
private Font _font;
#endregion
#region Properties
/// <summary>
/// Gets or sets an array of rectangles containing bounds additional to Bounds property.
/// </summary>
/// <remarks>
/// Items may contain additional bounds because of several graphical occourences, mostly when <see cref="Calendar"/> in
/// <see cref="CalendarDaysMode.Short"/> mode, due to the duration of the item; e.g. when an all day item lasts several weeks,
/// one rectangle for week must be drawn to indicate the presence of the item.
/// </remarks>
public virtual Rectangle[] AditionalBounds
{
get { return _additionalBounds; }
set { _additionalBounds = value; }
}
/// <summary>
/// Gets or sets the a background color for the object. If Color.Empty, renderer default's will be used.
/// </summary>
public Color BackgroundColor
{
get { return _backgroundColor; }
set { _backgroundColor = value; }
}
/// <summary>
/// Gets or sets the lighter background color of the item
/// </summary>
public Color BackgroundColorLighter
{
get { return _backgroundColorLighter; }
set { _backgroundColorLighter = value; }
}
/// <summary>
/// Gets or sets the bordercolor of the item. If Color.Empty, renderer default's will be used.
/// </summary>
public Color BorderColor
{
get { return _borderColor; }
set { _borderColor = value; }
}
/// <summary>
/// Gets the StartDate of the item. Implemented
/// </summary>
public override DateTime Date
{
get
{
return StartDate;
}
}
/// <summary>
/// Gets the day on the <see cref="Calendar"/> where this item ends
/// </summary>
/// <remarks>
/// This day is not necesarily the day corresponding to the day on <see cref="EndDate"/>,
/// since this date can be out of the range of the current view.
/// <para>If Item is not on view date range this property will return null.</para>
/// </remarks>
public CalendarDay DayEnd
{
get
{
if( !IsOnViewDateRange )
{
return null;
}
else if( IsOpenEnd )
{
return Calendar.Days[Calendar.Days.Length - 1];
}
else
{
return Calendar.FindDay( EndDate );
}
}
}
/// <summary>
/// Gets the day on the <see cref="Calendar"/> where this item starts
/// </summary>
/// <remarks>
/// This day is not necesarily the day corresponding to the day on <see cref="StartDate"/>,
/// since start date can be out of the range of the current view.
/// <para>If Item is not on view date range this property will return null.</para>
/// </remarks>
public CalendarDay DayStart
{
get
{
if( !IsOnViewDateRange )
{
return null;
}
else if( IsOpenStart )
{
return Calendar.Days[0];
}
else
{
return Calendar.FindDay( StartDate );
}
}
}
/// <summary>
/// Gets the duration of the item
/// </summary>
public TimeSpan Duration
{
get
{
if( _duration.TotalMinutes == 0 )
{
_duration = EndDate.Subtract( StartDate );
}
return _duration;
}
}
/// <summary>
/// Gets or sets the end time of the item
/// </summary>
public DateTime EndDate
{
get { return _endDate; }
set
{
_endDate = value;
_duration = new TimeSpan( 0, 0, 0 );
ClearPassings();
}
}
/// <summary>
/// Gets the text of the end date
/// </summary>
public virtual string EndDateText
{
get
{
string date = string.Empty;
string time = string.Empty;
if( IsOpenEnd )
{
date = EndDate.ToString( Calendar.ItemsDateFormat );
}
if( ShowEndTime && !EndDate.TimeOfDay.Equals( new TimeSpan( 23, 59, 59 ) ) )
{
time = EndDate.ToString( Calendar.ItemsTimeFormat );
}
return string.Format( "{0} {1}", date, time ).Trim();
}
}
/// <summary>
/// Gets or sets the forecolor of the item. If Color.Empty, renderer default's will be used.
/// </summary>
public Color ForeColor
{
get { return _foreColor; }
set
{
_foreColor = value;
}
}
/// <summary>
/// Gets or sets an image for the item
/// </summary>
public Image Image
{
get { return _image; }
set { _image = value; }
}
/// <summary>
/// Gets or sets the alignment of the image relative to the text
/// </summary>
public CalendarItemImageAlign ImageAlign
{
get { return _imageAlign; }
set { _imageAlign = value; }
}
/// <summary>
/// Gets a value indicating if the item is being dragged
/// </summary>
public bool IsDragging
{
get { return _isDragging; }
}
/// <summary>
/// Gets a value indicating if the item is currently being edited by the user
/// </summary>
public bool IsEditing
{
get { return _isEditing; }
}
/// <summary>
/// Gets a value indicating if the item goes on the DayTop area of the <see cref="CalendarDay"/>
/// </summary>
public bool IsOnDayTop
{
get
{
return StartDate.Day != EndDate.AddSeconds( 1 ).Day;
}
}
/// <summary>
/// Gets a value indicating if the item is currently on view.
/// </summary>
/// <remarks>
/// The item may not be on view because of scrolling
/// </remarks>
public bool IsOnView
{
get { return _isOnView; }
}
/// <summary>
/// Gets a value indicating if the item is on the range specified by <see cref="Calendar.ViewStart"/> and <see cref="Calendar.ViewEnd"/>
/// </summary>
public bool IsOnViewDateRange
{
get
{
//Checks for an intersection of item's dates against calendar dates
DateTime fd = Calendar.Days[0].Date;
DateTime ld = Calendar.Days[Calendar.Days.Length - 1].Date.Add( new TimeSpan( 23, 59, 59 ) );
DateTime sd = StartDate;
DateTime ed = EndDate;
return sd < ld && fd < ed;
}
}
/// <summary>
/// Gets a value indicating if the item's <see cref="StartDate"/> is before the <see cref="Calendar.ViewStart"/> date.
/// </summary>
public bool IsOpenStart
{
get
{
return StartDate.CompareTo( Calendar.Days[0].Date ) < 0;
}
}
/// <summary>
/// Gets a value indicating if the item's <see cref="EndDate"/> is aftter the <see cref="Calendar.ViewEnd"/> date.
/// </summary>
public bool IsOpenEnd
{
get
{
return EndDate.CompareTo( Calendar.Days[Calendar.Days.Length - 1].Date.Add( new TimeSpan( 23, 59, 59 ) ) ) > 0;
}
}
/// <summary>
/// Gets a value indicating if item is being resized by the <see cref="StartDate"/>
/// </summary>
public bool IsResizingStartDate
{
get { return _isResizingStartDate; }
}
/// <summary>
/// Gets a value indicating if item is being resized by the <see cref="EndDate"/>
/// </summary>
public bool IsResizingEndDate
{
get { return _isResizingEndDate; }
}
/// <summary>
/// Gets a value indicating if this item is locked.
/// </summary>
/// <remarks>
/// When an item is locked, the user can't drag it or change it's text
/// </remarks>
public bool Locked
{
get { return _locked; }
set { _locked = value; }
}
/// <summary>
/// Gets the top correspoinding to the ending minute
/// </summary>
public int MinuteEndTop
{
get { return _minuteEndTop; }
}
/// <summary>
/// Gets the top corresponding to the starting minute
/// </summary>
public int MinuteStartTop
{
get { return _minuteStartTop; }
}
/// <summary>
/// Gets or sets the units that this item passes by
/// </summary>
internal List<CalendarTimeScaleUnit> UnitsPassing
{
get { return _unitsPassing; }
set { _unitsPassing = value; }
}
/// <summary>
/// Gets or sets the pattern style to use in the background of item.
/// </summary>
public HatchStyle Pattern
{
get { return _pattern; }
set { _pattern = value; }
}
/// <summary>
/// Gets or sets the pattern's color
/// </summary>
public Color PatternColor
{
get { return _patternColor; }
set { _patternColor = value; }
}
/// <summary>
/// Gets the list of DayTops that this item passes thru
/// </summary>
internal List<CalendarDayTop> TopsPassing
{
get { return _topsPassing; }
}
/// <summary>
/// Gets a value indicating if the item should show the time of the <see cref="StartDate"/>
/// </summary>
public bool ShowStartTime
{
get
{
return IsOpenStart || ( ( this.IsOnDayTop || Calendar.DaysMode == CalendarDaysMode.Short ) && !StartDate.TimeOfDay.Equals( new TimeSpan( 0, 0, 0 ) ) );
}
}
/// <summary>
/// Gets a value indicating if the item should show the time of the <see cref="EndDate"/>
/// </summary>
public virtual bool ShowEndTime
{
get
{
return ( IsOpenEnd ||
( ( this.IsOnDayTop || Calendar.DaysMode == CalendarDaysMode.Short ) && !EndDate.TimeOfDay.Equals( new TimeSpan( 23, 59, 59 ) ) ) ) &&
!( Calendar.DaysMode == CalendarDaysMode.Short && StartDate.Date == EndDate.Date );
}
}
/// <summary>
/// Gets the text of the start date
/// </summary>
public virtual string StartDateText
{
get
{
string date = string.Empty;
string time = string.Empty;
if( IsOpenStart )
{
date = StartDate.ToString( Calendar.ItemsDateFormat );
}
if( ShowStartTime && !StartDate.TimeOfDay.Equals( new TimeSpan( 0, 0, 0 ) ) )
{
time = StartDate.ToString( Calendar.ItemsTimeFormat );
}
return string.Format( "{0} {1}", date, time ).Trim();
}
}
/// <summary>
/// Gets or sets the start time of the item
/// </summary>
public virtual DateTime StartDate
{
get { return _startDate; }
set
{
_startDate = value;
_duration = new TimeSpan( 0, 0, 0 );
ClearPassings();
}
}
/// <summary>
/// Gets or sets a tag object for the item
/// </summary>
public object Tag
{
get { return _tag; }
set { _tag = value; }
}
/// <summary>
/// Gets or sets the text of the item
/// </summary>
public virtual string Text
{
get { return _text; }
set { _text = value; }
}
/// <summary>
/// Gets or sets the font.
/// </summary>
/// <value>
/// The font.
/// </value>
public Font Font
{
get { return _font; }
set { _font = value; }
}
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="CalendarItem"/> class.
/// </summary>
/// <param name="calendar"></param>
public CalendarItem( Calendar calendar )
: base( calendar )
{
_unitsPassing = new List<CalendarTimeScaleUnit>();
_topsPassing = new List<CalendarDayTop>();
_backgroundColor = Color.Empty;
_borderColor = Color.Empty;
_foreColor = Color.Empty;
_backgroundColorLighter = Color.Empty;
_imageAlign = CalendarItemImageAlign.West;
_font = calendar.ItemsFont;
_backgroundColor = calendar.ItemsBackgroundColor;
_foreColor = calendar.ItemsForeColor;
}
/// <summary>
/// Initializes a new instance of the <see cref="CalendarItem"/> class.
/// </summary>
/// <param name="calendar">The calendar.</param>
/// <param name="startDate">The start date.</param>
/// <param name="endDate">The end date.</param>
/// <param name="text">The text.</param>
public CalendarItem( Calendar calendar, DateTime startDate, DateTime endDate, string text )
: this( calendar )
{
StartDate = startDate;
EndDate = endDate;
Text = text;
}
/// <summary>
/// Initializes a new instance of the <see cref="CalendarItem"/> class.
/// </summary>
/// <param name="calendar">The calendar.</param>
/// <param name="startDate">The start date.</param>
/// <param name="duration">The duration.</param>
/// <param name="text">The text.</param>
public CalendarItem( Calendar calendar, DateTime startDate, TimeSpan duration, string text )
: this( calendar, startDate, startDate.Add( duration ), text )
{ }
#region Public Methods
/// <summary>
/// Applies color to background, border, and forecolor, from the specified color.
/// </summary>
/// <param name="color">The color.</param>
public void ApplyColor( Color color )
{
BackgroundColor = color;
BackgroundColorLighter = Color.FromArgb(
color.R + ( 255 - color.R ) / 2 + ( 255 - color.R ) / 3,
color.G + ( 255 - color.G ) / 2 + ( 255 - color.G ) / 3,
color.B + ( 255 - color.B ) / 2 + ( 255 - color.B ) / 3 );
BorderColor = Color.FromArgb(
Convert.ToInt32( Convert.ToSingle( color.R ) * .8f ),
Convert.ToInt32( Convert.ToSingle( color.G ) * .8f ),
Convert.ToInt32( Convert.ToSingle( color.B ) * .8f ) );
int avg = ( color.R + color.G + color.B ) / 3;
if( avg > 255 / 2 )
{
ForeColor = Color.Black;
}
else
{
ForeColor = Color.White;
}
}
/// <summary>
/// Gets all the bounds related to the item.
/// </summary>
/// <returns></returns>
/// <remarks>
/// Items that are broken on two or more weeks may have more than one rectangle bounds.
/// </remarks>
public IEnumerable<Rectangle> GetAllBounds()
{
List<Rectangle> r = new List<Rectangle>( AditionalBounds == null ? new Rectangle[] { } : AditionalBounds );
r.Add( Bounds );
r.Sort( CompareBounds );
return r;
}
/// <summary>
/// Removes all specific coloring for the item.
/// </summary>
public void RemoveColors()
{
BackgroundColor = Color.Empty;
ForeColor = Color.Empty;
BorderColor = Color.Empty;
}
/// <summary>
/// Gets a value indicating if the specified point is in a resize zone of <see cref="StartDate"/>
/// </summary>
/// <param name="point">The point.</param>
/// <returns></returns>
public bool ResizeStartDateZone( Point point )
{
int margin = 4;
List<Rectangle> rects = new List<Rectangle>( GetAllBounds() );
Rectangle first = rects[0];
Rectangle last = rects[rects.Count - 1];
if( IsOnDayTop || Calendar.DaysMode == CalendarDaysMode.Short )
{
return Rectangle.FromLTRB( first.Left, first.Top, first.Left + margin, first.Bottom ).Contains( point );
}
else
{
return Rectangle.FromLTRB( first.Left, first.Top, first.Right, first.Top + margin ).Contains( point );
}
}
/// <summary>
/// Gets a value indicating if the specified point is in a resize zone of <see cref="EndDate"/>
/// </summary>
/// <param name="point">The point.</param>
/// <returns></returns>
public bool ResizeEndDateZone( Point point )
{
int margin = 4;
List<Rectangle> rects = new List<Rectangle>( GetAllBounds() );
Rectangle first = rects[0];
Rectangle last = rects[rects.Count - 1];
if( IsOnDayTop || Calendar.DaysMode == CalendarDaysMode.Short )
{
return Rectangle.FromLTRB( last.Right - margin, last.Top, last.Right, last.Bottom ).Contains( point );
}
else
{
return Rectangle.FromLTRB( last.Left, last.Bottom - margin, last.Right, last.Bottom ).Contains( point );
}
}
/// <summary>
/// Sets the bounds of the item
/// </summary>
/// <param name="rectangle">The rectangle.</param>
public new void SetBounds( Rectangle rectangle )
{
base.SetBounds( rectangle );
}
/// <summary>
/// Indicates if the time of the item intersects with the provided time
/// </summary>
/// <param name="startTime">The start time.</param>
/// <param name="endTime">The end time.</param>
/// <returns></returns>
public bool IntersectsWith( TimeSpan startTime, TimeSpan endTime )
{
Rectangle r1 = Rectangle.FromLTRB( 0, Convert.ToInt32( StartDate.TimeOfDay.TotalMinutes ), 5, Convert.ToInt32( EndDate.TimeOfDay.TotalMinutes ) );
Rectangle r2 = Rectangle.FromLTRB( 0, Convert.ToInt32( startTime.TotalMinutes ), 5, Convert.ToInt32( endTime.TotalMinutes - 1 ) );
return r1.IntersectsWith( r2 );
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String"/> that represents this instance.
/// </returns>
public override string ToString()
{
return string.Format( "{0} - {1}", StartDate.ToShortTimeString(), EndDate.ToShortTimeString() );
}
#endregion
#region Private Methods
/// <summary>
/// Adds bounds for the item
/// </summary>
/// <param name="r"></param>
internal void AddBounds( Rectangle r )
{
if( r.IsEmpty ) throw new ArgumentException( "r can't be empty" );
if( Bounds.IsEmpty )
{
SetBounds( r );
}
else
{
List<Rectangle> rs = new List<Rectangle>( AditionalBounds == null ? new Rectangle[] { } : AditionalBounds );
rs.Add( r );
AditionalBounds = rs.ToArray();
}
}
/// <summary>
/// Adds the specified unit as a passing unit
/// </summary>
/// <param name="calendarTimeScaleUnit"></param>
internal void AddUnitPassing( CalendarTimeScaleUnit calendarTimeScaleUnit )
{
if( !UnitsPassing.Contains( calendarTimeScaleUnit ) )
{
UnitsPassing.Add( calendarTimeScaleUnit );
}
}
/// <summary>
/// Adds the specified <see cref="CalendarDayTop"/> as a passing one
/// </summary>
/// <param name="top"></param>
internal void AddTopPassing( CalendarDayTop top )
{
if( !TopsPassing.Contains( top ) )
{
TopsPassing.Add( top );
}
}
/// <summary>
/// Clears the item's existance off passing units and tops
/// </summary>
internal void ClearPassings()
{
foreach( CalendarTimeScaleUnit unit in UnitsPassing )
{
unit.ClearItemExistance( this );
}
UnitsPassing.Clear();
TopsPassing.Clear();
}
/// <summary>
/// Clears all bounds of the item
/// </summary>
internal void ClearBounds()
{
SetBounds( Rectangle.Empty );
AditionalBounds = new Rectangle[] { };
SetMinuteStartTop( 0 );
SetMinuteEndTop( 0 );
}
/// <summary>
/// It pushes the left and the right to the center of the item
/// to visually indicate start and end time
/// </summary>
internal void FirstAndLastRectangleGapping()
{
if( !IsOpenStart )
SetBounds( Rectangle.FromLTRB( Bounds.Left + Calendar.Renderer.ItemsPadding,
Bounds.Top, Bounds.Right, Bounds.Bottom ) );
if( !IsOpenEnd )
{
if( AditionalBounds != null && AditionalBounds.Length > 0 )
{
Rectangle r = AditionalBounds[AditionalBounds.Length - 1];
AditionalBounds[AditionalBounds.Length - 1] = Rectangle.FromLTRB(
r.Left, r.Top, r.Right - Calendar.Renderer.ItemsPadding, r.Bottom );
}
else
{
Rectangle r = Bounds;
SetBounds( Rectangle.FromLTRB(
r.Left, r.Top, r.Right - Calendar.Renderer.ItemsPadding, r.Bottom ) );
}
}
}
/// <summary>
/// Sets the value of the IsDragging property
/// </summary>
/// <param name="dragging">Value indicating if the item is currently being dragged</param>
internal void SetIsDragging( bool dragging )
{
_isDragging = dragging;
}
/// <summary>
/// Sets the value of the <see cref="IsEditing"/> property
/// </summary>
/// <param name="editing">Value indicating if user is currently being editing</param>
internal void SetIsEditing( bool editing )
{
_isEditing = editing;
}
/// <summary>
/// Sets the value of the <see cref="IsOnView"/> property
/// </summary>
/// <param name="onView">Indicates if the item is currently on view</param>
internal void SetIsOnView( bool onView )
{
_isOnView = onView;
}
/// <summary>
/// Sets the value of the <see cref="IsResizingStartDate"/> property
/// </summary>
/// <param name="resizing"></param>
internal void SetIsResizingStartDate( bool resizing )
{
_isResizingStartDate = resizing;
}
/// <summary>
/// Sets the value of the <see cref="IsResizingEndDate"/> property
/// </summary>
/// <param name="resizing"></param>
internal void SetIsResizingEndDate( bool resizing )
{
_isResizingEndDate = resizing;
}
/// <summary>
/// Sets the value of the <see cref="MinuteStartTop"/> property
/// </summary>
/// <param name="top"></param>
internal void SetMinuteStartTop( int top )
{
_minuteStartTop = top;
}
/// <summary>
/// Sets the value of the <see cref="MinuteEndTop"/> property
/// </summary>
/// <param name="top"></param>
internal void SetMinuteEndTop( int top )
{
_minuteEndTop = top;
}
#endregion
}
}