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.Windows.Forms;
35 namespace System.Windows.Forms {
36 [DefaultProperty("SelectionRange")]
37 [DefaultEvent("DateChanged")]
38 [Designer ("System.Windows.Forms.Design.MonthCalendarDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
39 public class MonthCalendar : Control {
40 #region Local variables
41 DateTime [] annually_bolded_dates;
43 DateTime [] bolded_dates;
44 Size calendar_dimensions;
45 Day first_day_of_week;
48 int max_selection_count;
50 DateTime [] monthly_bolded_dates;
52 SelectionRange selection_range;
54 bool show_today_circle;
55 bool show_week_numbers;
56 Color title_back_color;
57 Color title_fore_color;
60 Color trailing_fore_color;
62 NumericUpDown year_updown;
65 // internal variables used
66 internal DateTime current_month; // the month that is being displayed in top left corner of the grid
67 internal DateTimePicker owner; // used if this control is popped up
68 internal int button_x_offset;
69 internal Size button_size;
70 internal Size title_size;
71 internal Size date_cell_size;
72 internal Size calendar_spacing;
73 internal int divider_line_offset;
74 internal DateTime clicked_date;
75 internal Rectangle clicked_rect;
76 internal bool is_date_clicked;
77 internal bool is_previous_clicked;
78 internal bool is_next_clicked;
79 internal bool is_shift_pressed;
80 internal DateTime first_select_start_date;
81 internal int last_clicked_calendar_index;
82 internal Rectangle last_clicked_calendar_rect;
83 internal Font bold_font; // Cache the font in FontStyle.Bold
84 internal StringFormat centered_format; // Cache centered string format
85 private Point month_title_click_location;
86 // this is used to see which item was actually clicked on in the beginning
87 // so that we know which item to fire on timer
89 // 1: previous clicked
91 private bool[] click_state;
93 // arraylists used to store new dates
94 ArrayList added_bolded_dates;
95 ArrayList removed_bolded_dates;
96 ArrayList added_annually_bolded_dates;
97 ArrayList removed_annually_bolded_dates;
98 ArrayList added_monthly_bolded_dates;
99 ArrayList removed_monthly_bolded_dates;
102 #endregion // Local variables
104 #region Public Constructors
106 public MonthCalendar () {
107 // set up the control painting
108 SetStyle (ControlStyles.UserPaint | ControlStyles.StandardClick, false);
111 timer = new Timer ();
112 timer.Interval = 500;
113 timer.Enabled = false;
115 // initialise default values
116 DateTime now = DateTime.Now.Date;
117 selection_range = new SelectionRange (now, now);
119 current_month = new DateTime (now.Year , now.Month, 1);
121 // iniatialise local members
122 annually_bolded_dates = null;
123 back_color = ThemeEngine.Current.ColorWindow;
125 calendar_dimensions = new Size (1,1);
126 first_day_of_week = Day.Default;
127 fore_color = SystemColors.ControlText;
128 max_date = new DateTime (9998, 12, 31);
129 max_selection_count = 7;
130 min_date = new DateTime (1953, 1, 1);
131 monthly_bolded_dates = null;
134 show_today_circle = true;
135 show_week_numbers = false;
136 title_back_color = ThemeEngine.Current.ColorActiveCaption;
137 title_fore_color = ThemeEngine.Current.ColorActiveCaptionText;
138 today_date_set = false;
139 trailing_fore_color = Color.Gray;
140 bold_font = new Font (Font, Font.Style | FontStyle.Bold);
141 centered_format = new StringFormat ();
142 centered_format.LineAlignment = StringAlignment.Center;
143 centered_format.Alignment = StringAlignment.Center;
145 // initialise the arraylest for bolded dates
146 added_bolded_dates = new ArrayList ();
147 removed_bolded_dates = new ArrayList ();
148 added_annually_bolded_dates = new ArrayList ();
149 removed_annually_bolded_dates = new ArrayList ();
150 added_monthly_bolded_dates = new ArrayList ();
151 removed_monthly_bolded_dates = new ArrayList ();
153 // intiailise internal variables used
155 button_size = new Size (22, 17);
156 // default settings based on 8.25 pt San Serif Font
157 // Not sure of algorithm used to establish this
158 date_cell_size = new Size (24, 16); // default size at san-serif 8.25
159 divider_line_offset = 4;
160 calendar_spacing = new Size (4, 5); // horiz and vert spacing between months in a calendar grid
162 // set some state info
164 is_date_clicked = false;
165 is_previous_clicked = false;
166 is_next_clicked = false;
167 is_shift_pressed = false;
168 click_state = new bool [] {false, false, false};
169 first_select_start_date = now;
170 month_title_click_location = Point.Empty;
172 // set up context menu
177 // LostFocus += new EventHandler (LostFocusHandler);
178 timer.Tick += new EventHandler (TimerHandler);
179 MouseMove += new MouseEventHandler (MouseMoveHandler);
180 MouseDown += new MouseEventHandler (MouseDownHandler);
181 KeyDown += new KeyEventHandler (KeyDownHandler);
182 MouseUp += new MouseEventHandler (MouseUpHandler);
183 KeyUp += new KeyEventHandler (KeyUpHandler);
185 // this replaces paint so call the control version
186 base.Paint += new PaintEventHandler (PaintHandler);
189 // called when this control is added to date time picker
190 internal MonthCalendar (DateTimePicker owner) : this () {
192 this.is_visible = false;
193 this.Size = this.DefaultSize;
196 #endregion // Public Constructors
198 #region Public Instance Properties
200 // dates to make bold on calendar annually (recurring)
202 public DateTime[] AnnuallyBoldedDates {
204 if (annually_bolded_dates == null || annually_bolded_dates != value) {
205 annually_bolded_dates = value;
206 this.UpdateBoldedDates ();
211 return annually_bolded_dates;
216 [EditorBrowsable(EditorBrowsableState.Never)]
217 public override Image BackgroundImage {
219 return base.BackgroundImage;
222 base.BackgroundImage = value;
227 // the back color for the main part of the calendar
228 public override Color BackColor {
230 if (back_color != value) {
232 this.OnBackColorChanged (EventArgs.Empty);
241 // specific dates to make bold on calendar (non-recurring)
243 public DateTime[] BoldedDates {
245 if (bolded_dates == null || bolded_dates != value) {
246 bolded_dates = value;
247 this.UpdateBoldedDates ();
256 // the configuration of the monthly grid display - only allowed to display at most,
257 // 1 calendar year at a time, will be trimmed to fit it properly
259 public Size CalendarDimensions {
261 if (value.Width < 0 || value.Height < 0) {
262 throw new ArgumentException ();
264 if (calendar_dimensions != value) {
265 // squeeze the grid into 1 calendar year
266 if (value.Width * value.Height > 12) {
267 // iteratively reduce the largest dimension till our
268 // product is less than 12
269 if (value.Width > 12 && value.Height > 12) {
270 calendar_dimensions = new Size (4, 3);
271 } else if (value.Width > 12) {
272 for (int i = 12; i > 0; i--) {
273 if (i * value.Height <= 12) {
274 calendar_dimensions = new Size (i, value.Height);
278 } else if (value.Height > 12) {
279 for (int i = 12; i > 0; i--) {
280 if (i * value.Width <= 12) {
281 calendar_dimensions = new Size (value.Width, i);
287 calendar_dimensions = value;
293 return calendar_dimensions;
297 // the first day of the week to display
299 [DefaultValue (Day.Default)]
300 public Day FirstDayOfWeek {
302 if (first_day_of_week != value) {
303 first_day_of_week = value;
308 return first_day_of_week;
312 // the fore color for the main part of the calendar
313 public override Color ForeColor {
315 if (fore_color != value) {
317 this.OnForeColorChanged (EventArgs.Empty);
327 [EditorBrowsable(EditorBrowsableState.Never)]
328 public ImeMode ImeMode {
334 if (ime_mode != value) {
337 if (ImeModeChanged != null) {
338 ImeModeChanged(this, EventArgs.Empty);
344 // the maximum date allowed to be selected on this month calendar
345 public DateTime MaxDate {
347 if (value < MinDate) {
348 throw new ArgumentException();
351 if (max_date != value) {
360 // the maximum number of selectable days
362 public int MaxSelectionCount {
365 throw new ArgumentException();
368 // can't set selectioncount less than already selected dates
369 if ((SelectionEnd - SelectionStart).Days > value) {
370 throw new ArgumentException();
373 if (max_selection_count != value) {
374 max_selection_count = value;
378 return max_selection_count;
382 // the minimum date allowed to be selected on this month calendar
383 public DateTime MinDate {
385 if (value < new DateTime (1953, 1, 1)) {
386 throw new ArgumentException();
389 if (value > MaxDate) {
390 throw new ArgumentException();
393 if (max_date != value) {
402 // dates to make bold on calendar monthly (recurring)
404 public DateTime[] MonthlyBoldedDates {
406 if (monthly_bolded_dates == null || monthly_bolded_dates != value) {
407 monthly_bolded_dates = value;
408 this.UpdateBoldedDates ();
413 return monthly_bolded_dates;
417 // the ammount by which to scroll this calendar by
419 public int ScrollChange {
421 if (value < 0 || value > 20000) {
422 throw new ArgumentException();
425 if (scroll_change != value) {
426 scroll_change = value;
430 // if zero it to the default -> the total number of months currently visible
431 if (scroll_change == 0) {
432 return CalendarDimensions.Width * CalendarDimensions.Height;
434 return scroll_change;
439 // the last selected date
441 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
442 public DateTime SelectionEnd {
444 if (value < MinDate || value > MaxDate) {
445 throw new ArgumentException();
448 if (SelectionRange.End != value) {
449 DateTime old_end = SelectionRange.End;
450 // make sure the end obeys the max selection range count
451 if (value < SelectionRange.Start) {
452 SelectionRange.Start = value;
454 if (value.AddDays((MaxSelectionCount-1)*-1) > SelectionRange.Start) {
455 SelectionRange.Start = value.AddDays((MaxSelectionCount-1)*-1);
457 SelectionRange.End = value;
458 this.InvalidateDateRange (new SelectionRange (old_end, SelectionRange.End));
459 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
463 return SelectionRange.End;
467 // the range of selected dates
468 public SelectionRange SelectionRange {
470 if (selection_range != value) {
471 SelectionRange old_range = selection_range;
473 // make sure the end obeys the max selection range count
474 if (value.End.AddDays((MaxSelectionCount-1)*-1) > value.Start) {
475 selection_range = new SelectionRange (value.End.AddDays((MaxSelectionCount-1)*-1), value.End);
477 selection_range = value;
479 SelectionRange visible_range = this.GetDisplayRange(true);
480 if(visible_range.Start > selection_range.End) {
481 this.current_month = new DateTime (selection_range.Start.Year, selection_range.Start.Month, 1);
483 } else if (visible_range.End < selection_range.Start) {
484 int year_diff = selection_range.End.Year - visible_range.End.Year;
485 int month_diff = selection_range.End.Month - visible_range.End.Month;
486 this.current_month = current_month.AddMonths(year_diff * 12 + month_diff);
489 // invalidate the selected range changes
490 DateTime diff_start = old_range.Start;
491 DateTime diff_end = old_range.End;
492 // now decide which region is greated
493 if (old_range.Start > SelectionRange.Start) {
494 diff_start = SelectionRange.Start;
495 } else if (old_range.Start == SelectionRange.Start) {
496 if (old_range.End < SelectionRange.End) {
497 diff_start = old_range.End;
499 diff_start = SelectionRange.End;
502 if (old_range.End < SelectionRange.End) {
503 diff_end = SelectionRange.End;
504 } else if (old_range.End == SelectionRange.End) {
505 if (old_range.Start < SelectionRange.Start) {
506 diff_end = SelectionRange.Start;
508 diff_end = old_range.Start;
513 // invalidate the region required
514 SelectionRange new_range = new SelectionRange (diff_start, diff_end);
515 if (new_range.End != old_range.End || new_range.Start != old_range.Start)
516 this.InvalidateDateRange (new_range);
517 // raise date changed event
518 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
522 return selection_range;
526 // the first selected date
528 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
529 public DateTime SelectionStart {
531 if (value < MinDate || value > MaxDate) {
532 throw new ArgumentException();
535 if (SelectionRange.Start != value) {
536 DateTime old_start = SelectionRange.Start;
537 // make sure the end obeys the max selection range count
538 if (value > SelectionRange.End) {
539 SelectionRange.End = value;
540 } else if (value.AddDays(MaxSelectionCount-1) < SelectionRange.End) {
541 SelectionRange.End = value.AddDays(MaxSelectionCount-1);
543 SelectionRange.Start = value;
544 DateTime new_month = new DateTime(value.Year, value.Month, 1);
545 if (current_month != new_month) {
546 current_month = new_month;
549 this.InvalidateDateRange (new SelectionRange (old_start, SelectionRange.Start));
551 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
555 return selection_range.Start;
559 // whether or not to show todays date
560 [DefaultValue (true)]
561 public bool ShowToday {
563 if (show_today != value) {
573 // whether or not to show a circle around todays date
574 [DefaultValue (true)]
575 public bool ShowTodayCircle {
577 if (show_today_circle != value) {
578 show_today_circle = value;
583 return show_today_circle;
587 // whether or not to show numbers beside each row of weeks
589 [DefaultValue (false)]
590 public bool ShowWeekNumbers {
592 if (show_week_numbers != value) {
593 show_week_numbers = value;
598 return show_week_numbers;
602 // the rectangle size required to render one month based on current font
604 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
605 public Size SingleMonthSize {
607 if (this.Font == null) {
608 throw new InvalidOperationException();
611 // multiplier is sucked out from the font size
612 int multiplier = this.Font.Height;
614 // establis how many columns and rows we have
615 int column_count = (ShowWeekNumbers) ? 8 : 7;
616 int row_count = 7; // not including the today date
618 // set the date_cell_size and the title_size
619 date_cell_size = new Size ((int) Math.Ceiling (1.8 * multiplier), multiplier);
620 title_size = new Size ((date_cell_size.Width * column_count), 2 * multiplier);
622 return new Size (column_count * date_cell_size.Width, row_count * date_cell_size.Height + title_size.Height);
628 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
629 [EditorBrowsable(EditorBrowsableState.Never)]
630 public override string Text {
639 // the back color for the title of the calendar and the
640 // forecolor for the day of the week text
641 public Color TitleBackColor {
643 if (title_back_color != value) {
644 title_back_color = value;
649 return title_back_color;
653 // the fore color for the title of the calendar
654 public Color TitleForeColor {
656 if (title_fore_color != value) {
657 title_fore_color = value;
662 return title_fore_color;
666 // the date this calendar is using to refer to today's date
667 public DateTime TodayDate {
669 today_date_set = true;
670 if (today_date != value) {
680 // tells if user specifically set today_date for this control
682 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
683 public bool TodayDateSet {
685 return today_date_set;
689 // the color used for trailing dates in the calendar
690 public Color TrailingForeColor {
692 if (trailing_fore_color != value) {
693 trailing_fore_color = value;
694 SelectionRange bounds = this.GetDisplayRange (false);
695 SelectionRange visible_bounds = this.GetDisplayRange (true);
696 this.InvalidateDateRange (new SelectionRange (bounds.Start, visible_bounds.Start));
697 this.InvalidateDateRange (new SelectionRange (bounds.End, visible_bounds.End));
701 return trailing_fore_color;
705 #endregion // Public Instance Properties
707 #region Protected Instance Properties
709 // overloaded to allow controll to be windowed for drop down
710 protected override CreateParams CreateParams {
712 if (this.owner == null) {
713 return base.CreateParams;
715 CreateParams cp = base.CreateParams;
716 cp.Style ^= (int) WindowStyles.WS_CHILD;
717 cp.Style |= (int) WindowStyles.WS_POPUP;
718 cp.ExStyle |= (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
725 // not sure what to put in here - just doing a base() call - jba
726 protected override ImeMode DefaultImeMode {
728 return base.DefaultImeMode;
732 protected override Size DefaultSize {
734 Size single_month = SingleMonthSize;
736 int width = calendar_dimensions.Width * single_month.Width;
737 if (calendar_dimensions.Width > 1) {
738 width += (calendar_dimensions.Width - 1) * calendar_spacing.Width;
742 int height = calendar_dimensions.Height * single_month.Height;
743 if (this.ShowToday) {
744 height += date_cell_size.Height + 2; // add the height of the "Today: " ...
746 if (calendar_dimensions.Height > 1) {
747 height += (calendar_dimensions.Height - 1) * calendar_spacing.Height;
750 // add the 1 pixel boundary
758 return new Size (width, height);
762 #endregion // Protected Instance Properties
764 #region Public Instance Methods
766 // add a date to the anually bolded date arraylist
767 public void AddAnnuallyBoldedDate (DateTime date) {
768 added_annually_bolded_dates.Add (date.Date);
771 // add a date to the normal bolded date arraylist
772 public void AddBoldedDate (DateTime date) {
773 added_bolded_dates.Add (date.Date);
776 // add a date to the anually monthly date arraylist
777 public void AddMonthlyBoldedDate (DateTime date) {
778 added_monthly_bolded_dates.Add (date.Date);
781 // if visible = true, return only the dates of full months, else return all dates visible
782 public SelectionRange GetDisplayRange (bool visible) {
785 start = new DateTime (current_month.Year, current_month.Month, 1);
786 end = start.AddMonths (calendar_dimensions.Width * calendar_dimensions.Height);
787 end = end.AddDays(-1);
789 // process all visible dates if needed (including the grayed out dates
791 start = GetFirstDateInMonthGrid (start);
792 end = GetLastDateInMonthGrid (end);
795 return new SelectionRange (start, end);
798 // HitTest overload that recieve's x and y co-ordinates as separate ints
799 public HitTestInfo HitTest (int x, int y) {
800 return HitTest (new Point (x, y));
803 // returns a HitTestInfo for MonthCalendar element's under the specified point
804 public HitTestInfo HitTest (Point point) {
805 return HitTest (point, out last_clicked_calendar_index, out last_clicked_calendar_rect);
808 // clears all the annually bolded dates
809 public void RemoveAllAnnuallyBoldedDates () {
810 annually_bolded_dates = null;
811 added_annually_bolded_dates.Clear ();
812 removed_annually_bolded_dates.Clear ();
815 // clears all the normal bolded dates
816 public void RemoveAllBoldedDates () {
818 added_bolded_dates.Clear ();
819 removed_bolded_dates.Clear ();
822 // clears all the monthly bolded dates
823 public void RemoveAllMonthlyBoldedDates () {
824 monthly_bolded_dates = null;
825 added_monthly_bolded_dates.Clear ();
826 removed_monthly_bolded_dates.Clear ();
829 // clears the specified annually bolded date (only compares day and month)
830 // only removes the first instance of the match
831 public void RemoveAnnuallyBoldedDate (DateTime date) {
832 if (!removed_annually_bolded_dates.Contains (date.Date)) {
833 removed_annually_bolded_dates.Add (date.Date);
837 // clears all the normal bolded date
838 // only removes the first instance of the match
839 public void RemoveBoldedDate (DateTime date) {
840 if (!removed_bolded_dates.Contains (date.Date)) {
841 removed_bolded_dates.Add (date.Date);
845 // clears the specified monthly bolded date (only compares day and month)
846 // only removes the first instance of the match
847 public void RemoveMonthlyBoldedDate (DateTime date) {
848 if (!removed_monthly_bolded_dates.Contains (date.Date)) {
849 removed_monthly_bolded_dates.Add (date.Date);
853 // sets the calendar_dimensions. If product is > 12, the larger dimension is reduced to make product < 12
854 public void SetCalendarDimensions(int x, int y) {
855 this.CalendarDimensions = new Size(x, y);
858 // sets the currently selected date as date
859 public void SetDate (DateTime date) {
860 this.SetSelectionRange (date.Date, date.Date);
863 // utility method set the SelectionRange property using individual dates
864 public void SetSelectionRange (DateTime date1, DateTime date2) {
865 this.SelectionRange = new SelectionRange(date1, date2);
868 public override string ToString () {
869 return this.GetType().Name + ", " + this.SelectionRange.ToString ();
872 // usually called after an AddBoldedDate method is called
873 // formats monthly and daily bolded dates according to the current calendar year
874 public void UpdateBoldedDates () {
875 UpdateDateArray (ref bolded_dates, added_bolded_dates, removed_bolded_dates);
876 UpdateDateArray (ref monthly_bolded_dates, added_monthly_bolded_dates, removed_monthly_bolded_dates);
877 UpdateDateArray (ref annually_bolded_dates, added_annually_bolded_dates, removed_annually_bolded_dates);
880 #endregion // Public Instance Methods
882 #region Protected Instance Methods
884 // not sure why this needs to be overriden
885 protected override void CreateHandle () {
886 base.CreateHandle ();
889 private void CreateYearUpDown ()
891 year_updown = new NumericUpDown ();
892 year_updown.Font = this.Font;
893 year_updown.Minimum = MinDate.Year;
894 year_updown.Maximum = MaxDate.Year;
895 year_updown.ReadOnly = true;
896 year_updown.Visible = false;
897 this.Controls.AddImplicit (year_updown);
898 year_updown.ValueChanged += new EventHandler(UpDownYearChangedHandler);
901 // not sure why this needs to be overriden
902 protected override void Dispose (bool disposing) {
903 base.Dispose (disposing);
906 // not sure why this needs to be overriden
907 protected override bool IsInputKey (Keys keyData) {
908 return base.IsInputKey (keyData);
911 // not sure why this needs to be overriden
912 protected override void OnBackColorChanged (EventArgs e) {
913 base.OnBackColorChanged (e);
917 // raises the date changed event
918 protected virtual void OnDateChanged (DateRangeEventArgs drevent) {
919 if (this.DateChanged != null) {
920 this.DateChanged (this, drevent);
924 // raises the DateSelected event
925 protected virtual void OnDateSelected (DateRangeEventArgs drevent) {
926 if (this.DateSelected != null) {
927 this.DateSelected (this, drevent);
931 protected override void OnFontChanged (EventArgs e) {
932 bold_font = new Font (Font, Font.Style | FontStyle.Bold);
933 base.OnFontChanged (e);
936 protected override void OnForeColorChanged (EventArgs e) {
937 base.OnForeColorChanged (e);
940 protected override void OnHandleCreated (EventArgs e) {
941 base.OnHandleCreated (e);
945 // i think this is overriden to not allow the control to be changed to an arbitrary size
946 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) {
947 if ((specified & BoundsSpecified.Height) == BoundsSpecified.Height ||
948 (specified & BoundsSpecified.Width) == BoundsSpecified.Width ||
949 (specified & BoundsSpecified.Size) == BoundsSpecified.Size) {
950 // only allow sizes = default size to be set
951 Size min_size = DefaultSize;
952 Size max_size = new Size (
953 DefaultSize.Width + SingleMonthSize.Width + calendar_spacing.Width,
954 DefaultSize.Height + SingleMonthSize.Height + calendar_spacing.Height);
955 int x_mid_point = (max_size.Width + min_size.Width)/2;
956 int y_mid_point = (max_size.Height + min_size.Height)/2;
957 if (width < x_mid_point) {
958 width = min_size.Width;
960 width = max_size.Width;
962 if (height < y_mid_point) {
963 height = min_size.Height;
965 height = max_size.Height;
967 base.SetBoundsCore (x, y, width, height, specified);
969 base.SetBoundsCore (x, y, width, height, specified);
973 protected override void WndProc (ref Message m) {
974 base.WndProc (ref m);
977 #endregion // Protected Instance Methods
979 #region public events
981 // fired when the date is changed (either explicitely or implicitely)
982 // when navigating the month selector
983 public event DateRangeEventHandler DateChanged;
985 // fired when the user explicitely clicks on date to select it
986 public event DateRangeEventHandler DateSelected;
989 [EditorBrowsable (EditorBrowsableState.Never)]
990 public event EventHandler BackgroundImageChanged;
992 // this event is overridden to supress it from being fired
994 [EditorBrowsable (EditorBrowsableState.Never)]
995 public event EventHandler Click;
997 // this event is overridden to supress it from being fired
999 [EditorBrowsable (EditorBrowsableState.Never)]
1000 public event EventHandler DoubleClick;
1003 [EditorBrowsable (EditorBrowsableState.Never)]
1004 public event EventHandler ImeModeChanged;
1007 [EditorBrowsable (EditorBrowsableState.Never)]
1008 public new event PaintEventHandler Paint;
1011 [EditorBrowsable (EditorBrowsableState.Never)]
1012 public event EventHandler TextChanged;
1013 #endregion // public events
1015 #region internal properties
1017 internal DateTime CurrentMonth {
1019 // only interested in if the month (not actual date) has changed
1020 if (value.Month != current_month.Month ||
1021 value.Year != current_month.Year) {
1022 this.SelectionRange = new SelectionRange(
1023 this.SelectionStart.Add(value.Subtract(current_month)),
1024 this.SelectionEnd.Add(value.Subtract(current_month)));
1025 current_month = value;
1026 UpdateBoldedDates();
1031 return current_month;
1035 #endregion // internal properties
1037 #region internal/private methods
1038 internal HitTestInfo HitTest (
1040 out int calendar_index,
1041 out Rectangle calendar_rect) {
1042 // start by initialising the ref parameters
1043 calendar_index = -1;
1044 calendar_rect = Rectangle.Empty;
1046 // before doing all the hard work, see if the today's date wasn't clicked
1047 Rectangle today_rect = new Rectangle (
1049 ClientRectangle.Bottom - date_cell_size.Height,
1050 7 * date_cell_size.Width,
1051 date_cell_size.Height);
1052 if (today_rect.Contains (point) && this.ShowToday) {
1053 return new HitTestInfo(HitArea.TodayLink, point, DateTime.Now);
1056 Size month_size = SingleMonthSize;
1057 // define calendar rect's that this thing can land in
1058 Rectangle[] calendars = new Rectangle [CalendarDimensions.Width * CalendarDimensions.Height];
1059 for (int i=0; i < CalendarDimensions.Width * CalendarDimensions.Height; i ++) {
1061 calendars[i] = new Rectangle (
1062 new Point (ClientRectangle.X + 1, ClientRectangle.Y + 1),
1065 // calendar on the next row
1066 if (i % CalendarDimensions.Width == 0) {
1067 calendars[i] = new Rectangle (
1068 new Point (calendars[i-CalendarDimensions.Width].X, calendars[i-CalendarDimensions.Width].Bottom + calendar_spacing.Height),
1071 // calendar on the next column
1072 calendars[i] = new Rectangle (
1073 new Point (calendars[i-1].Right + calendar_spacing.Width, calendars[i-1].Y),
1079 // through each trying to find a match
1080 for (int i = 0; i < calendars.Length ; i++) {
1081 if (calendars[i].Contains (point)) {
1082 // check the title section
1083 Rectangle title_rect = new Rectangle (
1084 calendars[i].Location,
1086 if (title_rect.Contains (point) ) {
1087 // make sure it's not a previous button
1089 Rectangle button_rect = new Rectangle(
1090 new Point (calendars[i].X + button_x_offset, (title_size.Height - button_size.Height)/2),
1092 if (button_rect.Contains (point)) {
1093 return new HitTestInfo(HitArea.PrevMonthButton, point, DateTime.Now);
1096 // make sure it's not the next button
1097 if (i % CalendarDimensions.Height == 0 && i % CalendarDimensions.Width == calendar_dimensions.Width - 1) {
1098 Rectangle button_rect = new Rectangle(
1099 new Point (calendars[i].Right - button_x_offset - button_size.Width, (title_size.Height - button_size.Height)/2),
1101 if (button_rect.Contains (point)) {
1102 return new HitTestInfo(HitArea.NextMonthButton, point, DateTime.Now);
1106 // indicate which calendar and month it was
1108 calendar_rect = calendars[i];
1110 // make sure it's not the month or the year of the calendar
1111 if (GetMonthNameRectangle (title_rect, i).Contains (point)) {
1112 return new HitTestInfo(HitArea.TitleMonth, point, DateTime.Now);
1114 if (GetYearNameRectangle (title_rect, i).Contains (point)) {
1115 return new HitTestInfo(HitArea.TitleYear, point, DateTime.Now);
1118 // return the hit test in the title background
1119 return new HitTestInfo(HitArea.TitleBackground, point, DateTime.Now);
1122 Point date_grid_location = new Point (calendars[i].X, title_rect.Bottom);
1124 // see if it's in the Week numbers
1125 if (ShowWeekNumbers) {
1126 Rectangle weeks_rect = new Rectangle (
1128 new Size (date_cell_size.Width,Math.Max (calendars[i].Height - title_rect.Height, 0)));
1129 if (weeks_rect.Contains (point)) {
1130 return new HitTestInfo(HitArea.WeekNumbers, point, DateTime.Now);
1133 // move the location of the grid over
1134 date_grid_location.X += date_cell_size.Width;
1137 // see if it's in the week names
1138 Rectangle day_rect = new Rectangle (
1140 new Size (Math.Max (calendars[i].Right - date_grid_location.X, 0), date_cell_size.Height));
1141 if (day_rect.Contains (point)) {
1142 return new HitTestInfo(HitArea.DayOfWeek, point, DateTime.Now);
1145 // finally see if it was a date that was clicked
1146 Rectangle date_grid = new Rectangle (
1147 new Point (day_rect.X, day_rect.Bottom),
1148 new Size (day_rect.Width, Math.Max(calendars[i].Bottom - day_rect.Bottom, 0)));
1149 if (date_grid.Contains (point)) {
1150 clicked_rect = date_grid;
1151 // okay so it's inside the grid, get the offset
1152 Point offset = new Point (point.X - date_grid.X, point.Y - date_grid.Y);
1153 int row = offset.Y / date_cell_size.Height;
1154 int col = offset.X / date_cell_size.Width;
1155 // establish our first day of the month
1156 DateTime calendar_month = this.CurrentMonth.AddMonths(i);
1157 DateTime first_day = GetFirstDateInMonthGrid (calendar_month);
1158 DateTime time = first_day.AddDays ((row * 7) + col);
1159 // establish which date was clicked
1160 if (time.Year != calendar_month.Year || time.Month != calendar_month.Month) {
1161 if (time < calendar_month && i == 0) {
1162 return new HitTestInfo(HitArea.PrevMonthDate, point, time);
1163 } else if (time > calendar_month && i == CalendarDimensions.Width*CalendarDimensions.Height - 1) {
1164 return new HitTestInfo(HitArea.NextMonthDate, point, time);
1166 return new HitTestInfo(HitArea.Nowhere, point, time);
1168 return new HitTestInfo(HitArea.Date, point, time);
1173 return new HitTestInfo ();
1176 // returns the date of the first cell of the specified month grid
1177 internal DateTime GetFirstDateInMonthGrid (DateTime month) {
1178 // convert the first_day_of_week into a DayOfWeekEnum
1179 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1180 // find the first day of the month
1181 DateTime first_date_of_month = new DateTime (month.Year, month.Month, 1);
1182 DayOfWeek first_day_of_month = first_date_of_month.DayOfWeek;
1183 // adjust for the starting day of the week
1184 int offset = first_day_of_month - first_day;
1188 return first_date_of_month.AddDays (-1*offset);
1191 // returns the date of the last cell of the specified month grid
1192 internal DateTime GetLastDateInMonthGrid (DateTime month)
1194 DateTime start = GetFirstDateInMonthGrid(month);
1195 return start.AddDays ((7 * 6)-1);
1198 internal bool IsBoldedDate (DateTime date) {
1199 // check bolded dates
1200 if (bolded_dates != null && bolded_dates.Length > 0) {
1201 foreach (DateTime bolded_date in bolded_dates) {
1202 if (bolded_date.Date == date.Date) {
1207 // check monthly dates
1208 if (monthly_bolded_dates != null && monthly_bolded_dates.Length > 0) {
1209 foreach (DateTime bolded_date in monthly_bolded_dates) {
1210 if (bolded_date.Day == date.Day) {
1215 // check yearly dates
1216 if (annually_bolded_dates != null && annually_bolded_dates.Length > 0) {
1217 foreach (DateTime bolded_date in annually_bolded_dates) {
1218 if (bolded_date.Month == date.Month && bolded_date.Day == date.Day) {
1224 return false; // no match
1227 // updates the specified bolded dates array with ones to add and ones to remove
1228 private void UpdateDateArray (ref DateTime [] dates, ArrayList to_add, ArrayList to_remove) {
1229 ArrayList list = new ArrayList ();
1231 // update normal bolded dates
1232 if (dates != null) {
1233 foreach (DateTime date in dates) {
1234 list.Add (date.Date);
1239 foreach (DateTime date in to_add) {
1240 if (!list.Contains (date.Date)) {
1241 list.Add (date.Date);
1246 // remove ones to remove
1247 foreach (DateTime date in to_remove) {
1248 if (list.Contains (date.Date)) {
1249 list.Remove (date.Date);
1253 // set up the array now
1254 if (list.Count > 0) {
1255 dates = (DateTime []) list.ToArray (typeof (DateTime));
1263 // initialise the context menu
1264 private void SetUpContextMenu () {
1265 menu = new ContextMenu ();
1266 for (int i=0; i < 12; i++) {
1267 MenuItem menu_item = new MenuItem ( new DateTime (2000, i+1, 1).ToString ("MMMM"));
1268 menu_item.Click += new EventHandler (MenuItemClickHandler);
1269 menu.MenuItems.Add (menu_item);
1273 // initialises text value and show's year up down in correct position
1274 private void PrepareYearUpDown (Point p) {
1275 Rectangle old_location = year_updown.Bounds;
1278 Rectangle title_rect = new Rectangle(
1279 last_clicked_calendar_rect.Location,
1282 year_updown.Bounds = GetYearNameRectangle(
1284 last_clicked_calendar_index);
1285 year_updown.Top -= 4;
1286 year_updown.Width += (int) (this.Font.Size * 4);
1287 // set year - only do this if this isn't being called because of a year up down click
1288 if(year_updown.Bounds != old_location) {
1289 year_updown.Value = current_month.AddMonths(last_clicked_calendar_index).Year;
1292 if(!year_updown.Visible) {
1293 year_updown.Visible = true;
1297 // returns the first date of the month
1298 private DateTime GetFirstDateInMonth (DateTime date) {
1299 return new DateTime (date.Year, date.Month, 1);
1302 // returns the last date of the month
1303 private DateTime GetLastDateInMonth (DateTime date) {
1304 return new DateTime (date.Year, date.Month, 1).AddMonths(1).AddDays(-1);
1307 // called in response to users seletion with shift key
1308 private void AddTimeToSelection (int delta, bool isDays)
1310 DateTime cursor_point;
1312 // okay we add the period to the date that is not the same as the
1313 // start date when shift was first clicked.
1314 if (SelectionStart != first_select_start_date) {
1315 cursor_point = SelectionStart;
1317 cursor_point = SelectionEnd;
1321 end_point = cursor_point.AddDays (delta);
1323 // delta must be months
1324 end_point = cursor_point.AddMonths (delta);
1326 // set the new selection range
1327 SelectionRange range = new SelectionRange (first_select_start_date, end_point);
1328 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1329 // okay the date is beyond what is allowed, lets set the maximum we can
1330 if (range.Start != first_select_start_date) {
1331 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1333 range.End = range.Start.AddDays (MaxSelectionCount-1);
1336 this.SelectionRange = range;
1339 // attempts to add the date to the selection without throwing exception
1340 private void SelectDate (DateTime date) {
1341 // try and add the new date to the selction range
1342 if (is_shift_pressed || (click_state [0])) {
1343 SelectionRange range = new SelectionRange (first_select_start_date, date);
1344 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1345 // okay the date is beyond what is allowed, lets set the maximum we can
1346 if (range.Start != first_select_start_date) {
1347 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1349 range.End = range.Start.AddDays (MaxSelectionCount-1);
1352 SelectionRange = range;
1354 SelectionRange = new SelectionRange (date, date);
1355 first_select_start_date = date;
1359 // gets the week of the year
1360 internal int GetWeekOfYear (DateTime date) {
1361 // convert the first_day_of_week into a DayOfWeekEnum
1362 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1363 // find the first day of the year
1364 DayOfWeek first_day_of_year = new DateTime (date.Year, 1, 1).DayOfWeek;
1365 // adjust for the starting day of the week
1366 int offset = first_day_of_year - first_day;
1367 int week = ((date.DayOfYear + offset) / 7) + 1;
1371 // convert a Day enum into a DayOfWeek enum
1372 internal DayOfWeek GetDayOfWeek (Day day) {
1373 if (day == Day.Default) {
1374 return DayOfWeek.Sunday;
1376 return (DayOfWeek) DayOfWeek.Parse (typeof (DayOfWeek), day.ToString ());
1380 // returns the rectangle for themonth name
1381 internal Rectangle GetMonthNameRectangle (Rectangle title_rect, int calendar_index) {
1382 Graphics g = this.DeviceContext;
1383 DateTime this_month = this.current_month.AddMonths (calendar_index);
1384 Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1385 Size month_size = g.MeasureString (this_month.ToString ("MMMM"), this.Font).ToSize ();
1386 // return only the month name part of that
1387 return new Rectangle (
1389 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1390 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1394 // returns the rectangle for the year in the title
1395 internal Rectangle GetYearNameRectangle (Rectangle title_rect, int calendar_index) {
1396 Graphics g = this.DeviceContext;
1397 DateTime this_month = this.current_month.AddMonths (calendar_index);
1398 Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1399 Size year_size = g.MeasureString (this_month.ToString ("yyyy"), this.Font).ToSize ();
1400 // find out how much space the title took
1401 Rectangle text_rect = new Rectangle (
1403 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1404 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1406 // return only the rect of the year
1407 return new Rectangle (
1409 text_rect.Right - year_size.Width,
1414 // determine if date is allowed to be drawn in month
1415 internal bool IsValidWeekToDraw (DateTime month, DateTime date, int row, int col) {
1416 DateTime tocheck = month.AddMonths (-1);
1417 if ((month.Year == date.Year && month.Month == date.Month) ||
1418 (tocheck.Year == date.Year && tocheck.Month == date.Month)) {
1422 // check the railing dates (days in the month after the last month in grid)
1423 if (row == CalendarDimensions.Height - 1 && col == CalendarDimensions.Width - 1) {
1424 tocheck = month.AddMonths (1);
1425 return (tocheck.Year == date.Year && tocheck.Month == date.Month) ;
1431 // set one item clicked and all others off
1432 private void SetItemClick(HitTestInfo hti)
1434 switch(hti.HitArea) {
1435 case HitArea.NextMonthButton:
1436 this.is_previous_clicked = false;
1437 this.is_next_clicked = true;
1438 this.is_date_clicked = false;
1440 case HitArea.PrevMonthButton:
1441 this.is_previous_clicked = true;
1442 this.is_next_clicked = false;
1443 this.is_date_clicked = false;
1445 case HitArea.PrevMonthDate:
1446 case HitArea.NextMonthDate:
1448 this.clicked_date = hti.Time;
1449 this.is_previous_clicked = false;
1450 this.is_next_clicked = false;
1451 this.is_date_clicked = true;
1454 this.is_previous_clicked = false;
1455 this.is_next_clicked = false;
1456 this.is_date_clicked = false;
1461 // called when the year is changed
1462 private void UpDownYearChangedHandler (object sender, EventArgs e) {
1463 int initial_year_value = this.CurrentMonth.AddMonths(last_clicked_calendar_index).Year;
1464 int diff = (int) year_updown.Value - initial_year_value;
1465 this.CurrentMonth = this.CurrentMonth.AddYears(diff);
1468 // called when context menu is clicked
1469 private void MenuItemClickHandler (object sender, EventArgs e) {
1470 MenuItem item = sender as MenuItem;
1471 if (item != null && month_title_click_location != Point.Empty) {
1472 // establish which month we want to move to
1473 if (item.Parent == null) {
1476 int new_month = item.Parent.MenuItems.IndexOf (item) + 1;
1477 if (new_month == 0) {
1480 // okay let's establish which calendar was hit
1481 Size month_size = this.SingleMonthSize;
1482 for (int i=0; i < CalendarDimensions.Height; i++) {
1483 for (int j=0; j < CalendarDimensions.Width; j++) {
1484 int month_index = (i * CalendarDimensions.Width) + j;
1485 Rectangle month_rect = new Rectangle ( new Point (0, 0), month_size);
1487 month_rect.X = this.ClientRectangle.X + 1;
1489 month_rect.X = this.ClientRectangle.X + 1 + ((j)*(month_size.Width+calendar_spacing.Width));
1492 month_rect.Y = this.ClientRectangle.Y + 1;
1494 month_rect.Y = this.ClientRectangle.Y + 1 + ((i)*(month_size.Height+calendar_spacing.Height));
1496 // see if the point is inside
1497 if (month_rect.Contains (month_title_click_location)) {
1498 DateTime clicked_month = CurrentMonth.AddMonths (month_index);
1499 // get the month that we want to move to
1500 int month_offset = new_month - clicked_month.Month;
1502 // move forward however more months we need to
1503 this.CurrentMonth = this.CurrentMonth.AddMonths (month_offset);
1510 month_title_click_location = Point.Empty;
1514 // raised on the timer, for mouse hold clicks
1515 private void TimerHandler (object sender, EventArgs e) {
1516 // now find out which area was click
1518 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1519 // see if it was clicked on the prev or next mouse
1520 if (click_state [1] || click_state [2]) {
1521 // invalidate the area where the mouse was last held
1523 // register the click
1524 if (hti.HitArea == HitArea.PrevMonthButton ||
1525 hti.HitArea == HitArea.NextMonthButton) {
1526 DoButtonMouseDown (hti);
1527 click_state [1] = (hti.HitArea == HitArea.PrevMonthButton);
1528 click_state [2] = !click_state [1];
1530 if (timer.Interval != 300) {
1531 timer.Interval = 300;
1535 timer.Enabled = false;
1539 // selects one of the buttons
1540 private void DoButtonMouseDown (HitTestInfo hti) {
1541 // show the click then move on
1543 if (hti.HitArea == HitArea.PrevMonthButton) {
1544 // invalidate the prev monthbutton
1547 this.ClientRectangle.X + 1 + button_x_offset,
1548 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1550 button_size.Height));
1551 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange*-1);
1553 // invalidate the next monthbutton
1556 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1557 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1559 button_size.Height));
1560 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange);
1564 // selects the clicked date
1565 private void DoDateMouseDown (HitTestInfo hti) {
1569 // event run on the mouse up event
1570 private void DoMouseUp () {
1572 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1573 switch (hti.HitArea) {
1574 case HitArea.PrevMonthDate:
1575 case HitArea.NextMonthDate:
1577 this.SelectDate (clicked_date);
1578 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1582 // invalidate the next monthbutton
1583 if (this.is_next_clicked) {
1586 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1587 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1589 button_size.Height));
1591 // invalidate the prev monthbutton
1592 if (this.is_previous_clicked) {
1595 this.ClientRectangle.X + 1 + button_x_offset,
1596 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1598 button_size.Height));
1600 if (this.is_date_clicked) {
1601 // invalidate the area under the cursor, to remove focus rect
1602 this.InvalidateDateRange (new SelectionRange (clicked_date, clicked_date));
1604 this.is_previous_clicked = false;
1605 this.is_next_clicked = false;
1606 this.is_date_clicked = false;
1609 // // need when in windowed mode
1610 // private void LostFocusHandler (object sender, EventArgs e)
1612 // if (this.owner != null) {
1613 // if (this.Visible) {
1614 // this.owner.HideMonthCalendar ();
1619 // occurs when mouse moves around control, used for selection
1620 private void MouseMoveHandler (object sender, MouseEventArgs e) {
1621 HitTestInfo hti = this.HitTest (e.X, e.Y);
1622 // clear the last clicked item
1623 if (click_state [0]) {
1624 // register the click
1625 if (hti.HitArea == HitArea.PrevMonthDate ||
1626 hti.HitArea == HitArea.NextMonthDate ||
1627 hti.HitArea == HitArea.Date)
1629 Rectangle prev_rect = clicked_rect;
1630 DateTime prev_clicked = clicked_date;
1631 DoDateMouseDown (hti);
1632 if (owner == null) {
1633 click_state [0] = true;
1635 click_state [0] = false;
1636 click_state [1] = false;
1637 click_state [2] = false;
1640 if (prev_clicked != clicked_date) {
1641 Rectangle invalid = Rectangle.Union (prev_rect, clicked_rect);
1642 Invalidate (invalid);
1649 // to check if the mouse has come down on this control
1650 private void MouseDownHandler (object sender, MouseEventArgs e)
1652 // clear the click_state variables
1653 click_state [0] = false;
1654 click_state [1] = false;
1655 click_state [2] = false;
1657 // disable the timer if it was enabled
1658 if (timer.Enabled) {
1660 timer.Enabled = false;
1663 Point point = new Point (e.X, e.Y);
1664 // figure out if we are in drop down mode and a click happened outside us
1665 if (this.owner != null) {
1666 if (!this.ClientRectangle.Contains (point)) {
1667 this.owner.HideMonthCalendar ();
1672 //establish where was hit
1673 HitTestInfo hti = this.HitTest(point);
1674 // hide the year numeric up down if it was clicked
1675 if (year_updown != null && year_updown.Visible && hti.HitArea != HitArea.TitleYear)
1677 year_updown.Visible = false;
1679 switch (hti.HitArea) {
1680 case HitArea.PrevMonthButton:
1681 case HitArea.NextMonthButton:
1682 DoButtonMouseDown (hti);
1683 click_state [1] = (hti.HitArea == HitArea.PrevMonthDate);
1684 click_state [2] = !click_state [1];
1685 timer.Interval = 750;
1689 case HitArea.PrevMonthDate:
1690 case HitArea.NextMonthDate:
1691 DoDateMouseDown (hti);
1692 // leave clicked state blank if drop down window
1693 if (owner == null) {
1694 click_state [0] = true;
1696 click_state [0] = false;
1697 click_state [1] = false;
1698 click_state [2] = false;
1701 case HitArea.TitleMonth:
1702 month_title_click_location = hti.Point;
1703 menu.Show (this, hti.Point);
1705 case HitArea.TitleYear:
1706 // place the numeric up down
1707 PrepareYearUpDown(hti.Point);
1709 case HitArea.TodayLink:
1710 this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date);
1711 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1714 this.is_previous_clicked = false;
1715 this.is_next_clicked = false;
1716 this.is_date_clicked = false;
1721 // raised by any key down events
1722 private void KeyDownHandler (object sender, KeyEventArgs e) {
1723 // send keys to the year_updown control, let it handle it
1724 if(year_updown.Visible) {
1725 switch (e.KeyCode) {
1727 year_updown.Visible = false;
1730 year_updown.Value = year_updown.Value + 1;
1733 year_updown.Value = year_updown.Value - 1;
1737 if (!is_shift_pressed && e.Shift) {
1738 first_select_start_date = SelectionStart;
1739 is_shift_pressed = e.Shift;
1741 switch (e.KeyCode) {
1743 // set the date to the start of the month
1744 if (is_shift_pressed) {
1745 DateTime date = GetFirstDateInMonth (first_select_start_date);
1746 if (date < first_select_start_date.AddDays ((MaxSelectionCount-1)*-1)) {
1747 date = first_select_start_date.AddDays ((MaxSelectionCount-1)*-1);
1749 this.SetSelectionRange (date, first_select_start_date);
1751 DateTime date = GetFirstDateInMonth (this.SelectionStart);
1752 this.SetSelectionRange (date, date);
1754 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1757 // set the date to the last of the month
1758 if (is_shift_pressed) {
1759 DateTime date = GetLastDateInMonth (first_select_start_date);
1760 if (date > first_select_start_date.AddDays (MaxSelectionCount-1)) {
1761 date = first_select_start_date.AddDays (MaxSelectionCount-1);
1763 this.SetSelectionRange (date, first_select_start_date);
1765 DateTime date = GetLastDateInMonth (this.SelectionStart);
1766 this.SetSelectionRange (date, date);
1768 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1771 // set the date to the last of the month
1772 if (is_shift_pressed) {
1773 this.AddTimeToSelection (-1, false);
1775 DateTime date = this.SelectionStart.AddMonths (-1);
1776 this.SetSelectionRange (date, date);
1778 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1781 // set the date to the last of the month
1782 if (is_shift_pressed) {
1783 this.AddTimeToSelection (1, false);
1785 DateTime date = this.SelectionStart.AddMonths (1);
1786 this.SetSelectionRange (date, date);
1788 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1791 // set the back 1 week
1792 if (is_shift_pressed) {
1793 this.AddTimeToSelection (-7, true);
1795 DateTime date = this.SelectionStart.AddDays (-7);
1796 this.SetSelectionRange (date, date);
1798 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1801 // set the date forward 1 week
1802 if (is_shift_pressed) {
1803 this.AddTimeToSelection (7, true);
1805 DateTime date = this.SelectionStart.AddDays (7);
1806 this.SetSelectionRange (date, date);
1808 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1812 if (is_shift_pressed) {
1813 this.AddTimeToSelection (-1, true);
1815 DateTime date = this.SelectionStart.AddDays (-1);
1816 this.SetSelectionRange (date, date);
1818 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1822 if (is_shift_pressed) {
1823 this.AddTimeToSelection (1, true);
1825 DateTime date = this.SelectionStart.AddDays (1);
1826 this.SetSelectionRange (date, date);
1828 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1837 // to check if the mouse has come up on this control
1838 private void MouseUpHandler (object sender, MouseEventArgs e)
1840 if (timer.Enabled) {
1843 // clear the click state array
1844 click_state [0] = false;
1845 click_state [1] = false;
1846 click_state [2] = false;
1847 // do the regulare mouseup stuff
1851 // raised by any key up events
1852 private void KeyUpHandler (object sender, KeyEventArgs e) {
1853 is_shift_pressed = e.Shift ;
1857 // paint this control now
1858 private void PaintHandler (object sender, PaintEventArgs pe) {
1859 if (Width <= 0 || Height <= 0 || Visible == false)
1862 Draw (pe.ClipRectangle, pe.Graphics);
1864 // fire the new paint handler
1865 if (this.Paint != null)
1867 this.Paint (sender, pe);
1871 // returns the region of the control that needs to be redrawn
1872 private void InvalidateDateRange (SelectionRange range) {
1873 SelectionRange bounds = this.GetDisplayRange (false);
1875 if (range.End < bounds.Start || range.Start > bounds.End) {
1876 // don't invalidate anything, as the modified date range
1877 // is outside the visible bounds of this control
1880 // adjust the start and end to be inside the visible range
1881 if (range.Start < bounds.Start) {
1882 range = new SelectionRange (bounds.Start, range.End);
1884 if (range.End > bounds.End) {
1885 range = new SelectionRange (range.Start, bounds.End);
1887 // now invalidate the date rectangles as series of rows
1888 DateTime last_month = this.current_month.AddMonths ((CalendarDimensions.Width * CalendarDimensions.Height)).AddDays (-1);
1889 DateTime current = range.Start;
1890 while (current <= range.End) {
1891 DateTime month_end = new DateTime (current.Year, current.Month, 1).AddMonths (1).AddDays (-1);;
1892 Rectangle start_rect;
1894 // see if entire selection is in this current month
1895 if (range.End <= month_end && current < last_month) {
1896 // the end is the last date
1897 if (current < this.current_month) {
1898 start_rect = GetDateRowRect (current_month, current_month);
1900 start_rect = GetDateRowRect (current, current);
1902 end_rect = GetDateRowRect (current, range.End);
1903 } else if (current < last_month) {
1904 // otherwise it simply means we have a selection spaning
1905 // multiple months simply set rectangle inside the current month
1906 start_rect = GetDateRowRect (current, current);
1907 end_rect = GetDateRowRect (month_end, month_end);
1909 // it's outside the visible range
1910 start_rect = GetDateRowRect (last_month, last_month.AddDays (1));
1911 end_rect = GetDateRowRect (last_month, range.End);
1913 // push to the next month
1914 current = month_end.AddDays (1);
1915 // invalidate from the start row to the end row for this month
1921 Math.Max (end_rect.Bottom - start_rect.Y, 0)));
1925 // gets the rect of the row where the specified date appears on the specified month
1926 private Rectangle GetDateRowRect (DateTime month, DateTime date) {
1927 // first get the general rect of the supplied month
1928 Size month_size = SingleMonthSize;
1929 Rectangle month_rect = Rectangle.Empty;
1930 for (int i=0; i < CalendarDimensions.Width*CalendarDimensions.Height; i++) {
1931 DateTime this_month = this.current_month.AddMonths (i);
1932 if (month.Year == this_month.Year && month.Month == this_month.Month) {
1933 month_rect = new Rectangle (
1934 this.ClientRectangle.X + 1 + (month_size.Width * (i%CalendarDimensions.Width)) + (this.calendar_spacing.Width * (i%CalendarDimensions.Width)),
1935 this.ClientRectangle.Y + 1 + (month_size.Height * (i/CalendarDimensions.Width)) + (this.calendar_spacing.Height * (i/CalendarDimensions.Width)),
1941 // now find out where in the month the supplied date is
1942 if (month_rect == Rectangle.Empty) {
1943 return Rectangle.Empty;
1945 // find out which row this date is in
1947 DateTime first_date = GetFirstDateInMonthGrid (month);
1948 DateTime end_date = first_date.AddDays (7);
1949 for (int i=0; i < 6; i++) {
1950 if (date >= first_date && date < end_date) {
1954 first_date = end_date;
1955 end_date = end_date.AddDays (7);
1957 // ensure it's a valid row
1959 return Rectangle.Empty;
1961 int x_offset = (this.ShowWeekNumbers) ? date_cell_size.Width : 0;
1962 int y_offset = title_size.Height + (date_cell_size.Height * (row + 1));
1963 return new Rectangle (
1964 month_rect.X + x_offset,
1965 month_rect.Y + y_offset,
1966 date_cell_size.Width * 7,
1967 date_cell_size.Height);
1970 internal void Draw (Rectangle clip_rect, Graphics dc)
1972 ThemeEngine.Current.DrawMonthCalendar (dc, clip_rect, this);
1975 #endregion //internal methods
1977 #region internal drawing methods
1980 #endregion // internal drawing methods
1982 #region inner classes and enumerations
1984 // enumeration about what type of area on the calendar was hit
1985 public enum HitArea {
2001 // info regarding to a hit test on this calendar
2002 public sealed class HitTestInfo {
2004 private HitArea hit_area;
2005 private Point point;
2006 private DateTime time;
2008 // default constructor
2009 internal HitTestInfo () {
2010 hit_area = HitArea.Nowhere;
2011 point = new Point (0, 0);
2012 time = DateTime.Now;
2015 // overload receives all properties
2016 internal HitTestInfo (HitArea hit_area, Point point, DateTime time) {
2017 this.hit_area = hit_area;
2022 // the type of area that was hit
2023 public HitArea HitArea {
2029 // the point that is being test
2030 public Point Point {
2036 // the date under the hit test point, only valid if HitArea is Date
2037 public DateTime Time {
2044 #endregion // inner classes