diff options
| author | MichaelTheShifter <[email protected]> | 2016-07-20 09:40:36 -0400 |
|---|---|---|
| committer | MichaelTheShifter <[email protected]> | 2016-07-20 09:40:36 -0400 |
| commit | d40fed5ce2bc806a91245adb18039634eac13ed0 (patch) | |
| tree | f1d7168aee6db109ac2c738ad18c9db667a6ba69 /source/ShiftUI/Widgets/MonthCalendar.cs | |
| parent | f1856e8ed30ed882229fd3fa2a4038122a5fb441 (diff) | |
| download | shiftos-c--d40fed5ce2bc806a91245adb18039634eac13ed0.tar.gz shiftos-c--d40fed5ce2bc806a91245adb18039634eac13ed0.tar.bz2 shiftos-c--d40fed5ce2bc806a91245adb18039634eac13ed0.zip | |
Move ShiftUI source code to ShiftOS
This'll be a lot easier to work on.
Diffstat (limited to 'source/ShiftUI/Widgets/MonthCalendar.cs')
| -rw-r--r-- | source/ShiftUI/Widgets/MonthCalendar.cs | 2430 |
1 files changed, 2430 insertions, 0 deletions
diff --git a/source/ShiftUI/Widgets/MonthCalendar.cs b/source/ShiftUI/Widgets/MonthCalendar.cs new file mode 100644 index 0000000..24d7c8f --- /dev/null +++ b/source/ShiftUI/Widgets/MonthCalendar.cs @@ -0,0 +1,2430 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2006 Novell, Inc. +// +// Authors: +// John BouAntoun [email protected] +// +// REMAINING TODO: +// - get the date_cell_size and title_size to be pixel perfect match of SWF + +using System; +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Threading; + +namespace ShiftUI { + [DefaultBindingProperty("SelectionRange")] + [ClassInterface(ClassInterfaceType.AutoDispatch)] + [ComVisible(true)] + [DefaultProperty("SelectionRange")] + [DefaultEvent("DateChanged")] + //[Designer ("ShiftUI.Design.MonthCalendarDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")] + public class MonthCalendar : Widget { + #region Local variables + ArrayList annually_bolded_dates; + ArrayList monthly_bolded_dates; + ArrayList bolded_dates; + Size calendar_dimensions; + Day first_day_of_week; + DateTime max_date; + int max_selection_count; + DateTime min_date; + int scroll_change; + SelectionRange selection_range; + bool show_today; + bool show_today_circle; + bool show_week_numbers; + Color title_back_color; + Color title_fore_color; + DateTime today_date; + bool today_date_set; + Color trailing_fore_color; + Timer timer; + Timer updown_timer; + const int initial_delay = 500; + const int subsequent_delay = 100; + private bool is_year_going_up; + private bool is_year_going_down; + private bool is_mouse_moving_year; + private int year_moving_count; + private bool date_selected_event_pending; + bool right_to_left_layout; + + // internal variables used + internal bool show_year_updown; + internal DateTime current_month; // the month that is being displayed in top left corner of the grid + internal DateTimePicker owner; // used if this control is popped up + internal int button_x_offset; + internal Size button_size; + internal Size title_size; + internal Size date_cell_size; + internal Size calendar_spacing; + internal int divider_line_offset; + internal DateTime clicked_date; + internal Rectangle clicked_rect; + internal bool is_date_clicked; + internal bool is_previous_clicked; + internal bool is_next_clicked; + internal bool is_shift_pressed; + internal DateTime first_select_start_date; + internal int last_clicked_calendar_index; + internal Rectangle last_clicked_calendar_rect; + internal Font bold_font; // Cache the font in FontStyle.Bold + internal StringFormat centered_format; // Cache centered string format + private Point month_title_click_location; + // this is used to see which item was actually clicked on in the beginning + // so that we know which item to fire on timer + // 0: date clicked + // 1: previous clicked + // 2: next clicked + private bool[] click_state; + + + + #endregion // Local variables + + #region Public Constructors + + public MonthCalendar () { + // set up the control painting + SetStyle (Widgetstyles.UserPaint | Widgetstyles.StandardClick, false); + + // mouse down timer + timer = new Timer (); + timer.Interval = 500; + timer.Enabled = false; + + // initialise default values + DateTime now = DateTime.Now.Date; + selection_range = new SelectionRange (now, now); + today_date = now; + current_month = new DateTime (now.Year , now.Month, 1); + + // iniatialise local members + annually_bolded_dates = null; + bolded_dates = null; + calendar_dimensions = new Size (1,1); + first_day_of_week = Day.Default; + max_date = new DateTime (9998, 12, 31); + max_selection_count = 7; + min_date = new DateTime (1753, 1, 1); + monthly_bolded_dates = null; + scroll_change = 0; + show_today = true; + show_today_circle = true; + show_week_numbers = false; + title_back_color = ThemeEngine.Current.ColorActiveCaption; + title_fore_color = ThemeEngine.Current.ColorActiveCaptionText; + today_date_set = false; + trailing_fore_color = SystemColors.GrayText; + bold_font = new Font (Font, Font.Style | FontStyle.Bold); + centered_format = new StringFormat (StringFormat.GenericTypographic); + centered_format.FormatFlags = centered_format.FormatFlags | StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.NoWrap | StringFormatFlags.FitBlackBox; + centered_format.FormatFlags &= ~StringFormatFlags.NoClip; + centered_format.LineAlignment = StringAlignment.Center; + centered_format.Alignment = StringAlignment.Center; + + // Set default values + ForeColor = SystemColors.WindowText; + BackColor = ThemeEngine.Current.ColorWindow; + + // intiailise internal variables used + button_x_offset = 5; + button_size = new Size (22, 17); + // default settings based on 8.25 pt San Serif Font + // Not sure of algorithm used to establish this + date_cell_size = new Size (24, 16); // default size at san-serif 8.25 + divider_line_offset = 4; + calendar_spacing = new Size (4, 5); // horiz and vert spacing between months in a calendar grid + + // set some state info + clicked_date = now; + is_date_clicked = false; + is_previous_clicked = false; + is_next_clicked = false; + is_shift_pressed = false; + click_state = new bool [] {false, false, false}; + first_select_start_date = now; + month_title_click_location = Point.Empty; + + // set up context menus + SetUpTodayMenu (); + SetUpMonthMenu (); + + // event handlers + timer.Tick += new EventHandler (TimerHandler); + MouseMove += new MouseEventHandler (MouseMoveHandler); + MouseDown += new MouseEventHandler (MouseDownHandler); + KeyDown += new KeyEventHandler (KeyDownHandler); + MouseUp += new MouseEventHandler (MouseUpHandler); + KeyUp += new KeyEventHandler (KeyUpHandler); + + // this replaces paint so call the control version + base.Paint += new PaintEventHandler (PaintHandler); + + Size = DefaultSize; + } + + // called when this control is added to date time picker + internal MonthCalendar (DateTimePicker owner) : this () { + this.owner = owner; + this.is_visible = false; + this.Size = this.DefaultSize; + } + + #endregion // Public Constructors + + #region Public Instance Properties + + // dates to make bold on calendar annually (recurring) + [Localizable (true)] + public DateTime [] AnnuallyBoldedDates { + set { + if (annually_bolded_dates == null) + annually_bolded_dates = new ArrayList (value); + else { + annually_bolded_dates.Clear (); + annually_bolded_dates.AddRange (value); + } + + UpdateBoldedDates (); + } + get { + if (annually_bolded_dates == null || annually_bolded_dates.Count == 0) { + return new DateTime [0]; + } + DateTime [] result = new DateTime [annually_bolded_dates.Count]; + annually_bolded_dates.CopyTo (result); + return result; + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override Image BackgroundImage { + get { + return base.BackgroundImage; + } + set { + base.BackgroundImage = value; + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + [Browsable (false)] + public override ImageLayout BackgroundImageLayout { + get { + return base.BackgroundImageLayout; + } + set { + base.BackgroundImageLayout = value; + } + } + + // the back color for the main part of the calendar + public override Color BackColor { + set { + base.BackColor = value; + } + get { + return base.BackColor; + } + } + + // specific dates to make bold on calendar (non-recurring) + [Localizable (true)] + public DateTime[] BoldedDates { + set { + if (bolded_dates == null) { + bolded_dates = new ArrayList (value); + } else { + bolded_dates.Clear (); + bolded_dates.AddRange (value); + } + + UpdateBoldedDates (); + } + get { + if (bolded_dates == null || bolded_dates.Count == 0) + return new DateTime [0]; + + DateTime [] result = new DateTime [bolded_dates.Count]; + bolded_dates.CopyTo (result); + return result; + } + } + + // the configuration of the monthly grid display - only allowed to display at most, + // 1 calendar year at a time, will be trimmed to fit it properly + [Localizable (true)] + public Size CalendarDimensions { + set { + if (value.Width < 0 || value.Height < 0) { + throw new ArgumentException (); + } + if (calendar_dimensions != value) { + // squeeze the grid into 1 calendar year + if (value.Width * value.Height > 12) { + // iteratively reduce the largest dimension till our + // product is less than 12 + if (value.Width > 12 && value.Height > 12) { + calendar_dimensions = new Size (4, 3); + } else if (value.Width > 12) { + for (int i = 12; i > 0; i--) { + if (i * value.Height <= 12) { + calendar_dimensions = new Size (i, value.Height); + break; + } + } + } else if (value.Height > 12) { + for (int i = 12; i > 0; i--) { + if (i * value.Width <= 12) { + calendar_dimensions = new Size (value.Width, i); + break; + } + } + } + } else { + calendar_dimensions = value; + } + this.Invalidate (); + } + } + get { + return calendar_dimensions; + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + protected override bool DoubleBuffered { + get { + return base.DoubleBuffered; + } + set { + base.DoubleBuffered = value; + } + } + + // the first day of the week to display + [Localizable (true)] + [DefaultValue (Day.Default)] + public Day FirstDayOfWeek { + set { + if (first_day_of_week != value) { + first_day_of_week = value; + this.Invalidate (); + } + } + get { + return first_day_of_week; + } + } + + // the fore color for the main part of the calendar + public override Color ForeColor { + set { + base.ForeColor = value; + } + get { + return base.ForeColor; + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new ImeMode ImeMode { + get { return base.ImeMode; } + set { base.ImeMode = value; } + } + + // the maximum date allowed to be selected on this month calendar + public DateTime MaxDate { + set { + if (value < MinDate) { + string msg = string.Format (CultureInfo.CurrentCulture, + "Value of '{0}' is not valid for 'MaxDate'. 'MaxDate' " + + "must be greater than or equal to MinDate.", + value.ToString ("d", CultureInfo.CurrentCulture)); + throw new ArgumentOutOfRangeException ("MaxDate", + msg); + } + + if (max_date == value) + return; + + max_date = value; + + if (max_date < selection_range.Start || max_date < selection_range.End) { + DateTime start = max_date < selection_range.Start ? max_date : selection_range.Start; + DateTime end = max_date < selection_range.End ? max_date : selection_range.End; + SelectionRange = new SelectionRange (start, end); + } + } + get { + return max_date; + } + } + + // the maximum number of selectable days + [DefaultValue (7)] + public int MaxSelectionCount { + set { + if (value < 1) { + string msg = string.Format (CultureInfo.CurrentCulture, + "Value of '{0}' is not valid for 'MaxSelectionCount'. " + + "'MaxSelectionCount' must be greater than or equal to {1}.", + value, 1); + throw new ArgumentOutOfRangeException ("MaxSelectionCount", + msg); + } + + // can't set selectioncount less than already selected dates + if ((SelectionEnd - SelectionStart).Days > value) { + throw new ArgumentException(); + } + + if (max_selection_count != value) { + max_selection_count = value; + this.OnUIAMaxSelectionCountChanged (); + } + } + get { + return max_selection_count; + } + } + + // the minimum date allowed to be selected on this month calendar + public DateTime MinDate { + set { + DateTime absoluteMinDate = new DateTime (1753, 1, 1); + + if (value < absoluteMinDate) { + string msg = string.Format (CultureInfo.CurrentCulture, + "Value of '{0}' is not valid for 'MinDate'. 'MinDate' " + + "must be greater than or equal to {1}.", + value.ToString ("d", CultureInfo.CurrentCulture), + absoluteMinDate.ToString ("d", CultureInfo.CurrentCulture)); + throw new ArgumentOutOfRangeException ("MinDate", + msg); + } + + if (value > MaxDate) { + string msg = string.Format (CultureInfo.CurrentCulture, + "Value of '{0}' is not valid for 'MinDate'. 'MinDate' " + + "must be less than MaxDate.", + value.ToString ("d", CultureInfo.CurrentCulture)); + throw new ArgumentOutOfRangeException ("MinDate", + msg); + } + + if (min_date == value) + return; + + min_date = value; + + if (min_date > selection_range.Start || min_date > selection_range.End) { + DateTime start = min_date > selection_range.Start ? min_date : selection_range.Start; + DateTime end = min_date > selection_range.End ? min_date : selection_range.End; + SelectionRange = new SelectionRange (start, end); + } + } + get { + return min_date; + } + } + + // dates to make bold on calendar monthly (recurring) + [Localizable (true)] + public DateTime[] MonthlyBoldedDates { + set { + if (monthly_bolded_dates == null) { + monthly_bolded_dates = new ArrayList (value); + } else { + monthly_bolded_dates.Clear (); + monthly_bolded_dates.AddRange (value); + } + + UpdateBoldedDates (); + } + get { + if (monthly_bolded_dates == null || monthly_bolded_dates.Count == 0) + return new DateTime [0]; + + DateTime [] result = new DateTime [monthly_bolded_dates.Count]; + monthly_bolded_dates.CopyTo (result); + return result; + } + } + + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + [Browsable (false)] + // Padding should not have any effect on the appearance of MonthCalendar. + public new Padding Padding { + get { + return base.Padding; + } + set { + base.Padding = value; + } + } + + [DefaultValue (false)] + [Localizable (true)] + public virtual bool RightToLeftLayout { + get { + return right_to_left_layout; + } + set { + right_to_left_layout = value; + } + } + + // the ammount by which to scroll this calendar by + [DefaultValue (0)] + public int ScrollChange { + set { + if (value < 0 || value > 20000) { + throw new ArgumentException(); + } + + if (scroll_change != value) { + scroll_change = value; + } + } + get { + return scroll_change; + } + } + + + // the last selected date + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public DateTime SelectionEnd { + set { + if (value < MinDate || value > MaxDate) { + throw new ArgumentException(); + } + + if (SelectionRange.End != value) { + DateTime old_end = SelectionRange.End; + // make sure the end obeys the max selection range count + if (value < SelectionRange.Start) { + SelectionRange.Start = value; + } + if (value.AddDays((MaxSelectionCount-1)*-1) > SelectionRange.Start) { + SelectionRange.Start = value.AddDays((MaxSelectionCount-1)*-1); + } + SelectionRange.End = value; + this.InvalidateDateRange (new SelectionRange (old_end, SelectionRange.End)); + this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd)); + this.OnUIASelectionChanged (); + } + } + get { + return SelectionRange.End; + } + } + + [Bindable(true)] + // the range of selected dates + public SelectionRange SelectionRange { + set { + if (selection_range != value) { + if (value.Start < MinDate) + throw new ArgumentException ("SelectionStart cannot be less than MinDate"); + else if (value.End > MaxDate) + throw new ArgumentException ("SelectionEnd cannot be greated than MaxDate"); + + SelectionRange old_range = selection_range; + + // make sure the end obeys the max selection range count + if (value.End.AddDays((MaxSelectionCount-1)*-1) > value.Start) { + selection_range = new SelectionRange (value.End.AddDays((MaxSelectionCount-1)*-1), value.End); + } else { + selection_range = value; + } + SelectionRange visible_range = this.GetDisplayRange(true); + if(visible_range.Start > selection_range.End) { + this.current_month = new DateTime (selection_range.Start.Year, selection_range.Start.Month, 1); + this.Invalidate (); + } else if (visible_range.End < selection_range.Start) { + int year_diff = selection_range.End.Year - visible_range.End.Year; + int month_diff = selection_range.End.Month - visible_range.End.Month; + this.current_month = current_month.AddMonths(year_diff * 12 + month_diff); + this.Invalidate (); + } + // invalidate the selected range changes + DateTime diff_start = old_range.Start; + DateTime diff_end = old_range.End; + // now decide which region is greated + if (old_range.Start > SelectionRange.Start) { + diff_start = SelectionRange.Start; + } else if (old_range.Start == SelectionRange.Start) { + if (old_range.End < SelectionRange.End) { + diff_start = old_range.End; + } else { + diff_start = SelectionRange.End; + } + } + if (old_range.End < SelectionRange.End) { + diff_end = SelectionRange.End; + } else if (old_range.End == SelectionRange.End) { + if (old_range.Start < SelectionRange.Start) { + diff_end = SelectionRange.Start; + } else { + diff_end = old_range.Start; + } + } + + + // invalidate the region required + SelectionRange new_range = new SelectionRange (diff_start, diff_end); + if (new_range.End != old_range.End || new_range.Start != old_range.Start) + this.InvalidateDateRange (new_range); + // raise date changed event + this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd)); + this.OnUIASelectionChanged (); + } + } + get { + return selection_range; + } + } + + // the first selected date + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public DateTime SelectionStart { + set { + if (value < MinDate || value > MaxDate) { + throw new ArgumentException(); + } + + if (SelectionRange.Start != value) { + // make sure the end obeys the max selection range count + if (value > SelectionRange.End) { + SelectionRange.End = value; + } else if (value.AddDays(MaxSelectionCount-1) < SelectionRange.End) { + SelectionRange.End = value.AddDays(MaxSelectionCount-1); + } + SelectionRange.Start = value; + DateTime new_month = new DateTime(value.Year, value.Month, 1); + if (current_month != new_month) + current_month = new_month; + + this.Invalidate (); + this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd)); + this.OnUIASelectionChanged (); + } + } + get { + return selection_range.Start; + } + } + + // whether or not to show todays date + [DefaultValue (true)] + public bool ShowToday { + set { + if (show_today != value) { + show_today = value; + this.Invalidate (); + } + } + get { + return show_today; + } + } + + // whether or not to show a circle around todays date + [DefaultValue (true)] + public bool ShowTodayCircle { + set { + if (show_today_circle != value) { + show_today_circle = value; + this.Invalidate (); + } + } + get { + return show_today_circle; + } + } + + // whether or not to show numbers beside each row of weeks + [Localizable (true)] + [DefaultValue (false)] + public bool ShowWeekNumbers { + set { + if (show_week_numbers != value) { + show_week_numbers = value; + // The values here don't matter, SetBoundsCore will calculate its own + SetBoundsCore (Left, Top, Width, Height, BoundsSpecified.Width); + this.Invalidate (); + } + } + get { + return show_week_numbers; + } + } + + // the rectangle size required to render one month based on current font + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public Size SingleMonthSize { + get { + if (this.Font == null) { + throw new InvalidOperationException(); + } + + // multiplier is sucked out from the font size + int multiplier = this.Font.Height; + + // establis how many columns and rows we have + int column_count = (ShowWeekNumbers) ? 8 : 7; + int row_count = 7; // not including the today date + + // set the date_cell_size and the title_size + date_cell_size = new Size ((int) Math.Ceiling (1.8 * multiplier), multiplier); + title_size = new Size ((date_cell_size.Width * column_count), 2 * multiplier); + + return new Size (column_count * date_cell_size.Width, row_count * date_cell_size.Height + title_size.Height); + } + } + + [Localizable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new Size Size { + get { + return base.Size; + } + set { + base.Size = value; + } + } + + [Bindable(false)] + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override string Text { + get { + return base.Text; + } + set { + base.Text = value; + } + } + + // the back color for the title of the calendar and the + // forecolor for the day of the week text + public Color TitleBackColor { + set { + if (title_back_color != value) { + title_back_color = value; + this.Invalidate (); + } + } + get { + return title_back_color; + } + } + + // the fore color for the title of the calendar + public Color TitleForeColor { + set { + if (title_fore_color != value) { + title_fore_color = value; + this.Invalidate (); + } + } + get { + return title_fore_color; + } + } + + // the date this calendar is using to refer to today's date + public DateTime TodayDate { + set { + today_date_set = true; + if (today_date != value) { + today_date = value; + this.Invalidate (); + } + } + get { + return today_date; + } + } + + // tells if user specifically set today_date for this control + [Browsable (false)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public bool TodayDateSet { + get { + return today_date_set; + } + } + + // the color used for trailing dates in the calendar + public Color TrailingForeColor { + set { + if (trailing_fore_color != value) { + trailing_fore_color = value; + SelectionRange bounds = this.GetDisplayRange (false); + SelectionRange visible_bounds = this.GetDisplayRange (true); + this.InvalidateDateRange (new SelectionRange (bounds.Start, visible_bounds.Start)); + this.InvalidateDateRange (new SelectionRange (bounds.End, visible_bounds.End)); + } + } + get { + return trailing_fore_color; + } + } + + #endregion // Public Instance Properties + + #region Protected Instance Properties + + // overloaded to allow controll to be windowed for drop down + protected override CreateParams CreateParams { + get { + if (this.owner == null) { + return base.CreateParams; + } else { + CreateParams cp = base.CreateParams; + cp.Style ^= (int) WindowStyles.WS_CHILD; + cp.Style |= (int) WindowStyles.WS_POPUP; + cp.ExStyle |= (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST); + + return cp; + } + } + } + + // not sure what to put in here - just doing a base() call - jba + protected override ImeMode DefaultImeMode { + get { + return base.DefaultImeMode; + } + } + + protected override Padding DefaultMargin { + get { + return new Padding (9); + } + } + + protected override Size DefaultSize { + get { + Size single_month = SingleMonthSize; + // get the width + int width = calendar_dimensions.Width * single_month.Width; + if (calendar_dimensions.Width > 1) { + width += (calendar_dimensions.Width - 1) * calendar_spacing.Width; + } + + // get the height + int height = calendar_dimensions.Height * single_month.Height; + if (this.ShowToday) { + height += date_cell_size.Height + 2; // add the height of the "Today: " ... + } + if (calendar_dimensions.Height > 1) { + height += (calendar_dimensions.Height - 1) * calendar_spacing.Height; + } + + // add the 1 pixel boundary + if (width > 0) { + width += 2; + } + if (height > 0) { + height +=2; + } + + return new Size (width, height); + } + } + + #endregion // Protected Instance Properties + + #region Public Instance Methods + + // add a date to the anually bolded date arraylist + public void AddAnnuallyBoldedDate (DateTime date) { + if (annually_bolded_dates == null) + annually_bolded_dates = new ArrayList (); + if (!annually_bolded_dates.Contains (date)) + annually_bolded_dates.Add (date); + } + + // add a date to the normal bolded date arraylist + public void AddBoldedDate (DateTime date) { + if (bolded_dates == null) + bolded_dates = new ArrayList (); + if (!bolded_dates.Contains (date)) + bolded_dates.Add (date); + } + + // add a date to the anually monthly date arraylist + public void AddMonthlyBoldedDate (DateTime date) { + if (monthly_bolded_dates == null) + monthly_bolded_dates = new ArrayList (); + if (!monthly_bolded_dates.Contains (date)) + monthly_bolded_dates.Add (date); + } + + // if visible = true, return only the dates of full months, else return all dates visible + public SelectionRange GetDisplayRange (bool visible) { + DateTime start; + DateTime end; + start = new DateTime (current_month.Year, current_month.Month, 1); + end = start.AddMonths (calendar_dimensions.Width * calendar_dimensions.Height); + end = end.AddDays(-1); + + // process all visible dates if needed (including the grayed out dates + if (!visible) { + start = GetFirstDateInMonthGrid (start); + end = GetLastDateInMonthGrid (end); + } + + return new SelectionRange (start, end); + } + + // HitTest overload that recieve's x and y co-ordinates as separate ints + public HitTestInfo HitTest (int x, int y) { + return HitTest (new Point (x, y)); + } + + // returns a HitTestInfo for MonthCalendar element's under the specified point + public HitTestInfo HitTest (Point point) { + return HitTest (point, out last_clicked_calendar_index, out last_clicked_calendar_rect); + } + + // clears all the annually bolded dates + public void RemoveAllAnnuallyBoldedDates () { + if (annually_bolded_dates != null) + annually_bolded_dates.Clear (); + } + + // clears all the normal bolded dates + public void RemoveAllBoldedDates () { + if (bolded_dates != null) + bolded_dates.Clear (); + } + + // clears all the monthly bolded dates + public void RemoveAllMonthlyBoldedDates () { + if (monthly_bolded_dates != null) + monthly_bolded_dates.Clear (); + } + + // clears the specified annually bolded date (only compares day and month) + // only removes the first instance of the match + public void RemoveAnnuallyBoldedDate (DateTime date) { + if (annually_bolded_dates == null) + return; + + for (int i = 0; i < annually_bolded_dates.Count; i++) { + DateTime dt = (DateTime) annually_bolded_dates [i]; + if (dt.Day == date.Day && dt.Month == date.Month) { + annually_bolded_dates.RemoveAt (i); + return; + } + } + } + + // clears all the normal bolded date + // only removes the first instance of the match + public void RemoveBoldedDate (DateTime date) { + if (bolded_dates == null) + return; + + for (int i = 0; i < bolded_dates.Count; i++) { + DateTime dt = (DateTime) bolded_dates [i]; + if (dt.Year == date.Year && dt.Month == date.Month && dt.Day == date.Day) { + bolded_dates.RemoveAt (i); + return; + } + } + } + + // clears the specified monthly bolded date (only compares day and month) + // only removes the first instance of the match + public void RemoveMonthlyBoldedDate (DateTime date) { + if (monthly_bolded_dates == null) + return; + + for (int i = 0; i < monthly_bolded_dates.Count; i++) { + DateTime dt = (DateTime) monthly_bolded_dates [i]; + if (dt.Day == date.Day && dt.Month == date.Month) { + monthly_bolded_dates.RemoveAt (i); + return; + } + } + } + + // sets the calendar_dimensions. If product is > 12, the larger dimension is reduced to make product < 12 + public void SetCalendarDimensions(int x, int y) { + this.CalendarDimensions = new Size(x, y); + } + + // sets the currently selected date as date + public void SetDate (DateTime date) { + this.SetSelectionRange (date.Date, date.Date); + } + + // utility method set the SelectionRange property using individual dates + public void SetSelectionRange (DateTime date1, DateTime date2) { + this.SelectionRange = new SelectionRange(date1, date2); + } + + public override string ToString () { + return this.GetType().Name + ", " + this.SelectionRange.ToString (); + } + + // usually called after an AddBoldedDate method is called + // formats monthly and daily bolded dates according to the current calendar year + public void UpdateBoldedDates () { + Invalidate (); + } + + #endregion // Public Instance Methods + + #region Protected Instance Methods + + // not sure why this needs to be overriden + protected override void CreateHandle () { + base.CreateHandle (); + } + + // not sure why this needs to be overriden + protected override void Dispose (bool disposing) { + base.Dispose (disposing); + } + + // Handle arrow keys + protected override bool IsInputKey (Keys keyData) { + switch (keyData) { + case Keys.Up: + case Keys.Down: + case Keys.Right: + case Keys.Left: + return true; + default: + break; + } + + return base.IsInputKey (keyData); + } + + // not sure why this needs to be overriden + protected override void OnBackColorChanged (EventArgs e) { + base.OnBackColorChanged (e); + this.Invalidate (); + } + + // raises the date changed event + protected virtual void OnDateChanged (DateRangeEventArgs drevent) { + DateRangeEventHandler eh = (DateRangeEventHandler) (Events [DateChangedEvent]); + if (eh != null) + eh (this, drevent); + } + + // raises the DateSelected event + protected virtual void OnDateSelected (DateRangeEventArgs drevent) { + DateRangeEventHandler eh = (DateRangeEventHandler) (Events [DateSelectedEvent]); + if (eh != null) + eh (this, drevent); + } + + protected override void OnFontChanged (EventArgs e) { + // Update size based on new font's space requirements + Size = new Size (CalendarDimensions.Width * SingleMonthSize.Width, + CalendarDimensions.Height * SingleMonthSize.Height); + bold_font = new Font (Font, Font.Style | FontStyle.Bold); + base.OnFontChanged (e); + } + + protected override void OnForeColorChanged (EventArgs e) { + base.OnForeColorChanged (e); + } + + protected override void OnHandleCreated (EventArgs e) { + base.OnHandleCreated (e); + } + + protected override void OnHandleDestroyed (EventArgs e) + { + base.OnHandleDestroyed (e); + } + + //[EditorBrowsable (EditorBrowsableState.Advanced)] + protected virtual void OnRightToLeftLayoutChanged (EventArgs e) { + EventHandler eh = (EventHandler) (Events [RightToLeftLayoutChangedEvent]); + if (eh != null) + eh (this, e); + } + + // i think this is overriden to not allow the control to be changed to an arbitrary size + protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) + { + // only allow sizes = default size to be set + Size default_size = DefaultSize; + Size min_size = default_size; + Size max_size = new Size (default_size.Width + SingleMonthSize.Width + calendar_spacing.Width, + default_size.Height + SingleMonthSize.Height + calendar_spacing.Height); + int x_mid_point = (max_size.Width + min_size.Width)/2; + int y_mid_point = (max_size.Height + min_size.Height)/2; + + if (width < x_mid_point) { + width = min_size.Width; + } else { + width = max_size.Width; + } + if (height < y_mid_point) { + height = min_size.Height; + } else { + height = max_size.Height; + } + base.SetBoundsCore (x, y, width, height, specified); + } + + protected override void WndProc (ref Message m) { + base.WndProc (ref m); + } + + #endregion // Protected Instance Methods + + #region public events + static object DateChangedEvent = new object (); + static object DateSelectedEvent = new object (); + static object RightToLeftLayoutChangedEvent = new object (); + + // fired when the date is changed (either explicitely or implicitely) + // when navigating the month selector + public event DateRangeEventHandler DateChanged { + add { Events.AddHandler (DateChangedEvent, value); } + remove { Events.RemoveHandler (DateChangedEvent, value); } + } + + // fired when the user explicitely clicks on date to select it + public event DateRangeEventHandler DateSelected { + add { Events.AddHandler (DateSelectedEvent, value); } + remove { Events.RemoveHandler (DateSelectedEvent, value); } + } + + [Browsable(false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged + { + add { base.BackgroundImageLayoutChanged += value;} + remove { base.BackgroundImageLayoutChanged += value;} + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler Click { + add {base.Click += value; } + remove {base.Click -= value;} + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler DoubleClick { + add {base.DoubleClick += value; } + remove {base.DoubleClick -= value; } + } + + [Browsable(false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler ImeModeChanged { + add { base.ImeModeChanged += value; } + remove { base.ImeModeChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseClick { + add { base.MouseClick += value;} + remove { base.MouseClick -= value;} + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event MouseEventHandler MouseDoubleClick { + add { base.MouseDoubleClick += value; } + remove { base.MouseDoubleClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler PaddingChanged { + add {base.PaddingChanged += value;} + remove {base.PaddingChanged -= value;} + } + + // XXX check this out + [Browsable(false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event PaintEventHandler Paint; + + public event EventHandler RightToLeftLayoutChanged { + add {Events.AddHandler (RightToLeftLayoutChangedEvent, value);} + remove {Events.RemoveHandler (RightToLeftLayoutChangedEvent, value);} + } + + [Browsable(false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler TextChanged { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + #endregion // public events + + #region internal properties + + private void AddYears (int years, bool fast) + { + DateTime newDate; + if (fast) { + if (!(CurrentMonth.Year + years * 5 > MaxDate.Year)) { + newDate = CurrentMonth.AddYears (years * 5); + if (MaxDate >= newDate && MinDate <= newDate) { + CurrentMonth = newDate; + return; + } + } + } + if (!(CurrentMonth.Year + years > MaxDate.Year)) { + newDate = CurrentMonth.AddYears (years); + if (MaxDate >= newDate && MinDate <= newDate) { + CurrentMonth = newDate; + } + } + } + + internal bool IsYearGoingUp { + get { + return is_year_going_up; + } + set { + if (value) { + is_year_going_down = false; + year_moving_count = (is_year_going_up ? year_moving_count + 1 : 1); + if (is_year_going_up) + year_moving_count++; + else { + year_moving_count = 1; + } + AddYears (1, year_moving_count > 10); + if (is_mouse_moving_year) + StartHideTimer (); + } else { + year_moving_count = 0; + } + is_year_going_up = value; + Invalidate (); + } + } + + internal bool IsYearGoingDown { + get { + return is_year_going_down; + } + set + { + if (value) { + is_year_going_up = false; + year_moving_count = (is_year_going_down ? year_moving_count + 1 : 1); + if (is_year_going_down) + year_moving_count++; + else { + year_moving_count = 1; + } + AddYears (-1, year_moving_count > 10); + if (is_mouse_moving_year) + StartHideTimer (); + } else { + year_moving_count = 0; + } + is_year_going_down = value; + Invalidate (); + } + } + + internal bool ShowYearUpDown { + get { + return show_year_updown; + } + set { + if (show_year_updown != value) { + show_year_updown = value; + Invalidate (); + } + } + } + + internal DateTime CurrentMonth { + set { + // only interested in if the month (not actual date) has change + if (value < MinDate || value > MaxDate) { + return; + } + + if (value.Month != current_month.Month || + value.Year != current_month.Year) { + this.SelectionRange = new SelectionRange( + this.SelectionStart.Add(value.Subtract(current_month)), + this.SelectionEnd.Add(value.Subtract(current_month))); + current_month = value; + UpdateBoldedDates(); + this.Invalidate(); + } + } + get { + return current_month; + } + } + + #endregion // internal properties + + #region internal/private methods + internal HitTestInfo HitTest ( + Point point, + out int calendar_index, + out Rectangle calendar_rect) { + // start by initialising the ref parameters + calendar_index = -1; + calendar_rect = Rectangle.Empty; + + // before doing all the hard work, see if the today's date wasn't clicked + Rectangle today_rect = new Rectangle ( + ClientRectangle.X, + ClientRectangle.Bottom - date_cell_size.Height, + 7 * date_cell_size.Width, + date_cell_size.Height); + if (today_rect.Contains (point) && this.ShowToday) { + return new HitTestInfo(HitArea.TodayLink, point, DateTime.Now); + } + + Size month_size = SingleMonthSize; + // define calendar rect's that this thing can land in + Rectangle[] calendars = new Rectangle [CalendarDimensions.Width * CalendarDimensions.Height]; + for (int i=0; i < CalendarDimensions.Width * CalendarDimensions.Height; i ++) { + if (i == 0) { + calendars[i] = new Rectangle ( + new Point (ClientRectangle.X + 1, ClientRectangle.Y + 1), + month_size); + } else { + // calendar on the next row + if (i % CalendarDimensions.Width == 0) { + calendars[i] = new Rectangle ( + new Point (calendars[i-CalendarDimensions.Width].X, calendars[i-CalendarDimensions.Width].Bottom + calendar_spacing.Height), + month_size); + } else { + // calendar on the next column + calendars[i] = new Rectangle ( + new Point (calendars[i-1].Right + calendar_spacing.Width, calendars[i-1].Y), + month_size); + } + } + } + + // through each trying to find a match + for (int i = 0; i < calendars.Length ; i++) { + if (calendars[i].Contains (point)) { + // check the title section + Rectangle title_rect = new Rectangle ( + calendars[i].Location, + title_size); + if (title_rect.Contains (point) ) { + // make sure it's not a previous button + if (i == 0) { + Rectangle button_rect = new Rectangle( + new Point (calendars[i].X + button_x_offset, (title_size.Height - button_size.Height)/2), + button_size); + if (button_rect.Contains (point)) { + return new HitTestInfo (HitArea.PrevMonthButton, point, new DateTime (1, 1, 1)); + } + } + // make sure it's not the next button + if (i % CalendarDimensions.Height == 0 && i % CalendarDimensions.Width == calendar_dimensions.Width - 1) { + Rectangle button_rect = new Rectangle( + new Point (calendars[i].Right - button_x_offset - button_size.Width, (title_size.Height - button_size.Height)/2), + button_size); + if (button_rect.Contains (point)) { + return new HitTestInfo (HitArea.NextMonthButton, point, new DateTime (1, 1, 1)); + } + } + + // indicate which calendar and month it was + calendar_index = i; + calendar_rect = calendars[i]; + + // make sure it's not the month or the year of the calendar + if (GetMonthNameRectangle (title_rect, i).Contains (point)) { + return new HitTestInfo (HitArea.TitleMonth, point, new DateTime (1, 1, 1)); + } + Rectangle year, up, down; + GetYearNameRectangles (title_rect, i, out year, out up, out down); + if (year.Contains (point)) { + return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.YearRectangle); + } else if (up.Contains (point)) { + return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.UpButton); + } else if (down.Contains (point)) { + return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.DownButton); + } + + // return the hit test in the title background + return new HitTestInfo (HitArea.TitleBackground, point, new DateTime (1, 1, 1)); + } + + Point date_grid_location = new Point (calendars[i].X, title_rect.Bottom); + + // see if it's in the Week numbers + if (ShowWeekNumbers) { + Rectangle weeks_rect = new Rectangle ( + date_grid_location, + new Size (date_cell_size.Width,Math.Max (calendars[i].Height - title_rect.Height, 0))); + if (weeks_rect.Contains (point)) { + return new HitTestInfo(HitArea.WeekNumbers, point, DateTime.Now); + } + + // move the location of the grid over + date_grid_location.X += date_cell_size.Width; + } + + // see if it's in the week names + Rectangle day_rect = new Rectangle ( + date_grid_location, + new Size (Math.Max (calendars[i].Right - date_grid_location.X, 0), date_cell_size.Height)); + if (day_rect.Contains (point)) { + return new HitTestInfo (HitArea.DayOfWeek, point, new DateTime (1, 1, 1)); + } + + // finally see if it was a date that was clicked + Rectangle date_grid = new Rectangle ( + new Point (day_rect.X, day_rect.Bottom), + new Size (day_rect.Width, Math.Max(calendars[i].Bottom - day_rect.Bottom, 0))); + if (date_grid.Contains (point)) { + clicked_rect = date_grid; + // okay so it's inside the grid, get the offset + Point offset = new Point (point.X - date_grid.X, point.Y - date_grid.Y); + int row = offset.Y / date_cell_size.Height; + int col = offset.X / date_cell_size.Width; + // establish our first day of the month + DateTime calendar_month = this.CurrentMonth.AddMonths(i); + DateTime first_day = GetFirstDateInMonthGrid (calendar_month); + DateTime time = first_day.AddDays ((row * 7) + col); + // establish which date was clicked + if (time.Year != calendar_month.Year || time.Month != calendar_month.Month) { + if (time < calendar_month && i == 0) { + return new HitTestInfo (HitArea.PrevMonthDate, point, new DateTime (1, 1, 1), time); + } else if (time > calendar_month && i == CalendarDimensions.Width*CalendarDimensions.Height - 1) { + return new HitTestInfo (HitArea.NextMonthDate, point, new DateTime (1, 1, 1), time); + } + return new HitTestInfo (HitArea.Nowhere, point, new DateTime (1, 1, 1)); + } + return new HitTestInfo(HitArea.Date, point, time); + } + } + } + + return new HitTestInfo (); + } + + // returns the date of the first cell of the specified month grid + internal DateTime GetFirstDateInMonthGrid (DateTime month) { + // convert the first_day_of_week into a DayOfWeekEnum + DayOfWeek first_day = GetDayOfWeek (first_day_of_week); + // find the first day of the month + DateTime first_date_of_month = new DateTime (month.Year, month.Month, 1); + DayOfWeek first_day_of_month = first_date_of_month.DayOfWeek; + // adjust for the starting day of the week + int offset = first_day_of_month - first_day; + if (offset < 0) { + offset += 7; + } + return first_date_of_month.AddDays (-1*offset); + } + + // returns the date of the last cell of the specified month grid + internal DateTime GetLastDateInMonthGrid (DateTime month) + { + DateTime start = GetFirstDateInMonthGrid(month); + return start.AddDays ((7 * 6)-1); + } + + internal bool IsBoldedDate (DateTime date) { + // check bolded dates + if (bolded_dates != null && bolded_dates.Count > 0) { + foreach (DateTime bolded_date in bolded_dates) { + if (bolded_date.Date == date.Date) { + return true; + } + } + } + // check monthly dates + if (monthly_bolded_dates != null && monthly_bolded_dates.Count > 0) { + foreach (DateTime bolded_date in monthly_bolded_dates) { + if (bolded_date.Day == date.Day) { + return true; + } + } + } + // check yearly dates + if (annually_bolded_dates != null && annually_bolded_dates.Count > 0) { + foreach (DateTime bolded_date in annually_bolded_dates) { + if (bolded_date.Month == date.Month && bolded_date.Day == date.Day) { + return true; + } + } + } + + return false; // no match + } + + // initialise the 'go to today' context menu + private void SetUpTodayMenu () { + } + + // initialise the month context menu + private void SetUpMonthMenu () { + + } + + // returns the first date of the month + private DateTime GetFirstDateInMonth (DateTime date) { + return new DateTime (date.Year, date.Month, 1); + } + + // returns the last date of the month + private DateTime GetLastDateInMonth (DateTime date) { + return new DateTime (date.Year, date.Month, 1).AddMonths(1).AddDays(-1); + } + + // called in response to users seletion with shift key + private void AddTimeToSelection (int delta, bool isDays) + { + DateTime cursor_point; + DateTime end_point; + // okay we add the period to the date that is not the same as the + // start date when shift was first clicked. + if (SelectionStart != first_select_start_date) { + cursor_point = SelectionStart; + } else { + cursor_point = SelectionEnd; + } + // add the days + if (isDays) { + end_point = cursor_point.AddDays (delta); + } else { + // delta must be months + end_point = cursor_point.AddMonths (delta); + } + // set the new selection range + SelectionRange range = new SelectionRange (first_select_start_date, end_point); + if (range.Start.AddDays (MaxSelectionCount-1) < range.End) { + // okay the date is beyond what is allowed, lets set the maximum we can + if (range.Start != first_select_start_date) { + range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1); + } else { + range.End = range.Start.AddDays (MaxSelectionCount-1); + } + } + + // Avoid re-setting SelectionRange to the same value and fire an extra DateChanged event + if (range.Start != selection_range.Start || range.End != selection_range.End) + SelectionRange = range; + } + + // attempts to add the date to the selection without throwing exception + private void SelectDate (DateTime date) { + // try and add the new date to the selction range + SelectionRange range = null; + if (is_shift_pressed || (click_state [0])) { + range = new SelectionRange (first_select_start_date, date); + if (range.Start.AddDays (MaxSelectionCount-1) < range.End) { + // okay the date is beyond what is allowed, lets set the maximum we can + if (range.Start != first_select_start_date) { + range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1); + } else { + range.End = range.Start.AddDays (MaxSelectionCount-1); + } + } + } else { + if (date >= MinDate && date <= MaxDate) { + range = new SelectionRange (date, date); + first_select_start_date = date; + } + } + + // Only set if we re actually getting a different range (avoid an extra DateChanged event) + if (range != null && range.Start != selection_range.Start || range.End != selection_range.End) + SelectionRange = range; + } + + // gets the week of the year + internal int GetWeekOfYear (DateTime date) { + // convert the first_day_of_week into a DayOfWeekEnum + DayOfWeek first_day = GetDayOfWeek (first_day_of_week); + // find the first day of the year + DayOfWeek first_day_of_year = new DateTime (date.Year, 1, 1).DayOfWeek; + // adjust for the starting day of the week + int offset = first_day_of_year - first_day; + int week = ((date.DayOfYear + offset) / 7) + 1; + return week; + } + + // convert a Day enum into a DayOfWeek enum + internal DayOfWeek GetDayOfWeek (Day day) { + if (day == Day.Default) { + return Thread.CurrentThread.CurrentCulture.DateTimeFormat.FirstDayOfWeek; + } else { + return (DayOfWeek) DayOfWeek.Parse (typeof (DayOfWeek), day.ToString ()); + } + } + + // returns the rectangle for themonth name + internal Rectangle GetMonthNameRectangle (Rectangle title_rect, int calendar_index) { + DateTime this_month = this.current_month.AddMonths (calendar_index); + Size title_text_size = TextRenderer.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize (); + Size month_size = TextRenderer.MeasureString (this_month.ToString ("MMMM"), this.Font).ToSize (); + // return only the month name part of that + return new Rectangle ( + new Point ( + title_rect.X + ((title_rect.Width - title_text_size.Width)/2), + title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)), + month_size); + } + + internal void GetYearNameRectangles (Rectangle title_rect, int calendar_index, out Rectangle year_rect, out Rectangle up_rect, out Rectangle down_rect) + { + DateTime this_month = this.current_month.AddMonths (calendar_index); + SizeF title_text_size = TextRenderer.MeasureString (this_month.ToString ("MMMM yyyy"), this.bold_font, int.MaxValue, centered_format); + SizeF year_size = TextRenderer.MeasureString (this_month.ToString ("yyyy"), this.bold_font, int.MaxValue, centered_format); + // find out how much space the title took + RectangleF text_rect = new RectangleF ( + new PointF ( + title_rect.X + ((title_rect.Width - title_text_size.Width) / 2), + title_rect.Y + ((title_rect.Height - title_text_size.Height) / 2)), + title_text_size); + // return only the rect of the year + year_rect = new Rectangle ( + new Point ( + ((int)(text_rect.Right - year_size.Width + 1)), + (int)text_rect.Y), + new Size ((int)(year_size.Width + 1), (int)(year_size.Height + 1))); + + year_rect.Inflate (0, 1); + up_rect = new Rectangle (); + up_rect.Location = new Point (year_rect.X + year_rect.Width + 2, year_rect.Y); + up_rect.Size = new Size (16, year_rect.Height / 2); + down_rect = new Rectangle (); + down_rect.Location = new Point (up_rect.X, up_rect.Y + up_rect.Height + 1); + down_rect.Size = up_rect.Size; + } + + // returns the rectangle for the year in the title + internal Rectangle GetYearNameRectangle (Rectangle title_rect, int calendar_index) { + Rectangle result, discard; + GetYearNameRectangles (title_rect, calendar_index, out result, out discard, out discard); + return result; + } + + // determine if date is allowed to be drawn in month + internal bool IsValidWeekToDraw (DateTime month, DateTime date, int row, int col) { + DateTime tocheck = month.AddMonths (-1); + if ((month.Year == date.Year && month.Month == date.Month) || + (tocheck.Year == date.Year && tocheck.Month == date.Month)) { + return true; + } + + // check the railing dates (days in the month after the last month in grid) + if (row == CalendarDimensions.Height - 1 && col == CalendarDimensions.Width - 1) { + tocheck = month.AddMonths (1); + return (tocheck.Year == date.Year && tocheck.Month == date.Month) ; + } + + return false; + } + + // set one item clicked and all others off + private void SetItemClick(HitTestInfo hti) + { + switch(hti.HitArea) { + case HitArea.NextMonthButton: + this.is_previous_clicked = false; + this.is_next_clicked = true; + this.is_date_clicked = false; + break; + case HitArea.PrevMonthButton: + this.is_previous_clicked = true; + this.is_next_clicked = false; + this.is_date_clicked = false; + break; + case HitArea.PrevMonthDate: + case HitArea.NextMonthDate: + case HitArea.Date: + this.clicked_date = hti.hit_time; + this.is_previous_clicked = false; + this.is_next_clicked = false; + this.is_date_clicked = true; + break; + default : + this.is_previous_clicked = false; + this.is_next_clicked = false; + this.is_date_clicked = false; + break; + } + } + + // called when today context menu is clicked + private void TodayMenuItemClickHandler (object sender, EventArgs e) + { + this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date); + this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd)); + } + + // called when month context menu is clicked + private void MonthMenuItemClickHandler (object sender, EventArgs e) { + MenuItem item = sender as MenuItem; + if (item != null && month_title_click_location != Point.Empty) { + // establish which month we want to move to + if (item.Parent == null) { + return; + } + int new_month = item.Parent.MenuItems.IndexOf (item) + 1; + if (new_month == 0) { + return; + } + // okay let's establish which calendar was hit + Size month_size = this.SingleMonthSize; + for (int i=0; i < CalendarDimensions.Height; i++) { + for (int j=0; j < CalendarDimensions.Width; j++) { + int month_index = (i * CalendarDimensions.Width) + j; + Rectangle month_rect = new Rectangle ( new Point (0, 0), month_size); + if (j == 0) { + month_rect.X = this.ClientRectangle.X + 1; + } else { + month_rect.X = this.ClientRectangle.X + 1 + ((j)*(month_size.Width+calendar_spacing.Width)); + } + if (i == 0) { + month_rect.Y = this.ClientRectangle.Y + 1; + } else { + month_rect.Y = this.ClientRectangle.Y + 1 + ((i)*(month_size.Height+calendar_spacing.Height)); + } + // see if the point is inside + if (month_rect.Contains (month_title_click_location)) { + DateTime clicked_month = CurrentMonth.AddMonths (month_index); + // get the month that we want to move to + int month_offset = new_month - clicked_month.Month; + + // move forward however more months we need to + this.CurrentMonth = this.CurrentMonth.AddMonths (month_offset); + break; + } + } + } + + // clear the point + month_title_click_location = Point.Empty; + } + } + + // raised on the timer, for mouse hold clicks + private void TimerHandler (object sender, EventArgs e) { + // now find out which area was click + if (this.Capture) { + HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition)); + // see if it was clicked on the prev or next mouse + if (click_state [1] || click_state [2]) { + // invalidate the area where the mouse was last held + DoMouseUp (); + // register the click + if (hti.HitArea == HitArea.PrevMonthButton || + hti.HitArea == HitArea.NextMonthButton) { + DoButtonMouseDown (hti); + click_state [1] = (hti.HitArea == HitArea.PrevMonthButton); + click_state [2] = !click_state [1]; + } + if (timer.Interval != 300) { + timer.Interval = 300; + } + } + } else { + timer.Enabled = false; + } + } + + // selects one of the buttons + private void DoButtonMouseDown (HitTestInfo hti) { + // show the click then move on + SetItemClick(hti); + if (hti.HitArea == HitArea.PrevMonthButton) { + // invalidate the prev monthbutton + this.Invalidate( + new Rectangle ( + this.ClientRectangle.X + 1 + button_x_offset, + this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2, + button_size.Width, + button_size.Height)); + int scroll = (scroll_change == 0 ? CalendarDimensions.Width * CalendarDimensions.Height : scroll_change); + this.CurrentMonth = this.CurrentMonth.AddMonths (-scroll); + } else { + // invalidate the next monthbutton + this.Invalidate( + new Rectangle ( + this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width, + this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2, + button_size.Width, + button_size.Height)); + int scroll = (scroll_change == 0 ? CalendarDimensions.Width * CalendarDimensions.Height : scroll_change); + this.CurrentMonth = this.CurrentMonth.AddMonths (scroll); + } + } + + // selects the clicked date + private void DoDateMouseDown (HitTestInfo hti) { + SetItemClick(hti); + } + + // event run on the mouse up event + private void DoMouseUp () { + + IsYearGoingDown = false; + IsYearGoingUp = false; + is_mouse_moving_year = false; + + // invalidate the next monthbutton + if (this.is_next_clicked) { + this.Invalidate( + new Rectangle ( + this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width, + this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2, + button_size.Width, + button_size.Height)); + } + // invalidate the prev monthbutton + if (this.is_previous_clicked) { + this.Invalidate( + new Rectangle ( + this.ClientRectangle.X + 1 + button_x_offset, + this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2, + button_size.Width, + button_size.Height)); + } + if (this.is_date_clicked) { + // invalidate the area under the cursor, to remove focus rect + this.InvalidateDateRange (new SelectionRange (clicked_date, clicked_date)); + } + this.is_previous_clicked = false; + this.is_next_clicked = false; + this.is_date_clicked = false; + } + + // needed when in windowed mode to close the calendar if no + // part of it has focus. + private void UpDownTimerTick(object sender, EventArgs e) + { + if (IsYearGoingUp) { + IsYearGoingUp = true; + } + if (IsYearGoingDown) { + IsYearGoingDown = true; + } + + if (!IsYearGoingDown && !IsYearGoingUp) { + updown_timer.Enabled = false; + } else if (IsYearGoingDown || IsYearGoingUp) { + updown_timer.Interval = subsequent_delay; + } + } + + // Needed when in windowed mode. + private void StartHideTimer () + { + if (updown_timer == null) { + updown_timer = new Timer (); + updown_timer.Tick += new EventHandler (UpDownTimerTick); + } + updown_timer.Interval = initial_delay; + updown_timer.Enabled = true; + } + + // occurs when mouse moves around control, used for selection + private void MouseMoveHandler (object sender, MouseEventArgs e) { + HitTestInfo hti = this.HitTest (e.X, e.Y); + // clear the last clicked item + if (click_state [0]) { + // register the click + if (hti.HitArea == HitArea.PrevMonthDate || + hti.HitArea == HitArea.NextMonthDate || + hti.HitArea == HitArea.Date) + { + Rectangle prev_rect = clicked_rect; + DateTime prev_clicked = clicked_date; + DoDateMouseDown (hti); + if (owner == null) { + click_state [0] = true; + } else { + click_state [0] = false; + click_state [1] = false; + click_state [2] = false; + } + + if (prev_clicked != clicked_date) { + // select date after updating click_state and clicked_date + SelectDate (clicked_date); + date_selected_event_pending = true; + + Rectangle invalid = Rectangle.Union (prev_rect, clicked_rect); + Invalidate (invalid); + } + } + + } + } + + // to check if the mouse has come down on this control + private void MouseDownHandler (object sender, MouseEventArgs e) + { + if ((e.Button & MouseButtons.Left) == 0) + return; + + // clear the click_state variables + click_state [0] = false; + click_state [1] = false; + click_state [2] = false; + + // disable the timer if it was enabled + if (timer.Enabled) { + timer.Stop (); + timer.Enabled = false; + } + + Point point = new Point (e.X, e.Y); + // figure out if we are in drop down mode and a click happened outside us + if (this.owner != null) { + if (!this.ClientRectangle.Contains (point)) { + this.owner.HideMonthCalendar (); + return; + } + } + + //establish where was hit + HitTestInfo hti = this.HitTest(point); + // hide the year numeric up down if it was clicked + if (ShowYearUpDown && hti.HitArea != HitArea.TitleYear) { + ShowYearUpDown = false; + } + switch (hti.HitArea) { + case HitArea.PrevMonthButton: + case HitArea.NextMonthButton: + DoButtonMouseDown (hti); + click_state [1] = (hti.HitArea == HitArea.PrevMonthDate); + click_state [2] = !click_state [1]; + timer.Interval = 750; + timer.Start (); + break; + case HitArea.Date: + case HitArea.PrevMonthDate: + case HitArea.NextMonthDate: + DoDateMouseDown (hti); + + // select date before updating click_state + SelectDate (clicked_date); + date_selected_event_pending = true; + + // leave clicked state blank if drop down window + if (owner == null) { + click_state [0] = true; + } else { + click_state [0] = false; + click_state [1] = false; + click_state [2] = false; + } + + break; + case HitArea.TitleMonth: + month_title_click_location = hti.Point; + if (this.Capture && owner != null) { + Capture = false; + Capture = true; + } + break; + case HitArea.TitleYear: + // place the numeric up down + if (ShowYearUpDown) { + if (hti.hit_area_extra == HitAreaExtra.UpButton) { + is_mouse_moving_year = true; + IsYearGoingUp = true; + } else if (hti.hit_area_extra == HitAreaExtra.DownButton) { + is_mouse_moving_year = true; + IsYearGoingDown = true; + } + return; + } else { + ShowYearUpDown = true; + } + break; + case HitArea.TodayLink: + this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date); + this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd)); + break; + default: + this.is_previous_clicked = false; + this.is_next_clicked = false; + this.is_date_clicked = false; + break; + } + } + + // raised by any key down events + private void KeyDownHandler (object sender, KeyEventArgs e) { + // send keys to the year_updown control, let it handle it + if(ShowYearUpDown) { + switch (e.KeyCode) { + case Keys.Enter: + ShowYearUpDown = false; + IsYearGoingDown = false; + IsYearGoingUp = false; + break; + case Keys.Up: { + IsYearGoingUp = true; + break; + } + case Keys.Down: { + IsYearGoingDown = true; + break; + } + } + } else { + if (!is_shift_pressed && e.Shift) { + first_select_start_date = SelectionStart; + is_shift_pressed = e.Shift; + e.Handled = true; + } + switch (e.KeyCode) { + case Keys.Home: + // set the date to the start of the month + if (is_shift_pressed) { + DateTime date = GetFirstDateInMonth (first_select_start_date); + if (date < first_select_start_date.AddDays ((MaxSelectionCount-1)*-1)) { + date = first_select_start_date.AddDays ((MaxSelectionCount-1)*-1); + } + this.SetSelectionRange (date, first_select_start_date); + } else { + DateTime date = GetFirstDateInMonth (this.SelectionStart); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.End: + // set the date to the last of the month + if (is_shift_pressed) { + DateTime date = GetLastDateInMonth (first_select_start_date); + if (date > first_select_start_date.AddDays (MaxSelectionCount-1)) { + date = first_select_start_date.AddDays (MaxSelectionCount-1); + } + this.SetSelectionRange (date, first_select_start_date); + } else { + DateTime date = GetLastDateInMonth (this.SelectionStart); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.PageUp: + // set the date to the last of the month + if (is_shift_pressed) { + this.AddTimeToSelection (-1, false); + } else { + DateTime date = this.SelectionStart.AddMonths (-1); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.PageDown: + // set the date to the last of the month + if (is_shift_pressed) { + this.AddTimeToSelection (1, false); + } else { + DateTime date = this.SelectionStart.AddMonths (1); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.Up: + // set the back 1 week + if (is_shift_pressed) { + this.AddTimeToSelection (-7, true); + } else { + DateTime date = this.SelectionStart.AddDays (-7); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.Down: + // set the date forward 1 week + if (is_shift_pressed) { + this.AddTimeToSelection (7, true); + } else { + DateTime date = this.SelectionStart.AddDays (7); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.Left: + // move one left + if (is_shift_pressed) { + this.AddTimeToSelection (-1, true); + } else { + DateTime date = this.SelectionStart.AddDays (-1); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.Right: + // move one left + if (is_shift_pressed) { + this.AddTimeToSelection (1, true); + } else { + DateTime date = this.SelectionStart.AddDays (1); + this.SetSelectionRange (date, date); + } + e.Handled = true; + break; + case Keys.F4: + // Close ourselves on Alt-F4 if we are a popup + if (e.Alt && owner != null) { + this.Hide (); + e.Handled = true; + } + break; + default: + break; + } + } + } + + // to check if the mouse has come up on this control + private void MouseUpHandler (object sender, MouseEventArgs e) + { + if ((e.Button & MouseButtons.Left) == 0) { + return; + } + + if (timer.Enabled) { + timer.Stop (); + } + // clear the click state array + click_state [0] = false; + click_state [1] = false; + click_state [2] = false; + // do the regulare mouseup stuff + this.DoMouseUp (); + + if (date_selected_event_pending) { + OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd)); + date_selected_event_pending = false; + } + } + + // raised by any key up events + private void KeyUpHandler (object sender, KeyEventArgs e) { + is_shift_pressed = e.Shift ; + e.Handled = true; + IsYearGoingUp = false; + IsYearGoingDown = false; + } + + // paint this control now + private void PaintHandler (object sender, PaintEventArgs pe) { + if (Width <= 0 || Height <= 0 || Visible == false) + return; + + Draw (pe.ClipRectangle, pe.Graphics); + + // fire the new paint handler + if (this.Paint != null) + { + this.Paint (sender, pe); + } + } + + // returns the region of the control that needs to be redrawn + private void InvalidateDateRange (SelectionRange range) { + SelectionRange bounds = this.GetDisplayRange (false); + + if (range.End < bounds.Start || range.Start > bounds.End) { + // don't invalidate anything, as the modified date range + // is outside the visible bounds of this control + return; + } + // adjust the start and end to be inside the visible range + if (range.Start < bounds.Start) { + range = new SelectionRange (bounds.Start, range.End); + } + if (range.End > bounds.End) { + range = new SelectionRange (range.Start, bounds.End); + } + // now invalidate the date rectangles as series of rows + DateTime last_month = this.current_month.AddMonths ((CalendarDimensions.Width * CalendarDimensions.Height)).AddDays (-1); + DateTime current = range.Start; + while (current <= range.End) { + DateTime month_end = new DateTime (current.Year, current.Month, 1).AddMonths (1).AddDays (-1);; + Rectangle start_rect; + Rectangle end_rect; + // see if entire selection is in this current month + if (range.End <= month_end && current < last_month) { + // the end is the last date + if (current < this.current_month) { + start_rect = GetDateRowRect (current_month, current_month); + } else { + start_rect = GetDateRowRect (current, current); + } + end_rect = GetDateRowRect (current, range.End); + } else if (current < last_month) { + // otherwise it simply means we have a selection spaning + // multiple months simply set rectangle inside the current month + start_rect = GetDateRowRect (current, current); + end_rect = GetDateRowRect (month_end, month_end); + } else { + // it's outside the visible range + start_rect = GetDateRowRect (last_month, last_month.AddDays (1)); + end_rect = GetDateRowRect (last_month, range.End); + } + // push to the next month + current = month_end.AddDays (1); + // invalidate from the start row to the end row for this month + this.Invalidate ( + new Rectangle ( + start_rect.X, + start_rect.Y, + start_rect.Width, + Math.Max (end_rect.Bottom - start_rect.Y, 0))); + } + } + + // gets the rect of the row where the specified date appears on the specified month + private Rectangle GetDateRowRect (DateTime month, DateTime date) { + // first get the general rect of the supplied month + Size month_size = SingleMonthSize; + Rectangle month_rect = Rectangle.Empty; + for (int i=0; i < CalendarDimensions.Width*CalendarDimensions.Height; i++) { + DateTime this_month = this.current_month.AddMonths (i); + if (month.Year == this_month.Year && month.Month == this_month.Month) { + month_rect = new Rectangle ( + this.ClientRectangle.X + 1 + (month_size.Width * (i%CalendarDimensions.Width)) + (this.calendar_spacing.Width * (i%CalendarDimensions.Width)), + this.ClientRectangle.Y + 1 + (month_size.Height * (i/CalendarDimensions.Width)) + (this.calendar_spacing.Height * (i/CalendarDimensions.Width)), + month_size.Width, + month_size.Height); + break; + } + } + // now find out where in the month the supplied date is + if (month_rect == Rectangle.Empty) { + return Rectangle.Empty; + } + // find out which row this date is in + int row = -1; + DateTime first_date = GetFirstDateInMonthGrid (month); + DateTime end_date = first_date.AddDays (7); + for (int i=0; i < 6; i++) { + if (date >= first_date && date < end_date) { + row = i; + break; + } + first_date = end_date; + end_date = end_date.AddDays (7); + } + // ensure it's a valid row + if (row < 0) { + return Rectangle.Empty; + } + int x_offset = (this.ShowWeekNumbers) ? date_cell_size.Width : 0; + int y_offset = title_size.Height + (date_cell_size.Height * (row + 1)); + return new Rectangle ( + month_rect.X + x_offset, + month_rect.Y + y_offset, + date_cell_size.Width * 7, + date_cell_size.Height); + } + + internal void Draw (Rectangle clip_rect, Graphics dc) + { + ThemeEngine.Current.DrawMonthCalendar (dc, clip_rect, this); + } + + internal override bool InternalCapture { + get { + return base.InternalCapture; + } + set { + // Don't allow internal capture when DateTimePicker is using us + // Widget sets this on MouseDown + if (owner == null) + base.InternalCapture = value; + } + } + + #endregion //internal methods + + #region internal drawing methods + + + #endregion // internal drawing methods + + #region inner classes and enumerations + + // enumeration about what type of area on the calendar was hit + public enum HitArea { + Nowhere, + TitleBackground, + TitleMonth, + TitleYear, + NextMonthButton, + PrevMonthButton, + CalendarBackground, + Date, + NextMonthDate, + PrevMonthDate, + DayOfWeek, + WeekNumbers, + TodayLink + } + + internal enum HitAreaExtra { + YearRectangle, + UpButton, + DownButton + } + + // info regarding to a hit test on this calendar + public sealed class HitTestInfo { + + private HitArea hit_area; + private Point point; + private DateTime time; + + internal HitAreaExtra hit_area_extra; + internal DateTime hit_time; + + // default constructor + internal HitTestInfo () { + hit_area = HitArea.Nowhere; + point = new Point (0, 0); + time = DateTime.Now; + } + + // overload receives all properties + internal HitTestInfo (HitArea hit_area, Point point, DateTime time) { + this.hit_area = hit_area; + this.point = point; + this.time = time; + this.hit_time = time; + } + + // overload receives all properties + internal HitTestInfo (HitArea hit_area, Point point, DateTime time, DateTime hit_time) + { + this.hit_area = hit_area; + this.point = point; + this.time = time; + this.hit_time = hit_time; + } + + internal HitTestInfo (HitArea hit_area, Point point, DateTime time, HitAreaExtra hit_area_extra) + { + this.hit_area = hit_area; + this.hit_area_extra = hit_area_extra; + this.point = point; + this.time = time; + } + + // the type of area that was hit + public HitArea HitArea { + get { + return hit_area; + } + } + + // the point that is being test + public Point Point { + get { + return point; + } + } + + // the date under the hit test point, only valid if HitArea is Date + public DateTime Time { + get { + return time; + } + } + } + + #endregion // inner classes + + #region UIA Framework: Methods, Properties and Events + + static object UIAMaxSelectionCountChangedEvent = new object (); + static object UIASelectionChangedEvent = new object (); + + internal event EventHandler UIAMaxSelectionCountChanged { + add { Events.AddHandler (UIAMaxSelectionCountChangedEvent, value); } + remove { Events.RemoveHandler (UIAMaxSelectionCountChangedEvent, value); } + } + + internal event EventHandler UIASelectionChanged { + add { Events.AddHandler (UIASelectionChangedEvent, value); } + remove { Events.RemoveHandler (UIASelectionChangedEvent, value); } + } + + private void OnUIAMaxSelectionCountChanged () + { + EventHandler eh = (EventHandler) Events [UIAMaxSelectionCountChangedEvent]; + if (eh != null) + eh (this, EventArgs.Empty); + } + + private void OnUIASelectionChanged () + { + EventHandler eh = (EventHandler) Events [UIASelectionChangedEvent]; + if (eh != null) + eh (this, EventArgs.Empty); + } + + #endregion + } +} |
