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 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
27 // - show the year spin control
30 using System.Collections;
31 using System.ComponentModel;
32 using System.ComponentModel.Design;
34 using System.Windows.Forms;
36 namespace System.Windows.Forms {
37 [DefaultProperty("SelectionRange")]
38 [DefaultEvent("DateChanged")]
39 [Designer ("System.Windows.Forms.Design.MonthCalendarDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
40 public class MonthCalendar : Control {
41 #region Local variables
42 DateTime [] annually_bolded_dates;
44 DateTime [] bolded_dates;
45 Size calendar_dimensions;
46 Day first_day_of_week;
49 int max_selection_count;
51 DateTime [] monthly_bolded_dates;
53 SelectionRange selection_range;
55 bool show_today_circle;
56 bool show_week_numbers;
57 Color title_back_color;
58 Color title_fore_color;
61 Color trailing_fore_color;
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 bool is_date_clicked;
76 internal bool is_previous_clicked;
77 internal bool is_next_clicked;
78 internal bool is_shift_pressed;
79 internal DateTime first_select_start_date;
80 private Point month_title_click_location;
81 // this is used to see which item was actually clicked on in the beginning
82 // so that we know which item to fire on timer
84 // 1: previous clicked
86 private bool[] click_state;
88 // arraylists used to store new dates
89 ArrayList added_bolded_dates;
90 ArrayList removed_bolded_dates;
91 ArrayList added_annually_bolded_dates;
92 ArrayList removed_annually_bolded_dates;
93 ArrayList added_monthly_bolded_dates;
94 ArrayList removed_monthly_bolded_dates;
97 #endregion // Local variables
99 #region Public Constructors
101 public MonthCalendar () {
102 // set up the control painting
103 SetStyle (ControlStyles.UserPaint, true);
104 SetStyle (ControlStyles.AllPaintingInWmPaint, true);
107 timer = new Timer ();
108 timer.Interval = 500;
109 timer.Enabled = false;
111 // initialise default values
112 DateTime now = DateTime.Now.Date;
113 selection_range = new SelectionRange (now, now);
115 current_month = new DateTime (now.Year , now.Month, 1);
117 // iniatialise local members
118 annually_bolded_dates = null;
119 back_color = ThemeEngine.Current.ColorWindow;
121 calendar_dimensions = new Size (1,1);
122 first_day_of_week = Day.Default;
123 fore_color = SystemColors.ControlText;
124 max_date = new DateTime (9998, 12, 31);
125 max_selection_count = 7;
126 min_date = new DateTime (1953, 1, 1);
127 monthly_bolded_dates = null;
130 show_today_circle = true;
131 show_week_numbers = false;
132 title_back_color = ThemeEngine.Current.ColorActiveTitle;
133 title_fore_color = ThemeEngine.Current.ColorTitleText;
134 today_date_set = false;
135 trailing_fore_color = Color.Gray;
137 // initialise the arraylest for bolded dates
138 added_bolded_dates = new ArrayList ();
139 removed_bolded_dates = new ArrayList ();
140 added_annually_bolded_dates = new ArrayList ();
141 removed_annually_bolded_dates = new ArrayList ();
142 added_monthly_bolded_dates = new ArrayList ();
143 removed_monthly_bolded_dates = new ArrayList ();
145 // intiailise internal variables used
147 button_size = new Size (22, 17);
148 // default settings based on 8.25 pt San Serif Font
149 // Not sure of algorithm used to establish this
150 date_cell_size = new Size (24, 16); // default size at san-serif 8.25
151 divider_line_offset = 4;
152 calendar_spacing = new Size (4, 5); // horiz and vert spacing between months in a calendar grid
154 // set some state info
156 is_date_clicked = false;
157 is_previous_clicked = false;
158 is_next_clicked = false;
159 is_shift_pressed = false;
160 click_state = new bool [] {false, false, false};
161 first_select_start_date = now;
162 month_title_click_location = Point.Empty;
167 // LostFocus += new EventHandler (LostFocusHandler);
168 timer.Tick += new EventHandler (TimerHandler);
169 MouseMove += new MouseEventHandler (MouseMoveHandler);
170 MouseDown += new MouseEventHandler (MouseDownHandler);
171 KeyDown += new KeyEventHandler (KeyDownHandler);
172 MouseUp += new MouseEventHandler (MouseUpHandler);
173 KeyUp += new KeyEventHandler (KeyUpHandler);
174 // this replaces paint so call the control version
175 ((Control)this).Paint += new PaintEventHandler (PaintHandler);
178 // called when this control is added to date time picker
179 internal MonthCalendar (DateTimePicker owner) : this () {
181 this.is_visible = false;
182 this.Size = this.DefaultSize;
185 #endregion // Public Constructors
187 #region Public Instance Properties
189 // dates to make bold on calendar annually (recurring)
191 public DateTime[] AnnuallyBoldedDates {
193 if (annually_bolded_dates == null || annually_bolded_dates != value) {
194 annually_bolded_dates = value;
195 this.UpdateBoldedDates ();
200 return annually_bolded_dates;
205 [EditorBrowsable(EditorBrowsableState.Never)]
206 public override Image BackgroundImage {
\r
208 return base.BackgroundImage;
\r
211 base.BackgroundImage = value;
\r
216 // the back color for the main part of the calendar
217 public Color BackColor {
219 if (back_color != value) {
221 this.OnBackColorChanged (EventArgs.Empty);
230 // specific dates to make bold on calendar (non-recurring)
232 public DateTime[] BoldedDates {
234 if (bolded_dates == null || bolded_dates != value) {
235 bolded_dates = value;
236 this.UpdateBoldedDates ();
245 // the configuration of the monthly grid display - only allowed to display at most,
246 // 1 calendar year at a time, will be trimmed to fit it properly
248 public Size CalendarDimensions {
250 if (value.Width < 0 || value.Height < 0) {
251 throw new ArgumentException ();
253 if (calendar_dimensions != value) {
254 // squeeze the grid into 1 calendar year
255 if (value.Width * value.Height > 12) {
256 // iteratively reduce the largest dimension till our
257 // product is less than 12
258 if (value.Width > 12 && value.Height > 12) {
259 calendar_dimensions = new Size (4, 3);
260 } else if (value.Width > 12) {
261 for (int i = 12; i > 0; i--) {
262 if (i * value.Height <= 12) {
263 calendar_dimensions = new Size (i, value.Height);
267 } else if (value.Height > 12) {
268 for (int i = 12; i > 0; i--) {
269 if (i * value.Width <= 12) {
270 calendar_dimensions = new Size (value.Width, i);
276 calendar_dimensions = value;
282 return calendar_dimensions;
286 // the first day of the week to display
288 [DefaultValue (Day.Default)]
289 public Day FirstDayOfWeek {
291 if (first_day_of_week != value) {
292 first_day_of_week = value;
297 return first_day_of_week;
301 // the fore color for the main part of the calendar
302 public Color ForeColor {
304 if (fore_color != value) {
306 this.OnForeColorChanged (EventArgs.Empty);
316 [EditorBrowsable(EditorBrowsableState.Never)]
317 public ImeMode ImeMode {
323 if (ime_mode != value) {
326 if (ImeModeChanged != null) {
327 ImeModeChanged(this, EventArgs.Empty);
333 // the maximum date allowed to be selected on this month calendar
334 public DateTime MaxDate {
336 if (value < MinDate) {
337 throw new ArgumentException();
340 if (max_date != value) {
349 // the maximum number of selectable days
351 public int MaxSelectionCount {
354 throw new ArgumentException();
357 // can't set selectioncount less than already selected dates
358 if ((SelectionEnd - SelectionStart).Days > value) {
359 throw new ArgumentException();
362 if (max_selection_count != value) {
363 max_selection_count = value;
367 return max_selection_count;
371 // the minimum date allowed to be selected on this month calendar
372 public DateTime MinDate {
374 if (value < new DateTime (1953, 1, 1)) {
375 throw new ArgumentException();
378 if (value > MaxDate) {
379 throw new ArgumentException();
382 if (max_date != value) {
391 // dates to make bold on calendar monthly (recurring)
393 public DateTime[] MonthlyBoldedDates {
395 if (monthly_bolded_dates == null || monthly_bolded_dates != value) {
396 monthly_bolded_dates = value;
397 this.UpdateBoldedDates ();
402 return monthly_bolded_dates;
406 // the ammount by which to scroll this calendar by
408 public int ScrollChange {
410 if (value < 0 || value > 20000) {
411 throw new ArgumentException();
414 if (scroll_change != value) {
415 scroll_change = value;
419 // if zero it to the default -> the total number of months currently visible
420 if (scroll_change == 0) {
421 return CalendarDimensions.Width * CalendarDimensions.Height;
423 return scroll_change;
428 // the last selected date
430 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
431 public DateTime SelectionEnd {
433 if (value < MinDate || value > MaxDate) {
434 throw new ArgumentException();
437 if (SelectionRange.End != value) {
438 DateTime old_end = SelectionRange.End;
439 // make sure the end obeys the max selection range count
440 if (value < SelectionRange.Start) {
441 SelectionRange.Start = value;
443 if (value.AddDays((MaxSelectionCount-1)*-1) > SelectionRange.Start) {
444 SelectionRange.Start = value.AddDays((MaxSelectionCount-1)*-1);
446 SelectionRange.End = value;
447 this.InvalidateDateRange (new SelectionRange (old_end, SelectionRange.End));
448 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
452 return SelectionRange.End;
456 // the range of selected dates
457 public SelectionRange SelectionRange {
459 if (selection_range != value) {
460 SelectionRange old_range = selection_range;
462 // make sure the end obeys the max selection range count
463 if (value.End.AddDays((MaxSelectionCount-1)*-1) > value.Start) {
464 selection_range = new SelectionRange (value.End.AddDays((MaxSelectionCount-1)*-1), value.End);
466 selection_range = value;
468 SelectionRange visible_range = this.GetDisplayRange(true);
469 if(visible_range.Start > selection_range.End) {
470 this.current_month = new DateTime (selection_range.Start.Year, selection_range.Start.Month, 1);
472 } else if (visible_range.End < selection_range.Start) {
473 int year_diff = selection_range.End.Year - visible_range.End.Year;
474 int month_diff = selection_range.End.Month - visible_range.End.Month;
475 this.current_month = current_month.AddMonths(year_diff * 12 + month_diff);
478 // invalidate the selected range changes
479 DateTime diff_start = old_range.Start;
480 DateTime diff_end = old_range.End;
481 // now decide which region is greated
482 if (old_range.Start > SelectionRange.Start) {
483 diff_start = SelectionRange.Start;
484 } else if (old_range.Start == SelectionRange.Start) {
485 if (old_range.End < SelectionRange.End) {
486 diff_start = old_range.End;
488 diff_start = SelectionRange.End;
491 if (old_range.End < SelectionRange.End) {
492 diff_end = SelectionRange.End;
493 } else if (old_range.End == SelectionRange.End) {
494 if (old_range.Start < SelectionRange.Start) {
495 diff_end = SelectionRange.Start;
497 diff_end = old_range.Start;
501 // invalidate the region required
502 this.InvalidateDateRange (new SelectionRange (diff_start, diff_end));
503 // raise date changed event
504 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
508 return selection_range;
512 // the first selected date
514 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
515 public DateTime SelectionStart {
517 if (value < MinDate || value > MaxDate) {
518 throw new ArgumentException();
521 if (SelectionRange.Start != value) {
522 DateTime old_start = SelectionRange.Start;
523 // make sure the end obeys the max selection range count
524 if (value > SelectionRange.End) {
525 SelectionRange.End = value;
526 } else if (value.AddDays(MaxSelectionCount-1) < SelectionRange.End) {
527 SelectionRange.End = value.AddDays(MaxSelectionCount-1);
529 SelectionRange.Start = value;
530 DateTime new_month = new DateTime(value.Year, value.Month, 1);
531 if (current_month != new_month) {
532 current_month = new_month;
535 this.InvalidateDateRange (new SelectionRange (old_start, SelectionRange.Start));
537 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
541 return selection_range.Start;
545 // whether or not to show todays date
546 [DefaultValue (true)]
547 public bool ShowToday {
549 if (show_today != value) {
559 // whether or not to show a circle around todays date
560 [DefaultValue (true)]
561 public bool ShowTodayCircle {
563 if (show_today_circle != value) {
564 show_today_circle = value;
569 return show_today_circle;
573 // whether or not to show numbers beside each row of weeks
575 [DefaultValue (false)]
576 public bool ShowWeekNumbers {
578 if (show_week_numbers != value) {
579 show_week_numbers = value;
584 return show_week_numbers;
588 // the rectangle size required to render one month based on current font
590 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
591 public Size SingleMonthSize {
593 if (this.Font == null) {
594 throw new InvalidOperationException();
597 // multiplier is sucked out from the font size
598 int multiplier = this.Font.Height;
600 // establis how many columns and rows we have
601 int column_count = (ShowWeekNumbers) ? 8 : 7;
602 int row_count = 7; // not including the today date
604 // set the date_cell_size and the title_size
605 date_cell_size = new Size ((int) Math.Ceiling (2.5 * multiplier), (int) Math.Ceiling (1.5 * multiplier));
606 title_size = new Size ((date_cell_size.Width * column_count), 3 * multiplier);
608 return new Size (column_count * date_cell_size.Width, row_count * date_cell_size.Height + title_size.Height);
614 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
615 [EditorBrowsable(EditorBrowsableState.Never)]
616 public override string Text {
\r
625 // the back color for the title of the calendar and the
626 // forecolor for the day of the week text
627 public Color TitleBackColor {
629 if (title_back_color != value) {
630 title_back_color = value;
635 return title_back_color;
639 // the fore color for the title of the calendar
640 public Color TitleForeColor {
642 if (title_fore_color != value) {
643 title_fore_color = value;
648 return title_fore_color;
652 // the date this calendar is using to refer to today's date
653 public DateTime TodayDate {
655 today_date_set = true;
656 if (today_date != value) {
666 // tells if user specifically set today_date for this control
668 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
669 public bool TodayDateSet {
671 return today_date_set;
675 // the color used for trailing dates in the calendar
676 public Color TrailingForeColor {
678 if (trailing_fore_color != value) {
679 trailing_fore_color = value;
680 SelectionRange bounds = this.GetDisplayRange (false);
681 SelectionRange visible_bounds = this.GetDisplayRange (true);
682 this.InvalidateDateRange (new SelectionRange (bounds.Start, visible_bounds.Start));
683 this.InvalidateDateRange (new SelectionRange (bounds.End, visible_bounds.End));
687 return trailing_fore_color;
691 #endregion // Public Instance Properties
693 #region Protected Instance Properties
695 // overloaded to allow controll to be windowed for drop down
696 protected override CreateParams CreateParams {
698 if (this.owner == null) {
699 return base.CreateParams;
701 CreateParams cp = base.CreateParams;
702 cp.Style = unchecked ((int)(WindowStyles.WS_POPUP | WindowStyles.WS_VISIBLE | WindowStyles.WS_CLIPSIBLINGS | WindowStyles.WS_CLIPCHILDREN));
703 cp.ExStyle |= (int)(WindowStyles.WS_EX_TOOLWINDOW | WindowStyles.WS_EX_TOPMOST);
709 // not sure what to put in here - just doing a base() call - jba
710 protected override ImeMode DefaultImeMode {
712 return base.DefaultImeMode;
716 protected override Size DefaultSize {
718 Size single_month = SingleMonthSize;
720 int width = calendar_dimensions.Width * single_month.Width;
721 if (calendar_dimensions.Width > 1) {
722 width += (calendar_dimensions.Width - 1) * calendar_spacing.Width;
726 int height = calendar_dimensions.Height * single_month.Height;
727 if (this.ShowToday) {
728 height += date_cell_size.Height + 2; // add the height of the "Today: " ...
730 if (calendar_dimensions.Height > 1) {
731 height += (calendar_dimensions.Height - 1) * calendar_spacing.Height;
734 // add the 1 pixel boundary
742 return new Size (width, height);
746 #endregion // Protected Instance Properties
748 #region Public Instance Methods
750 // add a date to the anually bolded date arraylist
751 public void AddAnnuallyBoldedDate (DateTime date) {
752 added_annually_bolded_dates.Add (date.Date);
755 // add a date to the normal bolded date arraylist
756 public void AddBoldedDate (DateTime date) {
757 added_bolded_dates.Add (date.Date);
760 // add a date to the anually monthly date arraylist
761 public void AddMonthlyBoldedDate (DateTime date) {
762 added_monthly_bolded_dates.Add (date.Date);
765 // if visible = true, return only the dates of full months, else return all dates visible
766 public SelectionRange GetDisplayRange (bool visible) {
769 start = new DateTime (current_month.Year, current_month.Month, 1);
770 end = start.AddMonths (calendar_dimensions.Width * calendar_dimensions.Height);
771 end = end.AddDays(-1);
773 // process all visible dates if needed (including the grayed out dates
775 start = GetFirstDateInMonthGrid (start);
776 end = GetLastDateInMonthGrid (end);
779 return new SelectionRange (start, end);
782 // HitTest overload that recieve's x and y co-ordinates as separate ints
783 public HitTestInfo HitTest (int x, int y) {
784 return HitTest (new Point (x, y));
787 // returns a HitTestInfo for MonthCalendar element's under the specified point
788 public HitTestInfo HitTest (Point point) {
789 // before doing all the hard work, see if the today's date wasn't clicked
790 Rectangle today_rect = new Rectangle (
792 ClientRectangle.Bottom - date_cell_size.Height,
793 7 * date_cell_size.Width,
794 date_cell_size.Height);
795 if (today_rect.Contains (point) && this.ShowToday) {
796 return new HitTestInfo(HitArea.TodayLink, point, DateTime.Now);
799 Size month_size = SingleMonthSize;
800 // define calendar rect's that this thing can land in
801 Rectangle[] calendars = new Rectangle [CalendarDimensions.Width * CalendarDimensions.Height];
802 for (int i=0; i < CalendarDimensions.Width * CalendarDimensions.Height; i ++) {
804 calendars[i] = new Rectangle (
805 new Point (ClientRectangle.X + 1, ClientRectangle.Y + 1),
808 // calendar on the next row
809 if (i % CalendarDimensions.Width == 0) {
810 calendars[i] = new Rectangle (
811 new Point (calendars[i-CalendarDimensions.Width].X, calendars[i-CalendarDimensions.Width].Bottom + calendar_spacing.Height),
814 // calendar on the next column
815 calendars[i] = new Rectangle (
816 new Point (calendars[i-1].Right + calendar_spacing.Width, calendars[i-1].Y),
822 // through each trying to find a match
823 for (int i = 0; i < calendars.Length ; i++) {
824 if (calendars[i].Contains (point)) {
825 // check the title section
826 Rectangle title_rect = new Rectangle (
827 calendars[i].Location,
829 if (title_rect.Contains (point) ) {
830 // make sure it's not a previous button
832 Rectangle button_rect = new Rectangle(
833 new Point (calendars[i].X + button_x_offset, (title_size.Height - button_size.Height)/2),
835 if (button_rect.Contains (point))
\r
837 return new HitTestInfo(HitArea.PrevMonthButton, point, DateTime.Now);
840 // make sure it's not the next button
841 if (i % CalendarDimensions.Height == 0 && i % CalendarDimensions.Width == calendar_dimensions.Width - 1) {
842 Rectangle button_rect = new Rectangle(
843 new Point (calendars[i].Right - button_x_offset - button_size.Width, (title_size.Height - button_size.Height)/2),
845 if (button_rect.Contains (point))
\r
847 return new HitTestInfo(HitArea.NextMonthButton, point, DateTime.Now);
851 // make sure it's not the month or the year of the calendar
852 if (GetMonthNameRectangle (title_rect, i).Contains (point)) {
853 return new HitTestInfo(HitArea.TitleMonth, point, DateTime.Now);
855 if (GetYearNameRectangle (title_rect, i).Contains (point)) {
856 return new HitTestInfo(HitArea.TitleYear, point, DateTime.Now);
859 // return the hit test in the title background
860 return new HitTestInfo(HitArea.TitleBackground, point, DateTime.Now);
863 Point date_grid_location = new Point (calendars[i].X, title_rect.Bottom);
865 // see if it's in the Week numbers
866 if (ShowWeekNumbers) {
867 Rectangle weeks_rect = new Rectangle (
869 new Size (date_cell_size.Width,Math.Max (calendars[i].Height - title_rect.Height, 0)));
870 if (weeks_rect.Contains (point)) {
871 return new HitTestInfo(HitArea.WeekNumbers, point, DateTime.Now);
874 // move the location of the grid over
875 date_grid_location.X += date_cell_size.Width;
878 // see if it's in the week names
879 Rectangle day_rect = new Rectangle (
881 new Size (Math.Max (calendars[i].Right - date_grid_location.X, 0), date_cell_size.Height));
882 if (day_rect.Contains (point)) {
883 return new HitTestInfo(HitArea.DayOfWeek, point, DateTime.Now);
886 // finally see if it was a date that was clicked
887 Rectangle date_grid = new Rectangle (
888 new Point (day_rect.X, day_rect.Bottom),
889 new Size (day_rect.Width, Math.Max(calendars[i].Bottom - day_rect.Bottom, 0)));
890 if (date_grid.Contains (point)) {
891 // okay so it's inside the grid, get the offset
892 Point offset = new Point (point.X - date_grid.X, point.Y - date_grid.Y);
893 int row = offset.Y / date_cell_size.Height;
894 int col = offset.X / date_cell_size.Width;
895 // establish our first day of the month
896 DateTime calendar_month = this.CurrentMonth.AddMonths(i);
897 DateTime first_day = GetFirstDateInMonthGrid (calendar_month);
898 DateTime time = first_day.AddDays ((row * 7) + col);
899 // establish which date was clicked
900 if (time.Year != calendar_month.Year || time.Month != calendar_month.Month) {
901 if (time < calendar_month && i == 0) {
902 return new HitTestInfo(HitArea.PrevMonthDate, point, time);
903 } else if (time > calendar_month && i == CalendarDimensions.Width*CalendarDimensions.Height - 1) {
904 return new HitTestInfo(HitArea.NextMonthDate, point, time);
906 return new HitTestInfo(HitArea.Nowhere, point, time);
908 return new HitTestInfo(HitArea.Date, point, time);
913 return new HitTestInfo ();
916 // clears all the annually bolded dates
917 public void RemoveAllAnnuallyBoldedDates () {
918 annually_bolded_dates = null;
919 added_annually_bolded_dates.Clear ();
920 removed_annually_bolded_dates.Clear ();
923 // clears all the normal bolded dates
924 public void RemoveAllBoldedDates () {
926 added_bolded_dates.Clear ();
927 removed_bolded_dates.Clear ();
930 // clears all the monthly bolded dates
931 public void RemoveAllMonthlyBoldedDates () {
932 monthly_bolded_dates = null;
933 added_monthly_bolded_dates.Clear ();
934 removed_monthly_bolded_dates.Clear ();
937 // clears the specified annually bolded date (only compares day and month)
938 // only removes the first instance of the match
939 public void RemoveAnnuallyBoldedDate (DateTime date) {
940 if (!removed_annually_bolded_dates.Contains (date.Date)) {
941 removed_annually_bolded_dates.Add (date.Date);
945 // clears all the normal bolded date
946 // only removes the first instance of the match
947 public void RemoveBoldedDate (DateTime date) {
948 if (!removed_bolded_dates.Contains (date.Date)) {
949 removed_bolded_dates.Add (date.Date);
953 // clears the specified monthly bolded date (only compares day and month)
954 // only removes the first instance of the match
955 public void RemoveMonthlyBoldedDate (DateTime date) {
956 if (!removed_monthly_bolded_dates.Contains (date.Date)) {
957 removed_monthly_bolded_dates.Add (date.Date);
961 // sets the calendar_dimensions. If product is > 12, the larger dimension is reduced to make product < 12
962 public void SetCalendarDimensions(int x, int y) {
963 this.CalendarDimensions = new Size(x, y);
966 // sets the currently selected date as date
967 public void SetDate (DateTime date) {
968 this.SetSelectionRange (date.Date, date.Date);
971 // utility method set the SelectionRange property using individual dates
972 public void SetSelectionRange (DateTime date1, DateTime date2) {
973 this.SelectionRange = new SelectionRange(date1, date2);
976 public override string ToString () {
977 return this.GetType().Name + ", " + this.SelectionRange.ToString ();
980 // usually called after an AddBoldedDate method is called
981 // formats monthly and daily bolded dates according to the current calendar year
982 public void UpdateBoldedDates () {
983 UpdateDateArray (ref bolded_dates, added_bolded_dates, removed_bolded_dates);
984 UpdateDateArray (ref monthly_bolded_dates, added_monthly_bolded_dates, removed_monthly_bolded_dates);
985 UpdateDateArray (ref annually_bolded_dates, added_annually_bolded_dates, removed_annually_bolded_dates);
988 #endregion // Public Instance Methods
990 #region Protected Instance Methods
992 // not sure why this needs to be overriden
993 protected override void CreateHandle () {
994 base.CreateHandle ();
997 // not sure why this needs to be overriden
998 protected override void Dispose (bool disposing) {
999 base.Dispose (disposing);
1002 // not sure why this needs to be overriden
1003 protected override bool IsInputKey (Keys keyData) {
1004 return base.IsInputKey (keyData);
1007 // not sure why this needs to be overriden
1008 protected override void OnBackColorChanged (EventArgs e) {
1009 base.OnBackColorChanged (e);
1013 // raises the date changed event
1014 protected virtual void OnDateChanged (DateRangeEventArgs drevent) {
1015 if (this.DateChanged != null) {
1016 this.DateChanged (this, drevent);
1020 // raises the DateSelected event
1021 protected virtual void OnDateSelected (DateRangeEventArgs drevent) {
1022 if (this.DateSelected != null) {
1023 this.DateSelected (this, drevent);
1027 protected override void OnFontChanged (EventArgs e) {
1028 base.OnFontChanged (e);
1031 protected override void OnForeColorChanged (EventArgs e) {
1032 base.OnForeColorChanged (e);
1035 protected override void OnHandleCreated (EventArgs e) {
1036 base.OnHandleCreated (e);
1039 // i think this is overriden to not allow the control to be changed to an arbitrary size
1040 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) {
1041 if ((specified & BoundsSpecified.Height) == BoundsSpecified.Height ||
1042 (specified & BoundsSpecified.Width) == BoundsSpecified.Width ||
1043 (specified & BoundsSpecified.Size) == BoundsSpecified.Size) {
1044 // only allow sizes = default size to be set
1045 Size min_size = DefaultSize;
1046 Size max_size = new Size (
1047 DefaultSize.Width + SingleMonthSize.Width + calendar_spacing.Width,
1048 DefaultSize.Height + SingleMonthSize.Height + calendar_spacing.Height);
1049 int x_mid_point = (max_size.Width + min_size.Width)/2;
1050 int y_mid_point = (max_size.Height + min_size.Height)/2;
1051 if (width < x_mid_point) {
1052 width = min_size.Width;
1054 width = max_size.Width;
1056 if (height < y_mid_point) {
1057 height = min_size.Height;
1059 height = max_size.Height;
1061 base.SetBoundsCore (x, y, width, height, specified);
1063 base.SetBoundsCore (x, y, width, height, specified);
1067 protected override void WndProc (ref Message m) {
1068 base.WndProc (ref m);
1071 #endregion // Protected Instance Methods
1073 #region public events
1075 // fired when the date is changed (either explicitely or implicitely)
1076 // when navigating the month selector
1077 public event DateRangeEventHandler DateChanged;
1079 // fired when the user explicitely clicks on date to select it
1080 public event DateRangeEventHandler DateSelected;
1083 [EditorBrowsable (EditorBrowsableState.Never)]
1084 public event EventHandler BackgroundImageChanged;
1086 [MonoTODO("Fire Click event")]
1088 [EditorBrowsable (EditorBrowsableState.Never)]
1089 public event EventHandler Click;
1091 [MonoTODO("Fire DoubleClick event")]
1093 [EditorBrowsable (EditorBrowsableState.Never)]
1094 public event EventHandler DoubleClick;
1097 [EditorBrowsable (EditorBrowsableState.Never)]
1098 public event EventHandler ImeModeChanged;
1101 [EditorBrowsable (EditorBrowsableState.Never)]
1102 public new event PaintEventHandler Paint;
1105 [EditorBrowsable (EditorBrowsableState.Never)]
1106 public event EventHandler TextChanged;
1107 #endregion // public events
1109 #region internal properties
1111 internal DateTime CurrentMonth {
1113 // only interested in if the month (not actual date) has changed
1114 if (value.Month != current_month.Month ||
1115 value.Year != current_month.Year) {
1116 this.SelectionRange = new SelectionRange(
1117 this.SelectionStart.Add(value.Subtract(current_month)),
1118 this.SelectionEnd.Add(value.Subtract(current_month)));
1119 current_month = value;
1120 UpdateBoldedDates();
1125 return current_month;
1129 #endregion // internal properties
1131 #region internal/private methods
1133 // returns the date of the first cell of the specified month grid
1134 internal DateTime GetFirstDateInMonthGrid (DateTime month) {
1135 // convert the first_day_of_week into a DayOfWeekEnum
1136 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1137 // find the first day of the month
1138 DateTime first_date_of_month = new DateTime (month.Year, month.Month, 1);
1139 DayOfWeek first_day_of_month = first_date_of_month.DayOfWeek;
1140 // adjust for the starting day of the week
1141 int offset = first_day_of_month - first_day;
1145 return first_date_of_month.AddDays (-1*offset);
1148 // returns the date of the last cell of the specified month grid
1149 internal DateTime GetLastDateInMonthGrid (DateTime month)
\r
1151 DateTime start = GetFirstDateInMonthGrid(month);
1152 return start.AddDays ((7 * 6)-1);
1155 internal bool IsBoldedDate (DateTime date) {
1156 // check bolded dates
1157 if (bolded_dates != null && bolded_dates.Length > 0) {
1158 foreach (DateTime bolded_date in bolded_dates) {
1159 if (bolded_date.Date == date.Date) {
1164 // check monthly dates
1165 if (monthly_bolded_dates != null && monthly_bolded_dates.Length > 0) {
1166 foreach (DateTime bolded_date in monthly_bolded_dates) {
1167 if (bolded_date.Day == date.Day) {
1172 // check yearly dates
1173 if (annually_bolded_dates != null && annually_bolded_dates.Length > 0) {
1174 foreach (DateTime bolded_date in annually_bolded_dates) {
1175 if (bolded_date.Month == date.Month && bolded_date.Day == date.Day) {
1181 return false; // no match
1184 // updates the specified bolded dates array with ones to add and ones to remove
1185 private void UpdateDateArray (ref DateTime [] dates, ArrayList to_add, ArrayList to_remove) {
1186 ArrayList list = new ArrayList ();
1188 // update normal bolded dates
1189 if (dates != null) {
1190 foreach (DateTime date in dates) {
1191 list.Add (date.Date);
1196 foreach (DateTime date in to_add) {
1197 if (!list.Contains (date.Date)) {
1198 list.Add (date.Date);
1203 // remove ones to remove
1204 foreach (DateTime date in to_remove) {
1205 if (list.Contains (date.Date)) {
1206 list.Remove (date.Date);
1210 // set up the array now
1211 if (list.Count > 0) {
1212 dates = (DateTime []) list.ToArray (typeof (DateTime));
1220 // initialise the context menu
1221 private void SetUpContextMenu () {
1222 menu = new ContextMenu ();
1223 for (int i=0; i < 12; i++) {
1224 MenuItem menu_item = new MenuItem ( new DateTime (2000, i+1, 1).ToString ("MMMM"));
1225 menu_item.Click += new EventHandler (MenuItemClickHandler);
1226 menu.MenuItems.Add (menu_item);
1230 // returns the first date of the month
1231 private DateTime GetFirstDateInMonth (DateTime date) {
1232 return new DateTime (date.Year, date.Month, 1);
1235 // returns the last date of the month
1236 private DateTime GetLastDateInMonth (DateTime date) {
1237 return new DateTime (date.Year, date.Month, 1).AddMonths(1).AddDays(-1);
1240 // called in response to users seletion with shift key
1241 private void AddTimeToSelection (int delta, bool isDays)
1243 DateTime cursor_point;
1245 // okay we add the period to the date that is not the same as the
1246 // start date when shift was first clicked.
1247 if (SelectionStart != first_select_start_date) {
1248 cursor_point = SelectionStart;
1250 cursor_point = SelectionEnd;
1254 end_point = cursor_point.AddDays (delta);
1256 // delta must be months
1257 end_point = cursor_point.AddMonths (delta);
1259 // set the new selection range
1260 SelectionRange range = new SelectionRange (first_select_start_date, end_point);
1261 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1262 // okay the date is beyond what is allowed, lets set the maximum we can
1263 if (range.Start != first_select_start_date) {
1264 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1266 range.End = range.Start.AddDays (MaxSelectionCount-1);
1269 this.SelectionRange = range;
1272 // attempts to add the date to the selection without throwing exception
1273 private void SelectDate (DateTime date) {
1274 // try and add the new date to the selction range
1275 if (is_shift_pressed || (click_state [0])) {
1276 SelectionRange range = new SelectionRange (first_select_start_date, date);
1277 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1278 // okay the date is beyond what is allowed, lets set the maximum we can
1279 if (range.Start != first_select_start_date) {
1280 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1282 range.End = range.Start.AddDays (MaxSelectionCount-1);
1285 SelectionRange = range;
1287 SelectionRange = new SelectionRange (date, date);
1288 first_select_start_date = date;
1292 // gets the week of the year
1293 internal int GetWeekOfYear (DateTime date) {
1294 // convert the first_day_of_week into a DayOfWeekEnum
1295 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1296 // find the first day of the year
1297 DayOfWeek first_day_of_year = new DateTime (date.Year, 1, 1).DayOfWeek;
1298 // adjust for the starting day of the week
1299 int offset = first_day_of_year - first_day;
1300 int week = ((date.DayOfYear + offset) / 7) + 1;
1304 // convert a Day enum into a DayOfWeek enum
1305 internal DayOfWeek GetDayOfWeek (Day day) {
1306 if (day == Day.Default) {
1307 return DayOfWeek.Sunday;
1309 return (DayOfWeek) DayOfWeek.Parse (typeof (DayOfWeek), day.ToString ());
1313 // returns the rectangle for themonth name
1314 internal Rectangle GetMonthNameRectangle (Rectangle title_rect, int calendar_index) {
1315 Graphics g = this.DeviceContext;
1316 DateTime this_month = this.current_month.AddMonths (calendar_index);
1317 Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1318 Size month_size = g.MeasureString (this_month.ToString ("MMMM"), this.Font).ToSize ();
1319 // return only the month name part of that
1320 return new Rectangle (
1322 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1323 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1327 // returns the rectangle for the year in the title
1328 internal Rectangle GetYearNameRectangle (Rectangle title_rect, int calendar_index) {
1329 Graphics g = this.DeviceContext;
1330 DateTime this_month = this.current_month.AddMonths (calendar_index);
1331 Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1332 Size year_size = g.MeasureString (this_month.ToString ("yyyy"), this.Font).ToSize ();
1333 // find out how much space the title took
1334 Rectangle text_rect = new Rectangle (
1336 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1337 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1339 // return only the rect of the year
1340 return new Rectangle (
1342 text_rect.Right - year_size.Width,
1347 // determine if date is allowed to be drawn in month
1348 internal bool IsValidWeekToDraw (DateTime month, DateTime date, int row, int col) {
1349 DateTime tocheck = month.AddMonths (-1);
1350 if ((month.Year == date.Year && month.Month == date.Month) ||
1351 (tocheck.Year == date.Year && tocheck.Month == date.Month)) {
1355 // check the railing dates (days in the month after the last month in grid)
1356 if (row == CalendarDimensions.Height - 1 && col == CalendarDimensions.Width - 1) {
1357 tocheck = month.AddMonths (1);
1358 return (tocheck.Year == date.Year && tocheck.Month == date.Month) ;
1364 // set one item clicked and all others off
1365 private void SetItemClick(HitTestInfo hti)
\r
1367 switch(hti.HitArea) {
1368 case HitArea.NextMonthButton:
1369 this.is_previous_clicked = false;
1370 this.is_next_clicked = true;
1371 this.is_date_clicked = false;
1373 case HitArea.PrevMonthButton:
1374 this.is_previous_clicked = true;
1375 this.is_next_clicked = false;
1376 this.is_date_clicked = false;
1378 case HitArea.PrevMonthDate:
1379 case HitArea.NextMonthDate:
1381 this.clicked_date = hti.Time;
1382 this.is_previous_clicked = false;
1383 this.is_next_clicked = false;
1384 this.is_date_clicked = true;
1387 this.is_previous_clicked = false;
1388 this.is_next_clicked = false;
1389 this.is_date_clicked = false;
1394 // called when context menu is clicked
1395 private void MenuItemClickHandler (object sender, EventArgs e) {
1396 MenuItem item = sender as MenuItem;
1397 if (item != null && month_title_click_location != Point.Empty) {
1398 // establish which month we want to move to
1399 if (item.Parent == null) {
1402 int new_month = item.Parent.MenuItems.IndexOf (item) + 1;
1403 if (new_month == 0) {
1406 // okay let's establish which calendar was hit
1407 Size month_size = this.SingleMonthSize;
1408 for (int i=0; i < CalendarDimensions.Height; i++) {
1409 for (int j=0; j < CalendarDimensions.Width; j++) {
1410 int month_index = (i * CalendarDimensions.Width) + j;
1411 Rectangle month_rect = new Rectangle ( new Point (0, 0), month_size);
1413 month_rect.X = this.ClientRectangle.X + 1;
1415 month_rect.X = this.ClientRectangle.X + 1 + ((j)*(month_size.Width+calendar_spacing.Width));
1418 month_rect.Y = this.ClientRectangle.Y + 1;
1420 month_rect.Y = this.ClientRectangle.Y + 1 + ((i)*(month_size.Height+calendar_spacing.Height));
1422 // see if the point is inside
1423 if (month_rect.Contains (month_title_click_location)) {
1424 DateTime clicked_month = CurrentMonth.AddMonths (month_index);
1425 // get the month that we want to move to
1426 int month_offset = new_month - clicked_month.Month;
1428 // move forward however more months we need to
1429 this.CurrentMonth = this.CurrentMonth.AddMonths (month_offset);
1436 month_title_click_location = Point.Empty;
1440 // raised on the timer, for mouse hold clicks
1441 private void TimerHandler (object sender, EventArgs e) {
1442 // now find out which area was click
1444 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1445 // see if it was clicked on the prev or next mouse
1446 if (click_state [1] || click_state [2]) {
1447 // invalidate the area where the mouse was last held
1449 Application.DoEvents ();
1450 // register the click
1451 if (hti.HitArea == HitArea.PrevMonthButton ||
1452 hti.HitArea == HitArea.NextMonthButton) {
1453 DoButtonMouseDown (hti);
1454 click_state [1] = (hti.HitArea == HitArea.PrevMonthButton);
1455 click_state [2] = !click_state [1];
1457 if (timer.Interval != 100) {
1458 timer.Interval = 100;
1462 timer.Enabled = false;
1466 // selects one of the buttons
1467 private void DoButtonMouseDown (HitTestInfo hti) {
1468 // show the click then move on
1470 if (hti.HitArea == HitArea.PrevMonthButton) {
1471 // invalidate the prev monthbutton
1474 this.ClientRectangle.X + 1 + button_x_offset,
1475 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1477 button_size.Height));
1478 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange*-1);
1480 // invalidate the next monthbutton
1483 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1484 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1486 button_size.Height));
1487 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange);
1491 // selects the clicked date
1492 private void DoDateMouseDown (HitTestInfo hti) {
1494 this.SelectDate (clicked_date);
1495 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1498 // event run on the mouse up event
1499 private void DoMouseUp () {
1500 // invalidate the next monthbutton
1501 if (this.is_next_clicked) {
1504 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1505 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1507 button_size.Height));
1509 // invalidate the prev monthbutton
1510 if (this.is_previous_clicked) {
1513 this.ClientRectangle.X + 1 + button_x_offset,
1514 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1516 button_size.Height));
1518 if (this.is_date_clicked) {
1519 // invalidate the area under the cursor, to remove focus rect
1520 this.InvalidateDateRange (new SelectionRange (clicked_date, clicked_date));
1522 this.is_previous_clicked = false;
1523 this.is_next_clicked = false;
1524 this.is_date_clicked = false;
1527 // // need when in windowed mode
1528 // private void LostFocusHandler (object sender, EventArgs e)
1530 // if (this.owner != null) {
1531 // if (this.Visible) {
1532 // this.owner.HideMonthCalendar ();
1537 // occurs when mouse moves around control, used for selection
1538 private void MouseMoveHandler (object sender, MouseEventArgs e) {
1539 HitTestInfo hti = this.HitTest (e.X, e.Y);
1540 // clear the last clicked item
1541 if (click_state [0]) {
1542 // register the click
1543 if (hti.HitArea == HitArea.PrevMonthDate ||
1544 hti.HitArea == HitArea.NextMonthDate ||
1545 hti.HitArea == HitArea.Date)
1547 DoDateMouseDown (hti);
1548 if (owner == null) {
1549 click_state [0] = true;
1551 click_state [0] = false;
1552 click_state [1] = false;
1553 click_state [2] = false;
1560 // to check if the mouse has come down on this control
1561 private void MouseDownHandler (object sender, MouseEventArgs e)
1563 // clear the click_state variables
1564 click_state [0] = false;
1565 click_state [1] = false;
1566 click_state [2] = false;
1568 // disable the timer if it was enabled
1569 if (timer.Enabled) {
1571 timer.Enabled = false;
1574 Point point = new Point (e.X, e.Y);
1575 // figure out if we are in drop down mode and a click happened outside us
1576 if (this.owner != null) {
1577 if (!this.ClientRectangle.Contains (point)) {
1578 this.owner.HideMonthCalendar ();
1583 //establish where was hit
1584 HitTestInfo hti = this.HitTest(point);
1585 switch (hti.HitArea) {
\r
1586 case HitArea.PrevMonthButton:
1587 case HitArea.NextMonthButton:
1588 DoButtonMouseDown (hti);
1589 click_state [1] = (hti.HitArea == HitArea.PrevMonthDate);
1590 click_state [2] = !click_state [1];
1591 timer.Interval = 500;
1595 case HitArea.PrevMonthDate:
1596 case HitArea.NextMonthDate:
1597 DoDateMouseDown (hti);
1598 // leave clicked state blank if drop down window
1599 if (owner == null) {
1600 click_state [0] = true;
1602 click_state [0] = false;
1603 click_state [1] = false;
1604 click_state [2] = false;
1607 case HitArea.TitleMonth:
1608 month_title_click_location = hti.Point;
1609 menu.Show (this, hti.Point);
1611 case HitArea.TitleYear:
1612 //TODO: show the year spin control
1613 System.Console.WriteLine ("//TODO: show the year spin control");
1615 case HitArea.TodayLink:
1616 this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date);
1617 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1620 this.is_previous_clicked = false;
1621 this.is_next_clicked = false;
1622 this.is_date_clicked = false;
1627 // raised by any key down events
1628 private void KeyDownHandler (object sender, KeyEventArgs e) {
1629 if (!is_shift_pressed && e.Shift) {
1630 first_select_start_date = SelectionStart;
1631 is_shift_pressed = e.Shift;
1633 switch (e.KeyCode) {
1635 // set the date to the start of the month
1636 if (is_shift_pressed) {
1637 DateTime date = GetFirstDateInMonth (first_select_start_date);
1638 if (date < first_select_start_date.AddDays ((MaxSelectionCount-1)*-1)) {
1639 date = first_select_start_date.AddDays ((MaxSelectionCount-1)*-1);
1641 this.SetSelectionRange (date, first_select_start_date);
1643 DateTime date = GetFirstDateInMonth (this.SelectionStart);
1644 this.SetSelectionRange (date, date);
1646 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1649 // set the date to the last of the month
1650 if (is_shift_pressed) {
1651 DateTime date = GetLastDateInMonth (first_select_start_date);
1652 if (date > first_select_start_date.AddDays (MaxSelectionCount-1)) {
1653 date = first_select_start_date.AddDays (MaxSelectionCount-1);
1655 this.SetSelectionRange (date, first_select_start_date);
1657 DateTime date = GetLastDateInMonth (this.SelectionStart);
1658 this.SetSelectionRange (date, date);
1660 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1663 // set the date to the last of the month
1664 if (is_shift_pressed) {
1665 this.AddTimeToSelection (-1, false);
1667 DateTime date = this.SelectionStart.AddMonths (-1);
1668 this.SetSelectionRange (date, date);
1670 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1673 // set the date to the last of the month
1674 if (is_shift_pressed) {
1675 this.AddTimeToSelection (1, false);
1677 DateTime date = this.SelectionStart.AddMonths (1);
1678 this.SetSelectionRange (date, date);
1680 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1683 // set the back 1 week
1684 if (is_shift_pressed) {
1685 this.AddTimeToSelection (-7, true);
1687 DateTime date = this.SelectionStart.AddDays (-7);
1688 this.SetSelectionRange (date, date);
1690 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1693 // set the date forward 1 week
1694 if (is_shift_pressed) {
1695 this.AddTimeToSelection (7, true);
1697 DateTime date = this.SelectionStart.AddDays (7);
1698 this.SetSelectionRange (date, date);
1700 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1704 if (is_shift_pressed) {
1705 this.AddTimeToSelection (-1, true);
1707 DateTime date = this.SelectionStart.AddDays (-1);
1708 this.SetSelectionRange (date, date);
1710 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1714 if (is_shift_pressed) {
1715 this.AddTimeToSelection (1, true);
1717 DateTime date = this.SelectionStart.AddDays (1);
1718 this.SetSelectionRange (date, date);
1720 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1728 // to check if the mouse has come up on this control
1729 private void MouseUpHandler (object sender, MouseEventArgs e)
1731 if (timer.Enabled) {
1734 // clear the click state array
1735 click_state [0] = false;
1736 click_state [1] = false;
1737 click_state [2] = false;
1738 // do the regulare mouseup stuff
1742 // raised by any key up events
1743 private void KeyUpHandler (object sender, KeyEventArgs e) {
1744 is_shift_pressed = e.Shift ;
1748 // paint this control now
1749 private void PaintHandler (object sender, PaintEventArgs pe) {
1750 if (Width <= 0 || Height <= 0 || Visible == false)
1753 Draw (pe.ClipRectangle, pe.Graphics);
1755 // fire the new paint handler
1756 if (this.Paint != null)
1758 this.Paint (sender, pe);
1762 // returns the region of the control that needs to be redrawn
1763 private void InvalidateDateRange (SelectionRange range) {
1764 SelectionRange bounds = this.GetDisplayRange (false);
1765 if (range.End < bounds.Start || range.Start > bounds.End) {
1766 // don't invalidate anything, as the modified date range
1767 // is outside the visible bounds of this control
1770 // adjust the start and end to be inside the visible range
1771 if (range.Start < bounds.Start) {
1772 range = new SelectionRange (bounds.Start, range.End);
1774 if (range.End > bounds.End) {
1775 range = new SelectionRange (range.Start, bounds.End);
1777 // now invalidate the date rectangles as series of rows
1778 DateTime last_month = this.current_month.AddMonths ((CalendarDimensions.Width * CalendarDimensions.Height)).AddDays (-1);
1779 DateTime current = range.Start;
1780 while (current <= range.End) {
1781 DateTime month_end = new DateTime (current.Year, current.Month, 1).AddMonths (1).AddDays (-1);;
1782 Rectangle start_rect;
1784 // see if entire selection is in this current month
1785 if (range.End <= month_end && current < last_month) {
1786 // the end is the last date
1787 if (current < this.current_month) {
1788 start_rect = GetDateRowRect (current_month, current_month);
1790 start_rect = GetDateRowRect (current, current);
1792 end_rect = GetDateRowRect (current, range.End);
1793 } else if (current < last_month) {
1794 // otherwise it simply means we have a selection spaning
1795 // multiple months simply set rectangle inside the current month
1796 start_rect = GetDateRowRect (current, current);
1797 end_rect = GetDateRowRect (last_month, month_end);
1799 // it's outside the visible range
1800 start_rect = GetDateRowRect (last_month, last_month.AddDays (1));
1801 end_rect = GetDateRowRect (last_month, range.End);
1803 // push to the next month
1804 current = month_end.AddDays (1);
1805 // invalidate from the start row to the end row for this month
1811 Math.Max (end_rect.Bottom - start_rect.Y, 0)));
1815 // gets the rect of the row where the specified date appears on the specified month
1816 private Rectangle GetDateRowRect (DateTime month, DateTime date) {
1817 // first get the general rect of the supplied month
1818 Size month_size = SingleMonthSize;
1819 Rectangle month_rect = Rectangle.Empty;
1820 for (int i=0; i < CalendarDimensions.Width*CalendarDimensions.Height; i++) {
1821 DateTime this_month = this.current_month.AddMonths (i);
1822 if (month.Year == this_month.Year && month.Month == this_month.Month) {
1823 month_rect = new Rectangle (
1824 this.ClientRectangle.X + 1 + (month_size.Width * (i%CalendarDimensions.Width)) + (this.calendar_spacing.Width * (i%CalendarDimensions.Width)),
1825 this.ClientRectangle.Y + 1 + (month_size.Height * (i/CalendarDimensions.Width)) + (this.calendar_spacing.Height * (i/CalendarDimensions.Width)),
1831 // now find out where in the month the supplied date is
1832 if (month_rect == Rectangle.Empty) {
1833 return Rectangle.Empty;
1835 // find out which row this date is in
1837 DateTime first_date = GetFirstDateInMonthGrid (month);
1838 DateTime end_date = first_date.AddDays (7);
1839 for (int i=0; i < 6; i++) {
1840 if (date >= first_date && date < end_date) {
1844 first_date = end_date;
1845 end_date = end_date.AddDays (7);
1847 // ensure it's a valid row
1849 return Rectangle.Empty;
1851 int x_offset = (this.ShowWeekNumbers) ? date_cell_size.Width : 0;
1852 int y_offset = title_size.Height + (date_cell_size.Height * (row + 1));
1853 return new Rectangle (
1854 month_rect.X + x_offset,
1855 month_rect.Y + y_offset,
1856 date_cell_size.Width * 7,
1857 date_cell_size.Height);
1860 internal void Draw (Rectangle clip_rect, Graphics dc)
1862 ThemeEngine.Current.DrawMonthCalendar (dc, clip_rect, this);
1865 #endregion //internal methods
1867 #region internal drawing methods
1870 #endregion // internal drawing methods
1872 #region inner classes and enumerations
1874 // enumeration about what type of area on the calendar was hit
1875 public enum HitArea {
1891 // info regarding to a hit test on this calendar
1892 public sealed class HitTestInfo {
1894 private HitArea hit_area;
1895 private Point point;
1896 private DateTime time;
1898 // default constructor
1899 internal HitTestInfo () {
1900 hit_area = HitArea.Nowhere;
1901 point = new Point (0, 0);
1902 time = DateTime.Now;
1905 // overload receives all properties
1906 internal HitTestInfo (HitArea hit_area, Point point, DateTime time) {
1907 this.hit_area = hit_area;
1912 // the type of area that was hit
1913 public HitArea HitArea {
1919 // the point that is being test
1920 public Point Point {
1926 // the date under the hit test point, only valid if HitArea is Date
1927 public DateTime Time {
1934 #endregion // inner classes