1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004-2006 Novell, Inc.
23 // John BouAntoun jba-mono@optusnet.com.au
26 // - get the date_cell_size and title_size to be pixel perfect match of SWF
29 using System.Collections;
30 using System.ComponentModel;
31 using System.ComponentModel.Design;
33 using System.Globalization;
34 using System.Windows.Forms;
35 using System.Runtime.InteropServices;
37 namespace System.Windows.Forms {
38 [DefaultBindingProperty("SelectionRange")]
39 [ClassInterface(ClassInterfaceType.AutoDispatch)]
41 [DefaultProperty("SelectionRange")]
42 [DefaultEvent("DateChanged")]
43 [Designer ("System.Windows.Forms.Design.MonthCalendarDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
44 public class MonthCalendar : Control {
45 #region Local variables
46 ArrayList annually_bolded_dates;
47 ArrayList monthly_bolded_dates;
48 ArrayList bolded_dates;
49 Size calendar_dimensions;
50 Day first_day_of_week;
52 int max_selection_count;
55 SelectionRange selection_range;
57 bool show_today_circle;
58 bool show_week_numbers;
59 Color title_back_color;
60 Color title_fore_color;
63 Color trailing_fore_color;
64 ContextMenu today_menu;
65 ContextMenu month_menu;
68 const int initial_delay = 500;
69 const int subsequent_delay = 100;
70 private bool is_year_going_up;
71 private bool is_year_going_down;
72 private bool is_mouse_moving_year;
73 private int year_moving_count;
74 private bool date_selected_event_pending;
75 bool right_to_left_layout;
77 // internal variables used
78 internal bool show_year_updown;
79 internal DateTime current_month; // the month that is being displayed in top left corner of the grid
80 internal DateTimePicker owner; // used if this control is popped up
81 internal int button_x_offset;
82 internal Size button_size;
83 internal Size title_size;
84 internal Size date_cell_size;
85 internal Size calendar_spacing;
86 internal int divider_line_offset;
87 internal DateTime clicked_date;
88 internal Rectangle clicked_rect;
89 internal bool is_date_clicked;
90 internal bool is_previous_clicked;
91 internal bool is_next_clicked;
92 internal bool is_shift_pressed;
93 internal DateTime first_select_start_date;
94 internal int last_clicked_calendar_index;
95 internal Rectangle last_clicked_calendar_rect;
96 internal Font bold_font; // Cache the font in FontStyle.Bold
97 internal StringFormat centered_format; // Cache centered string format
98 private Point month_title_click_location;
99 // this is used to see which item was actually clicked on in the beginning
100 // so that we know which item to fire on timer
102 // 1: previous clicked
104 private bool[] click_state;
108 #endregion // Local variables
110 #region Public Constructors
112 public MonthCalendar () {
113 // set up the control painting
114 SetStyle (ControlStyles.UserPaint | ControlStyles.StandardClick, false);
117 timer = new Timer ();
118 timer.Interval = 500;
119 timer.Enabled = false;
121 // initialise default values
122 DateTime now = DateTime.Now.Date;
123 selection_range = new SelectionRange (now, now);
125 current_month = new DateTime (now.Year , now.Month, 1);
127 // iniatialise local members
128 annually_bolded_dates = null;
130 calendar_dimensions = new Size (1,1);
131 first_day_of_week = Day.Default;
132 max_date = new DateTime (9998, 12, 31);
133 max_selection_count = 7;
134 min_date = new DateTime (1753, 1, 1);
135 monthly_bolded_dates = null;
138 show_today_circle = true;
139 show_week_numbers = false;
140 title_back_color = ThemeEngine.Current.ColorActiveCaption;
141 title_fore_color = ThemeEngine.Current.ColorActiveCaptionText;
142 today_date_set = false;
143 trailing_fore_color = SystemColors.GrayText;
144 bold_font = new Font (Font, Font.Style | FontStyle.Bold);
145 centered_format = new StringFormat (StringFormat.GenericTypographic);
146 centered_format.FormatFlags = centered_format.FormatFlags | StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.NoWrap | StringFormatFlags.FitBlackBox;
147 centered_format.FormatFlags &= ~StringFormatFlags.NoClip;
148 centered_format.LineAlignment = StringAlignment.Center;
149 centered_format.Alignment = StringAlignment.Center;
151 // Set default values
152 ForeColor = SystemColors.WindowText;
153 BackColor = ThemeEngine.Current.ColorWindow;
155 // intiailise internal variables used
157 button_size = new Size (22, 17);
158 // default settings based on 8.25 pt San Serif Font
159 // Not sure of algorithm used to establish this
160 date_cell_size = new Size (24, 16); // default size at san-serif 8.25
161 divider_line_offset = 4;
162 calendar_spacing = new Size (4, 5); // horiz and vert spacing between months in a calendar grid
164 // set some state info
166 is_date_clicked = false;
167 is_previous_clicked = false;
168 is_next_clicked = false;
169 is_shift_pressed = false;
170 click_state = new bool [] {false, false, false};
171 first_select_start_date = now;
172 month_title_click_location = Point.Empty;
174 // set up context menus
179 timer.Tick += new EventHandler (TimerHandler);
180 MouseMove += new MouseEventHandler (MouseMoveHandler);
181 MouseDown += new MouseEventHandler (MouseDownHandler);
182 KeyDown += new KeyEventHandler (KeyDownHandler);
183 MouseUp += new MouseEventHandler (MouseUpHandler);
184 KeyUp += new KeyEventHandler (KeyUpHandler);
186 // this replaces paint so call the control version
187 base.Paint += new PaintEventHandler (PaintHandler);
192 // called when this control is added to date time picker
193 internal MonthCalendar (DateTimePicker owner) : this () {
195 this.is_visible = false;
196 this.Size = this.DefaultSize;
199 #endregion // Public Constructors
201 #region Public Instance Properties
203 // dates to make bold on calendar annually (recurring)
205 public DateTime [] AnnuallyBoldedDates {
207 if (annually_bolded_dates == null)
208 annually_bolded_dates = new ArrayList (value);
210 annually_bolded_dates.Clear ();
211 annually_bolded_dates.AddRange (value);
214 UpdateBoldedDates ();
217 if (annually_bolded_dates == null || annually_bolded_dates.Count == 0) {
218 return new DateTime [0];
220 DateTime [] result = new DateTime [annually_bolded_dates.Count];
221 annually_bolded_dates.CopyTo (result);
227 [EditorBrowsable(EditorBrowsableState.Never)]
228 public override Image BackgroundImage {
230 return base.BackgroundImage;
233 base.BackgroundImage = value;
237 [EditorBrowsable (EditorBrowsableState.Never)]
239 public override ImageLayout BackgroundImageLayout {
241 return base.BackgroundImageLayout;
244 base.BackgroundImageLayout = value;
248 // the back color for the main part of the calendar
249 public override Color BackColor {
251 base.BackColor = value;
254 return base.BackColor;
258 // specific dates to make bold on calendar (non-recurring)
260 public DateTime[] BoldedDates {
262 if (bolded_dates == null) {
263 bolded_dates = new ArrayList (value);
265 bolded_dates.Clear ();
266 bolded_dates.AddRange (value);
269 UpdateBoldedDates ();
272 if (bolded_dates == null || bolded_dates.Count == 0)
273 return new DateTime [0];
275 DateTime [] result = new DateTime [bolded_dates.Count];
276 bolded_dates.CopyTo (result);
281 // the configuration of the monthly grid display - only allowed to display at most,
282 // 1 calendar year at a time, will be trimmed to fit it properly
284 public Size CalendarDimensions {
286 if (value.Width < 0 || value.Height < 0) {
287 throw new ArgumentException ();
289 if (calendar_dimensions != value) {
290 // squeeze the grid into 1 calendar year
291 if (value.Width * value.Height > 12) {
292 // iteratively reduce the largest dimension till our
293 // product is less than 12
294 if (value.Width > 12 && value.Height > 12) {
295 calendar_dimensions = new Size (4, 3);
296 } else if (value.Width > 12) {
297 for (int i = 12; i > 0; i--) {
298 if (i * value.Height <= 12) {
299 calendar_dimensions = new Size (i, value.Height);
303 } else if (value.Height > 12) {
304 for (int i = 12; i > 0; i--) {
305 if (i * value.Width <= 12) {
306 calendar_dimensions = new Size (value.Width, i);
312 calendar_dimensions = value;
318 return calendar_dimensions;
322 [EditorBrowsable (EditorBrowsableState.Never)]
323 protected override bool DoubleBuffered {
325 return base.DoubleBuffered;
328 base.DoubleBuffered = value;
332 // the first day of the week to display
334 [DefaultValue (Day.Default)]
335 public Day FirstDayOfWeek {
337 if (first_day_of_week != value) {
338 first_day_of_week = value;
343 return first_day_of_week;
347 // the fore color for the main part of the calendar
348 public override Color ForeColor {
350 base.ForeColor = value;
353 return base.ForeColor;
358 [EditorBrowsable(EditorBrowsableState.Never)]
359 public new ImeMode ImeMode {
360 get { return base.ImeMode; }
361 set { base.ImeMode = value; }
364 // the maximum date allowed to be selected on this month calendar
365 public DateTime MaxDate {
367 if (value < MinDate) {
368 string msg = string.Format (CultureInfo.CurrentCulture,
369 "Value of '{0}' is not valid for 'MaxDate'. 'MaxDate' " +
370 "must be greater than or equal to MinDate.",
371 value.ToString ("d", CultureInfo.CurrentCulture));
372 throw new ArgumentOutOfRangeException ("MaxDate",
376 if (max_date == value)
381 if (max_date < selection_range.Start || max_date < selection_range.End) {
382 DateTime start = max_date < selection_range.Start ? max_date : selection_range.Start;
383 DateTime end = max_date < selection_range.End ? max_date : selection_range.End;
384 SelectionRange = new SelectionRange (start, end);
392 // the maximum number of selectable days
394 public int MaxSelectionCount {
397 string msg = string.Format (CultureInfo.CurrentCulture,
398 "Value of '{0}' is not valid for 'MaxSelectionCount'. " +
399 "'MaxSelectionCount' must be greater than or equal to {1}.",
401 throw new ArgumentOutOfRangeException ("MaxSelectionCount",
405 // can't set selectioncount less than already selected dates
406 if ((SelectionEnd - SelectionStart).Days > value) {
407 throw new ArgumentException();
410 if (max_selection_count != value) {
411 max_selection_count = value;
412 this.OnUIAMaxSelectionCountChanged ();
416 return max_selection_count;
420 // the minimum date allowed to be selected on this month calendar
421 public DateTime MinDate {
423 DateTime absoluteMinDate = new DateTime (1753, 1, 1);
425 if (value < absoluteMinDate) {
426 string msg = string.Format (CultureInfo.CurrentCulture,
427 "Value of '{0}' is not valid for 'MinDate'. 'MinDate' " +
428 "must be greater than or equal to {1}.",
429 value.ToString ("d", CultureInfo.CurrentCulture),
430 absoluteMinDate.ToString ("d", CultureInfo.CurrentCulture));
431 throw new ArgumentOutOfRangeException ("MinDate",
435 if (value > MaxDate) {
436 string msg = string.Format (CultureInfo.CurrentCulture,
437 "Value of '{0}' is not valid for 'MinDate'. 'MinDate' " +
438 "must be less than MaxDate.",
439 value.ToString ("d", CultureInfo.CurrentCulture));
440 throw new ArgumentOutOfRangeException ("MinDate",
444 if (min_date == value)
449 if (min_date > selection_range.Start || min_date > selection_range.End) {
450 DateTime start = min_date > selection_range.Start ? min_date : selection_range.Start;
451 DateTime end = min_date > selection_range.End ? min_date : selection_range.End;
452 SelectionRange = new SelectionRange (start, end);
460 // dates to make bold on calendar monthly (recurring)
462 public DateTime[] MonthlyBoldedDates {
464 if (monthly_bolded_dates == null) {
465 monthly_bolded_dates = new ArrayList (value);
467 monthly_bolded_dates.Clear ();
468 monthly_bolded_dates.AddRange (value);
471 UpdateBoldedDates ();
474 if (monthly_bolded_dates == null || monthly_bolded_dates.Count == 0)
475 return new DateTime [0];
477 DateTime [] result = new DateTime [monthly_bolded_dates.Count];
478 monthly_bolded_dates.CopyTo (result);
483 [EditorBrowsable (EditorBrowsableState.Never)]
484 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
486 // Padding should not have any effect on the appearance of MonthCalendar.
487 public new Padding Padding {
492 base.Padding = value;
496 [DefaultValue (false)]
498 public virtual bool RightToLeftLayout {
500 return right_to_left_layout;
503 right_to_left_layout = value;
507 // the ammount by which to scroll this calendar by
509 public int ScrollChange {
511 if (value < 0 || value > 20000) {
512 throw new ArgumentException();
515 if (scroll_change != value) {
516 scroll_change = value;
520 return scroll_change;
525 // the last selected date
527 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
528 public DateTime SelectionEnd {
530 if (value < MinDate || value > MaxDate) {
531 throw new ArgumentException();
534 if (SelectionRange.End != value) {
535 DateTime old_end = SelectionRange.End;
536 // make sure the end obeys the max selection range count
537 if (value < SelectionRange.Start) {
538 SelectionRange.Start = value;
540 if (value.AddDays((MaxSelectionCount-1)*-1) > SelectionRange.Start) {
541 SelectionRange.Start = value.AddDays((MaxSelectionCount-1)*-1);
543 SelectionRange.End = value;
544 this.InvalidateDateRange (new SelectionRange (old_end, SelectionRange.End));
545 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
546 this.OnUIASelectionChanged ();
550 return SelectionRange.End;
555 // the range of selected dates
556 public SelectionRange SelectionRange {
558 if (selection_range != value) {
559 if (value.Start < MinDate)
560 throw new ArgumentException ("SelectionStart cannot be less than MinDate");
561 else if (value.End > MaxDate)
562 throw new ArgumentException ("SelectionEnd cannot be greated than MaxDate");
564 SelectionRange old_range = selection_range;
566 // make sure the end obeys the max selection range count
567 if (value.End.AddDays((MaxSelectionCount-1)*-1) > value.Start) {
568 selection_range = new SelectionRange (value.End.AddDays((MaxSelectionCount-1)*-1), value.End);
570 selection_range = value;
572 SelectionRange visible_range = this.GetDisplayRange(true);
573 if(visible_range.Start > selection_range.End) {
574 this.current_month = new DateTime (selection_range.Start.Year, selection_range.Start.Month, 1);
576 } else if (visible_range.End < selection_range.Start) {
577 int year_diff = selection_range.End.Year - visible_range.End.Year;
578 int month_diff = selection_range.End.Month - visible_range.End.Month;
579 this.current_month = current_month.AddMonths(year_diff * 12 + month_diff);
582 // invalidate the selected range changes
583 DateTime diff_start = old_range.Start;
584 DateTime diff_end = old_range.End;
585 // now decide which region is greated
586 if (old_range.Start > SelectionRange.Start) {
587 diff_start = SelectionRange.Start;
588 } else if (old_range.Start == SelectionRange.Start) {
589 if (old_range.End < SelectionRange.End) {
590 diff_start = old_range.End;
592 diff_start = SelectionRange.End;
595 if (old_range.End < SelectionRange.End) {
596 diff_end = SelectionRange.End;
597 } else if (old_range.End == SelectionRange.End) {
598 if (old_range.Start < SelectionRange.Start) {
599 diff_end = SelectionRange.Start;
601 diff_end = old_range.Start;
606 // invalidate the region required
607 SelectionRange new_range = new SelectionRange (diff_start, diff_end);
608 if (new_range.End != old_range.End || new_range.Start != old_range.Start)
609 this.InvalidateDateRange (new_range);
610 // raise date changed event
611 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
612 this.OnUIASelectionChanged ();
616 return selection_range;
620 // the first selected date
622 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
623 public DateTime SelectionStart {
625 if (value < MinDate || value > MaxDate) {
626 throw new ArgumentException();
629 if (SelectionRange.Start != value) {
630 // make sure the end obeys the max selection range count
631 if (value > SelectionRange.End) {
632 SelectionRange.End = value;
633 } else if (value.AddDays(MaxSelectionCount-1) < SelectionRange.End) {
634 SelectionRange.End = value.AddDays(MaxSelectionCount-1);
636 SelectionRange.Start = value;
637 DateTime new_month = new DateTime(value.Year, value.Month, 1);
638 if (current_month != new_month)
639 current_month = new_month;
642 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
643 this.OnUIASelectionChanged ();
647 return selection_range.Start;
651 // whether or not to show todays date
652 [DefaultValue (true)]
653 public bool ShowToday {
655 if (show_today != value) {
665 // whether or not to show a circle around todays date
666 [DefaultValue (true)]
667 public bool ShowTodayCircle {
669 if (show_today_circle != value) {
670 show_today_circle = value;
675 return show_today_circle;
679 // whether or not to show numbers beside each row of weeks
681 [DefaultValue (false)]
682 public bool ShowWeekNumbers {
684 if (show_week_numbers != value) {
685 show_week_numbers = value;
686 // The values here don't matter, SetBoundsCore will calculate its own
687 SetBoundsCore (Left, Top, Width, Height, BoundsSpecified.Width);
692 return show_week_numbers;
696 // the rectangle size required to render one month based on current font
698 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
699 public Size SingleMonthSize {
701 if (this.Font == null) {
702 throw new InvalidOperationException();
705 // multiplier is sucked out from the font size
706 int multiplier = this.Font.Height;
708 // establis how many columns and rows we have
709 int column_count = (ShowWeekNumbers) ? 8 : 7;
710 int row_count = 7; // not including the today date
712 // set the date_cell_size and the title_size
713 date_cell_size = new Size ((int) Math.Ceiling (1.8 * multiplier), multiplier);
714 title_size = new Size ((date_cell_size.Width * column_count), 2 * multiplier);
716 return new Size (column_count * date_cell_size.Width, row_count * date_cell_size.Height + title_size.Height);
721 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
722 public new Size Size {
733 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
734 [EditorBrowsable(EditorBrowsableState.Never)]
735 public override string Text {
744 // the back color for the title of the calendar and the
745 // forecolor for the day of the week text
746 public Color TitleBackColor {
748 if (title_back_color != value) {
749 title_back_color = value;
754 return title_back_color;
758 // the fore color for the title of the calendar
759 public Color TitleForeColor {
761 if (title_fore_color != value) {
762 title_fore_color = value;
767 return title_fore_color;
771 // the date this calendar is using to refer to today's date
772 public DateTime TodayDate {
774 today_date_set = true;
775 if (today_date != value) {
785 // tells if user specifically set today_date for this control
787 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
788 public bool TodayDateSet {
790 return today_date_set;
794 // the color used for trailing dates in the calendar
795 public Color TrailingForeColor {
797 if (trailing_fore_color != value) {
798 trailing_fore_color = value;
799 SelectionRange bounds = this.GetDisplayRange (false);
800 SelectionRange visible_bounds = this.GetDisplayRange (true);
801 this.InvalidateDateRange (new SelectionRange (bounds.Start, visible_bounds.Start));
802 this.InvalidateDateRange (new SelectionRange (bounds.End, visible_bounds.End));
806 return trailing_fore_color;
810 #endregion // Public Instance Properties
812 #region Protected Instance Properties
814 // overloaded to allow controll to be windowed for drop down
815 protected override CreateParams CreateParams {
817 if (this.owner == null) {
818 return base.CreateParams;
820 CreateParams cp = base.CreateParams;
821 cp.Style ^= (int) WindowStyles.WS_CHILD;
822 cp.Style |= (int) WindowStyles.WS_POPUP;
823 cp.ExStyle |= (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
830 // not sure what to put in here - just doing a base() call - jba
831 protected override ImeMode DefaultImeMode {
833 return base.DefaultImeMode;
837 protected override Padding DefaultMargin {
839 return new Padding (9);
843 protected override Size DefaultSize {
845 Size single_month = SingleMonthSize;
847 int width = calendar_dimensions.Width * single_month.Width;
848 if (calendar_dimensions.Width > 1) {
849 width += (calendar_dimensions.Width - 1) * calendar_spacing.Width;
853 int height = calendar_dimensions.Height * single_month.Height;
854 if (this.ShowToday) {
855 height += date_cell_size.Height + 2; // add the height of the "Today: " ...
857 if (calendar_dimensions.Height > 1) {
858 height += (calendar_dimensions.Height - 1) * calendar_spacing.Height;
861 // add the 1 pixel boundary
869 return new Size (width, height);
873 #endregion // Protected Instance Properties
875 #region Public Instance Methods
877 // add a date to the anually bolded date arraylist
878 public void AddAnnuallyBoldedDate (DateTime date) {
879 if (annually_bolded_dates == null)
880 annually_bolded_dates = new ArrayList ();
881 if (!annually_bolded_dates.Contains (date))
882 annually_bolded_dates.Add (date);
885 // add a date to the normal bolded date arraylist
886 public void AddBoldedDate (DateTime date) {
887 if (bolded_dates == null)
888 bolded_dates = new ArrayList ();
889 if (!bolded_dates.Contains (date))
890 bolded_dates.Add (date);
893 // add a date to the anually monthly date arraylist
894 public void AddMonthlyBoldedDate (DateTime date) {
895 if (monthly_bolded_dates == null)
896 monthly_bolded_dates = new ArrayList ();
897 if (!monthly_bolded_dates.Contains (date))
898 monthly_bolded_dates.Add (date);
901 // if visible = true, return only the dates of full months, else return all dates visible
902 public SelectionRange GetDisplayRange (bool visible) {
905 start = new DateTime (current_month.Year, current_month.Month, 1);
906 end = start.AddMonths (calendar_dimensions.Width * calendar_dimensions.Height);
907 end = end.AddDays(-1);
909 // process all visible dates if needed (including the grayed out dates
911 start = GetFirstDateInMonthGrid (start);
912 end = GetLastDateInMonthGrid (end);
915 return new SelectionRange (start, end);
918 // HitTest overload that recieve's x and y co-ordinates as separate ints
919 public HitTestInfo HitTest (int x, int y) {
920 return HitTest (new Point (x, y));
923 // returns a HitTestInfo for MonthCalendar element's under the specified point
924 public HitTestInfo HitTest (Point point) {
925 return HitTest (point, out last_clicked_calendar_index, out last_clicked_calendar_rect);
928 // clears all the annually bolded dates
929 public void RemoveAllAnnuallyBoldedDates () {
930 if (annually_bolded_dates != null)
931 annually_bolded_dates.Clear ();
934 // clears all the normal bolded dates
935 public void RemoveAllBoldedDates () {
936 if (bolded_dates != null)
937 bolded_dates.Clear ();
940 // clears all the monthly bolded dates
941 public void RemoveAllMonthlyBoldedDates () {
942 if (monthly_bolded_dates != null)
943 monthly_bolded_dates.Clear ();
946 // clears the specified annually bolded date (only compares day and month)
947 // only removes the first instance of the match
948 public void RemoveAnnuallyBoldedDate (DateTime date) {
949 if (annually_bolded_dates == null)
952 for (int i = 0; i < annually_bolded_dates.Count; i++) {
953 DateTime dt = (DateTime) annually_bolded_dates [i];
954 if (dt.Day == date.Day && dt.Month == date.Month) {
955 annually_bolded_dates.RemoveAt (i);
961 // clears all the normal bolded date
962 // only removes the first instance of the match
963 public void RemoveBoldedDate (DateTime date) {
964 if (bolded_dates == null)
967 for (int i = 0; i < bolded_dates.Count; i++) {
968 DateTime dt = (DateTime) bolded_dates [i];
969 if (dt.Year == date.Year && dt.Month == date.Month && dt.Day == date.Day) {
970 bolded_dates.RemoveAt (i);
976 // clears the specified monthly bolded date (only compares day and month)
977 // only removes the first instance of the match
978 public void RemoveMonthlyBoldedDate (DateTime date) {
979 if (monthly_bolded_dates == null)
982 for (int i = 0; i < monthly_bolded_dates.Count; i++) {
983 DateTime dt = (DateTime) monthly_bolded_dates [i];
984 if (dt.Day == date.Day && dt.Month == date.Month) {
985 monthly_bolded_dates.RemoveAt (i);
991 // sets the calendar_dimensions. If product is > 12, the larger dimension is reduced to make product < 12
992 public void SetCalendarDimensions(int x, int y) {
993 this.CalendarDimensions = new Size(x, y);
996 // sets the currently selected date as date
997 public void SetDate (DateTime date) {
998 this.SetSelectionRange (date.Date, date.Date);
1001 // utility method set the SelectionRange property using individual dates
1002 public void SetSelectionRange (DateTime date1, DateTime date2) {
1003 this.SelectionRange = new SelectionRange(date1, date2);
1006 public override string ToString () {
1007 return this.GetType().Name + ", " + this.SelectionRange.ToString ();
1010 // usually called after an AddBoldedDate method is called
1011 // formats monthly and daily bolded dates according to the current calendar year
1012 public void UpdateBoldedDates () {
1016 #endregion // Public Instance Methods
1018 #region Protected Instance Methods
1020 // not sure why this needs to be overriden
1021 protected override void CreateHandle () {
1022 base.CreateHandle ();
1025 // not sure why this needs to be overriden
1026 protected override void Dispose (bool disposing) {
1027 base.Dispose (disposing);
1030 // Handle arrow keys
1031 protected override bool IsInputKey (Keys keyData) {
1042 return base.IsInputKey (keyData);
1045 // not sure why this needs to be overriden
1046 protected override void OnBackColorChanged (EventArgs e) {
1047 base.OnBackColorChanged (e);
1051 // raises the date changed event
1052 protected virtual void OnDateChanged (DateRangeEventArgs drevent) {
1053 DateRangeEventHandler eh = (DateRangeEventHandler) (Events [DateChangedEvent]);
1058 // raises the DateSelected event
1059 protected virtual void OnDateSelected (DateRangeEventArgs drevent) {
1060 DateRangeEventHandler eh = (DateRangeEventHandler) (Events [DateSelectedEvent]);
1065 protected override void OnFontChanged (EventArgs e) {
1066 // Update size based on new font's space requirements
1067 Size = new Size (CalendarDimensions.Width * SingleMonthSize.Width,
1068 CalendarDimensions.Height * SingleMonthSize.Height);
1069 bold_font = new Font (Font, Font.Style | FontStyle.Bold);
1070 base.OnFontChanged (e);
1073 protected override void OnForeColorChanged (EventArgs e) {
1074 base.OnForeColorChanged (e);
1077 protected override void OnHandleCreated (EventArgs e) {
1078 base.OnHandleCreated (e);
1081 protected override void OnHandleDestroyed (EventArgs e)
1083 base.OnHandleDestroyed (e);
1086 [EditorBrowsable (EditorBrowsableState.Advanced)]
1087 protected virtual void OnRightToLeftLayoutChanged (EventArgs e) {
1088 EventHandler eh = (EventHandler) (Events [RightToLeftLayoutChangedEvent]);
1093 // i think this is overriden to not allow the control to be changed to an arbitrary size
1094 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified)
1096 // only allow sizes = default size to be set
1097 Size default_size = DefaultSize;
1098 Size min_size = default_size;
1099 Size max_size = new Size (default_size.Width + SingleMonthSize.Width + calendar_spacing.Width,
1100 default_size.Height + SingleMonthSize.Height + calendar_spacing.Height);
1101 int x_mid_point = (max_size.Width + min_size.Width)/2;
1102 int y_mid_point = (max_size.Height + min_size.Height)/2;
1104 if (width < x_mid_point) {
1105 width = min_size.Width;
1107 width = max_size.Width;
1109 if (height < y_mid_point) {
1110 height = min_size.Height;
1112 height = max_size.Height;
1114 base.SetBoundsCore (x, y, width, height, specified);
1117 protected override void WndProc (ref Message m) {
1118 base.WndProc (ref m);
1121 #endregion // Protected Instance Methods
1123 #region public events
1124 static object DateChangedEvent = new object ();
1125 static object DateSelectedEvent = new object ();
1126 static object RightToLeftLayoutChangedEvent = new object ();
1128 // fired when the date is changed (either explicitely or implicitely)
1129 // when navigating the month selector
1130 public event DateRangeEventHandler DateChanged {
1131 add { Events.AddHandler (DateChangedEvent, value); }
1132 remove { Events.RemoveHandler (DateChangedEvent, value); }
1135 // fired when the user explicitely clicks on date to select it
1136 public event DateRangeEventHandler DateSelected {
1137 add { Events.AddHandler (DateSelectedEvent, value); }
1138 remove { Events.RemoveHandler (DateSelectedEvent, value); }
1142 [EditorBrowsable (EditorBrowsableState.Never)]
1143 public new event EventHandler BackgroundImageChanged {
1144 add { base.BackgroundImageChanged += value; }
1145 remove { base.BackgroundImageChanged -= value; }
1149 [EditorBrowsable (EditorBrowsableState.Never)]
1150 public new event EventHandler BackgroundImageLayoutChanged
1152 add { base.BackgroundImageLayoutChanged += value;}
1153 remove { base.BackgroundImageLayoutChanged += value;}
1157 [EditorBrowsable (EditorBrowsableState.Never)]
1158 public new event EventHandler Click {
1159 add {base.Click += value; }
1160 remove {base.Click -= value;}
1164 [EditorBrowsable (EditorBrowsableState.Never)]
1165 public new event EventHandler DoubleClick {
1166 add {base.DoubleClick += value; }
1167 remove {base.DoubleClick -= value; }
1171 [EditorBrowsable (EditorBrowsableState.Never)]
1172 public new event EventHandler ImeModeChanged {
1173 add { base.ImeModeChanged += value; }
1174 remove { base.ImeModeChanged -= value; }
1178 [EditorBrowsable (EditorBrowsableState.Never)]
1179 public new event MouseEventHandler MouseClick {
1180 add { base.MouseClick += value;}
1181 remove { base.MouseClick -= value;}
1185 [EditorBrowsable (EditorBrowsableState.Never)]
1186 public new event MouseEventHandler MouseDoubleClick {
1187 add { base.MouseDoubleClick += value; }
1188 remove { base.MouseDoubleClick -= value; }
1192 [EditorBrowsable (EditorBrowsableState.Never)]
1193 public new event EventHandler PaddingChanged {
1194 add {base.PaddingChanged += value;}
1195 remove {base.PaddingChanged -= value;}
1198 // XXX check this out
1200 [EditorBrowsable (EditorBrowsableState.Never)]
1201 public new event PaintEventHandler Paint;
1203 public event EventHandler RightToLeftLayoutChanged {
1204 add {Events.AddHandler (RightToLeftLayoutChangedEvent, value);}
1205 remove {Events.RemoveHandler (RightToLeftLayoutChangedEvent, value);}
1209 [EditorBrowsable (EditorBrowsableState.Never)]
1210 public new event EventHandler TextChanged {
1211 add { base.TextChanged += value; }
1212 remove { base.TextChanged -= value; }
1214 #endregion // public events
1216 #region internal properties
1218 private void AddYears (int years, bool fast)
1222 if (!(CurrentMonth.Year + years * 5 > MaxDate.Year)) {
1223 newDate = CurrentMonth.AddYears (years * 5);
1224 if (MaxDate >= newDate && MinDate <= newDate) {
1225 CurrentMonth = newDate;
1230 if (!(CurrentMonth.Year + years > MaxDate.Year)) {
1231 newDate = CurrentMonth.AddYears (years);
1232 if (MaxDate >= newDate && MinDate <= newDate) {
1233 CurrentMonth = newDate;
1238 internal bool IsYearGoingUp {
1240 return is_year_going_up;
1244 is_year_going_down = false;
1245 year_moving_count = (is_year_going_up ? year_moving_count + 1 : 1);
1246 if (is_year_going_up)
1247 year_moving_count++;
1249 year_moving_count = 1;
1251 AddYears (1, year_moving_count > 10);
1252 if (is_mouse_moving_year)
1255 year_moving_count = 0;
1257 is_year_going_up = value;
1262 internal bool IsYearGoingDown {
1264 return is_year_going_down;
1269 is_year_going_up = false;
1270 year_moving_count = (is_year_going_down ? year_moving_count + 1 : 1);
1271 if (is_year_going_down)
1272 year_moving_count++;
1274 year_moving_count = 1;
1276 AddYears (-1, year_moving_count > 10);
1277 if (is_mouse_moving_year)
1280 year_moving_count = 0;
1282 is_year_going_down = value;
1287 internal bool ShowYearUpDown {
1289 return show_year_updown;
1292 if (show_year_updown != value) {
1293 show_year_updown = value;
1299 internal DateTime CurrentMonth {
1301 // only interested in if the month (not actual date) has change
1302 if (value < MinDate || value > MaxDate) {
1306 if (value.Month != current_month.Month ||
1307 value.Year != current_month.Year) {
1308 this.SelectionRange = new SelectionRange(
1309 this.SelectionStart.Add(value.Subtract(current_month)),
1310 this.SelectionEnd.Add(value.Subtract(current_month)));
1311 current_month = value;
1312 UpdateBoldedDates();
1317 return current_month;
1321 #endregion // internal properties
1323 #region internal/private methods
1324 internal HitTestInfo HitTest (
1326 out int calendar_index,
1327 out Rectangle calendar_rect) {
1328 // start by initialising the ref parameters
1329 calendar_index = -1;
1330 calendar_rect = Rectangle.Empty;
1332 // before doing all the hard work, see if the today's date wasn't clicked
1333 Rectangle today_rect = new Rectangle (
1335 ClientRectangle.Bottom - date_cell_size.Height,
1336 7 * date_cell_size.Width,
1337 date_cell_size.Height);
1338 if (today_rect.Contains (point) && this.ShowToday) {
1339 return new HitTestInfo(HitArea.TodayLink, point, DateTime.Now);
1342 Size month_size = SingleMonthSize;
1343 // define calendar rect's that this thing can land in
1344 Rectangle[] calendars = new Rectangle [CalendarDimensions.Width * CalendarDimensions.Height];
1345 for (int i=0; i < CalendarDimensions.Width * CalendarDimensions.Height; i ++) {
1347 calendars[i] = new Rectangle (
1348 new Point (ClientRectangle.X + 1, ClientRectangle.Y + 1),
1351 // calendar on the next row
1352 if (i % CalendarDimensions.Width == 0) {
1353 calendars[i] = new Rectangle (
1354 new Point (calendars[i-CalendarDimensions.Width].X, calendars[i-CalendarDimensions.Width].Bottom + calendar_spacing.Height),
1357 // calendar on the next column
1358 calendars[i] = new Rectangle (
1359 new Point (calendars[i-1].Right + calendar_spacing.Width, calendars[i-1].Y),
1365 // through each trying to find a match
1366 for (int i = 0; i < calendars.Length ; i++) {
1367 if (calendars[i].Contains (point)) {
1368 // check the title section
1369 Rectangle title_rect = new Rectangle (
1370 calendars[i].Location,
1372 if (title_rect.Contains (point) ) {
1373 // make sure it's not a previous button
1375 Rectangle button_rect = new Rectangle(
1376 new Point (calendars[i].X + button_x_offset, (title_size.Height - button_size.Height)/2),
1378 if (button_rect.Contains (point)) {
1379 return new HitTestInfo (HitArea.PrevMonthButton, point, new DateTime (1, 1, 1));
1382 // make sure it's not the next button
1383 if (i % CalendarDimensions.Height == 0 && i % CalendarDimensions.Width == calendar_dimensions.Width - 1) {
1384 Rectangle button_rect = new Rectangle(
1385 new Point (calendars[i].Right - button_x_offset - button_size.Width, (title_size.Height - button_size.Height)/2),
1387 if (button_rect.Contains (point)) {
1388 return new HitTestInfo (HitArea.NextMonthButton, point, new DateTime (1, 1, 1));
1392 // indicate which calendar and month it was
1394 calendar_rect = calendars[i];
1396 // make sure it's not the month or the year of the calendar
1397 if (GetMonthNameRectangle (title_rect, i).Contains (point)) {
1398 return new HitTestInfo (HitArea.TitleMonth, point, new DateTime (1, 1, 1));
1400 Rectangle year, up, down;
1401 GetYearNameRectangles (title_rect, i, out year, out up, out down);
1402 if (year.Contains (point)) {
1403 return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.YearRectangle);
1404 } else if (up.Contains (point)) {
1405 return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.UpButton);
1406 } else if (down.Contains (point)) {
1407 return new HitTestInfo (HitArea.TitleYear, point, new DateTime (1, 1, 1), HitAreaExtra.DownButton);
1410 // return the hit test in the title background
1411 return new HitTestInfo (HitArea.TitleBackground, point, new DateTime (1, 1, 1));
1414 Point date_grid_location = new Point (calendars[i].X, title_rect.Bottom);
1416 // see if it's in the Week numbers
1417 if (ShowWeekNumbers) {
1418 Rectangle weeks_rect = new Rectangle (
1420 new Size (date_cell_size.Width,Math.Max (calendars[i].Height - title_rect.Height, 0)));
1421 if (weeks_rect.Contains (point)) {
1422 return new HitTestInfo(HitArea.WeekNumbers, point, DateTime.Now);
1425 // move the location of the grid over
1426 date_grid_location.X += date_cell_size.Width;
1429 // see if it's in the week names
1430 Rectangle day_rect = new Rectangle (
1432 new Size (Math.Max (calendars[i].Right - date_grid_location.X, 0), date_cell_size.Height));
1433 if (day_rect.Contains (point)) {
1434 return new HitTestInfo (HitArea.DayOfWeek, point, new DateTime (1, 1, 1));
1437 // finally see if it was a date that was clicked
1438 Rectangle date_grid = new Rectangle (
1439 new Point (day_rect.X, day_rect.Bottom),
1440 new Size (day_rect.Width, Math.Max(calendars[i].Bottom - day_rect.Bottom, 0)));
1441 if (date_grid.Contains (point)) {
1442 clicked_rect = date_grid;
1443 // okay so it's inside the grid, get the offset
1444 Point offset = new Point (point.X - date_grid.X, point.Y - date_grid.Y);
1445 int row = offset.Y / date_cell_size.Height;
1446 int col = offset.X / date_cell_size.Width;
1447 // establish our first day of the month
1448 DateTime calendar_month = this.CurrentMonth.AddMonths(i);
1449 DateTime first_day = GetFirstDateInMonthGrid (calendar_month);
1450 DateTime time = first_day.AddDays ((row * 7) + col);
1451 // establish which date was clicked
1452 if (time.Year != calendar_month.Year || time.Month != calendar_month.Month) {
1453 if (time < calendar_month && i == 0) {
1454 return new HitTestInfo (HitArea.PrevMonthDate, point, new DateTime (1, 1, 1), time);
1455 } else if (time > calendar_month && i == CalendarDimensions.Width*CalendarDimensions.Height - 1) {
1456 return new HitTestInfo (HitArea.NextMonthDate, point, new DateTime (1, 1, 1), time);
1458 return new HitTestInfo (HitArea.Nowhere, point, new DateTime (1, 1, 1));
1460 return new HitTestInfo(HitArea.Date, point, time);
1465 return new HitTestInfo ();
1468 // returns the date of the first cell of the specified month grid
1469 internal DateTime GetFirstDateInMonthGrid (DateTime month) {
1470 // convert the first_day_of_week into a DayOfWeekEnum
1471 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1472 // find the first day of the month
1473 DateTime first_date_of_month = new DateTime (month.Year, month.Month, 1);
1474 DayOfWeek first_day_of_month = first_date_of_month.DayOfWeek;
1475 // adjust for the starting day of the week
1476 int offset = first_day_of_month - first_day;
1480 return first_date_of_month.AddDays (-1*offset);
1483 // returns the date of the last cell of the specified month grid
1484 internal DateTime GetLastDateInMonthGrid (DateTime month)
1486 DateTime start = GetFirstDateInMonthGrid(month);
1487 return start.AddDays ((7 * 6)-1);
1490 internal bool IsBoldedDate (DateTime date) {
1491 // check bolded dates
1492 if (bolded_dates != null && bolded_dates.Count > 0) {
1493 foreach (DateTime bolded_date in bolded_dates) {
1494 if (bolded_date.Date == date.Date) {
1499 // check monthly dates
1500 if (monthly_bolded_dates != null && monthly_bolded_dates.Count > 0) {
1501 foreach (DateTime bolded_date in monthly_bolded_dates) {
1502 if (bolded_date.Day == date.Day) {
1507 // check yearly dates
1508 if (annually_bolded_dates != null && annually_bolded_dates.Count > 0) {
1509 foreach (DateTime bolded_date in annually_bolded_dates) {
1510 if (bolded_date.Month == date.Month && bolded_date.Day == date.Day) {
1516 return false; // no match
1519 // initialise the 'go to today' context menu
1520 private void SetUpTodayMenu () {
1521 today_menu = new ContextMenu ();
1522 MenuItem menu_item = new MenuItem ("Go to today");
1523 menu_item.Click += new EventHandler (TodayMenuItemClickHandler);
1524 today_menu.MenuItems.Add (menu_item);
1527 // initialise the month context menu
1528 private void SetUpMonthMenu () {
1529 month_menu = new ContextMenu ();
1530 for (int i=0; i < 12; i++) {
1531 MenuItem menu_item = new MenuItem ( new DateTime (2000, i+1, 1).ToString ("MMMM"));
1532 menu_item.Click += new EventHandler (MonthMenuItemClickHandler);
1533 month_menu.MenuItems.Add (menu_item);
1537 // returns the first date of the month
1538 private DateTime GetFirstDateInMonth (DateTime date) {
1539 return new DateTime (date.Year, date.Month, 1);
1542 // returns the last date of the month
1543 private DateTime GetLastDateInMonth (DateTime date) {
1544 return new DateTime (date.Year, date.Month, 1).AddMonths(1).AddDays(-1);
1547 // called in response to users seletion with shift key
1548 private void AddTimeToSelection (int delta, bool isDays)
1550 DateTime cursor_point;
1552 // okay we add the period to the date that is not the same as the
1553 // start date when shift was first clicked.
1554 if (SelectionStart != first_select_start_date) {
1555 cursor_point = SelectionStart;
1557 cursor_point = SelectionEnd;
1561 end_point = cursor_point.AddDays (delta);
1563 // delta must be months
1564 end_point = cursor_point.AddMonths (delta);
1566 // set the new selection range
1567 SelectionRange range = new SelectionRange (first_select_start_date, end_point);
1568 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1569 // okay the date is beyond what is allowed, lets set the maximum we can
1570 if (range.Start != first_select_start_date) {
1571 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1573 range.End = range.Start.AddDays (MaxSelectionCount-1);
1577 // Avoid re-setting SelectionRange to the same value and fire an extra DateChanged event
1578 if (range.Start != selection_range.Start || range.End != selection_range.End)
1579 SelectionRange = range;
1582 // attempts to add the date to the selection without throwing exception
1583 private void SelectDate (DateTime date) {
1584 // try and add the new date to the selction range
1585 SelectionRange range = null;
1586 if (is_shift_pressed || (click_state [0])) {
1587 range = new SelectionRange (first_select_start_date, date);
1588 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1589 // okay the date is beyond what is allowed, lets set the maximum we can
1590 if (range.Start != first_select_start_date) {
1591 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1593 range.End = range.Start.AddDays (MaxSelectionCount-1);
1597 if (date >= MinDate && date <= MaxDate) {
1598 range = new SelectionRange (date, date);
1599 first_select_start_date = date;
1603 // Only set if we re actually getting a different range (avoid an extra DateChanged event)
1604 if (range != null && range.Start != selection_range.Start || range.End != selection_range.End)
1605 SelectionRange = range;
1608 // gets the week of the year
1609 internal int GetWeekOfYear (DateTime date) {
1610 // convert the first_day_of_week into a DayOfWeekEnum
1611 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1612 // find the first day of the year
1613 DayOfWeek first_day_of_year = new DateTime (date.Year, 1, 1).DayOfWeek;
1614 // adjust for the starting day of the week
1615 int offset = first_day_of_year - first_day;
1616 int week = ((date.DayOfYear + offset) / 7) + 1;
1620 // convert a Day enum into a DayOfWeek enum
1621 internal DayOfWeek GetDayOfWeek (Day day) {
1622 if (day == Day.Default) {
1623 return Threading.Thread.CurrentThread.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
1625 return (DayOfWeek) DayOfWeek.Parse (typeof (DayOfWeek), day.ToString ());
1629 // returns the rectangle for themonth name
1630 internal Rectangle GetMonthNameRectangle (Rectangle title_rect, int calendar_index) {
1631 DateTime this_month = this.current_month.AddMonths (calendar_index);
1632 Size title_text_size = TextRenderer.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1633 Size month_size = TextRenderer.MeasureString (this_month.ToString ("MMMM"), this.Font).ToSize ();
1634 // return only the month name part of that
1635 return new Rectangle (
1637 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1638 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1642 internal void GetYearNameRectangles (Rectangle title_rect, int calendar_index, out Rectangle year_rect, out Rectangle up_rect, out Rectangle down_rect)
1644 DateTime this_month = this.current_month.AddMonths (calendar_index);
1645 SizeF title_text_size = TextRenderer.MeasureString (this_month.ToString ("MMMM yyyy"), this.bold_font, int.MaxValue, centered_format);
1646 SizeF year_size = TextRenderer.MeasureString (this_month.ToString ("yyyy"), this.bold_font, int.MaxValue, centered_format);
1647 // find out how much space the title took
1648 RectangleF text_rect = new RectangleF (
1650 title_rect.X + ((title_rect.Width - title_text_size.Width) / 2),
1651 title_rect.Y + ((title_rect.Height - title_text_size.Height) / 2)),
1653 // return only the rect of the year
1654 year_rect = new Rectangle (
1656 ((int)(text_rect.Right - year_size.Width + 1)),
1658 new Size ((int)(year_size.Width + 1), (int)(year_size.Height + 1)));
1660 year_rect.Inflate (0, 1);
1661 up_rect = new Rectangle ();
1662 up_rect.Location = new Point (year_rect.X + year_rect.Width + 2, year_rect.Y);
1663 up_rect.Size = new Size (16, year_rect.Height / 2);
1664 down_rect = new Rectangle ();
1665 down_rect.Location = new Point (up_rect.X, up_rect.Y + up_rect.Height + 1);
1666 down_rect.Size = up_rect.Size;
1669 // returns the rectangle for the year in the title
1670 internal Rectangle GetYearNameRectangle (Rectangle title_rect, int calendar_index) {
1671 Rectangle result, discard;
1672 GetYearNameRectangles (title_rect, calendar_index, out result, out discard, out discard);
1676 // determine if date is allowed to be drawn in month
1677 internal bool IsValidWeekToDraw (DateTime month, DateTime date, int row, int col) {
1678 DateTime tocheck = month.AddMonths (-1);
1679 if ((month.Year == date.Year && month.Month == date.Month) ||
1680 (tocheck.Year == date.Year && tocheck.Month == date.Month)) {
1684 // check the railing dates (days in the month after the last month in grid)
1685 if (row == CalendarDimensions.Height - 1 && col == CalendarDimensions.Width - 1) {
1686 tocheck = month.AddMonths (1);
1687 return (tocheck.Year == date.Year && tocheck.Month == date.Month) ;
1693 // set one item clicked and all others off
1694 private void SetItemClick(HitTestInfo hti)
1696 switch(hti.HitArea) {
1697 case HitArea.NextMonthButton:
1698 this.is_previous_clicked = false;
1699 this.is_next_clicked = true;
1700 this.is_date_clicked = false;
1702 case HitArea.PrevMonthButton:
1703 this.is_previous_clicked = true;
1704 this.is_next_clicked = false;
1705 this.is_date_clicked = false;
1707 case HitArea.PrevMonthDate:
1708 case HitArea.NextMonthDate:
1710 this.clicked_date = hti.hit_time;
1711 this.is_previous_clicked = false;
1712 this.is_next_clicked = false;
1713 this.is_date_clicked = true;
1716 this.is_previous_clicked = false;
1717 this.is_next_clicked = false;
1718 this.is_date_clicked = false;
1723 // called when today context menu is clicked
1724 private void TodayMenuItemClickHandler (object sender, EventArgs e)
1726 this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date);
1727 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1730 // called when month context menu is clicked
1731 private void MonthMenuItemClickHandler (object sender, EventArgs e) {
1732 MenuItem item = sender as MenuItem;
1733 if (item != null && month_title_click_location != Point.Empty) {
1734 // establish which month we want to move to
1735 if (item.Parent == null) {
1738 int new_month = item.Parent.MenuItems.IndexOf (item) + 1;
1739 if (new_month == 0) {
1742 // okay let's establish which calendar was hit
1743 Size month_size = this.SingleMonthSize;
1744 for (int i=0; i < CalendarDimensions.Height; i++) {
1745 for (int j=0; j < CalendarDimensions.Width; j++) {
1746 int month_index = (i * CalendarDimensions.Width) + j;
1747 Rectangle month_rect = new Rectangle ( new Point (0, 0), month_size);
1749 month_rect.X = this.ClientRectangle.X + 1;
1751 month_rect.X = this.ClientRectangle.X + 1 + ((j)*(month_size.Width+calendar_spacing.Width));
1754 month_rect.Y = this.ClientRectangle.Y + 1;
1756 month_rect.Y = this.ClientRectangle.Y + 1 + ((i)*(month_size.Height+calendar_spacing.Height));
1758 // see if the point is inside
1759 if (month_rect.Contains (month_title_click_location)) {
1760 DateTime clicked_month = CurrentMonth.AddMonths (month_index);
1761 // get the month that we want to move to
1762 int month_offset = new_month - clicked_month.Month;
1764 // move forward however more months we need to
1765 this.CurrentMonth = this.CurrentMonth.AddMonths (month_offset);
1772 month_title_click_location = Point.Empty;
1776 // raised on the timer, for mouse hold clicks
1777 private void TimerHandler (object sender, EventArgs e) {
1778 // now find out which area was click
1780 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1781 // see if it was clicked on the prev or next mouse
1782 if (click_state [1] || click_state [2]) {
1783 // invalidate the area where the mouse was last held
1785 // register the click
1786 if (hti.HitArea == HitArea.PrevMonthButton ||
1787 hti.HitArea == HitArea.NextMonthButton) {
1788 DoButtonMouseDown (hti);
1789 click_state [1] = (hti.HitArea == HitArea.PrevMonthButton);
1790 click_state [2] = !click_state [1];
1792 if (timer.Interval != 300) {
1793 timer.Interval = 300;
1797 timer.Enabled = false;
1801 // selects one of the buttons
1802 private void DoButtonMouseDown (HitTestInfo hti) {
1803 // show the click then move on
1805 if (hti.HitArea == HitArea.PrevMonthButton) {
1806 // invalidate the prev monthbutton
1809 this.ClientRectangle.X + 1 + button_x_offset,
1810 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1812 button_size.Height));
1813 int scroll = (scroll_change == 0 ? CalendarDimensions.Width * CalendarDimensions.Height : scroll_change);
1814 this.CurrentMonth = this.CurrentMonth.AddMonths (-scroll);
1816 // invalidate the next monthbutton
1819 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1820 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1822 button_size.Height));
1823 int scroll = (scroll_change == 0 ? CalendarDimensions.Width * CalendarDimensions.Height : scroll_change);
1824 this.CurrentMonth = this.CurrentMonth.AddMonths (scroll);
1828 // selects the clicked date
1829 private void DoDateMouseDown (HitTestInfo hti) {
1833 // event run on the mouse up event
1834 private void DoMouseUp () {
1836 IsYearGoingDown = false;
1837 IsYearGoingUp = false;
1838 is_mouse_moving_year = false;
1840 // invalidate the next monthbutton
1841 if (this.is_next_clicked) {
1844 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1845 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1847 button_size.Height));
1849 // invalidate the prev monthbutton
1850 if (this.is_previous_clicked) {
1853 this.ClientRectangle.X + 1 + button_x_offset,
1854 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1856 button_size.Height));
1858 if (this.is_date_clicked) {
1859 // invalidate the area under the cursor, to remove focus rect
1860 this.InvalidateDateRange (new SelectionRange (clicked_date, clicked_date));
1862 this.is_previous_clicked = false;
1863 this.is_next_clicked = false;
1864 this.is_date_clicked = false;
1867 // needed when in windowed mode to close the calendar if no
1868 // part of it has focus.
1869 private void UpDownTimerTick(object sender, EventArgs e)
1871 if (IsYearGoingUp) {
1872 IsYearGoingUp = true;
1874 if (IsYearGoingDown) {
1875 IsYearGoingDown = true;
1878 if (!IsYearGoingDown && !IsYearGoingUp) {
1879 updown_timer.Enabled = false;
1880 } else if (IsYearGoingDown || IsYearGoingUp) {
1881 updown_timer.Interval = subsequent_delay;
1885 // Needed when in windowed mode.
1886 private void StartHideTimer ()
1888 if (updown_timer == null) {
1889 updown_timer = new Timer ();
1890 updown_timer.Tick += new EventHandler (UpDownTimerTick);
1892 updown_timer.Interval = initial_delay;
1893 updown_timer.Enabled = true;
1896 // occurs when mouse moves around control, used for selection
1897 private void MouseMoveHandler (object sender, MouseEventArgs e) {
1898 HitTestInfo hti = this.HitTest (e.X, e.Y);
1899 // clear the last clicked item
1900 if (click_state [0]) {
1901 // register the click
1902 if (hti.HitArea == HitArea.PrevMonthDate ||
1903 hti.HitArea == HitArea.NextMonthDate ||
1904 hti.HitArea == HitArea.Date)
1906 Rectangle prev_rect = clicked_rect;
1907 DateTime prev_clicked = clicked_date;
1908 DoDateMouseDown (hti);
1909 if (owner == null) {
1910 click_state [0] = true;
1912 click_state [0] = false;
1913 click_state [1] = false;
1914 click_state [2] = false;
1917 if (prev_clicked != clicked_date) {
1918 // select date after updating click_state and clicked_date
1919 SelectDate (clicked_date);
1920 date_selected_event_pending = true;
1922 Rectangle invalid = Rectangle.Union (prev_rect, clicked_rect);
1923 Invalidate (invalid);
1930 // to check if the mouse has come down on this control
1931 private void MouseDownHandler (object sender, MouseEventArgs e)
1933 if ((e.Button & MouseButtons.Left) == 0)
1936 // clear the click_state variables
1937 click_state [0] = false;
1938 click_state [1] = false;
1939 click_state [2] = false;
1941 // disable the timer if it was enabled
1942 if (timer.Enabled) {
1944 timer.Enabled = false;
1947 Point point = new Point (e.X, e.Y);
1948 // figure out if we are in drop down mode and a click happened outside us
1949 if (this.owner != null) {
1950 if (!this.ClientRectangle.Contains (point)) {
1951 this.owner.HideMonthCalendar ();
1956 //establish where was hit
1957 HitTestInfo hti = this.HitTest(point);
1958 // hide the year numeric up down if it was clicked
1959 if (ShowYearUpDown && hti.HitArea != HitArea.TitleYear) {
1960 ShowYearUpDown = false;
1962 switch (hti.HitArea) {
1963 case HitArea.PrevMonthButton:
1964 case HitArea.NextMonthButton:
1965 DoButtonMouseDown (hti);
1966 click_state [1] = (hti.HitArea == HitArea.PrevMonthDate);
1967 click_state [2] = !click_state [1];
1968 timer.Interval = 750;
1972 case HitArea.PrevMonthDate:
1973 case HitArea.NextMonthDate:
1974 DoDateMouseDown (hti);
1976 // select date before updating click_state
1977 SelectDate (clicked_date);
1978 date_selected_event_pending = true;
1980 // leave clicked state blank if drop down window
1981 if (owner == null) {
1982 click_state [0] = true;
1984 click_state [0] = false;
1985 click_state [1] = false;
1986 click_state [2] = false;
1990 case HitArea.TitleMonth:
1991 month_title_click_location = hti.Point;
1992 month_menu.Show (this, hti.Point);
1993 if (this.Capture && owner != null) {
1998 case HitArea.TitleYear:
1999 // place the numeric up down
2000 if (ShowYearUpDown) {
2001 if (hti.hit_area_extra == HitAreaExtra.UpButton) {
2002 is_mouse_moving_year = true;
2003 IsYearGoingUp = true;
2004 } else if (hti.hit_area_extra == HitAreaExtra.DownButton) {
2005 is_mouse_moving_year = true;
2006 IsYearGoingDown = true;
2010 ShowYearUpDown = true;
2013 case HitArea.TodayLink:
2014 this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date);
2015 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2018 this.is_previous_clicked = false;
2019 this.is_next_clicked = false;
2020 this.is_date_clicked = false;
2025 // raised by any key down events
2026 private void KeyDownHandler (object sender, KeyEventArgs e) {
2027 // send keys to the year_updown control, let it handle it
2028 if(ShowYearUpDown) {
2029 switch (e.KeyCode) {
2031 ShowYearUpDown = false;
2032 IsYearGoingDown = false;
2033 IsYearGoingUp = false;
2036 IsYearGoingUp = true;
2040 IsYearGoingDown = true;
2045 if (!is_shift_pressed && e.Shift) {
2046 first_select_start_date = SelectionStart;
2047 is_shift_pressed = e.Shift;
2050 switch (e.KeyCode) {
2052 // set the date to the start of the month
2053 if (is_shift_pressed) {
2054 DateTime date = GetFirstDateInMonth (first_select_start_date);
2055 if (date < first_select_start_date.AddDays ((MaxSelectionCount-1)*-1)) {
2056 date = first_select_start_date.AddDays ((MaxSelectionCount-1)*-1);
2058 this.SetSelectionRange (date, first_select_start_date);
2060 DateTime date = GetFirstDateInMonth (this.SelectionStart);
2061 this.SetSelectionRange (date, date);
2066 // set the date to the last of the month
2067 if (is_shift_pressed) {
2068 DateTime date = GetLastDateInMonth (first_select_start_date);
2069 if (date > first_select_start_date.AddDays (MaxSelectionCount-1)) {
2070 date = first_select_start_date.AddDays (MaxSelectionCount-1);
2072 this.SetSelectionRange (date, first_select_start_date);
2074 DateTime date = GetLastDateInMonth (this.SelectionStart);
2075 this.SetSelectionRange (date, date);
2080 // set the date to the last of the month
2081 if (is_shift_pressed) {
2082 this.AddTimeToSelection (-1, false);
2084 DateTime date = this.SelectionStart.AddMonths (-1);
2085 this.SetSelectionRange (date, date);
2090 // set the date to the last of the month
2091 if (is_shift_pressed) {
2092 this.AddTimeToSelection (1, false);
2094 DateTime date = this.SelectionStart.AddMonths (1);
2095 this.SetSelectionRange (date, date);
2100 // set the back 1 week
2101 if (is_shift_pressed) {
2102 this.AddTimeToSelection (-7, true);
2104 DateTime date = this.SelectionStart.AddDays (-7);
2105 this.SetSelectionRange (date, date);
2110 // set the date forward 1 week
2111 if (is_shift_pressed) {
2112 this.AddTimeToSelection (7, true);
2114 DateTime date = this.SelectionStart.AddDays (7);
2115 this.SetSelectionRange (date, date);
2121 if (is_shift_pressed) {
2122 this.AddTimeToSelection (-1, true);
2124 DateTime date = this.SelectionStart.AddDays (-1);
2125 this.SetSelectionRange (date, date);
2131 if (is_shift_pressed) {
2132 this.AddTimeToSelection (1, true);
2134 DateTime date = this.SelectionStart.AddDays (1);
2135 this.SetSelectionRange (date, date);
2140 // Close ourselves on Alt-F4 if we are a popup
2141 if (e.Alt && owner != null) {
2152 // to check if the mouse has come up on this control
2153 private void MouseUpHandler (object sender, MouseEventArgs e)
2155 if ((e.Button & MouseButtons.Left) == 0) {
2156 if (show_today && (this.ContextMenu == null))
2157 today_menu.Show (this, new Point (e.X, e.Y));
2161 if (timer.Enabled) {
2164 // clear the click state array
2165 click_state [0] = false;
2166 click_state [1] = false;
2167 click_state [2] = false;
2168 // do the regulare mouseup stuff
2171 if (date_selected_event_pending) {
2172 OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
2173 date_selected_event_pending = false;
2177 // raised by any key up events
2178 private void KeyUpHandler (object sender, KeyEventArgs e) {
2179 is_shift_pressed = e.Shift ;
2181 IsYearGoingUp = false;
2182 IsYearGoingDown = false;
2185 // paint this control now
2186 private void PaintHandler (object sender, PaintEventArgs pe) {
2187 if (Width <= 0 || Height <= 0 || Visible == false)
2190 Draw (pe.ClipRectangle, pe.Graphics);
2192 // fire the new paint handler
2193 if (this.Paint != null)
2195 this.Paint (sender, pe);
2199 // returns the region of the control that needs to be redrawn
2200 private void InvalidateDateRange (SelectionRange range) {
2201 SelectionRange bounds = this.GetDisplayRange (false);
2203 if (range.End < bounds.Start || range.Start > bounds.End) {
2204 // don't invalidate anything, as the modified date range
2205 // is outside the visible bounds of this control
2208 // adjust the start and end to be inside the visible range
2209 if (range.Start < bounds.Start) {
2210 range = new SelectionRange (bounds.Start, range.End);
2212 if (range.End > bounds.End) {
2213 range = new SelectionRange (range.Start, bounds.End);
2215 // now invalidate the date rectangles as series of rows
2216 DateTime last_month = this.current_month.AddMonths ((CalendarDimensions.Width * CalendarDimensions.Height)).AddDays (-1);
2217 DateTime current = range.Start;
2218 while (current <= range.End) {
2219 DateTime month_end = new DateTime (current.Year, current.Month, 1).AddMonths (1).AddDays (-1);;
2220 Rectangle start_rect;
2222 // see if entire selection is in this current month
2223 if (range.End <= month_end && current < last_month) {
2224 // the end is the last date
2225 if (current < this.current_month) {
2226 start_rect = GetDateRowRect (current_month, current_month);
2228 start_rect = GetDateRowRect (current, current);
2230 end_rect = GetDateRowRect (current, range.End);
2231 } else if (current < last_month) {
2232 // otherwise it simply means we have a selection spaning
2233 // multiple months simply set rectangle inside the current month
2234 start_rect = GetDateRowRect (current, current);
2235 end_rect = GetDateRowRect (month_end, month_end);
2237 // it's outside the visible range
2238 start_rect = GetDateRowRect (last_month, last_month.AddDays (1));
2239 end_rect = GetDateRowRect (last_month, range.End);
2241 // push to the next month
2242 current = month_end.AddDays (1);
2243 // invalidate from the start row to the end row for this month
2249 Math.Max (end_rect.Bottom - start_rect.Y, 0)));
2253 // gets the rect of the row where the specified date appears on the specified month
2254 private Rectangle GetDateRowRect (DateTime month, DateTime date) {
2255 // first get the general rect of the supplied month
2256 Size month_size = SingleMonthSize;
2257 Rectangle month_rect = Rectangle.Empty;
2258 for (int i=0; i < CalendarDimensions.Width*CalendarDimensions.Height; i++) {
2259 DateTime this_month = this.current_month.AddMonths (i);
2260 if (month.Year == this_month.Year && month.Month == this_month.Month) {
2261 month_rect = new Rectangle (
2262 this.ClientRectangle.X + 1 + (month_size.Width * (i%CalendarDimensions.Width)) + (this.calendar_spacing.Width * (i%CalendarDimensions.Width)),
2263 this.ClientRectangle.Y + 1 + (month_size.Height * (i/CalendarDimensions.Width)) + (this.calendar_spacing.Height * (i/CalendarDimensions.Width)),
2269 // now find out where in the month the supplied date is
2270 if (month_rect == Rectangle.Empty) {
2271 return Rectangle.Empty;
2273 // find out which row this date is in
2275 DateTime first_date = GetFirstDateInMonthGrid (month);
2276 DateTime end_date = first_date.AddDays (7);
2277 for (int i=0; i < 6; i++) {
2278 if (date >= first_date && date < end_date) {
2282 first_date = end_date;
2283 end_date = end_date.AddDays (7);
2285 // ensure it's a valid row
2287 return Rectangle.Empty;
2289 int x_offset = (this.ShowWeekNumbers) ? date_cell_size.Width : 0;
2290 int y_offset = title_size.Height + (date_cell_size.Height * (row + 1));
2291 return new Rectangle (
2292 month_rect.X + x_offset,
2293 month_rect.Y + y_offset,
2294 date_cell_size.Width * 7,
2295 date_cell_size.Height);
2298 internal void Draw (Rectangle clip_rect, Graphics dc)
2300 ThemeEngine.Current.DrawMonthCalendar (dc, clip_rect, this);
2303 internal override bool InternalCapture {
2305 return base.InternalCapture;
2308 // Don't allow internal capture when DateTimePicker is using us
2309 // Control sets this on MouseDown
2311 base.InternalCapture = value;
2315 #endregion //internal methods
2317 #region internal drawing methods
2320 #endregion // internal drawing methods
2322 #region inner classes and enumerations
2324 // enumeration about what type of area on the calendar was hit
2325 public enum HitArea {
2341 internal enum HitAreaExtra {
2347 // info regarding to a hit test on this calendar
2348 public sealed class HitTestInfo {
2350 private HitArea hit_area;
2351 private Point point;
2352 private DateTime time;
2354 internal HitAreaExtra hit_area_extra;
2355 internal DateTime hit_time;
2357 // default constructor
2358 internal HitTestInfo () {
2359 hit_area = HitArea.Nowhere;
2360 point = new Point (0, 0);
2361 time = DateTime.Now;
2364 // overload receives all properties
2365 internal HitTestInfo (HitArea hit_area, Point point, DateTime time) {
2366 this.hit_area = hit_area;
2369 this.hit_time = time;
2372 // overload receives all properties
2373 internal HitTestInfo (HitArea hit_area, Point point, DateTime time, DateTime hit_time)
2375 this.hit_area = hit_area;
2378 this.hit_time = hit_time;
2381 internal HitTestInfo (HitArea hit_area, Point point, DateTime time, HitAreaExtra hit_area_extra)
2383 this.hit_area = hit_area;
2384 this.hit_area_extra = hit_area_extra;
2389 // the type of area that was hit
2390 public HitArea HitArea {
2396 // the point that is being test
2397 public Point Point {
2403 // the date under the hit test point, only valid if HitArea is Date
2404 public DateTime Time {
2411 #endregion // inner classes
2413 #region UIA Framework: Methods, Properties and Events
2415 static object UIAMaxSelectionCountChangedEvent = new object ();
2416 static object UIASelectionChangedEvent = new object ();
2418 internal event EventHandler UIAMaxSelectionCountChanged {
2419 add { Events.AddHandler (UIAMaxSelectionCountChangedEvent, value); }
2420 remove { Events.RemoveHandler (UIAMaxSelectionCountChangedEvent, value); }
2423 internal event EventHandler UIASelectionChanged {
2424 add { Events.AddHandler (UIASelectionChangedEvent, value); }
2425 remove { Events.RemoveHandler (UIASelectionChangedEvent, value); }
2428 private void OnUIAMaxSelectionCountChanged ()
2430 EventHandler eh = (EventHandler) Events [UIAMaxSelectionCountChangedEvent];
2432 eh (this, EventArgs.Empty);
2435 private void OnUIASelectionChanged ()
2437 EventHandler eh = (EventHandler) Events [UIASelectionChangedEvent];
2439 eh (this, EventArgs.Empty);