* TextBoxBase.cs: Take HideSelection into account when
[mono-project.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / MonthCalendar.cs
blobc516aaadab854a8c67f7c8767e1fc1a926659799
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:
8 //
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 //
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.
22 // Authors:
23 // John BouAntoun jba-mono@optusnet.com.au
25 // REMAINING TODO:
26 // - get the date_cell_size and title_size to be pixel perfect match of SWF
28 using System;
29 using System.Collections;
30 using System.ComponentModel;
31 using System.ComponentModel.Design;
32 using System.Drawing;
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;
42 Color back_color;
43 DateTime [] bolded_dates;
44 Size calendar_dimensions;
45 Day first_day_of_week;
46 Color fore_color;
47 DateTime max_date;
48 int max_selection_count;
49 DateTime min_date;
50 DateTime [] monthly_bolded_dates;
51 int scroll_change;
52 SelectionRange selection_range;
53 bool show_today;
54 bool show_today_circle;
55 bool show_week_numbers;
56 Color title_back_color;
57 Color title_fore_color;
58 DateTime today_date;
59 bool today_date_set;
60 Color trailing_fore_color;
61 ContextMenu menu;
62 NumericUpDown year_updown;
63 Timer timer;
64 Timer updown_timer;
65 bool showing_context_menu;
66 bool updown_has_focus;
68 // internal variables used
69 internal DateTime current_month; // the month that is being displayed in top left corner of the grid
70 internal DateTimePicker owner; // used if this control is popped up
71 internal int button_x_offset;
72 internal Size button_size;
73 internal Size title_size;
74 internal Size date_cell_size;
75 internal Size calendar_spacing;
76 internal int divider_line_offset;
77 internal DateTime clicked_date;
78 internal Rectangle clicked_rect;
79 internal bool is_date_clicked;
80 internal bool is_previous_clicked;
81 internal bool is_next_clicked;
82 internal bool is_shift_pressed;
83 internal DateTime first_select_start_date;
84 internal int last_clicked_calendar_index;
85 internal Rectangle last_clicked_calendar_rect;
86 internal Font bold_font; // Cache the font in FontStyle.Bold
87 internal StringFormat centered_format; // Cache centered string format
88 private Point month_title_click_location;
89 // this is used to see which item was actually clicked on in the beginning
90 // so that we know which item to fire on timer
91 // 0: date clicked
92 // 1: previous clicked
93 // 2: next clicked
94 private bool[] click_state;
96 // arraylists used to store new dates
97 ArrayList added_bolded_dates;
98 ArrayList removed_bolded_dates;
99 ArrayList added_annually_bolded_dates;
100 ArrayList removed_annually_bolded_dates;
101 ArrayList added_monthly_bolded_dates;
102 ArrayList removed_monthly_bolded_dates;
105 #endregion // Local variables
107 #region Public Constructors
109 public MonthCalendar () {
110 // set up the control painting
111 SetStyle (ControlStyles.UserPaint | ControlStyles.StandardClick, false);
113 // mouse down timer
114 timer = new Timer ();
115 timer.Interval = 500;
116 timer.Enabled = false;
118 // initialise default values
119 DateTime now = DateTime.Now.Date;
120 selection_range = new SelectionRange (now, now);
121 today_date = now;
122 current_month = new DateTime (now.Year , now.Month, 1);
124 // iniatialise local members
125 annually_bolded_dates = null;
126 back_color = ThemeEngine.Current.ColorWindow;
127 bolded_dates = null;
128 calendar_dimensions = new Size (1,1);
129 first_day_of_week = Day.Default;
130 fore_color = SystemColors.ControlText;
131 max_date = new DateTime (9998, 12, 31);
132 max_selection_count = 7;
133 min_date = new DateTime (1953, 1, 1);
134 monthly_bolded_dates = null;
135 scroll_change = 0;
136 show_today = true;
137 show_today_circle = true;
138 show_week_numbers = false;
139 title_back_color = ThemeEngine.Current.ColorActiveCaption;
140 title_fore_color = ThemeEngine.Current.ColorActiveCaptionText;
141 today_date_set = false;
142 trailing_fore_color = Color.Gray;
143 bold_font = new Font (Font, Font.Style | FontStyle.Bold);
144 centered_format = new StringFormat ();
145 centered_format.LineAlignment = StringAlignment.Center;
146 centered_format.Alignment = StringAlignment.Center;
148 // initialise the arraylest for bolded dates
149 added_bolded_dates = new ArrayList ();
150 removed_bolded_dates = new ArrayList ();
151 added_annually_bolded_dates = new ArrayList ();
152 removed_annually_bolded_dates = new ArrayList ();
153 added_monthly_bolded_dates = new ArrayList ();
154 removed_monthly_bolded_dates = new ArrayList ();
156 // intiailise internal variables used
157 button_x_offset = 5;
158 button_size = new Size (22, 17);
159 // default settings based on 8.25 pt San Serif Font
160 // Not sure of algorithm used to establish this
161 date_cell_size = new Size (24, 16); // default size at san-serif 8.25
162 divider_line_offset = 4;
163 calendar_spacing = new Size (4, 5); // horiz and vert spacing between months in a calendar grid
165 // set some state info
166 clicked_date = now;
167 is_date_clicked = false;
168 is_previous_clicked = false;
169 is_next_clicked = false;
170 is_shift_pressed = false;
171 click_state = new bool [] {false, false, false};
172 first_select_start_date = now;
173 month_title_click_location = Point.Empty;
175 // set up context menu
176 SetUpContextMenu ();
179 // event handlers
180 LostFocus += new EventHandler (LostFocusHandler);
181 timer.Tick += new EventHandler (TimerHandler);
182 MouseMove += new MouseEventHandler (MouseMoveHandler);
183 MouseDown += new MouseEventHandler (MouseDownHandler);
184 KeyDown += new KeyEventHandler (KeyDownHandler);
185 MouseUp += new MouseEventHandler (MouseUpHandler);
186 KeyUp += new KeyEventHandler (KeyUpHandler);
188 // this replaces paint so call the control version
189 base.Paint += new PaintEventHandler (PaintHandler);
192 // called when this control is added to date time picker
193 internal MonthCalendar (DateTimePicker owner) : this () {
194 this.owner = owner;
195 this.is_visible = false;
196 this.Size = this.DefaultSize;
199 #endregion // Public Constructors
201 #region Public Instance Properties
203 // dates to make bold on calendar annually (recurring)
204 [Localizable (true)]
205 public DateTime[] AnnuallyBoldedDates {
206 set {
207 if (annually_bolded_dates == null || annually_bolded_dates != value) {
208 annually_bolded_dates = value;
209 this.UpdateBoldedDates ();
210 this.Invalidate ();
213 get {
214 return annually_bolded_dates;
218 [Browsable(false)]
219 [EditorBrowsable(EditorBrowsableState.Never)]
220 public override Image BackgroundImage {
221 get {
222 return base.BackgroundImage;
224 set {
225 base.BackgroundImage = value;
230 // the back color for the main part of the calendar
231 public override Color BackColor {
232 set {
233 if (back_color != value) {
234 back_color = value;
235 this.OnBackColorChanged (EventArgs.Empty);
236 this.Invalidate ();
239 get {
240 return back_color;
244 // specific dates to make bold on calendar (non-recurring)
245 [Localizable (true)]
246 public DateTime[] BoldedDates {
247 set {
248 if (bolded_dates == null || bolded_dates != value) {
249 bolded_dates = value;
250 this.UpdateBoldedDates ();
251 this.Invalidate ();
254 get {
255 return bolded_dates;
259 // the configuration of the monthly grid display - only allowed to display at most,
260 // 1 calendar year at a time, will be trimmed to fit it properly
261 [Localizable (true)]
262 public Size CalendarDimensions {
263 set {
264 if (value.Width < 0 || value.Height < 0) {
265 throw new ArgumentException ();
267 if (calendar_dimensions != value) {
268 // squeeze the grid into 1 calendar year
269 if (value.Width * value.Height > 12) {
270 // iteratively reduce the largest dimension till our
271 // product is less than 12
272 if (value.Width > 12 && value.Height > 12) {
273 calendar_dimensions = new Size (4, 3);
274 } else if (value.Width > 12) {
275 for (int i = 12; i > 0; i--) {
276 if (i * value.Height <= 12) {
277 calendar_dimensions = new Size (i, value.Height);
278 break;
281 } else if (value.Height > 12) {
282 for (int i = 12; i > 0; i--) {
283 if (i * value.Width <= 12) {
284 calendar_dimensions = new Size (value.Width, i);
285 break;
289 } else {
290 calendar_dimensions = value;
292 this.Invalidate ();
295 get {
296 return calendar_dimensions;
300 // the first day of the week to display
301 [Localizable (true)]
302 [DefaultValue (Day.Default)]
303 public Day FirstDayOfWeek {
304 set {
305 if (first_day_of_week != value) {
306 first_day_of_week = value;
307 this.Invalidate ();
310 get {
311 return first_day_of_week;
315 // the fore color for the main part of the calendar
316 public override Color ForeColor {
317 set {
318 if (fore_color != value) {
319 fore_color = value;
320 this.OnForeColorChanged (EventArgs.Empty);
321 this.Invalidate ();
324 get {
325 return fore_color;
329 [Browsable(false)]
330 [EditorBrowsable(EditorBrowsableState.Never)]
331 public new ImeMode ImeMode {
332 get { return base.ImeMode; }
333 set { base.ImeMode = value; }
336 // the maximum date allowed to be selected on this month calendar
337 public DateTime MaxDate {
338 set {
339 if (value < MinDate) {
340 throw new ArgumentException();
343 if (max_date != value) {
344 max_date = value;
347 get {
348 return max_date;
352 // the maximum number of selectable days
353 [DefaultValue (7)]
354 public int MaxSelectionCount {
355 set {
356 if (value < 0) {
357 throw new ArgumentException();
360 // can't set selectioncount less than already selected dates
361 if ((SelectionEnd - SelectionStart).Days > value) {
362 throw new ArgumentException();
365 if (max_selection_count != value) {
366 max_selection_count = value;
369 get {
370 return max_selection_count;
374 // the minimum date allowed to be selected on this month calendar
375 public DateTime MinDate {
376 set {
377 if (value < new DateTime (1953, 1, 1)) {
378 throw new ArgumentException();
381 if (value > MaxDate) {
382 throw new ArgumentException();
385 if (max_date != value) {
386 min_date = value;
389 get {
390 return min_date;
394 // dates to make bold on calendar monthly (recurring)
395 [Localizable (true)]
396 public DateTime[] MonthlyBoldedDates {
397 set {
398 if (monthly_bolded_dates == null || monthly_bolded_dates != value) {
399 monthly_bolded_dates = value;
400 this.UpdateBoldedDates ();
401 this.Invalidate ();
404 get {
405 return monthly_bolded_dates;
409 // the ammount by which to scroll this calendar by
410 [DefaultValue (0)]
411 public int ScrollChange {
412 set {
413 if (value < 0 || value > 20000) {
414 throw new ArgumentException();
417 if (scroll_change != value) {
418 scroll_change = value;
421 get {
422 // if zero it to the default -> the total number of months currently visible
423 if (scroll_change == 0) {
424 return CalendarDimensions.Width * CalendarDimensions.Height;
426 return scroll_change;
431 // the last selected date
432 [Browsable (false)]
433 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
434 public DateTime SelectionEnd {
435 set {
436 if (value < MinDate || value > MaxDate) {
437 throw new ArgumentException();
440 if (SelectionRange.End != value) {
441 DateTime old_end = SelectionRange.End;
442 // make sure the end obeys the max selection range count
443 if (value < SelectionRange.Start) {
444 SelectionRange.Start = value;
446 if (value.AddDays((MaxSelectionCount-1)*-1) > SelectionRange.Start) {
447 SelectionRange.Start = value.AddDays((MaxSelectionCount-1)*-1);
449 SelectionRange.End = value;
450 this.InvalidateDateRange (new SelectionRange (old_end, SelectionRange.End));
451 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
454 get {
455 return SelectionRange.End;
459 // the range of selected dates
460 public SelectionRange SelectionRange {
461 set {
462 if (selection_range != value) {
463 SelectionRange old_range = selection_range;
465 // make sure the end obeys the max selection range count
466 if (value.End.AddDays((MaxSelectionCount-1)*-1) > value.Start) {
467 selection_range = new SelectionRange (value.End.AddDays((MaxSelectionCount-1)*-1), value.End);
468 } else {
469 selection_range = value;
471 SelectionRange visible_range = this.GetDisplayRange(true);
472 if(visible_range.Start > selection_range.End) {
473 this.current_month = new DateTime (selection_range.Start.Year, selection_range.Start.Month, 1);
474 this.Invalidate ();
475 } else if (visible_range.End < selection_range.Start) {
476 int year_diff = selection_range.End.Year - visible_range.End.Year;
477 int month_diff = selection_range.End.Month - visible_range.End.Month;
478 this.current_month = current_month.AddMonths(year_diff * 12 + month_diff);
479 this.Invalidate ();
481 // invalidate the selected range changes
482 DateTime diff_start = old_range.Start;
483 DateTime diff_end = old_range.End;
484 // now decide which region is greated
485 if (old_range.Start > SelectionRange.Start) {
486 diff_start = SelectionRange.Start;
487 } else if (old_range.Start == SelectionRange.Start) {
488 if (old_range.End < SelectionRange.End) {
489 diff_start = old_range.End;
490 } else {
491 diff_start = SelectionRange.End;
494 if (old_range.End < SelectionRange.End) {
495 diff_end = SelectionRange.End;
496 } else if (old_range.End == SelectionRange.End) {
497 if (old_range.Start < SelectionRange.Start) {
498 diff_end = SelectionRange.Start;
499 } else {
500 diff_end = old_range.Start;
505 // invalidate the region required
506 SelectionRange new_range = new SelectionRange (diff_start, diff_end);
507 if (new_range.End != old_range.End || new_range.Start != old_range.Start)
508 this.InvalidateDateRange (new_range);
509 // raise date changed event
510 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
513 get {
514 return selection_range;
518 // the first selected date
519 [Browsable (false)]
520 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
521 public DateTime SelectionStart {
522 set {
523 if (value < MinDate || value > MaxDate) {
524 throw new ArgumentException();
527 if (SelectionRange.Start != value) {
528 DateTime old_start = SelectionRange.Start;
529 // make sure the end obeys the max selection range count
530 if (value > SelectionRange.End) {
531 SelectionRange.End = value;
532 } else if (value.AddDays(MaxSelectionCount-1) < SelectionRange.End) {
533 SelectionRange.End = value.AddDays(MaxSelectionCount-1);
535 SelectionRange.Start = value;
536 DateTime new_month = new DateTime(value.Year, value.Month, 1);
537 if (current_month != new_month) {
538 current_month = new_month;
539 this.Invalidate ();
540 } else {
541 this.InvalidateDateRange (new SelectionRange (old_start, SelectionRange.Start));
543 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
546 get {
547 return selection_range.Start;
551 // whether or not to show todays date
552 [DefaultValue (true)]
553 public bool ShowToday {
554 set {
555 if (show_today != value) {
556 show_today = value;
557 this.Invalidate ();
560 get {
561 return show_today;
565 // whether or not to show a circle around todays date
566 [DefaultValue (true)]
567 public bool ShowTodayCircle {
568 set {
569 if (show_today_circle != value) {
570 show_today_circle = value;
571 this.Invalidate ();
574 get {
575 return show_today_circle;
579 // whether or not to show numbers beside each row of weeks
580 [Localizable (true)]
581 [DefaultValue (false)]
582 public bool ShowWeekNumbers {
583 set {
584 if (show_week_numbers != value) {
585 show_week_numbers = value;
586 this.Invalidate ();
589 get {
590 return show_week_numbers;
594 // the rectangle size required to render one month based on current font
595 [Browsable (false)]
596 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
597 public Size SingleMonthSize {
598 get {
599 if (this.Font == null) {
600 throw new InvalidOperationException();
603 // multiplier is sucked out from the font size
604 int multiplier = this.Font.Height;
606 // establis how many columns and rows we have
607 int column_count = (ShowWeekNumbers) ? 8 : 7;
608 int row_count = 7; // not including the today date
610 // set the date_cell_size and the title_size
611 date_cell_size = new Size ((int) Math.Ceiling (1.8 * multiplier), multiplier);
612 title_size = new Size ((date_cell_size.Width * column_count), 2 * multiplier);
614 return new Size (column_count * date_cell_size.Width, row_count * date_cell_size.Height + title_size.Height);
618 [Bindable(false)]
619 [Browsable(false)]
620 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
621 [EditorBrowsable(EditorBrowsableState.Never)]
622 public override string Text {
623 get {
624 return base.Text;
626 set {
627 base.Text = value;
631 // the back color for the title of the calendar and the
632 // forecolor for the day of the week text
633 public Color TitleBackColor {
634 set {
635 if (title_back_color != value) {
636 title_back_color = value;
637 this.Invalidate ();
640 get {
641 return title_back_color;
645 // the fore color for the title of the calendar
646 public Color TitleForeColor {
647 set {
648 if (title_fore_color != value) {
649 title_fore_color = value;
650 this.Invalidate ();
653 get {
654 return title_fore_color;
658 // the date this calendar is using to refer to today's date
659 public DateTime TodayDate {
660 set {
661 today_date_set = true;
662 if (today_date != value) {
663 today_date = value;
664 this.Invalidate ();
667 get {
668 return today_date;
672 // tells if user specifically set today_date for this control
673 [Browsable (false)]
674 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
675 public bool TodayDateSet {
676 get {
677 return today_date_set;
681 // the color used for trailing dates in the calendar
682 public Color TrailingForeColor {
683 set {
684 if (trailing_fore_color != value) {
685 trailing_fore_color = value;
686 SelectionRange bounds = this.GetDisplayRange (false);
687 SelectionRange visible_bounds = this.GetDisplayRange (true);
688 this.InvalidateDateRange (new SelectionRange (bounds.Start, visible_bounds.Start));
689 this.InvalidateDateRange (new SelectionRange (bounds.End, visible_bounds.End));
692 get {
693 return trailing_fore_color;
697 #endregion // Public Instance Properties
699 #region Protected Instance Properties
701 // overloaded to allow controll to be windowed for drop down
702 protected override CreateParams CreateParams {
703 get {
704 if (this.owner == null) {
705 return base.CreateParams;
706 } else {
707 CreateParams cp = base.CreateParams;
708 cp.Style ^= (int) WindowStyles.WS_CHILD;
709 cp.Style |= (int) WindowStyles.WS_POPUP;
710 cp.ExStyle |= (int)(WindowExStyles.WS_EX_TOOLWINDOW | WindowExStyles.WS_EX_TOPMOST);
712 return cp;
717 // not sure what to put in here - just doing a base() call - jba
718 protected override ImeMode DefaultImeMode {
719 get {
720 return base.DefaultImeMode;
724 protected override Size DefaultSize {
725 get {
726 Size single_month = SingleMonthSize;
727 // get the width
728 int width = calendar_dimensions.Width * single_month.Width;
729 if (calendar_dimensions.Width > 1) {
730 width += (calendar_dimensions.Width - 1) * calendar_spacing.Width;
733 // get the height
734 int height = calendar_dimensions.Height * single_month.Height;
735 if (this.ShowToday) {
736 height += date_cell_size.Height + 2; // add the height of the "Today: " ...
738 if (calendar_dimensions.Height > 1) {
739 height += (calendar_dimensions.Height - 1) * calendar_spacing.Height;
742 // add the 1 pixel boundary
743 if (width > 0) {
744 width += 2;
746 if (height > 0) {
747 height +=2;
750 return new Size (width, height);
754 #endregion // Protected Instance Properties
756 #region Public Instance Methods
758 // add a date to the anually bolded date arraylist
759 public void AddAnnuallyBoldedDate (DateTime date) {
760 added_annually_bolded_dates.Add (date.Date);
763 // add a date to the normal bolded date arraylist
764 public void AddBoldedDate (DateTime date) {
765 added_bolded_dates.Add (date.Date);
768 // add a date to the anually monthly date arraylist
769 public void AddMonthlyBoldedDate (DateTime date) {
770 added_monthly_bolded_dates.Add (date.Date);
773 // if visible = true, return only the dates of full months, else return all dates visible
774 public SelectionRange GetDisplayRange (bool visible) {
775 DateTime start;
776 DateTime end;
777 start = new DateTime (current_month.Year, current_month.Month, 1);
778 end = start.AddMonths (calendar_dimensions.Width * calendar_dimensions.Height);
779 end = end.AddDays(-1);
781 // process all visible dates if needed (including the grayed out dates
782 if (!visible) {
783 start = GetFirstDateInMonthGrid (start);
784 end = GetLastDateInMonthGrid (end);
787 return new SelectionRange (start, end);
790 // HitTest overload that recieve's x and y co-ordinates as separate ints
791 public HitTestInfo HitTest (int x, int y) {
792 return HitTest (new Point (x, y));
795 // returns a HitTestInfo for MonthCalendar element's under the specified point
796 public HitTestInfo HitTest (Point point) {
797 return HitTest (point, out last_clicked_calendar_index, out last_clicked_calendar_rect);
800 // clears all the annually bolded dates
801 public void RemoveAllAnnuallyBoldedDates () {
802 annually_bolded_dates = null;
803 added_annually_bolded_dates.Clear ();
804 removed_annually_bolded_dates.Clear ();
807 // clears all the normal bolded dates
808 public void RemoveAllBoldedDates () {
809 bolded_dates = null;
810 added_bolded_dates.Clear ();
811 removed_bolded_dates.Clear ();
814 // clears all the monthly bolded dates
815 public void RemoveAllMonthlyBoldedDates () {
816 monthly_bolded_dates = null;
817 added_monthly_bolded_dates.Clear ();
818 removed_monthly_bolded_dates.Clear ();
821 // clears the specified annually bolded date (only compares day and month)
822 // only removes the first instance of the match
823 public void RemoveAnnuallyBoldedDate (DateTime date) {
824 if (!removed_annually_bolded_dates.Contains (date.Date)) {
825 removed_annually_bolded_dates.Add (date.Date);
829 // clears all the normal bolded date
830 // only removes the first instance of the match
831 public void RemoveBoldedDate (DateTime date) {
832 if (!removed_bolded_dates.Contains (date.Date)) {
833 removed_bolded_dates.Add (date.Date);
837 // clears the specified monthly bolded date (only compares day and month)
838 // only removes the first instance of the match
839 public void RemoveMonthlyBoldedDate (DateTime date) {
840 if (!removed_monthly_bolded_dates.Contains (date.Date)) {
841 removed_monthly_bolded_dates.Add (date.Date);
845 // sets the calendar_dimensions. If product is > 12, the larger dimension is reduced to make product < 12
846 public void SetCalendarDimensions(int x, int y) {
847 this.CalendarDimensions = new Size(x, y);
850 // sets the currently selected date as date
851 public void SetDate (DateTime date) {
852 this.SetSelectionRange (date.Date, date.Date);
855 // utility method set the SelectionRange property using individual dates
856 public void SetSelectionRange (DateTime date1, DateTime date2) {
857 this.SelectionRange = new SelectionRange(date1, date2);
860 public override string ToString () {
861 return this.GetType().Name + ", " + this.SelectionRange.ToString ();
864 // usually called after an AddBoldedDate method is called
865 // formats monthly and daily bolded dates according to the current calendar year
866 public void UpdateBoldedDates () {
867 UpdateDateArray (ref bolded_dates, added_bolded_dates, removed_bolded_dates);
868 UpdateDateArray (ref monthly_bolded_dates, added_monthly_bolded_dates, removed_monthly_bolded_dates);
869 UpdateDateArray (ref annually_bolded_dates, added_annually_bolded_dates, removed_annually_bolded_dates);
872 #endregion // Public Instance Methods
874 #region Protected Instance Methods
876 // not sure why this needs to be overriden
877 protected override void CreateHandle () {
878 base.CreateHandle ();
881 private void CreateYearUpDown ()
883 year_updown = new NumericUpDown ();
884 year_updown.Font = this.Font;
885 year_updown.Minimum = MinDate.Year;
886 year_updown.Maximum = MaxDate.Year;
887 year_updown.ReadOnly = true;
888 year_updown.Visible = false;
889 this.Controls.AddImplicit (year_updown);
890 year_updown.ValueChanged += new EventHandler(UpDownYearChangedHandler);
891 year_updown.GotFocus += new EventHandler(UpDownYearGotFocusHandler);
892 year_updown.LostFocus += new EventHandler(UpDownYearLostFocusHandler);
895 // not sure why this needs to be overriden
896 protected override void Dispose (bool disposing) {
897 base.Dispose (disposing);
900 // Handle arrow keys
901 protected override bool IsInputKey (Keys keyData) {
902 switch (keyData) {
903 case Keys.Up:
904 case Keys.Down:
905 case Keys.Right:
906 case Keys.Left:
907 return true;
908 default:
909 break;
912 return base.IsInputKey (keyData);
915 // not sure why this needs to be overriden
916 protected override void OnBackColorChanged (EventArgs e) {
917 base.OnBackColorChanged (e);
918 this.Invalidate ();
921 // raises the date changed event
922 protected virtual void OnDateChanged (DateRangeEventArgs drevent) {
923 EventHandler eh = (EventHandler)(Events [DateChangedEvent]);
924 if (eh != null)
925 eh (this, drevent);
928 // raises the DateSelected event
929 protected virtual void OnDateSelected (DateRangeEventArgs drevent) {
930 EventHandler eh = (EventHandler)(Events [DateSelectedEvent]);
931 if (eh != null)
932 eh (this, drevent);
935 protected override void OnFontChanged (EventArgs e) {
936 // Update size based on new font's space requirements
937 Size = new Size (CalendarDimensions.Width * SingleMonthSize.Width,
938 CalendarDimensions.Height * SingleMonthSize.Height);
939 bold_font = new Font (Font, Font.Style | FontStyle.Bold);
940 base.OnFontChanged (e);
943 protected override void OnForeColorChanged (EventArgs e) {
944 base.OnForeColorChanged (e);
947 protected override void OnHandleCreated (EventArgs e) {
948 base.OnHandleCreated (e);
949 CreateYearUpDown ();
952 // i think this is overriden to not allow the control to be changed to an arbitrary size
953 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) {
954 if ((specified & BoundsSpecified.Height) == BoundsSpecified.Height ||
955 (specified & BoundsSpecified.Width) == BoundsSpecified.Width ||
956 (specified & BoundsSpecified.Size) == BoundsSpecified.Size) {
957 // only allow sizes = default size to be set
958 Size min_size = DefaultSize;
959 Size max_size = new Size (
960 DefaultSize.Width + SingleMonthSize.Width + calendar_spacing.Width,
961 DefaultSize.Height + SingleMonthSize.Height + calendar_spacing.Height);
962 int x_mid_point = (max_size.Width + min_size.Width)/2;
963 int y_mid_point = (max_size.Height + min_size.Height)/2;
964 if (width < x_mid_point) {
965 width = min_size.Width;
966 } else {
967 width = max_size.Width;
969 if (height < y_mid_point) {
970 height = min_size.Height;
971 } else {
972 height = max_size.Height;
974 base.SetBoundsCore (x, y, width, height, specified);
975 } else {
976 base.SetBoundsCore (x, y, width, height, specified);
980 protected override void WndProc (ref Message m) {
981 base.WndProc (ref m);
984 #endregion // Protected Instance Methods
986 #region public events
987 static object DateChangedEvent = new object ();
988 static object DateSelectedEvent = new object ();
990 // fired when the date is changed (either explicitely or implicitely)
991 // when navigating the month selector
992 public event DateRangeEventHandler DateChanged {
993 add { Events.AddHandler (DateChangedEvent, value); }
994 remove { Events.RemoveHandler (DateChangedEvent, value); }
997 // fired when the user explicitely clicks on date to select it
998 public event DateRangeEventHandler DateSelected {
999 add { Events.AddHandler (DateSelectedEvent, value); }
1000 remove { Events.RemoveHandler (DateSelectedEvent, value); }
1003 [Browsable(false)]
1004 [EditorBrowsable (EditorBrowsableState.Never)]
1005 public new event EventHandler BackgroundImageChanged {
1006 add { base.BackgroundImageChanged += value; }
1007 remove { base.BackgroundImageChanged -= value; }
1010 // this event is overridden to supress it from being fired
1011 // XXX check this out
1012 [Browsable(false)]
1013 [EditorBrowsable (EditorBrowsableState.Never)]
1014 public new event EventHandler Click;
1016 // this event is overridden to supress it from being fired
1017 // XXX check this out
1018 [Browsable(false)]
1019 [EditorBrowsable (EditorBrowsableState.Never)]
1020 public new event EventHandler DoubleClick;
1022 [Browsable(false)]
1023 [EditorBrowsable (EditorBrowsableState.Never)]
1024 public new event EventHandler ImeModeChanged {
1025 add { base.ImeModeChanged += value; }
1026 remove { base.ImeModeChanged -= value; }
1029 // XXX check this out
1030 [Browsable(false)]
1031 [EditorBrowsable (EditorBrowsableState.Never)]
1032 public new event PaintEventHandler Paint;
1034 [Browsable(false)]
1035 [EditorBrowsable (EditorBrowsableState.Never)]
1036 public new event EventHandler TextChanged {
1037 add { base.TextChanged += value; }
1038 remove { base.TextChanged -= value; }
1040 #endregion // public events
1042 #region internal properties
1044 internal DateTime CurrentMonth {
1045 set {
1046 // only interested in if the month (not actual date) has changed
1047 if (value.Month != current_month.Month ||
1048 value.Year != current_month.Year) {
1049 this.SelectionRange = new SelectionRange(
1050 this.SelectionStart.Add(value.Subtract(current_month)),
1051 this.SelectionEnd.Add(value.Subtract(current_month)));
1052 current_month = value;
1053 UpdateBoldedDates();
1054 this.Invalidate();
1057 get {
1058 return current_month;
1062 #endregion // internal properties
1064 #region internal/private methods
1065 internal HitTestInfo HitTest (
1066 Point point,
1067 out int calendar_index,
1068 out Rectangle calendar_rect) {
1069 // start by initialising the ref parameters
1070 calendar_index = -1;
1071 calendar_rect = Rectangle.Empty;
1073 // before doing all the hard work, see if the today's date wasn't clicked
1074 Rectangle today_rect = new Rectangle (
1075 ClientRectangle.X,
1076 ClientRectangle.Bottom - date_cell_size.Height,
1077 7 * date_cell_size.Width,
1078 date_cell_size.Height);
1079 if (today_rect.Contains (point) && this.ShowToday) {
1080 return new HitTestInfo(HitArea.TodayLink, point, DateTime.Now);
1083 Size month_size = SingleMonthSize;
1084 // define calendar rect's that this thing can land in
1085 Rectangle[] calendars = new Rectangle [CalendarDimensions.Width * CalendarDimensions.Height];
1086 for (int i=0; i < CalendarDimensions.Width * CalendarDimensions.Height; i ++) {
1087 if (i == 0) {
1088 calendars[i] = new Rectangle (
1089 new Point (ClientRectangle.X + 1, ClientRectangle.Y + 1),
1090 month_size);
1091 } else {
1092 // calendar on the next row
1093 if (i % CalendarDimensions.Width == 0) {
1094 calendars[i] = new Rectangle (
1095 new Point (calendars[i-CalendarDimensions.Width].X, calendars[i-CalendarDimensions.Width].Bottom + calendar_spacing.Height),
1096 month_size);
1097 } else {
1098 // calendar on the next column
1099 calendars[i] = new Rectangle (
1100 new Point (calendars[i-1].Right + calendar_spacing.Width, calendars[i-1].Y),
1101 month_size);
1106 // through each trying to find a match
1107 for (int i = 0; i < calendars.Length ; i++) {
1108 if (calendars[i].Contains (point)) {
1109 // check the title section
1110 Rectangle title_rect = new Rectangle (
1111 calendars[i].Location,
1112 title_size);
1113 if (title_rect.Contains (point) ) {
1114 // make sure it's not a previous button
1115 if (i == 0) {
1116 Rectangle button_rect = new Rectangle(
1117 new Point (calendars[i].X + button_x_offset, (title_size.Height - button_size.Height)/2),
1118 button_size);
1119 if (button_rect.Contains (point)) {
1120 return new HitTestInfo(HitArea.PrevMonthButton, point, DateTime.Now);
1123 // make sure it's not the next button
1124 if (i % CalendarDimensions.Height == 0 && i % CalendarDimensions.Width == calendar_dimensions.Width - 1) {
1125 Rectangle button_rect = new Rectangle(
1126 new Point (calendars[i].Right - button_x_offset - button_size.Width, (title_size.Height - button_size.Height)/2),
1127 button_size);
1128 if (button_rect.Contains (point)) {
1129 return new HitTestInfo(HitArea.NextMonthButton, point, DateTime.Now);
1133 // indicate which calendar and month it was
1134 calendar_index = i;
1135 calendar_rect = calendars[i];
1137 // make sure it's not the month or the year of the calendar
1138 if (GetMonthNameRectangle (title_rect, i).Contains (point)) {
1139 return new HitTestInfo(HitArea.TitleMonth, point, DateTime.Now);
1141 if (GetYearNameRectangle (title_rect, i).Contains (point)) {
1142 return new HitTestInfo(HitArea.TitleYear, point, DateTime.Now);
1145 // return the hit test in the title background
1146 return new HitTestInfo(HitArea.TitleBackground, point, DateTime.Now);
1149 Point date_grid_location = new Point (calendars[i].X, title_rect.Bottom);
1151 // see if it's in the Week numbers
1152 if (ShowWeekNumbers) {
1153 Rectangle weeks_rect = new Rectangle (
1154 date_grid_location,
1155 new Size (date_cell_size.Width,Math.Max (calendars[i].Height - title_rect.Height, 0)));
1156 if (weeks_rect.Contains (point)) {
1157 return new HitTestInfo(HitArea.WeekNumbers, point, DateTime.Now);
1160 // move the location of the grid over
1161 date_grid_location.X += date_cell_size.Width;
1164 // see if it's in the week names
1165 Rectangle day_rect = new Rectangle (
1166 date_grid_location,
1167 new Size (Math.Max (calendars[i].Right - date_grid_location.X, 0), date_cell_size.Height));
1168 if (day_rect.Contains (point)) {
1169 return new HitTestInfo(HitArea.DayOfWeek, point, DateTime.Now);
1172 // finally see if it was a date that was clicked
1173 Rectangle date_grid = new Rectangle (
1174 new Point (day_rect.X, day_rect.Bottom),
1175 new Size (day_rect.Width, Math.Max(calendars[i].Bottom - day_rect.Bottom, 0)));
1176 if (date_grid.Contains (point)) {
1177 clicked_rect = date_grid;
1178 // okay so it's inside the grid, get the offset
1179 Point offset = new Point (point.X - date_grid.X, point.Y - date_grid.Y);
1180 int row = offset.Y / date_cell_size.Height;
1181 int col = offset.X / date_cell_size.Width;
1182 // establish our first day of the month
1183 DateTime calendar_month = this.CurrentMonth.AddMonths(i);
1184 DateTime first_day = GetFirstDateInMonthGrid (calendar_month);
1185 DateTime time = first_day.AddDays ((row * 7) + col);
1186 // establish which date was clicked
1187 if (time.Year != calendar_month.Year || time.Month != calendar_month.Month) {
1188 if (time < calendar_month && i == 0) {
1189 return new HitTestInfo(HitArea.PrevMonthDate, point, time);
1190 } else if (time > calendar_month && i == CalendarDimensions.Width*CalendarDimensions.Height - 1) {
1191 return new HitTestInfo(HitArea.NextMonthDate, point, time);
1193 return new HitTestInfo(HitArea.Nowhere, point, time);
1195 return new HitTestInfo(HitArea.Date, point, time);
1200 return new HitTestInfo ();
1203 // returns the date of the first cell of the specified month grid
1204 internal DateTime GetFirstDateInMonthGrid (DateTime month) {
1205 // convert the first_day_of_week into a DayOfWeekEnum
1206 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1207 // find the first day of the month
1208 DateTime first_date_of_month = new DateTime (month.Year, month.Month, 1);
1209 DayOfWeek first_day_of_month = first_date_of_month.DayOfWeek;
1210 // adjust for the starting day of the week
1211 int offset = first_day_of_month - first_day;
1212 if (offset < 0) {
1213 offset += 7;
1215 return first_date_of_month.AddDays (-1*offset);
1218 // returns the date of the last cell of the specified month grid
1219 internal DateTime GetLastDateInMonthGrid (DateTime month)
1221 DateTime start = GetFirstDateInMonthGrid(month);
1222 return start.AddDays ((7 * 6)-1);
1225 internal bool IsBoldedDate (DateTime date) {
1226 // check bolded dates
1227 if (bolded_dates != null && bolded_dates.Length > 0) {
1228 foreach (DateTime bolded_date in bolded_dates) {
1229 if (bolded_date.Date == date.Date) {
1230 return true;
1234 // check monthly dates
1235 if (monthly_bolded_dates != null && monthly_bolded_dates.Length > 0) {
1236 foreach (DateTime bolded_date in monthly_bolded_dates) {
1237 if (bolded_date.Day == date.Day) {
1238 return true;
1242 // check yearly dates
1243 if (annually_bolded_dates != null && annually_bolded_dates.Length > 0) {
1244 foreach (DateTime bolded_date in annually_bolded_dates) {
1245 if (bolded_date.Month == date.Month && bolded_date.Day == date.Day) {
1246 return true;
1251 return false; // no match
1254 // updates the specified bolded dates array with ones to add and ones to remove
1255 private void UpdateDateArray (ref DateTime [] dates, ArrayList to_add, ArrayList to_remove) {
1256 ArrayList list = new ArrayList ();
1258 // update normal bolded dates
1259 if (dates != null) {
1260 foreach (DateTime date in dates) {
1261 list.Add (date.Date);
1265 // add new ones
1266 foreach (DateTime date in to_add) {
1267 if (!list.Contains (date.Date)) {
1268 list.Add (date.Date);
1271 to_add.Clear ();
1273 // remove ones to remove
1274 foreach (DateTime date in to_remove) {
1275 if (list.Contains (date.Date)) {
1276 list.Remove (date.Date);
1279 to_remove.Clear ();
1280 // set up the array now
1281 if (list.Count > 0) {
1282 dates = (DateTime []) list.ToArray (typeof (DateTime));
1283 Array.Sort (dates);
1284 list.Clear ();
1285 } else {
1286 dates = null;
1290 // initialise the context menu
1291 private void SetUpContextMenu () {
1292 menu = new ContextMenu ();
1293 for (int i=0; i < 12; i++) {
1294 MenuItem menu_item = new MenuItem ( new DateTime (2000, i+1, 1).ToString ("MMMM"));
1295 menu_item.Click += new EventHandler (MenuItemClickHandler);
1296 menu.MenuItems.Add (menu_item);
1300 // initialises text value and show's year up down in correct position
1301 private void PrepareYearUpDown (Point p) {
1302 Rectangle old_location = year_updown.Bounds;
1304 // set position
1305 Rectangle title_rect = new Rectangle(
1306 last_clicked_calendar_rect.Location,
1307 title_size);
1309 year_updown.Bounds = GetYearNameRectangle(
1310 title_rect,
1311 last_clicked_calendar_index);
1312 year_updown.Top -= 4;
1313 year_updown.Width += (int) (this.Font.Size * 4);
1314 // set year - only do this if this isn't being called because of a year up down click
1315 if(year_updown.Bounds != old_location) {
1316 year_updown.Value = current_month.AddMonths(last_clicked_calendar_index).Year;
1319 if(!year_updown.Visible) {
1320 year_updown.Visible = true;
1324 // returns the first date of the month
1325 private DateTime GetFirstDateInMonth (DateTime date) {
1326 return new DateTime (date.Year, date.Month, 1);
1329 // returns the last date of the month
1330 private DateTime GetLastDateInMonth (DateTime date) {
1331 return new DateTime (date.Year, date.Month, 1).AddMonths(1).AddDays(-1);
1334 // called in response to users seletion with shift key
1335 private void AddTimeToSelection (int delta, bool isDays)
1337 DateTime cursor_point;
1338 DateTime end_point;
1339 // okay we add the period to the date that is not the same as the
1340 // start date when shift was first clicked.
1341 if (SelectionStart != first_select_start_date) {
1342 cursor_point = SelectionStart;
1343 } else {
1344 cursor_point = SelectionEnd;
1346 // add the days
1347 if (isDays) {
1348 end_point = cursor_point.AddDays (delta);
1349 } else {
1350 // delta must be months
1351 end_point = cursor_point.AddMonths (delta);
1353 // set the new selection range
1354 SelectionRange range = new SelectionRange (first_select_start_date, end_point);
1355 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1356 // okay the date is beyond what is allowed, lets set the maximum we can
1357 if (range.Start != first_select_start_date) {
1358 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1359 } else {
1360 range.End = range.Start.AddDays (MaxSelectionCount-1);
1363 this.SelectionRange = range;
1366 // attempts to add the date to the selection without throwing exception
1367 private void SelectDate (DateTime date) {
1368 // try and add the new date to the selction range
1369 if (is_shift_pressed || (click_state [0])) {
1370 SelectionRange range = new SelectionRange (first_select_start_date, date);
1371 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1372 // okay the date is beyond what is allowed, lets set the maximum we can
1373 if (range.Start != first_select_start_date) {
1374 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1375 } else {
1376 range.End = range.Start.AddDays (MaxSelectionCount-1);
1379 SelectionRange = range;
1380 } else {
1381 SelectionRange = new SelectionRange (date, date);
1382 first_select_start_date = date;
1386 // gets the week of the year
1387 internal int GetWeekOfYear (DateTime date) {
1388 // convert the first_day_of_week into a DayOfWeekEnum
1389 DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1390 // find the first day of the year
1391 DayOfWeek first_day_of_year = new DateTime (date.Year, 1, 1).DayOfWeek;
1392 // adjust for the starting day of the week
1393 int offset = first_day_of_year - first_day;
1394 int week = ((date.DayOfYear + offset) / 7) + 1;
1395 return week;
1398 // convert a Day enum into a DayOfWeek enum
1399 internal DayOfWeek GetDayOfWeek (Day day) {
1400 if (day == Day.Default) {
1401 return Threading.Thread.CurrentThread.CurrentUICulture.DateTimeFormat.FirstDayOfWeek;
1402 } else {
1403 return (DayOfWeek) DayOfWeek.Parse (typeof (DayOfWeek), day.ToString ());
1407 // returns the rectangle for themonth name
1408 internal Rectangle GetMonthNameRectangle (Rectangle title_rect, int calendar_index) {
1409 Graphics g = this.DeviceContext;
1410 DateTime this_month = this.current_month.AddMonths (calendar_index);
1411 Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1412 Size month_size = g.MeasureString (this_month.ToString ("MMMM"), this.Font).ToSize ();
1413 // return only the month name part of that
1414 return new Rectangle (
1415 new Point (
1416 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1417 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1418 month_size);
1421 // returns the rectangle for the year in the title
1422 internal Rectangle GetYearNameRectangle (Rectangle title_rect, int calendar_index) {
1423 Graphics g = this.DeviceContext;
1424 DateTime this_month = this.current_month.AddMonths (calendar_index);
1425 Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1426 Size year_size = g.MeasureString (this_month.ToString ("yyyy"), this.Font).ToSize ();
1427 // find out how much space the title took
1428 Rectangle text_rect = new Rectangle (
1429 new Point (
1430 title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1431 title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1432 title_text_size);
1433 // return only the rect of the year
1434 return new Rectangle (
1435 new Point (
1436 text_rect.Right - year_size.Width,
1437 text_rect.Y),
1438 year_size);
1441 // determine if date is allowed to be drawn in month
1442 internal bool IsValidWeekToDraw (DateTime month, DateTime date, int row, int col) {
1443 DateTime tocheck = month.AddMonths (-1);
1444 if ((month.Year == date.Year && month.Month == date.Month) ||
1445 (tocheck.Year == date.Year && tocheck.Month == date.Month)) {
1446 return true;
1449 // check the railing dates (days in the month after the last month in grid)
1450 if (row == CalendarDimensions.Height - 1 && col == CalendarDimensions.Width - 1) {
1451 tocheck = month.AddMonths (1);
1452 return (tocheck.Year == date.Year && tocheck.Month == date.Month) ;
1455 return false;
1458 // set one item clicked and all others off
1459 private void SetItemClick(HitTestInfo hti)
1461 switch(hti.HitArea) {
1462 case HitArea.NextMonthButton:
1463 this.is_previous_clicked = false;
1464 this.is_next_clicked = true;
1465 this.is_date_clicked = false;
1466 break;
1467 case HitArea.PrevMonthButton:
1468 this.is_previous_clicked = true;
1469 this.is_next_clicked = false;
1470 this.is_date_clicked = false;
1471 break;
1472 case HitArea.PrevMonthDate:
1473 case HitArea.NextMonthDate:
1474 case HitArea.Date:
1475 this.clicked_date = hti.Time;
1476 this.is_previous_clicked = false;
1477 this.is_next_clicked = false;
1478 this.is_date_clicked = true;
1479 break;
1480 default :
1481 this.is_previous_clicked = false;
1482 this.is_next_clicked = false;
1483 this.is_date_clicked = false;
1484 break;
1488 // called when the year is changed
1489 private void UpDownYearChangedHandler (object sender, EventArgs e) {
1490 int initial_year_value = this.CurrentMonth.AddMonths(last_clicked_calendar_index).Year;
1491 int diff = (int) year_updown.Value - initial_year_value;
1492 this.CurrentMonth = this.CurrentMonth.AddYears(diff);
1495 // called when context menu is clicked
1496 private void MenuItemClickHandler (object sender, EventArgs e) {
1497 MenuItem item = sender as MenuItem;
1498 if (item != null && month_title_click_location != Point.Empty) {
1499 // establish which month we want to move to
1500 if (item.Parent == null) {
1501 return;
1503 int new_month = item.Parent.MenuItems.IndexOf (item) + 1;
1504 if (new_month == 0) {
1505 return;
1507 // okay let's establish which calendar was hit
1508 Size month_size = this.SingleMonthSize;
1509 for (int i=0; i < CalendarDimensions.Height; i++) {
1510 for (int j=0; j < CalendarDimensions.Width; j++) {
1511 int month_index = (i * CalendarDimensions.Width) + j;
1512 Rectangle month_rect = new Rectangle ( new Point (0, 0), month_size);
1513 if (j == 0) {
1514 month_rect.X = this.ClientRectangle.X + 1;
1515 } else {
1516 month_rect.X = this.ClientRectangle.X + 1 + ((j)*(month_size.Width+calendar_spacing.Width));
1518 if (i == 0) {
1519 month_rect.Y = this.ClientRectangle.Y + 1;
1520 } else {
1521 month_rect.Y = this.ClientRectangle.Y + 1 + ((i)*(month_size.Height+calendar_spacing.Height));
1523 // see if the point is inside
1524 if (month_rect.Contains (month_title_click_location)) {
1525 DateTime clicked_month = CurrentMonth.AddMonths (month_index);
1526 // get the month that we want to move to
1527 int month_offset = new_month - clicked_month.Month;
1529 // move forward however more months we need to
1530 this.CurrentMonth = this.CurrentMonth.AddMonths (month_offset);
1531 break;
1536 // clear the point
1537 month_title_click_location = Point.Empty;
1541 // raised on the timer, for mouse hold clicks
1542 private void TimerHandler (object sender, EventArgs e) {
1543 // now find out which area was click
1544 if (this.Capture) {
1545 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1546 // see if it was clicked on the prev or next mouse
1547 if (click_state [1] || click_state [2]) {
1548 // invalidate the area where the mouse was last held
1549 DoMouseUp ();
1550 // register the click
1551 if (hti.HitArea == HitArea.PrevMonthButton ||
1552 hti.HitArea == HitArea.NextMonthButton) {
1553 DoButtonMouseDown (hti);
1554 click_state [1] = (hti.HitArea == HitArea.PrevMonthButton);
1555 click_state [2] = !click_state [1];
1557 if (timer.Interval != 300) {
1558 timer.Interval = 300;
1561 } else {
1562 timer.Enabled = false;
1566 // selects one of the buttons
1567 private void DoButtonMouseDown (HitTestInfo hti) {
1568 // show the click then move on
1569 SetItemClick(hti);
1570 if (hti.HitArea == HitArea.PrevMonthButton) {
1571 // invalidate the prev monthbutton
1572 this.Invalidate(
1573 new Rectangle (
1574 this.ClientRectangle.X + 1 + button_x_offset,
1575 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1576 button_size.Width,
1577 button_size.Height));
1578 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange*-1);
1579 } else {
1580 // invalidate the next monthbutton
1581 this.Invalidate(
1582 new Rectangle (
1583 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1584 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1585 button_size.Width,
1586 button_size.Height));
1587 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange);
1591 // selects the clicked date
1592 private void DoDateMouseDown (HitTestInfo hti) {
1593 SetItemClick(hti);
1596 // event run on the mouse up event
1597 private void DoMouseUp () {
1599 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1600 switch (hti.HitArea) {
1601 case HitArea.PrevMonthDate:
1602 case HitArea.NextMonthDate:
1603 case HitArea.Date:
1604 this.SelectDate (clicked_date);
1605 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1606 break;
1609 // invalidate the next monthbutton
1610 if (this.is_next_clicked) {
1611 this.Invalidate(
1612 new Rectangle (
1613 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1614 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1615 button_size.Width,
1616 button_size.Height));
1618 // invalidate the prev monthbutton
1619 if (this.is_previous_clicked) {
1620 this.Invalidate(
1621 new Rectangle (
1622 this.ClientRectangle.X + 1 + button_x_offset,
1623 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1624 button_size.Width,
1625 button_size.Height));
1627 if (this.is_date_clicked) {
1628 // invalidate the area under the cursor, to remove focus rect
1629 this.InvalidateDateRange (new SelectionRange (clicked_date, clicked_date));
1631 this.is_previous_clicked = false;
1632 this.is_next_clicked = false;
1633 this.is_date_clicked = false;
1636 // need when in windowed mode
1637 private void LostFocusHandler(object sender, EventArgs e)
1639 StartHideTimer ();
1642 // needed when in windowed mode to close the calendar if no
1643 // part of it has focus.
1644 private void UpDownTimerTick(object sender, EventArgs e)
1646 HideMonthCalendarIfWindowed ();
1647 if (updown_timer != null)
1649 updown_timer.Dispose();
1650 updown_timer = null;
1654 // Needed when in windowed mode.
1655 private void UpDownYearLostFocusHandler(object sender, EventArgs e)
1657 updown_has_focus = false;
1658 StartHideTimer ();
1661 // Needed when in windowed mode.
1662 private void UpDownYearGotFocusHandler(object sender, EventArgs e)
1664 updown_has_focus = true;
1667 // Needed when in windowed mode.
1668 private void StartHideTimer()
1670 if (updown_timer == null) {
1671 updown_timer = new Timer ();
1672 updown_timer.Interval = 50;
1673 updown_timer.Tick += new EventHandler (UpDownTimerTick);
1674 updown_timer.Enabled = true;
1678 // needed when in windowed mode.
1679 private void HideMonthCalendarIfWindowed()
1681 if (this.owner != null && this.Visible) {
1682 if (updown_has_focus)
1683 return;
1684 if (showing_context_menu)
1685 return;
1687 this.owner.HideMonthCalendar ();
1691 // occurs when mouse moves around control, used for selection
1692 private void MouseMoveHandler (object sender, MouseEventArgs e) {
1693 HitTestInfo hti = this.HitTest (e.X, e.Y);
1694 // clear the last clicked item
1695 if (click_state [0]) {
1696 // register the click
1697 if (hti.HitArea == HitArea.PrevMonthDate ||
1698 hti.HitArea == HitArea.NextMonthDate ||
1699 hti.HitArea == HitArea.Date)
1701 Rectangle prev_rect = clicked_rect;
1702 DateTime prev_clicked = clicked_date;
1703 DoDateMouseDown (hti);
1704 if (owner == null) {
1705 click_state [0] = true;
1706 } else {
1707 click_state [0] = false;
1708 click_state [1] = false;
1709 click_state [2] = false;
1712 if (prev_clicked != clicked_date) {
1713 Rectangle invalid = Rectangle.Union (prev_rect, clicked_rect);
1714 Invalidate (invalid);
1721 // to check if the mouse has come down on this control
1722 private void MouseDownHandler (object sender, MouseEventArgs e)
1724 // clear the click_state variables
1725 click_state [0] = false;
1726 click_state [1] = false;
1727 click_state [2] = false;
1729 // disable the timer if it was enabled
1730 if (timer.Enabled) {
1731 timer.Stop ();
1732 timer.Enabled = false;
1735 Point point = new Point (e.X, e.Y);
1736 // figure out if we are in drop down mode and a click happened outside us
1737 if (this.owner != null) {
1738 if (!this.ClientRectangle.Contains (point)) {
1739 this.owner.HideMonthCalendar ();
1740 return;
1744 //establish where was hit
1745 HitTestInfo hti = this.HitTest(point);
1746 // hide the year numeric up down if it was clicked
1747 if (year_updown != null && year_updown.Visible && hti.HitArea != HitArea.TitleYear)
1749 year_updown.Visible = false;
1751 switch (hti.HitArea) {
1752 case HitArea.PrevMonthButton:
1753 case HitArea.NextMonthButton:
1754 DoButtonMouseDown (hti);
1755 click_state [1] = (hti.HitArea == HitArea.PrevMonthDate);
1756 click_state [2] = !click_state [1];
1757 timer.Interval = 750;
1758 timer.Start ();
1759 break;
1760 case HitArea.Date:
1761 case HitArea.PrevMonthDate:
1762 case HitArea.NextMonthDate:
1763 DoDateMouseDown (hti);
1764 // leave clicked state blank if drop down window
1765 if (owner == null) {
1766 click_state [0] = true;
1767 } else {
1768 click_state [0] = false;
1769 click_state [1] = false;
1770 click_state [2] = false;
1772 break;
1773 case HitArea.TitleMonth:
1774 month_title_click_location = hti.Point;
1775 showing_context_menu = true;
1776 menu.Show (this, hti.Point);
1777 showing_context_menu = false;
1778 break;
1779 case HitArea.TitleYear:
1780 // place the numeric up down
1781 PrepareYearUpDown(hti.Point);
1782 break;
1783 case HitArea.TodayLink:
1784 this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date);
1785 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1786 break;
1787 default:
1788 this.is_previous_clicked = false;
1789 this.is_next_clicked = false;
1790 this.is_date_clicked = false;
1791 break;
1795 // raised by any key down events
1796 private void KeyDownHandler (object sender, KeyEventArgs e) {
1797 // send keys to the year_updown control, let it handle it
1798 if(year_updown.Visible) {
1799 switch (e.KeyCode) {
1800 case Keys.Enter:
1801 year_updown.Visible = false;
1802 break;
1803 case Keys.Up:
1804 year_updown.Value = year_updown.Value + 1;
1805 break;
1806 case Keys.Down:
1807 year_updown.Value = year_updown.Value - 1;
1808 break;
1810 } else {
1811 if (!is_shift_pressed && e.Shift) {
1812 first_select_start_date = SelectionStart;
1813 is_shift_pressed = e.Shift;
1815 switch (e.KeyCode) {
1816 case Keys.Home:
1817 // set the date to the start of the month
1818 if (is_shift_pressed) {
1819 DateTime date = GetFirstDateInMonth (first_select_start_date);
1820 if (date < first_select_start_date.AddDays ((MaxSelectionCount-1)*-1)) {
1821 date = first_select_start_date.AddDays ((MaxSelectionCount-1)*-1);
1823 this.SetSelectionRange (date, first_select_start_date);
1824 } else {
1825 DateTime date = GetFirstDateInMonth (this.SelectionStart);
1826 this.SetSelectionRange (date, date);
1828 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1829 break;
1830 case Keys.End:
1831 // set the date to the last of the month
1832 if (is_shift_pressed) {
1833 DateTime date = GetLastDateInMonth (first_select_start_date);
1834 if (date > first_select_start_date.AddDays (MaxSelectionCount-1)) {
1835 date = first_select_start_date.AddDays (MaxSelectionCount-1);
1837 this.SetSelectionRange (date, first_select_start_date);
1838 } else {
1839 DateTime date = GetLastDateInMonth (this.SelectionStart);
1840 this.SetSelectionRange (date, date);
1842 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1843 break;
1844 case Keys.PageUp:
1845 // set the date to the last of the month
1846 if (is_shift_pressed) {
1847 this.AddTimeToSelection (-1, false);
1848 } else {
1849 DateTime date = this.SelectionStart.AddMonths (-1);
1850 this.SetSelectionRange (date, date);
1852 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1853 break;
1854 case Keys.PageDown:
1855 // set the date to the last of the month
1856 if (is_shift_pressed) {
1857 this.AddTimeToSelection (1, false);
1858 } else {
1859 DateTime date = this.SelectionStart.AddMonths (1);
1860 this.SetSelectionRange (date, date);
1862 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1863 break;
1864 case Keys.Up:
1865 // set the back 1 week
1866 if (is_shift_pressed) {
1867 this.AddTimeToSelection (-7, true);
1868 } else {
1869 DateTime date = this.SelectionStart.AddDays (-7);
1870 this.SetSelectionRange (date, date);
1872 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1873 break;
1874 case Keys.Down:
1875 // set the date forward 1 week
1876 if (is_shift_pressed) {
1877 this.AddTimeToSelection (7, true);
1878 } else {
1879 DateTime date = this.SelectionStart.AddDays (7);
1880 this.SetSelectionRange (date, date);
1882 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1883 break;
1884 case Keys.Left:
1885 // move one left
1886 if (is_shift_pressed) {
1887 this.AddTimeToSelection (-1, true);
1888 } else {
1889 DateTime date = this.SelectionStart.AddDays (-1);
1890 this.SetSelectionRange (date, date);
1892 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1893 break;
1894 case Keys.Right:
1895 // move one left
1896 if (is_shift_pressed) {
1897 this.AddTimeToSelection (1, true);
1898 } else {
1899 DateTime date = this.SelectionStart.AddDays (1);
1900 this.SetSelectionRange (date, date);
1902 this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1903 break;
1904 default:
1905 break;
1907 e.Handled = true;
1911 // to check if the mouse has come up on this control
1912 private void MouseUpHandler (object sender, MouseEventArgs e)
1914 if (timer.Enabled) {
1915 timer.Stop ();
1917 // clear the click state array
1918 click_state [0] = false;
1919 click_state [1] = false;
1920 click_state [2] = false;
1921 // do the regulare mouseup stuff
1922 this.DoMouseUp ();
1925 // raised by any key up events
1926 private void KeyUpHandler (object sender, KeyEventArgs e) {
1927 is_shift_pressed = e.Shift ;
1928 e.Handled = true;
1931 // paint this control now
1932 private void PaintHandler (object sender, PaintEventArgs pe) {
1933 if (Width <= 0 || Height <= 0 || Visible == false)
1934 return;
1936 Draw (pe.ClipRectangle, pe.Graphics);
1938 // fire the new paint handler
1939 if (this.Paint != null)
1941 this.Paint (sender, pe);
1945 // returns the region of the control that needs to be redrawn
1946 private void InvalidateDateRange (SelectionRange range) {
1947 SelectionRange bounds = this.GetDisplayRange (false);
1949 if (range.End < bounds.Start || range.Start > bounds.End) {
1950 // don't invalidate anything, as the modified date range
1951 // is outside the visible bounds of this control
1952 return;
1954 // adjust the start and end to be inside the visible range
1955 if (range.Start < bounds.Start) {
1956 range = new SelectionRange (bounds.Start, range.End);
1958 if (range.End > bounds.End) {
1959 range = new SelectionRange (range.Start, bounds.End);
1961 // now invalidate the date rectangles as series of rows
1962 DateTime last_month = this.current_month.AddMonths ((CalendarDimensions.Width * CalendarDimensions.Height)).AddDays (-1);
1963 DateTime current = range.Start;
1964 while (current <= range.End) {
1965 DateTime month_end = new DateTime (current.Year, current.Month, 1).AddMonths (1).AddDays (-1);;
1966 Rectangle start_rect;
1967 Rectangle end_rect;
1968 // see if entire selection is in this current month
1969 if (range.End <= month_end && current < last_month) {
1970 // the end is the last date
1971 if (current < this.current_month) {
1972 start_rect = GetDateRowRect (current_month, current_month);
1973 } else {
1974 start_rect = GetDateRowRect (current, current);
1976 end_rect = GetDateRowRect (current, range.End);
1977 } else if (current < last_month) {
1978 // otherwise it simply means we have a selection spaning
1979 // multiple months simply set rectangle inside the current month
1980 start_rect = GetDateRowRect (current, current);
1981 end_rect = GetDateRowRect (month_end, month_end);
1982 } else {
1983 // it's outside the visible range
1984 start_rect = GetDateRowRect (last_month, last_month.AddDays (1));
1985 end_rect = GetDateRowRect (last_month, range.End);
1987 // push to the next month
1988 current = month_end.AddDays (1);
1989 // invalidate from the start row to the end row for this month
1990 this.Invalidate (
1991 new Rectangle (
1992 start_rect.X,
1993 start_rect.Y,
1994 start_rect.Width,
1995 Math.Max (end_rect.Bottom - start_rect.Y, 0)));
1999 // gets the rect of the row where the specified date appears on the specified month
2000 private Rectangle GetDateRowRect (DateTime month, DateTime date) {
2001 // first get the general rect of the supplied month
2002 Size month_size = SingleMonthSize;
2003 Rectangle month_rect = Rectangle.Empty;
2004 for (int i=0; i < CalendarDimensions.Width*CalendarDimensions.Height; i++) {
2005 DateTime this_month = this.current_month.AddMonths (i);
2006 if (month.Year == this_month.Year && month.Month == this_month.Month) {
2007 month_rect = new Rectangle (
2008 this.ClientRectangle.X + 1 + (month_size.Width * (i%CalendarDimensions.Width)) + (this.calendar_spacing.Width * (i%CalendarDimensions.Width)),
2009 this.ClientRectangle.Y + 1 + (month_size.Height * (i/CalendarDimensions.Width)) + (this.calendar_spacing.Height * (i/CalendarDimensions.Width)),
2010 month_size.Width,
2011 month_size.Height);
2012 break;
2015 // now find out where in the month the supplied date is
2016 if (month_rect == Rectangle.Empty) {
2017 return Rectangle.Empty;
2019 // find out which row this date is in
2020 int row = -1;
2021 DateTime first_date = GetFirstDateInMonthGrid (month);
2022 DateTime end_date = first_date.AddDays (7);
2023 for (int i=0; i < 6; i++) {
2024 if (date >= first_date && date < end_date) {
2025 row = i;
2026 break;
2028 first_date = end_date;
2029 end_date = end_date.AddDays (7);
2031 // ensure it's a valid row
2032 if (row < 0) {
2033 return Rectangle.Empty;
2035 int x_offset = (this.ShowWeekNumbers) ? date_cell_size.Width : 0;
2036 int y_offset = title_size.Height + (date_cell_size.Height * (row + 1));
2037 return new Rectangle (
2038 month_rect.X + x_offset,
2039 month_rect.Y + y_offset,
2040 date_cell_size.Width * 7,
2041 date_cell_size.Height);
2044 internal void Draw (Rectangle clip_rect, Graphics dc)
2046 ThemeEngine.Current.DrawMonthCalendar (dc, clip_rect, this);
2049 #endregion //internal methods
2051 #region internal drawing methods
2054 #endregion // internal drawing methods
2056 #region inner classes and enumerations
2058 // enumeration about what type of area on the calendar was hit
2059 public enum HitArea {
2060 Nowhere,
2061 TitleBackground,
2062 TitleMonth,
2063 TitleYear,
2064 NextMonthButton,
2065 PrevMonthButton,
2066 CalendarBackground,
2067 Date,
2068 NextMonthDate,
2069 PrevMonthDate,
2070 DayOfWeek,
2071 WeekNumbers,
2072 TodayLink
2075 // info regarding to a hit test on this calendar
2076 public sealed class HitTestInfo {
2078 private HitArea hit_area;
2079 private Point point;
2080 private DateTime time;
2082 // default constructor
2083 internal HitTestInfo () {
2084 hit_area = HitArea.Nowhere;
2085 point = new Point (0, 0);
2086 time = DateTime.Now;
2089 // overload receives all properties
2090 internal HitTestInfo (HitArea hit_area, Point point, DateTime time) {
2091 this.hit_area = hit_area;
2092 this.point = point;
2093 this.time = time;
2096 // the type of area that was hit
2097 public HitArea HitArea {
2098 get {
2099 return hit_area;
2103 // the point that is being test
2104 public Point Point {
2105 get {
2106 return point;
2110 // the date under the hit test point, only valid if HitArea is Date
2111 public DateTime Time {
2112 get {
2113 return time;
2118 #endregion // inner classes