/* Copyright (c) Cloud Software Group, Inc. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; using XenAdmin.Core; namespace XenAdmin.Controls.Wlb { public partial class WeekView : UserControl { #region Public Events public event MouseEventHandler OnTriggerPointClick; public event MouseEventHandler OnTriggerPointDoubleClick; #endregion #region Private Fields private const int CONTROL_MIMIMUM_WIDTH = 350; private const int CONTROL_MINIMUM_HEIGHT = 45; private const int DAYS_IN_WEEK = 7; private const int HOURS_IN_DAY = 24; private const int TOOLTIP_DELAY = 500; private Color _gridColor = SystemColors.Control; //Color.Black; private Color _hourLineColor = Color.DarkGray; private int _hourLineInterval = 6; //one tick line every 6 hours private int _smallTickHeight = 3; private int _largeTickHeight = 5; private Color _hourLabelColor = Color.DarkGray; private Padding _hourLabelPadding = new Padding(3); private Font _hourLabelFont; private int _hourLabelInterval = 6; //one hour label every 6 hours private Color _dayLabelColor = Color.DarkGray; private Padding _dayLabelPadding = new Padding(3); private Color _currentTimeColor = Color.Red; private bool _showCurrenttimeMarkWidth = true; private Color _hightlightColor = Color.Yellow; private HighlightType _highlightType = HighlightType.None; private Padding _barPadding = new Padding(3); private int _barHeight = 10; private TriggerPoint _selectedTriggerPoint = null; #endregion #region Local Variables private int _gridWidth; private int _gridHeight; private float _dayLabelHeight; private float _hourLabelHeight; private float _dayWidth; private float _hourWidth; private float _hourIntervalWidth; private int _tickLineY; private int _barLineY; private int _barAreaHeight; private Point _lastMouseLocation = new Point(); private bool _ignoreNextOnMouseMove; private TriggerPoints _triggerPoints; private Dictionary _taskMap = new Dictionary(); private Point _toolTipLocation = new Point(); private ToolTip _toolTip = new ToolTip(); private Timer _toolTipTimer = new Timer(); private string _toolTipText = string.Empty; #endregion #region Public Enums public enum HighlightType { None, Bar, Box } #endregion #region Public Properties [EditorBrowsable(EditorBrowsableState.Always), Browsable(true), Bindable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color GridColor { get { return _gridColor; } set { _gridColor = value; } } public Color HourLineColor { get { return _hourLineColor; } set { _hourLineColor = value; } } public Color HourLabelColor { get { return _hourLabelColor; } set { _hourLabelColor = value; } } public Color DayLabelColor { get { return _dayLabelColor; } set { _dayLabelColor = value; } } public Color CurrentTimeMarkColor { get { return _currentTimeColor; } set { _currentTimeColor = value; } } public Color HightlightColor { get { return _hightlightColor; } set { _hightlightColor = value; } } public HighlightType SelectedItemHighlightType { get { return _highlightType; } set { _highlightType = value; } } public Padding DayLabelPadding { get { return _dayLabelPadding; } set { _dayLabelPadding = value; } } public Padding HourLabelPadding { get { return _hourLabelPadding; } set { _hourLabelPadding = value; } } public Padding BarPadding { get { return _barPadding; } set { _barPadding = value; } } //Day font uses the control's Font property public Font HourLabelFont { get { return _hourLabelFont; } set { _hourLabelFont = value; } } public int HourLineInterval { get { return _hourLineInterval; } set { _hourLineInterval = value; } } public int HourLabelInterval { get { return _hourLabelInterval; } set { _hourLabelInterval = value; } } public int LargeTickHeight { get { return _largeTickHeight; } set { if (value > _smallTickHeight) { _largeTickHeight = value; } } } public int SmalltickHeight { get { return _smallTickHeight; } set { if (value < _largeTickHeight) { _smallTickHeight = value; } } } public int BarHeight { get { return _barHeight; } set { _barHeight = value; } } public TriggerPoints TriggerPoints { get { return _triggerPoints; } set { _triggerPoints = value; _taskMap = new Dictionary(); this.Refresh(); } } public bool ShowCurrentTimeMark { get { return _showCurrenttimeMarkWidth; } set { _showCurrenttimeMarkWidth = value; } } public override Size MinimumSize { get { return new Size(CONTROL_MIMIMUM_WIDTH, CONTROL_MINIMUM_HEIGHT); } set { if (value.Width >= CONTROL_MIMIMUM_WIDTH && value.Height >= CONTROL_MINIMUM_HEIGHT) { base.MinimumSize = value; } } } public TriggerPoint SelectedTriggerPoint { get { return _selectedTriggerPoint; } } #endregion #region Public ctor public WeekView() { InitializeComponent(); this.GridColor = Color.Black; this.HourLabelFont = this.Font; _triggerPoints = new TriggerPoints(); _toolTip.Popup += toolTip_Popup; _toolTipTimer.Interval = TOOLTIP_DELAY; _toolTipTimer.Tick += toolTipTimer_Tick; } #endregion #region Public Methods public TriggerPoint AddTriggerPoint(TriggerPoint triggerPoint) { _triggerPoints.Add(triggerPoint); this.Refresh(); return _triggerPoints.List[triggerPoint.SortKey]; } public void ClearTriggerPoints() { _triggerPoints.Clear(); this.Refresh(); } #endregion #region Control Event Handlers private void WeekView_Paint(object sender, PaintEventArgs e) { Graphics graphics = e.Graphics; //initialize grid width _gridWidth = this.Width - this.Margin.Left - this.Margin.Right; _gridHeight = this.Height - this.Margin.Top - this.Margin.Bottom; //Virtual spacing elements _dayWidth = _gridWidth / (float)DAYS_IN_WEEK; _hourWidth = _dayWidth / (float)HOURS_IN_DAY; _hourIntervalWidth = _hourWidth * (float)_hourLabelInterval; //text label elements _dayLabelHeight = this.Font.GetHeight(graphics) + _dayLabelPadding.Top + _dayLabelPadding.Bottom; _hourLabelHeight = _hourLabelFont.GetHeight(graphics) + _hourLabelPadding.Top + _hourLabelPadding.Bottom; //determine the bar area height to make life easier. //_barAreaHeight = _barHeight + _barPadding.Top + _barPadding.Bottom; // = (int)_gridHeight - (int)_dayLabelHeight - _largeTickHeight; _barAreaHeight = _gridHeight - (int)_hourLabelHeight - _largeTickHeight; _barHeight = _barAreaHeight - _barPadding.Top - _barPadding.Bottom; _barLineY = this.Margin.Top + (_barAreaHeight / 2); _tickLineY = _gridHeight - (int)_hourLabelHeight; //Draw the control grid DrawControl(graphics); } private void OnMouseMove(object sender, MouseEventArgs e) { if (_lastMouseLocation != e.Location) { // CA-38305: on some PCs OnMouseMove gets repeatedly called while the mouse cursor is still. // this if statement ensures that the code below is only called while the mouse is actually moving. if (_ignoreNextOnMouseMove) { // The reason for this is that when the tooltip pops up, we get an // additional onMouseMove which would otherwise hide the tooltip again. _ignoreNextOnMouseMove = false; return; } _toolTipTimer.Stop(); _toolTip.Hide(this); _lastMouseLocation = e.Location; _toolTipTimer.Start(); } } private void OnMouseLeave(object sender, EventArgs e) { _toolTipTimer.Stop(); _toolTip.Hide(this); } private void OnMouseClick(object sender, MouseEventArgs e) { MouseEventHandler handler = OnTriggerPointClick; _selectedTriggerPoint = GetSelectedTriggerPoint(e.Location); if (null != handler && null != _selectedTriggerPoint) { handler(this, e); } } private void OnMouseDoubleClick(object sender, MouseEventArgs e) { MouseEventHandler handler = OnTriggerPointDoubleClick; _selectedTriggerPoint = GetSelectedTriggerPoint(e.Location); if (null != handler && null != _selectedTriggerPoint) { handler(this, e); } } private void toolTipTimer_Tick(object sender, EventArgs e) { TriggerPoint triggerPoint = GetSelectedTriggerPoint(_lastMouseLocation); if (null != triggerPoint) { string toolTipText = triggerPoint.ToolTip; ShowTooltip(toolTipText, _lastMouseLocation); _toolTipTimer.Stop(); } } private void toolTip_Popup(object sender, PopupEventArgs e) { Point correctLocation = _lastMouseLocation + new Size(1, -e.ToolTipSize.Height); // put it above-right of the mouse if (_toolTipLocation != correctLocation) { ShowTooltip(_toolTipText, correctLocation); return; } _ignoreNextOnMouseMove = true; } #endregion #region Private Methods private void DrawControl(Graphics graphics) { //Draw the box DrawControlBorder(graphics); //Draw hour lines DrawHourLines(graphics); //Draw the hours DrawDateTimeLabels(graphics); //Draw the schedule data DrawTriggerPoints(graphics); } private void DrawControlBorder(Graphics graphics) { using (Pen gridPen = new Pen(_gridColor)) { Rectangle rect = new Rectangle(this.Margin.Left, this.Margin.Top, (int)_gridWidth, (int)_gridHeight); graphics.DrawRectangle(gridPen, rect); } } private void DrawHourLines(Graphics graphics) { using (Pen pen = new Pen(_hourLineColor)) { graphics.DrawLine(pen, this.Margin.Left + 1, _tickLineY, this.Width - this.Margin.Right - 1, _tickLineY); //Draw day divider lines for (int i = 1; i < DAYS_IN_WEEK; i++) { float dayLineX = this.Margin.Left + _dayWidth * (float)i; graphics.DrawLine(pen, dayLineX, this.Margin.Top, dayLineX, _gridHeight); } for (int dayIncr = 0; dayIncr < DAYS_IN_WEEK; dayIncr++) { //Draw small hour ticks for (float hourIncr = _hourWidth; hourIncr < _dayWidth - 1; hourIncr += _hourWidth) { float hourLineX = this.Margin.Left + (_dayWidth * dayIncr) + hourIncr; float hourLineY1 = _tickLineY - _smallTickHeight; float hourLineY2 = _tickLineY; // _hourLineY + _hourTickSize; float markerX = this.Margin.Left + (int)DateTime.Now.DayOfWeek * _dayWidth + DateTime.Now.Hour * _hourWidth; if (_showCurrenttimeMarkWidth && (int)hourLineX == (int)markerX) { using (Pen markerPen = new Pen(_currentTimeColor)) { graphics.DrawLine(markerPen, hourLineX, hourLineY1, hourLineX, hourLineY2); } } else { graphics.DrawLine(pen, hourLineX, hourLineY1, hourLineX, hourLineY2); } } //Draw large hour marks for (float hourIncr = _hourIntervalWidth; hourIncr < _dayWidth - 1; hourIncr += _hourIntervalWidth) { float hourLineX = this.Margin.Left + (_dayWidth * dayIncr) + hourIncr; float hourLineY1 = _tickLineY - _largeTickHeight; float hourLineY2 = _tickLineY + 2; // _hourLineY + _hourTickSize; float markerX = this.Margin.Left + (int)DateTime.Now.DayOfWeek * _dayWidth + DateTime.Now.Hour * _hourWidth; if (_showCurrenttimeMarkWidth && (int)hourLineX == (int)markerX) { using (Pen markerPen = new Pen(_currentTimeColor)) { graphics.DrawLine(markerPen, hourLineX, hourLineY1, hourLineX, hourLineY2); } } else { graphics.DrawLine(pen, hourLineX, hourLineY1, hourLineX, hourLineY2); } } } } } private void DrawDateTimeLabels(Graphics graphics) { float dayLabelY = this.Margin.Top + _gridHeight - _hourLabelHeight; for (int dayIncr = 0; dayIncr < DAYS_IN_WEEK; dayIncr++) { for (float hourIncr = 0; hourIncr < _dayWidth - 1; hourIncr += _hourIntervalWidth) { float dayLabelX = this.Margin.Left + _dayWidth * dayIncr + hourIncr; RectangleF textRectF = new RectangleF(dayLabelX, dayLabelY, _hourIntervalWidth * HOURS_IN_DAY / _hourLabelInterval, _hourLabelHeight); StringFormat textFormat = new StringFormat(StringFormatFlags.NoWrap); textFormat.Alignment = StringAlignment.Near; textFormat.LineAlignment = StringAlignment.Near; if (hourIncr == 0) { using (Brush brush = new SolidBrush(_dayLabelColor)) { string dayString = AbbreviatedDayNames(dayIncr); graphics.DrawString(dayString, _hourLabelFont, brush, textRectF, textFormat); } } else { DateTime time = new DateTime(DateTime.Now.Year, 1, 1, (int)(_hourLabelInterval * ((int)hourIncr / (int)_hourIntervalWidth)), 0, 0); using (Brush brush = new SolidBrush(_hourLabelColor)) { string timeString = HelpersGUI.DateTimeToString(time, Messages.DATEFORMAT_H_SHORT, true); graphics.DrawString(timeString, _hourLabelFont, brush, textRectF, textFormat); } } } } } private string AbbreviatedDayNames(int dayIncr) { switch (dayIncr) { case 0: return Messages.SUNDAY_SHORT; case 1: return Messages.MONDAY_SHORT; case 2: return Messages.TUESDAY_SHORT; case 3: return Messages.WEDNESDAY_SHORT; case 4: return Messages.THURSDAY_SHORT; case 5: return Messages.FRIDAY_SHORT; default: return Messages.SATURDAY_SHORT; } } private void DrawTriggerPoints(Graphics graphics) { try { _taskMap.Clear(); foreach (TriggerPoint triggerPoint in _triggerPoints.List.Values) { DrawTriggerPoint(graphics, triggerPoint); } // unless the first trigger starts at Sunday 12a, we need to fill in the beginning of the bar // with the "end" of the last trigger of the week, so insert a fake trigger at Sunday 12a. // Also, if there is only one trigger defined, we need to add the same fake Sunday trigger if (_triggerPoints.List.Count > 0) { TriggerPoint firstTriggerPoint = _triggerPoints.List.Values[0]; TriggerPoint lastTriggerPoint = _triggerPoints.List.Values[_triggerPoints.List.Values.Count - 1]; if (firstTriggerPoint.Hour > 0 || _triggerPoints.List.Keys.Count == 1) { TriggerPoint fillerTriggerPoint = new TriggerPoint(); fillerTriggerPoint.Day = DayOfWeek.Sunday; fillerTriggerPoint.Hour = 0; fillerTriggerPoint.Color = lastTriggerPoint.Color; fillerTriggerPoint.ToolTip = lastTriggerPoint.ToolTip; fillerTriggerPoint.Tag = lastTriggerPoint.Tag; fillerTriggerPoint.IsSelected = lastTriggerPoint.IsSelected; DrawTriggerPoint(graphics, fillerTriggerPoint); } } } catch (Exception) { // ignored } } private void DrawTriggerPoint(Graphics graphics, TriggerPoint triggerPoint) { int lineX = (int)this.Margin.Left + (int)triggerPoint.Day * (int)_dayWidth + (int)triggerPoint.Hour * (int)_hourWidth; int lineY1 = (int)_barLineY - (_barHeight / 2) + _barPadding.Top; int lineY2 = (int)_barLineY + (_barHeight / 2) - _barPadding.Bottom; TriggerPoint nextTriggerPoint = _triggerPoints.GetNextTriggerPoint(triggerPoint); bool isLastTriggerPoint = (nextTriggerPoint.SortKey <= triggerPoint.SortKey); //If this is the last trigger point, we need to draw up to the end of the control int barWidth; if (isLastTriggerPoint) { barWidth = (int)this.Margin.Left + (int)_gridWidth - lineX; } else //otherwise, draw up to the beginning of the next trigger { barWidth = (this.Margin.Left + (int)nextTriggerPoint.Day * (int)_dayWidth + nextTriggerPoint.Hour * (int)_hourWidth) - lineX; } //Draw the main bar with Gradient RectangleF triggerRectangle = new RectangleF(lineX, lineY1, barWidth, (lineY2 - lineY1)); graphics.Clip = new Region(triggerRectangle); graphics.SmoothingMode = SmoothingMode.AntiAlias; Color barColor; if (_highlightType == WeekView.HighlightType.Bar && triggerPoint.IsSelected) { barColor = _hightlightColor; } else { barColor = triggerPoint.Color; } using (GraphicsPath outerPath = GetRoundedPath(triggerRectangle, 2)) { using (LinearGradientBrush brush = new LinearGradientBrush(triggerRectangle, barColor, ControlPaint.LightLight(barColor), LinearGradientMode.Vertical)) { graphics.FillPath(brush, outerPath); } } //Draw the second gradient to make it shiny RectangleF shinyRectangle = new RectangleF(triggerRectangle.X + _barPadding.Left, triggerRectangle.Y + _barPadding.Top, triggerRectangle.Width - (_barPadding.Left + _barPadding.Right), triggerRectangle.Height * 0.49f); using (GraphicsPath innerPath = GetRoundedPath(shinyRectangle, 2)) { int alphaTop = 120, alphaBottom = 30; Color topColor = Color.FromArgb(alphaTop, Color.White); Color bottomColor = Color.FromArgb(alphaBottom, Color.White); shinyRectangle = new RectangleF(shinyRectangle.X, shinyRectangle.Y, shinyRectangle.Width, shinyRectangle.Height + 1); // + _barPadding.Top + _barPadding.Bottom + 1); using (LinearGradientBrush lighterBrush = new LinearGradientBrush(shinyRectangle, topColor, bottomColor, LinearGradientMode.Vertical)) { graphics.FillPath(lighterBrush, innerPath); } } //Draw hightlight for selected TriggerPoint if (_highlightType == HighlightType.Box && triggerPoint.IsSelected) { Rectangle highlightRectangle = new Rectangle(lineX, lineY1, barWidth - 1, (lineY2 - lineY1) - 1); using (Pen highlightPen = new Pen(_hightlightColor)) { graphics.DrawRectangle(highlightPen, highlightRectangle); } } if (!_taskMap.ContainsKey(triggerRectangle)) { _taskMap.Add(triggerRectangle, triggerPoint); } } private void ShowTooltip(string text, Point location) { _toolTipText = text; _toolTipLocation = location; _toolTip.Show(text, this, location); } private TriggerPoint GetSelectedTriggerPoint(Point point) { TriggerPoint triggerPoint = null; foreach (KeyValuePair pair in _taskMap) { if (pair.Key.Contains(_lastMouseLocation)) { triggerPoint = pair.Value; } } return triggerPoint; } #endregion #region Private Static Methods private static GraphicsPath GetRoundedPath(RectangleF rect, float radius) { float diameter = 2 * radius; GraphicsPath path = new GraphicsPath(); path.StartFigure(); path.AddArc(rect.X, rect.Y, diameter, diameter, 180, 90); path.AddArc(rect.Right - diameter, rect.Y, diameter, diameter, 270, 90); path.AddArc(rect.Right - diameter, rect.Bottom - diameter - 1, diameter, diameter, 0, 90); path.AddArc(rect.X, rect.Bottom - diameter - 1, diameter, diameter, 90, 90); path.CloseFigure(); return path; } #endregion } public class TriggerPoint { #region Private Fields private DayOfWeek _day = DayOfWeek.Sunday; private int _hour = 0; private Color _color = Color.Transparent; private int _sortKey = 0; private bool _isSelected = false; private object _tag = null; private string _toolTip = string.Empty; #endregion #region Public ctors public TriggerPoint() { ;} public TriggerPoint(DayOfWeek Day, int Hour, Color Color) { this.Day = Day; this.Hour = Hour; this.Color = Color; } public TriggerPoint(DayOfWeek Day, int Hour, Color Color, string ToolTip) { this.Day = Day; this.Hour = Hour; this.Color = Color; this.ToolTip = ToolTip; } #endregion #region Public Properties public DayOfWeek Day { get { return _day; } set { _day = value; CalcSortKey(); } } public int Hour { get { return _hour; } set { _hour = value; CalcSortKey(); } } public Color Color { get { return _color; } set { _color = value; } } public bool IsSelected { get { return _isSelected; } internal set { _isSelected = value; } } public object Tag { get { return _tag; } set { _tag = value; } } public string ToolTip { get { return _toolTip; } set { _toolTip = value; } } public int SortKey { get { return _sortKey; } } #endregion #region Private Methods private void CalcSortKey() { _sortKey = (int)_day * 1000 + _hour; } #endregion } public class TriggerPoints { #region Private Fields private SortedList _triggerPoints = new SortedList(); #endregion #region Public ctor public TriggerPoints() { ;} #endregion #region Public Properties public TriggerPoint Selected { get { foreach (TriggerPoint triggerPoint in _triggerPoints.Values) { if (triggerPoint.IsSelected) { return triggerPoint; } } return null; } set { if (null != value) { foreach (TriggerPoint triggerPoint in _triggerPoints.Values) { triggerPoint.IsSelected = (triggerPoint.SortKey == value.SortKey); } } } } #endregion #region Public Methods public SortedList List { get { return _triggerPoints; } } internal void Clear() { _triggerPoints = new SortedList(); } public void ClearSelected() { foreach (TriggerPoint triggerPoint in _triggerPoints.Values) { triggerPoint.IsSelected = false; } } public TriggerPoint Add(TriggerPoint triggerPoint) { _triggerPoints.Add(triggerPoint.SortKey, triggerPoint); return triggerPoint; } public TriggerPoint Add(DayOfWeek day, int hour, Color color) { TriggerPoint triggerPoint = new TriggerPoint(day, hour, color); this.Add(triggerPoint); return triggerPoint; } public TriggerPoint Add(DayOfWeek day, int hour, Color color, string toolTip) { TriggerPoint triggerPoint = new TriggerPoint(day, hour, color, toolTip); this.Add(triggerPoint); return triggerPoint; } public TriggerPoints FindByTag(object tag) { TriggerPoints triggerPoints = null; foreach (TriggerPoint triggerPoint in _triggerPoints.Values) { if (object.Equals(triggerPoint.Tag, tag)) { if (triggerPoints == null) { triggerPoints = new TriggerPoints(); } triggerPoints.Add(triggerPoint); } } return triggerPoints; } public IEnumerator> GetEnumerator() { return _triggerPoints.GetEnumerator(); } public TriggerPoint GetNextTriggerPoint(TriggerPoint triggerPoint) { TriggerPoint firstTriggerPoint = null; TriggerPoint nextTriggerPoint = null; int currentIndex = _triggerPoints.IndexOfKey(triggerPoint.SortKey); foreach (int key in _triggerPoints.Keys) { if (null == firstTriggerPoint) { firstTriggerPoint = _triggerPoints[key]; } if (key > triggerPoint.SortKey) { nextTriggerPoint = _triggerPoints[key]; break; } } if (null == nextTriggerPoint) { nextTriggerPoint = firstTriggerPoint; } return nextTriggerPoint; } #endregion } }