merge -r 53370:58178
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / MonthCalendar.cs
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.
19 //
20 // Copyright (c) 2004 Novell, Inc.
21 //
22 // Authors:
23 //      John BouAntoun  jba-mono@optusnet.com.au
24 //
25 // REMAINING TODO:
26 //      - get the date_cell_size and title_size to be pixel perfect match of SWF
27
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;
34
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                 
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                 internal int                    last_clicked_calendar_index;
81                 internal Rectangle              last_clicked_calendar_rect;
82                 private Point                   month_title_click_location;
83                 // this is used to see which item was actually clicked on in the beginning
84                 // so that we know which item to fire on timer
85                 //      0: date clicked
86                 //      1: previous clicked
87                 //      2: next clicked
88                 private bool[]                  click_state;    
89                 
90                 // arraylists used to store new dates
91                 ArrayList                               added_bolded_dates;
92                 ArrayList                               removed_bolded_dates;
93                 ArrayList                               added_annually_bolded_dates;
94                 ArrayList                               removed_annually_bolded_dates;
95                 ArrayList                               added_monthly_bolded_dates;
96                 ArrayList                               removed_monthly_bolded_dates;
97                 
98                 
99                 #endregion      // Local variables
100
101                 #region Public Constructors
102
103                 public MonthCalendar () {
104                         // set up the control painting
105                         SetStyle (ControlStyles.UserPaint | ControlStyles.StandardClick, false);
106                         
107                         // mouse down timer
108                         timer = new Timer ();
109                         timer.Interval = 500;
110                         timer.Enabled = false;
111                         
112                         // initialise default values 
113                         DateTime now = DateTime.Now.Date;
114                         selection_range = new SelectionRange (now, now);
115                         today_date = now;
116                         current_month = new DateTime (now.Year , now.Month, 1);
117
118                         // iniatialise local members
119                         annually_bolded_dates = null;
120                         back_color = ThemeEngine.Current.ColorWindow;
121                         bolded_dates = null;
122                         calendar_dimensions = new Size (1,1);
123                         first_day_of_week = Day.Default;
124                         fore_color = SystemColors.ControlText;
125                         max_date = new DateTime (9998, 12, 31);
126                         max_selection_count = 7;
127                         min_date = new DateTime (1953, 1, 1);
128                         monthly_bolded_dates = null;
129                         scroll_change = 0;
130                         show_today = true;
131                         show_today_circle = true;
132                         show_week_numbers = false;
133                         title_back_color = ThemeEngine.Current.ColorActiveCaption;
134                         title_fore_color = ThemeEngine.Current.ColorActiveCaptionText;
135                         today_date_set = false;
136                         trailing_fore_color = Color.Gray;
137                         
138                         // initialise the arraylest for bolded dates
139                         added_bolded_dates = new ArrayList ();
140                         removed_bolded_dates = new ArrayList ();
141                         added_annually_bolded_dates = new ArrayList ();
142                         removed_annually_bolded_dates = new ArrayList ();
143                         added_monthly_bolded_dates = new ArrayList ();
144                         removed_monthly_bolded_dates = new ArrayList ();
145                 
146                         // intiailise internal variables used
147                         button_x_offset = 5;
148                         button_size = new Size (22, 17);
149                         // default settings based on 8.25 pt San Serif Font
150                         // Not sure of algorithm used to establish this
151                         date_cell_size = new Size (24, 16);             // default size at san-serif 8.25
152                         divider_line_offset = 4;
153                         calendar_spacing = new Size (4, 5);             // horiz and vert spacing between months in a calendar grid
154
155                         // set some state info
156                         clicked_date = now;
157                         is_date_clicked = false;
158                         is_previous_clicked = false;
159                         is_next_clicked = false;
160                         is_shift_pressed = false;
161                         click_state = new bool [] {false, false, false};
162                         first_select_start_date = now;
163                         month_title_click_location = Point.Empty;
164
165                         // set up context menu
166                         SetUpContextMenu ();
167
168                         
169                         // event handlers
170 //                      LostFocus += new EventHandler (LostFocusHandler);
171                         timer.Tick += new EventHandler (TimerHandler);
172                         MouseMove += new MouseEventHandler (MouseMoveHandler);
173                         MouseDown += new MouseEventHandler (MouseDownHandler);
174                         KeyDown += new KeyEventHandler (KeyDownHandler);
175                         MouseUp += new MouseEventHandler (MouseUpHandler);
176                         KeyUp += new KeyEventHandler (KeyUpHandler);
177                         
178                         // this replaces paint so call the control version                      
179                         base.Paint += new PaintEventHandler (PaintHandler);
180                 }
181                 
182                 // called when this control is added to date time picker
183                 internal MonthCalendar (DateTimePicker owner) : this () {
184                         this.owner = owner;
185                         this.is_visible = false;
186                         this.Size = this.DefaultSize;
187                 }
188
189                 #endregion      // Public Constructors
190
191                 #region Public Instance Properties
192
193                 // dates to make bold on calendar annually (recurring)
194                 [Localizable (true)]
195                 public DateTime[] AnnuallyBoldedDates {
196                         set {
197                                 if (annually_bolded_dates == null || annually_bolded_dates != value) {
198                                         annually_bolded_dates = value;
199                                         this.UpdateBoldedDates ();
200                                         this.Invalidate ();
201                                 }
202                         }
203                         get {
204                                         return annually_bolded_dates;
205                         }
206                 }
207
208                 [Browsable(false)]
209                 [EditorBrowsable(EditorBrowsableState.Never)]
210                 public override Image BackgroundImage {
211                         get {
212                                 return base.BackgroundImage;
213                         }
214                         set {
215                                 base.BackgroundImage = value;
216                         }
217                 }
218
219
220                 // the back color for the main part of the calendar
221                 public override Color BackColor {
222                         set {
223                                 if (back_color != value) {
224                                         back_color = value;
225                                         this.OnBackColorChanged (EventArgs.Empty);
226                                         this.Invalidate ();
227                                 }
228                         }
229                         get {
230                                 return back_color;
231                         }
232                 }
233
234                 // specific dates to make bold on calendar (non-recurring)
235                 [Localizable (true)]
236                 public DateTime[] BoldedDates {
237                         set {
238                                 if (bolded_dates == null || bolded_dates != value) {
239                                         bolded_dates = value;
240                                         this.UpdateBoldedDates ();
241                                         this.Invalidate ();
242                                 }
243                         }
244                         get {
245                                         return bolded_dates;
246                         }
247                 }
248
249                 // the configuration of the monthly grid display - only allowed to display at most,
250                 // 1 calendar year at a time, will be trimmed to fit it properly
251                 [Localizable (true)]
252                 public Size CalendarDimensions {
253                         set {
254                                 if (value.Width < 0 || value.Height < 0) {
255                                         throw new ArgumentException ();
256                                 }
257                                 if (calendar_dimensions != value) {
258                                         // squeeze the grid into 1 calendar year
259                                         if (value.Width * value.Height > 12) {
260                                                 // iteratively reduce the largest dimension till our
261                                                 // product is less than 12
262                                                 if (value.Width > 12 && value.Height > 12) {
263                                                         calendar_dimensions = new Size (4, 3);
264                                                 } else if (value.Width > 12) {
265                                                         for (int i = 12; i > 0; i--) {
266                                                                 if (i * value.Height <= 12) {
267                                                                         calendar_dimensions = new Size (i, value.Height);
268                                                                         break;
269                                                                 }
270                                                         }
271                                                 } else if (value.Height > 12) {
272                                                         for (int i = 12; i > 0; i--) {
273                                                                 if (i * value.Width <= 12) {
274                                                                         calendar_dimensions = new Size (value.Width, i);
275                                                                         break;
276                                                                 }
277                                                         }
278                                                 }
279                                         } else {
280                                                 calendar_dimensions = value;
281                                         }
282                                         this.Invalidate ();
283                                 }
284                         }
285                         get {
286                                 return calendar_dimensions;
287                         }
288                 }
289
290                 // the first day of the week to display
291                 [Localizable (true)]
292                 [DefaultValue (Day.Default)]
293                 public Day FirstDayOfWeek {
294                         set {
295                                 if (first_day_of_week != value) {
296                                         first_day_of_week = value;
297                                         this.Invalidate ();
298                                 }
299                         }
300                         get {
301                                 return first_day_of_week;
302                         }
303                 }
304
305                 // the fore color for the main part of the calendar
306                 public override Color ForeColor {
307                         set {
308                                 if (fore_color != value) {
309                                         fore_color = value;
310                                         this.OnForeColorChanged (EventArgs.Empty);
311                                         this.Invalidate ();
312                                 }
313                         }
314                         get {
315                                 return fore_color;
316                         }
317                 }
318
319                 [Browsable(false)]
320                 [EditorBrowsable(EditorBrowsableState.Never)]
321                 public ImeMode ImeMode {
322                         get {
323                                 return ime_mode;
324                         }
325
326                         set {
327                                 if (ime_mode != value) {
328                                         ime_mode = value;
329
330                                         if (ImeModeChanged != null) {
331                                                 ImeModeChanged(this, EventArgs.Empty);
332                                         }
333                                 }
334                         }
335                 }
336
337                 // the maximum date allowed to be selected on this month calendar
338                 public DateTime MaxDate {
339                         set {
340                                 if (value < MinDate) {
341                                         throw new ArgumentException();
342                                 }
343
344                                 if (max_date != value) {
345                                         max_date = value;
346                                 }
347                         }
348                         get {
349                                 return max_date;
350                         }
351                 }
352
353                 // the maximum number of selectable days
354                 [DefaultValue (7)]
355                 public int MaxSelectionCount {
356                         set {
357                                 if (value < 0) {
358                                         throw new ArgumentException();
359                                 }
360                 
361                                 // can't set selectioncount less than already selected dates
362                                 if ((SelectionEnd - SelectionStart).Days > value) {
363                                         throw new ArgumentException();
364                                 }
365                         
366                                 if (max_selection_count != value) {
367                                         max_selection_count = value;
368                                 }
369                         }
370                         get {
371                                 return max_selection_count;
372                         }
373                 }
374
375                 // the minimum date allowed to be selected on this month calendar
376                 public DateTime MinDate {
377                         set {
378                                 if (value < new DateTime (1953, 1, 1)) {
379                                         throw new ArgumentException();
380                                 }
381
382                                 if (value > MaxDate) {
383                                         throw new ArgumentException();
384                                 }
385
386                                 if (max_date != value) {
387                                         min_date = value;
388                                 }
389                         }
390                         get {
391                                 return min_date;
392                         }
393                 }
394
395                 // dates to make bold on calendar monthly (recurring)
396                 [Localizable (true)]
397                 public DateTime[] MonthlyBoldedDates {
398                         set {
399                                 if (monthly_bolded_dates == null || monthly_bolded_dates != value) {
400                                         monthly_bolded_dates = value;
401                                         this.UpdateBoldedDates ();
402                                         this.Invalidate ();
403                                 }
404                         }
405                         get {
406                                         return monthly_bolded_dates;
407                         }
408                 }
409
410                 // the ammount by which to scroll this calendar by
411                 [DefaultValue (0)]
412                 public int ScrollChange {
413                         set {
414                                 if (value < 0 || value > 20000) {
415                                         throw new ArgumentException();
416                                 }
417
418                                 if (scroll_change != value) {
419                                         scroll_change = value;
420                                 }
421                         }
422                         get {
423                                 // if zero it to the default -> the total number of months currently visible
424                                 if (scroll_change == 0) {
425                                         return CalendarDimensions.Width * CalendarDimensions.Height;
426                                 }
427                                 return scroll_change;
428                         }
429                 }
430
431
432                 // the last selected date
433                 [Browsable (false)]
434                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
435                 public DateTime SelectionEnd {
436                         set {
437                                 if (value < MinDate || value > MaxDate) {
438                                         throw new ArgumentException();
439                                 }
440
441                                 if (SelectionRange.End != value) {
442                                         DateTime old_end = SelectionRange.End; 
443                                         // make sure the end obeys the max selection range count
444                                         if (value < SelectionRange.Start) {
445                                                 SelectionRange.Start = value;
446                                         }
447                                         if (value.AddDays((MaxSelectionCount-1)*-1) > SelectionRange.Start) {
448                                                 SelectionRange.Start = value.AddDays((MaxSelectionCount-1)*-1);
449                                         }
450                                         SelectionRange.End = value;
451                                         this.InvalidateDateRange (new SelectionRange (old_end, SelectionRange.End));
452                                         this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
453                                 }
454                         }
455                         get {
456                                 return SelectionRange.End;
457                         }
458                 }
459
460                 // the range of selected dates
461                 public SelectionRange SelectionRange {
462                         set {
463                                 if (selection_range != value) {
464                                         SelectionRange old_range = selection_range;
465
466                                         // make sure the end obeys the max selection range count
467                                         if (value.End.AddDays((MaxSelectionCount-1)*-1) > value.Start) {
468                                                 selection_range = new SelectionRange (value.End.AddDays((MaxSelectionCount-1)*-1), value.End);
469                                         } else {
470                                                 selection_range = value;
471                                         }
472                                         SelectionRange visible_range = this.GetDisplayRange(true);
473                                         if(visible_range.Start > selection_range.End) {
474                                                 this.current_month = new DateTime (selection_range.Start.Year, selection_range.Start.Month, 1);
475                                                 this.Invalidate ();
476                                         } else if (visible_range.End < selection_range.Start) {
477                                                 int year_diff = selection_range.End.Year - visible_range.End.Year;
478                                                 int month_diff = selection_range.End.Month - visible_range.End.Month;
479                                                 this.current_month = current_month.AddMonths(year_diff * 12 + month_diff);
480                                                 this.Invalidate ();
481                                         }
482                                         // invalidate the selected range changes
483                                         DateTime diff_start = old_range.Start;
484                                         DateTime diff_end = old_range.End;
485                                         // now decide which region is greated
486                                         if (old_range.Start > SelectionRange.Start) {
487                                                 diff_start = SelectionRange.Start;
488                                         } else if (old_range.Start == SelectionRange.Start) {
489                                                 if (old_range.End < SelectionRange.End) {
490                                                         diff_start = old_range.End;
491                                                 } else {
492                                                         diff_start = SelectionRange.End;
493                                                 }
494                                         }
495                                         if (old_range.End < SelectionRange.End) {
496                                                 diff_end = SelectionRange.End;
497                                         } else if (old_range.End == SelectionRange.End) {
498                                                 if (old_range.Start < SelectionRange.Start) {
499                                                         diff_end = SelectionRange.Start;
500                                                 } else {
501                                                         diff_end = old_range.Start;
502                                                 }
503                                         }
504
505                                         // invalidate the region required
506                                         this.InvalidateDateRange (new SelectionRange (diff_start, diff_end));
507                                         // raise date changed event
508                                         this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
509                                 }
510                         }
511                         get {
512                                 return selection_range;
513                         }
514                 }
515
516                 // the first selected date
517                 [Browsable (false)]
518                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
519                 public DateTime SelectionStart {
520                         set {
521                                 if (value < MinDate || value > MaxDate) {
522                                         throw new ArgumentException();
523                                 }
524
525                                 if (SelectionRange.Start != value) {
526                                         DateTime old_start = SelectionRange.Start; 
527                                         // make sure the end obeys the max selection range count
528                                         if (value > SelectionRange.End) {
529                                                 SelectionRange.End = value;
530                                         } else if (value.AddDays(MaxSelectionCount-1) < SelectionRange.End) {
531                                                 SelectionRange.End = value.AddDays(MaxSelectionCount-1);
532                                         }
533                                         SelectionRange.Start = value;
534                                         DateTime new_month = new DateTime(value.Year, value.Month, 1);
535                                         if (current_month != new_month) {
536                                                 current_month = new_month;
537                                                 this.Invalidate ();
538                                         } else {
539                                                 this.InvalidateDateRange (new SelectionRange (old_start, SelectionRange.Start));
540                                         }
541                                         this.OnDateChanged (new DateRangeEventArgs (SelectionStart, SelectionEnd));
542                                 }
543                         }
544                         get {
545                                 return selection_range.Start;
546                         }
547                 }
548
549                 // whether or not to show todays date
550                 [DefaultValue (true)]
551                 public bool ShowToday {
552                         set {
553                                 if (show_today != value) {
554                                         show_today = value;
555                                         this.Invalidate ();
556                                 }
557                         }
558                         get {
559                                 return show_today;
560                         }
561                 }
562
563                 // whether or not to show a circle around todays date
564                 [DefaultValue (true)]
565                 public bool ShowTodayCircle {
566                         set {
567                                 if (show_today_circle != value) {
568                                         show_today_circle = value;
569                                         this.Invalidate ();
570                                 }
571                         }
572                         get {
573                                 return show_today_circle;
574                         }
575                 }
576
577                 // whether or not to show numbers beside each row of weeks
578                 [Localizable (true)]
579                 [DefaultValue (false)]
580                 public bool ShowWeekNumbers {
581                         set {
582                                 if (show_week_numbers != value) {
583                                         show_week_numbers = value;
584                                         this.Invalidate ();
585                                 }
586                         }
587                         get {
588                                 return show_week_numbers;
589                         }
590                 }
591
592                 // the rectangle size required to render one month based on current font
593                 [Browsable (false)]
594                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
595                 public Size SingleMonthSize {
596                         get {
597                                 if (this.Font == null) {
598                                         throw new InvalidOperationException();
599                                 }
600
601                                 // multiplier is sucked out from the font size
602                                 int multiplier = this.Font.Height;
603
604                                 // establis how many columns and rows we have                                   
605                                 int column_count = (ShowWeekNumbers) ? 8 : 7;
606                                 int row_count = 7;              // not including the today date
607
608                                 // set the date_cell_size and the title_size
609                                 date_cell_size = new Size ((int) Math.Ceiling (2.5 * multiplier), (int) Math.Ceiling (1.5 * multiplier));
610                                 title_size = new Size ((date_cell_size.Width * column_count), 3 * multiplier);
611
612                                 return new Size (column_count * date_cell_size.Width, row_count * date_cell_size.Height + title_size.Height);
613                         }
614                 }
615
616                 [Bindable(false)]
617                 [Browsable(false)]
618                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
619                 [EditorBrowsable(EditorBrowsableState.Never)]
620                 public override string Text {
621                         get {
622                                 return base.Text;
623                         }
624                         set {
625                                 base.Text = value;
626                         }
627                 }
628
629                 // the back color for the title of the calendar and the
630                 // forecolor for the day of the week text
631                 public Color TitleBackColor {
632                         set {
633                                 if (title_back_color != value) {
634                                         title_back_color = value;
635                                         this.Invalidate ();
636                                 }
637                         }
638                         get {
639                                 return title_back_color;
640                         }
641                 }
642
643                 // the fore color for the title of the calendar
644                 public Color TitleForeColor {
645                         set {
646                                 if (title_fore_color != value) {
647                                         title_fore_color = value;
648                                         this.Invalidate ();
649                                 }
650                         }
651                         get {
652                                 return title_fore_color;
653                         }
654                 }
655
656                 // the date this calendar is using to refer to today's date
657                 public DateTime TodayDate {
658                         set {
659                                 today_date_set = true;
660                                 if (today_date != value) {
661                                         today_date = value;
662                                         this.Invalidate ();
663                                 }
664                         }
665                         get {
666                                 return today_date;
667                         }
668                 }
669
670                 // tells if user specifically set today_date for this control           
671                 [Browsable (false)]
672                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
673                 public bool TodayDateSet {
674                         get {
675                                 return today_date_set;
676                         }
677                 }
678
679                 // the color used for trailing dates in the calendar
680                 public Color TrailingForeColor {
681                         set {
682                                 if (trailing_fore_color != value) {
683                                         trailing_fore_color = value;
684                                         SelectionRange bounds = this.GetDisplayRange (false);
685                                         SelectionRange visible_bounds = this.GetDisplayRange (true);
686                                         this.InvalidateDateRange (new SelectionRange (bounds.Start, visible_bounds.Start));
687                                         this.InvalidateDateRange (new SelectionRange (bounds.End, visible_bounds.End));
688                                 }
689                         }
690                         get {
691                                 return trailing_fore_color;
692                         }
693                 }
694
695                 #endregion      // Public Instance Properties
696
697                 #region Protected Instance Properties
698
699                 // overloaded to allow controll to be windowed for drop down
700                 protected override CreateParams CreateParams {
701                         get {
702                                 if (this.owner == null) {
703                                         return base.CreateParams;                                       
704                                 } else {
705                                         CreateParams cp = base.CreateParams;                                    
706                                         cp.Style = unchecked ((int)(WindowStyles.WS_POPUP | WindowStyles.WS_VISIBLE | WindowStyles.WS_CLIPSIBLINGS | WindowStyles.WS_CLIPCHILDREN));
707                                         cp.ExStyle |= (int)(WindowStyles.WS_EX_TOOLWINDOW | WindowStyles.WS_EX_TOPMOST);
708                                         return cp;
709                                 }
710                         }
711                 }
712         
713                 // not sure what to put in here - just doing a base() call - jba
714                 protected override ImeMode DefaultImeMode {
715                         get {
716                                 return base.DefaultImeMode;
717                         }
718                 }
719
720                 protected override Size DefaultSize {
721                         get {
722                                 Size single_month = SingleMonthSize;
723                                 // get the width
724                                 int width = calendar_dimensions.Width * single_month.Width;
725                                 if (calendar_dimensions.Width > 1) {
726                                         width += (calendar_dimensions.Width - 1) * calendar_spacing.Width;
727                                 }
728
729                                 // get the height
730                                 int height = calendar_dimensions.Height * single_month.Height;
731                                 if (this.ShowToday) {
732                                         height += date_cell_size.Height + 2;            // add the height of the "Today: " ...
733                                 }
734                                 if (calendar_dimensions.Height > 1) {
735                                         height += (calendar_dimensions.Height - 1) * calendar_spacing.Height;
736                                 }
737
738                                 // add the 1 pixel boundary
739                                 if (width > 0) {
740                                         width += 2;
741                                 }
742                                 if (height > 0) {
743                                         height +=2;
744                                 }
745
746                                 return new Size (width, height);
747                         }
748                 }
749
750                 #endregion      // Protected Instance Properties
751
752                 #region Public Instance Methods
753
754                 // add a date to the anually bolded date arraylist
755                 public void AddAnnuallyBoldedDate (DateTime date) {
756                         added_annually_bolded_dates.Add (date.Date);
757                 }
758
759                 // add a date to the normal bolded date arraylist
760                 public void AddBoldedDate (DateTime date) {
761                         added_bolded_dates.Add (date.Date);
762                 }
763
764                 // add a date to the anually monthly date arraylist
765                 public void AddMonthlyBoldedDate (DateTime date) {
766                         added_monthly_bolded_dates.Add (date.Date);
767                 }
768
769                 // if visible = true, return only the dates of full months, else return all dates visible
770                 public SelectionRange GetDisplayRange (bool visible) {
771                         DateTime start;
772                         DateTime end;
773                         start = new DateTime (current_month.Year, current_month.Month, 1);
774                         end = start.AddMonths (calendar_dimensions.Width * calendar_dimensions.Height);
775                         end = end.AddDays(-1);
776
777                         // process all visible dates if needed (including the grayed out dates
778                         if (!visible) {
779                                 start = GetFirstDateInMonthGrid (start);
780                                 end = GetLastDateInMonthGrid (end);
781                         }
782
783                         return new SelectionRange (start, end);
784                 }
785
786                 // HitTest overload that recieve's x and y co-ordinates as separate ints
787                 public HitTestInfo HitTest (int x, int y) {
788                         return HitTest (new Point (x, y));
789                 }
790
791                 // returns a HitTestInfo for MonthCalendar element's under the specified point
792                 public HitTestInfo HitTest (Point point) {
793                         return HitTest (point, out last_clicked_calendar_index, out last_clicked_calendar_rect);
794                 }
795
796                 // clears all the annually bolded dates
797                 public void RemoveAllAnnuallyBoldedDates () {
798                         annually_bolded_dates = null;
799                         added_annually_bolded_dates.Clear ();
800                         removed_annually_bolded_dates.Clear ();
801                 }
802
803                 // clears all the normal bolded dates
804                 public void RemoveAllBoldedDates () {
805                         bolded_dates = null;
806                         added_bolded_dates.Clear ();
807                         removed_bolded_dates.Clear ();
808                 }
809
810                 // clears all the monthly bolded dates
811                 public void RemoveAllMonthlyBoldedDates () {
812                         monthly_bolded_dates = null;
813                         added_monthly_bolded_dates.Clear ();
814                         removed_monthly_bolded_dates.Clear ();
815                 }
816
817                 // clears the specified annually bolded date (only compares day and month)
818                 // only removes the first instance of the match
819                 public void RemoveAnnuallyBoldedDate (DateTime date) {
820                         if (!removed_annually_bolded_dates.Contains (date.Date)) {
821                                 removed_annually_bolded_dates.Add (date.Date);
822                         }
823                 }
824
825                 // clears all the normal bolded date
826                 // only removes the first instance of the match
827                 public void RemoveBoldedDate (DateTime date) {
828                         if (!removed_bolded_dates.Contains (date.Date)) {
829                                 removed_bolded_dates.Add (date.Date);
830                         }
831                 }
832
833                 // clears the specified monthly bolded date (only compares day and month)
834                 // only removes the first instance of the match
835                 public void RemoveMonthlyBoldedDate (DateTime date) {
836                         if (!removed_monthly_bolded_dates.Contains (date.Date)) {
837                                 removed_monthly_bolded_dates.Add (date.Date);
838                         }
839                 }
840
841                 // sets the calendar_dimensions. If product is > 12, the larger dimension is reduced to make product < 12
842                 public void SetCalendarDimensions(int x, int y) {
843                         this.CalendarDimensions = new Size(x, y);
844                 }
845
846                 // sets the currently selected date as date
847                 public void SetDate (DateTime date) {
848                         this.SetSelectionRange (date.Date, date.Date);
849                 }
850
851                 // utility method set the SelectionRange property using individual dates
852                 public void SetSelectionRange (DateTime date1, DateTime date2) {
853                         this.SelectionRange = new SelectionRange(date1, date2);
854                 }
855
856                 public override string ToString () {
857                         return this.GetType().Name + ", " + this.SelectionRange.ToString ();
858                 }
859                                 
860                 // usually called after an AddBoldedDate method is called
861                 // formats monthly and daily bolded dates according to the current calendar year
862                 public void UpdateBoldedDates () {
863                         UpdateDateArray (ref bolded_dates, added_bolded_dates, removed_bolded_dates);
864                         UpdateDateArray (ref monthly_bolded_dates, added_monthly_bolded_dates, removed_monthly_bolded_dates);
865                         UpdateDateArray (ref annually_bolded_dates, added_annually_bolded_dates, removed_annually_bolded_dates);
866                 }
867
868                 #endregion      // Public Instance Methods
869
870                 #region Protected Instance Methods
871
872                 // not sure why this needs to be overriden
873                 protected override void CreateHandle () {
874                         base.CreateHandle ();
875                 }
876
877                 private void CreateYearUpDown ()
878                 {
879                         year_updown = new NumericUpDown ();
880                         year_updown.Font = this.Font;
881                         year_updown.Minimum = MinDate.Year;
882                         year_updown.Maximum = MaxDate.Year;
883                         year_updown.ReadOnly = true;
884                         year_updown.Visible = false;
885                         this.Controls.AddImplicit (year_updown);
886                         year_updown.ValueChanged += new EventHandler(UpDownYearChangedHandler);
887                 }
888
889                 // not sure why this needs to be overriden
890                 protected override void Dispose (bool disposing) {
891                         base.Dispose (disposing);
892                 }
893
894                 // not sure why this needs to be overriden
895                 protected override bool IsInputKey (Keys keyData) {
896                         return base.IsInputKey (keyData);
897                 }
898
899                 // not sure why this needs to be overriden
900                 protected override void OnBackColorChanged (EventArgs e) {
901                         base.OnBackColorChanged (e);
902                         this.Invalidate ();
903                 }
904
905                 // raises the date changed event
906                 protected virtual void OnDateChanged (DateRangeEventArgs drevent) {
907                         if (this.DateChanged != null) {
908                                 this.DateChanged (this, drevent);
909                         }
910                 }
911
912                 // raises the DateSelected event
913                 protected virtual void OnDateSelected (DateRangeEventArgs drevent) {
914                         if (this.DateSelected != null) {
915                                 this.DateSelected (this, drevent);
916                         }
917                 }
918
919                 protected override void OnFontChanged (EventArgs e) {
920                         base.OnFontChanged (e);
921                 }
922
923                 protected override void OnForeColorChanged (EventArgs e) {
924                         base.OnForeColorChanged (e);
925                 }
926
927                 protected override void OnHandleCreated (EventArgs e) {
928                         base.OnHandleCreated (e);
929                         CreateYearUpDown ();
930                 }
931
932                 // i think this is overriden to not allow the control to be changed to an arbitrary size
933                 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) {
934                         if ((specified & BoundsSpecified.Height) == BoundsSpecified.Height ||
935                                 (specified & BoundsSpecified.Width) == BoundsSpecified.Width ||
936                                 (specified & BoundsSpecified.Size) == BoundsSpecified.Size) {
937                                 // only allow sizes = default size to be set
938                                 Size min_size = DefaultSize;
939                                 Size max_size = new Size (
940                                         DefaultSize.Width + SingleMonthSize.Width + calendar_spacing.Width,
941                                         DefaultSize.Height + SingleMonthSize.Height + calendar_spacing.Height);
942                                 int x_mid_point = (max_size.Width + min_size.Width)/2;
943                                 int y_mid_point = (max_size.Height + min_size.Height)/2;
944                                 if (width < x_mid_point) {
945                                         width = min_size.Width;
946                                 } else {
947                                         width = max_size.Width;
948                                 }
949                                 if (height < y_mid_point) {
950                                         height = min_size.Height;
951                                 } else {
952                                         height = max_size.Height;
953                                 }
954                                 base.SetBoundsCore (x, y, width, height, specified);
955                         } else {
956                                 base.SetBoundsCore (x, y, width, height, specified);
957                         }
958                 }
959
960                 protected override void WndProc (ref Message m) {
961                         base.WndProc (ref m);
962                 }
963
964                 #endregion      // Protected Instance Methods
965
966                 #region public events
967
968                 // fired when the date is changed (either explicitely or implicitely)
969                 // when navigating the month selector
970                 public event DateRangeEventHandler DateChanged;
971
972                 // fired when the user explicitely clicks on date to select it
973                 public event DateRangeEventHandler DateSelected;
974
975                 [Browsable(false)]
976                 [EditorBrowsable (EditorBrowsableState.Never)]
977                 public event EventHandler BackgroundImageChanged;
978
979                 // this event is overridden to supress it from being fired
980                 [Browsable(false)]
981                 [EditorBrowsable (EditorBrowsableState.Never)]
982                 public event EventHandler Click;
983
984                 // this event is overridden to supress it from being fired
985                 [Browsable(false)]
986                 [EditorBrowsable (EditorBrowsableState.Never)]
987                 public event EventHandler DoubleClick;
988
989                 [Browsable(false)]
990                 [EditorBrowsable (EditorBrowsableState.Never)]
991                 public event EventHandler ImeModeChanged;
992
993                 [Browsable(false)]
994                 [EditorBrowsable (EditorBrowsableState.Never)]
995                 public new event PaintEventHandler Paint;
996
997                 [Browsable(false)]
998                 [EditorBrowsable (EditorBrowsableState.Never)]
999                 public event EventHandler TextChanged;
1000                 #endregion      // public events
1001
1002                 #region internal properties
1003
1004                 internal DateTime CurrentMonth {
1005                         set {
1006                                 // only interested in if the month (not actual date) has changed
1007                                 if (value.Month != current_month.Month ||
1008                                         value.Year != current_month.Year) {
1009                                         this.SelectionRange = new SelectionRange(
1010                                                 this.SelectionStart.Add(value.Subtract(current_month)),
1011                                                 this.SelectionEnd.Add(value.Subtract(current_month)));
1012                                         current_month = value;
1013                                         UpdateBoldedDates();
1014                                         this.Invalidate();
1015                                 }
1016                         }
1017                         get {
1018                                 return current_month;
1019                         }
1020                 }
1021
1022                 #endregion      // internal properties
1023
1024                 #region internal/private methods
1025                 internal HitTestInfo HitTest (
1026                         Point point,
1027                         out int calendar_index,
1028                         out Rectangle calendar_rect) {
1029                         // start by initialising the ref parameters
1030                         calendar_index = -1;
1031                         calendar_rect = Rectangle.Empty;
1032
1033                         // before doing all the hard work, see if the today's date wasn't clicked
1034                         Rectangle today_rect = new Rectangle (
1035                                 ClientRectangle.X, 
1036                                 ClientRectangle.Bottom - date_cell_size.Height,
1037                                 7 * date_cell_size.Width,
1038                                 date_cell_size.Height);
1039                         if (today_rect.Contains (point) && this.ShowToday) {
1040                                 return new HitTestInfo(HitArea.TodayLink, point, DateTime.Now);
1041                         }
1042
1043                         Size month_size = SingleMonthSize;
1044                         // define calendar rect's that this thing can land in
1045                         Rectangle[] calendars = new Rectangle [CalendarDimensions.Width * CalendarDimensions.Height];
1046                         for (int i=0; i < CalendarDimensions.Width * CalendarDimensions.Height; i ++) {
1047                                 if (i == 0) {
1048                                         calendars[i] = new Rectangle (
1049                                                 new Point (ClientRectangle.X + 1, ClientRectangle.Y + 1),
1050                                                 month_size);
1051                                 } else {
1052                                         // calendar on the next row
1053                                         if (i % CalendarDimensions.Width == 0) {
1054                                                 calendars[i] = new Rectangle (
1055                                                         new Point (calendars[i-CalendarDimensions.Width].X, calendars[i-CalendarDimensions.Width].Bottom + calendar_spacing.Height),
1056                                                         month_size);
1057                                         } else {
1058                                                 // calendar on the next column
1059                                                 calendars[i] = new Rectangle (
1060                                                         new Point (calendars[i-1].Right + calendar_spacing.Width, calendars[i-1].Y),
1061                                                         month_size);
1062                                         }
1063                                 }
1064                         }
1065                         
1066                         // through each trying to find a match
1067                         for (int i = 0; i < calendars.Length ; i++) {
1068                                 if (calendars[i].Contains (point)) {                                    
1069                                         // check the title section
1070                                         Rectangle title_rect = new Rectangle (
1071                                                 calendars[i].Location,
1072                                                 title_size);
1073                                         if (title_rect.Contains (point) ) {
1074                                                 // make sure it's not a previous button
1075                                                 if (i == 0) {
1076                                                         Rectangle button_rect = new Rectangle(
1077                                                                 new Point (calendars[i].X + button_x_offset, (title_size.Height - button_size.Height)/2),
1078                                                                 button_size);
1079                                                         if (button_rect.Contains (point)) {
1080                                                                 return new HitTestInfo(HitArea.PrevMonthButton, point, DateTime.Now);
1081                                                         }
1082                                                 }
1083                                                 // make sure it's not the next button
1084                                                 if (i % CalendarDimensions.Height == 0 && i % CalendarDimensions.Width == calendar_dimensions.Width - 1) {
1085                                                         Rectangle button_rect = new Rectangle(
1086                                                                 new Point (calendars[i].Right - button_x_offset - button_size.Width, (title_size.Height - button_size.Height)/2),
1087                                                                 button_size);
1088                                                         if (button_rect.Contains (point)) {
1089                                                                 return new HitTestInfo(HitArea.NextMonthButton, point, DateTime.Now);
1090                                                         }
1091                                                 }
1092
1093                                                 // indicate which calendar and month it was
1094                                                 calendar_index = i;
1095                                                 calendar_rect = calendars[i];
1096
1097                                                 // make sure it's not the month or the year of the calendar
1098                                                 if (GetMonthNameRectangle (title_rect, i).Contains (point)) {
1099                                                         return new HitTestInfo(HitArea.TitleMonth, point, DateTime.Now);
1100                                                 }
1101                                                 if (GetYearNameRectangle (title_rect, i).Contains (point)) {
1102                                                         return new HitTestInfo(HitArea.TitleYear, point, DateTime.Now);
1103                                                 }
1104
1105                                                 // return the hit test in the title background
1106                                                 return new HitTestInfo(HitArea.TitleBackground, point, DateTime.Now);
1107                                         }
1108
1109                                         Point date_grid_location = new Point (calendars[i].X, title_rect.Bottom);
1110
1111                                         // see if it's in the Week numbers
1112                                         if (ShowWeekNumbers) {
1113                                                 Rectangle weeks_rect = new Rectangle (
1114                                                         date_grid_location,
1115                                                         new Size (date_cell_size.Width,Math.Max (calendars[i].Height - title_rect.Height, 0)));
1116                                                 if (weeks_rect.Contains (point)) {
1117                                                         return new HitTestInfo(HitArea.WeekNumbers, point, DateTime.Now);
1118                                                 }
1119
1120                                                 // move the location of the grid over
1121                                                 date_grid_location.X += date_cell_size.Width;
1122                                         }
1123
1124                                         // see if it's in the week names
1125                                         Rectangle day_rect = new Rectangle (
1126                                                 date_grid_location,
1127                                                 new Size (Math.Max (calendars[i].Right - date_grid_location.X, 0), date_cell_size.Height));
1128                                         if (day_rect.Contains (point)) {
1129                                                 return new HitTestInfo(HitArea.DayOfWeek, point, DateTime.Now);
1130                                         }
1131                                                 
1132                                         // finally see if it was a date that was clicked
1133                                         Rectangle date_grid = new Rectangle (
1134                                                 new Point (day_rect.X, day_rect.Bottom),
1135                                                 new Size (day_rect.Width, Math.Max(calendars[i].Bottom - day_rect.Bottom, 0)));
1136                                         if (date_grid.Contains (point)) {
1137                                                 // okay so it's inside the grid, get the offset
1138                                                 Point offset = new Point (point.X - date_grid.X, point.Y - date_grid.Y);
1139                                                 int row = offset.Y / date_cell_size.Height;
1140                                                 int col = offset.X / date_cell_size.Width;
1141                                                 // establish our first day of the month
1142                                                 DateTime calendar_month = this.CurrentMonth.AddMonths(i);
1143                                                 DateTime first_day = GetFirstDateInMonthGrid (calendar_month);
1144                                                 DateTime time = first_day.AddDays ((row * 7) + col);
1145                                                 // establish which date was clicked
1146                                                 if (time.Year != calendar_month.Year || time.Month != calendar_month.Month) {
1147                                                         if (time < calendar_month && i == 0) {
1148                                                                 return new HitTestInfo(HitArea.PrevMonthDate, point, time);
1149                                                         } else if (time > calendar_month && i == CalendarDimensions.Width*CalendarDimensions.Height - 1) {
1150                                                                 return new HitTestInfo(HitArea.NextMonthDate, point, time);
1151                                                         }                                                       
1152                                                         return new HitTestInfo(HitArea.Nowhere, point, time);
1153                                                 }
1154                                                 return new HitTestInfo(HitArea.Date, point, time);
1155                                         }
1156                                 }                               
1157                         }
1158
1159                         return new HitTestInfo ();
1160                 }
1161
1162                 // returns the date of the first cell of the specified month grid
1163                 internal DateTime GetFirstDateInMonthGrid (DateTime month) {
1164                         // convert the first_day_of_week into a DayOfWeekEnum
1165                         DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1166                         // find the first day of the month
1167                         DateTime first_date_of_month = new DateTime (month.Year, month.Month, 1);
1168                         DayOfWeek first_day_of_month = first_date_of_month.DayOfWeek;                   
1169                         // adjust for the starting day of the week
1170                         int offset = first_day_of_month - first_day;
1171                         if (offset < 0) {
1172                                 offset += 7;
1173                         }
1174                         return first_date_of_month.AddDays (-1*offset);
1175                 }
1176
1177                 // returns the date of the last cell of the specified month grid
1178                 internal DateTime GetLastDateInMonthGrid (DateTime month) 
1179                 {
1180                         DateTime start = GetFirstDateInMonthGrid(month);
1181                         return start.AddDays ((7 * 6)-1);
1182                 }
1183                 
1184                 internal bool IsBoldedDate (DateTime date) {
1185                         // check bolded dates
1186                         if (bolded_dates != null && bolded_dates.Length > 0) {
1187                                 foreach (DateTime bolded_date in bolded_dates) {
1188                                         if (bolded_date.Date == date.Date) {
1189                                                 return true;
1190                                         }
1191                                 }
1192                         }
1193                         // check monthly dates
1194                         if (monthly_bolded_dates != null && monthly_bolded_dates.Length > 0) {
1195                                 foreach (DateTime bolded_date in monthly_bolded_dates) {
1196                                         if (bolded_date.Day == date.Day) {
1197                                                 return true;
1198                                         }
1199                                 }
1200                         }
1201                         // check yearly dates
1202                         if (annually_bolded_dates != null && annually_bolded_dates.Length > 0) {
1203                                 foreach (DateTime bolded_date in annually_bolded_dates) {
1204                                         if (bolded_date.Month == date.Month && bolded_date.Day == date.Day) {
1205                                                 return true;
1206                                         }
1207                                 }
1208                         }
1209                         
1210                         return false;  // no match
1211                 }
1212                 
1213                 // updates the specified bolded dates array with ones to add and ones to remove
1214                 private void UpdateDateArray (ref DateTime [] dates, ArrayList to_add, ArrayList to_remove) {
1215                         ArrayList list = new ArrayList ();
1216                         
1217                         // update normal bolded dates
1218                         if (dates != null) {
1219                                 foreach (DateTime date in dates) {
1220                                         list.Add (date.Date);
1221                                 }
1222                         }
1223                         
1224                         // add new ones
1225                         foreach (DateTime date in to_add) {
1226                                 if (!list.Contains (date.Date)) {
1227                                         list.Add (date.Date);
1228                                 }
1229                         }
1230                         to_add.Clear ();
1231                         
1232                         // remove ones to remove
1233                         foreach (DateTime date in to_remove) {
1234                                 if (list.Contains (date.Date)) {
1235                                         list.Remove (date.Date);
1236                                 }
1237                         }
1238                         to_remove.Clear ();
1239                         // set up the array now 
1240                         if (list.Count > 0) {
1241                                 dates = (DateTime []) list.ToArray (typeof (DateTime));
1242                                 Array.Sort (dates);
1243                                 list.Clear ();
1244                         } else {
1245                                 dates = null;
1246                         }
1247                 }
1248
1249                 // initialise the context menu
1250                 private void SetUpContextMenu () {
1251                         menu = new ContextMenu ();
1252                         for (int i=0; i < 12; i++) {
1253                                 MenuItem menu_item = new MenuItem ( new DateTime (2000, i+1, 1).ToString ("MMMM"));
1254                                 menu_item.Click += new EventHandler (MenuItemClickHandler);
1255                                 menu.MenuItems.Add (menu_item);
1256                         }
1257                 }
1258
1259                 // initialises text value and show's year up down in correct position
1260                 private void PrepareYearUpDown (Point p) {
1261                         Rectangle old_location = year_updown.Bounds;
1262
1263                         // set position
1264                         Rectangle title_rect = new Rectangle(
1265                                 last_clicked_calendar_rect.Location,
1266                                 title_size);
1267
1268                         year_updown.Bounds = GetYearNameRectangle(
1269                                 title_rect, 
1270                                 last_clicked_calendar_index);
1271                         year_updown.Top -= 4;
1272                         year_updown.Width += (int) (this.Font.Size * 4);
1273                         // set year - only do this if this isn't being called because of a year up down click
1274                         if(year_updown.Bounds != old_location) {
1275                                 year_updown.Value = current_month.AddMonths(last_clicked_calendar_index).Year;                  
1276                         }
1277
1278                         if(!year_updown.Visible) {
1279                                 year_updown.Visible = true;
1280                         }
1281                 }
1282
1283                 // returns the first date of the month
1284                 private DateTime GetFirstDateInMonth (DateTime date) {
1285                         return new DateTime (date.Year, date.Month, 1);
1286                 }
1287
1288                 // returns the last date of the month
1289                 private DateTime GetLastDateInMonth (DateTime date) {
1290                         return new DateTime (date.Year, date.Month, 1).AddMonths(1).AddDays(-1);
1291                 }
1292
1293                 // called in response to users seletion with shift key
1294                 private void AddTimeToSelection (int delta, bool isDays)
1295                 {
1296                         DateTime cursor_point;
1297                         DateTime end_point;
1298                         // okay we add the period to the date that is not the same as the 
1299                         // start date when shift was first clicked.
1300                         if (SelectionStart != first_select_start_date) {
1301                                 cursor_point = SelectionStart;
1302                         } else {
1303                                 cursor_point = SelectionEnd;
1304                         }
1305                         // add the days
1306                         if (isDays) {
1307                                 end_point = cursor_point.AddDays (delta);
1308                         } else {
1309                                 // delta must be months
1310                                 end_point = cursor_point.AddMonths (delta);
1311                         }
1312                         // set the new selection range
1313                         SelectionRange range = new SelectionRange (first_select_start_date, end_point);
1314                         if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1315                                 // okay the date is beyond what is allowed, lets set the maximum we can
1316                                 if (range.Start != first_select_start_date) {
1317                                         range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1318                                 } else {
1319                                         range.End = range.Start.AddDays (MaxSelectionCount-1);
1320                                 }
1321                         }
1322                         this.SelectionRange = range;
1323                 }
1324
1325                 // attempts to add the date to the selection without throwing exception
1326                 private void SelectDate (DateTime date) {
1327                         // try and add the new date to the selction range
1328                         if (is_shift_pressed || (click_state [0])) {
1329                                 SelectionRange range = new SelectionRange (first_select_start_date, date);
1330                                 if (range.Start.AddDays (MaxSelectionCount-1) < range.End) {
1331                                         // okay the date is beyond what is allowed, lets set the maximum we can
1332                                         if (range.Start != first_select_start_date) {
1333                                                 range.Start = range.End.AddDays ((MaxSelectionCount-1)*-1);
1334                                         } else {
1335                                                 range.End = range.Start.AddDays (MaxSelectionCount-1);
1336                                         }
1337                                 }
1338                                 SelectionRange = range;
1339                         } else {
1340                                 SelectionRange = new SelectionRange (date, date);
1341                                 first_select_start_date = date;
1342                         }
1343                 }
1344
1345                 // gets the week of the year
1346                 internal int GetWeekOfYear (DateTime date) {
1347                         // convert the first_day_of_week into a DayOfWeekEnum
1348                         DayOfWeek first_day = GetDayOfWeek (first_day_of_week);
1349                         // find the first day of the year
1350                         DayOfWeek first_day_of_year = new DateTime (date.Year, 1, 1).DayOfWeek;
1351                         // adjust for the starting day of the week
1352                         int offset = first_day_of_year - first_day;
1353                         int week = ((date.DayOfYear + offset) / 7) + 1;
1354                         return week;
1355                 }
1356
1357                 // convert a Day enum into a DayOfWeek enum
1358                 internal DayOfWeek GetDayOfWeek (Day day) {
1359                         if (day == Day.Default) {
1360                                 return DayOfWeek.Sunday;
1361                         } else {
1362                                 return (DayOfWeek) DayOfWeek.Parse (typeof (DayOfWeek), day.ToString ());
1363                         }
1364                 }
1365
1366                 // returns the rectangle for themonth name
1367                 internal Rectangle GetMonthNameRectangle (Rectangle title_rect, int calendar_index) {
1368                         Graphics g = this.DeviceContext;
1369                         DateTime this_month = this.current_month.AddMonths (calendar_index);
1370                         Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1371                         Size month_size = g.MeasureString (this_month.ToString ("MMMM"), this.Font).ToSize ();
1372                         // return only the month name part of that
1373                         return new Rectangle (
1374                                 new Point (
1375                                         title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1376                                         title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1377                                 month_size);
1378                 }
1379
1380                 // returns the rectangle for the year in the title
1381                 internal Rectangle GetYearNameRectangle (Rectangle title_rect, int calendar_index) {                    
1382                         Graphics g = this.DeviceContext;
1383                         DateTime this_month = this.current_month.AddMonths (calendar_index);
1384                         Size title_text_size = g.MeasureString (this_month.ToString ("MMMM yyyy"), this.Font).ToSize ();
1385                         Size year_size = g.MeasureString (this_month.ToString ("yyyy"), this.Font).ToSize ();
1386                         // find out how much space the title took
1387                         Rectangle text_rect =  new Rectangle (
1388                                 new Point (
1389                                         title_rect.X + ((title_rect.Width - title_text_size.Width)/2),
1390                                         title_rect.Y + ((title_rect.Height - title_text_size.Height)/2)),
1391                                 title_text_size);
1392                         // return only the rect of the year
1393                         return new Rectangle (
1394                                 new Point (
1395                                         text_rect.Right - year_size.Width,
1396                                         text_rect.Y),
1397                                 year_size);
1398                 }
1399
1400                 // determine if date is allowed to be drawn in month
1401                 internal bool IsValidWeekToDraw (DateTime month, DateTime date, int row, int col) {
1402                         DateTime tocheck = month.AddMonths (-1);
1403                         if ((month.Year == date.Year && month.Month == date.Month) ||
1404                                 (tocheck.Year == date.Year && tocheck.Month == date.Month)) {
1405                                 return true;
1406                         }
1407
1408                         // check the railing dates (days in the month after the last month in grid)
1409                         if (row == CalendarDimensions.Height - 1 && col == CalendarDimensions.Width - 1) {
1410                                 tocheck = month.AddMonths (1);
1411                                 return (tocheck.Year == date.Year && tocheck.Month == date.Month) ;
1412                         }
1413
1414                         return false;                   
1415                 }
1416
1417                 // set one item clicked and all others off
1418                 private void SetItemClick(HitTestInfo hti) 
1419                 {
1420                         switch(hti.HitArea) {
1421                                 case HitArea.NextMonthButton:
1422                                         this.is_previous_clicked = false;
1423                                         this.is_next_clicked = true;
1424                                         this.is_date_clicked = false;
1425                                         break;
1426                                 case HitArea.PrevMonthButton:
1427                                         this.is_previous_clicked = true;
1428                                         this.is_next_clicked = false;
1429                                         this.is_date_clicked = false;
1430                                         break;
1431                                 case HitArea.PrevMonthDate:
1432                                 case HitArea.NextMonthDate:
1433                                 case HitArea.Date:
1434                                         this.clicked_date = hti.Time;
1435                                         this.is_previous_clicked = false;
1436                                         this.is_next_clicked = false;
1437                                         this.is_date_clicked = true;
1438                                         break;
1439                                 default :
1440                                         this.is_previous_clicked = false;
1441                                         this.is_next_clicked = false;
1442                                         this.is_date_clicked = false;
1443                                         break;
1444                         }
1445                 }
1446
1447                 // called when the year is changed
1448                 private void UpDownYearChangedHandler (object sender, EventArgs e) {
1449                         int initial_year_value = this.CurrentMonth.AddMonths(last_clicked_calendar_index).Year;
1450                         int diff = (int) year_updown.Value - initial_year_value;
1451                         this.CurrentMonth = this.CurrentMonth.AddYears(diff);
1452                 }
1453
1454                 // called when context menu is clicked
1455                 private void MenuItemClickHandler (object sender, EventArgs e) {
1456                         MenuItem item = sender as MenuItem;
1457                         if (item != null && month_title_click_location != Point.Empty) {
1458                                 // establish which month we want to move to
1459                                 if (item.Parent == null) {
1460                                         return;
1461                                 }
1462                                 int new_month = item.Parent.MenuItems.IndexOf (item) + 1;
1463                                 if (new_month == 0) {
1464                                         return;
1465                                 }
1466                                 // okay let's establish which calendar was hit
1467                                 Size month_size = this.SingleMonthSize;
1468                                 for (int i=0; i < CalendarDimensions.Height; i++) {
1469                                         for (int j=0; j < CalendarDimensions.Width; j++) {
1470                                                 int month_index = (i * CalendarDimensions.Width) + j;
1471                                                 Rectangle month_rect = new Rectangle ( new Point (0, 0), month_size);
1472                                                 if (j == 0) {
1473                                                         month_rect.X = this.ClientRectangle.X + 1;
1474                                                 } else {
1475                                                         month_rect.X = this.ClientRectangle.X + 1 + ((j)*(month_size.Width+calendar_spacing.Width));
1476                                                 }
1477                                                 if (i == 0) {
1478                                                         month_rect.Y = this.ClientRectangle.Y + 1;
1479                                                 } else {
1480                                                         month_rect.Y = this.ClientRectangle.Y + 1 + ((i)*(month_size.Height+calendar_spacing.Height));
1481                                                 }
1482                                                 // see if the point is inside
1483                                                 if (month_rect.Contains (month_title_click_location)) {
1484                                                         DateTime clicked_month = CurrentMonth.AddMonths (month_index);
1485                                                         // get the month that we want to move to
1486                                                         int month_offset = new_month - clicked_month.Month;
1487                                                         
1488                                                         // move forward however more months we need to
1489                                                         this.CurrentMonth = this.CurrentMonth.AddMonths (month_offset);
1490                                                         break;
1491                                                 }
1492                                         }
1493                                 }
1494
1495                                 // clear the point
1496                                 month_title_click_location = Point.Empty;
1497                         }
1498                 }
1499                 
1500                 // raised on the timer, for mouse hold clicks
1501                 private void TimerHandler (object sender, EventArgs e) {
1502                         // now find out which area was click
1503                         if (this.Capture) {
1504                                 HitTestInfo hti = this.HitTest (this.PointToClient (MousePosition));
1505                                 // see if it was clicked on the prev or next mouse 
1506                                 if (click_state [1] || click_state [2]) {
1507                                         // invalidate the area where the mouse was last held
1508                                         DoMouseUp ();
1509                                         Application.DoEvents ();
1510                                         // register the click
1511                                         if (hti.HitArea == HitArea.PrevMonthButton ||
1512                                                 hti.HitArea == HitArea.NextMonthButton) {
1513                                                 DoButtonMouseDown (hti);
1514                                                 click_state [1] = (hti.HitArea == HitArea.PrevMonthButton);
1515                                                 click_state [2] = !click_state [1];
1516                                         }
1517                                         if (timer.Interval != 100) {
1518                                                 timer.Interval = 100;
1519                                         }
1520                                 }
1521                         } else  {
1522                                 timer.Enabled = false;
1523                         }
1524                 }
1525                 
1526                 // selects one of the buttons
1527                 private void DoButtonMouseDown (HitTestInfo hti) {
1528                         // show the click then move on
1529                         SetItemClick(hti);
1530                         if (hti.HitArea == HitArea.PrevMonthButton) {
1531                                 // invalidate the prev monthbutton
1532                                 this.Invalidate(
1533                                         new Rectangle (
1534                                                 this.ClientRectangle.X + 1 + button_x_offset,
1535                                                 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1536                                                 button_size.Width,
1537                                                 button_size.Height));
1538                                 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange*-1);
1539                         } else {
1540                                 // invalidate the next monthbutton
1541                                 this.Invalidate(
1542                                         new Rectangle (
1543                                                 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1544                                                 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1545                                                 button_size.Width,
1546                                                 button_size.Height));                                   
1547                                 this.CurrentMonth = this.CurrentMonth.AddMonths (ScrollChange);
1548                         }
1549                 }
1550                 
1551                 // selects the clicked date
1552                 private void DoDateMouseDown (HitTestInfo hti) {
1553                         SetItemClick(hti);
1554                         this.SelectDate (clicked_date);
1555                         this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1556                 }
1557                 
1558                 // event run on the mouse up event
1559                 private void DoMouseUp () {
1560                         // invalidate the next monthbutton
1561                         if (this.is_next_clicked) {
1562                                 this.Invalidate(
1563                                         new Rectangle (
1564                                                 this.ClientRectangle.Right - 1 - button_x_offset - button_size.Width,
1565                                                 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1566                                                 button_size.Width,
1567                                                 button_size.Height));
1568                         }                                       
1569                         // invalidate the prev monthbutton
1570                         if (this.is_previous_clicked) {
1571                                 this.Invalidate(
1572                                         new Rectangle (
1573                                                 this.ClientRectangle.X + 1 + button_x_offset,
1574                                                 this.ClientRectangle.Y + 1 + (title_size.Height - button_size.Height)/2,
1575                                                 button_size.Width,
1576                                                 button_size.Height));
1577                         }
1578                         if (this.is_date_clicked) {
1579                                 // invalidate the area under the cursor, to remove focus rect
1580                                 this.InvalidateDateRange (new SelectionRange (clicked_date, clicked_date));                             
1581                         }
1582                         this.is_previous_clicked = false;
1583                         this.is_next_clicked = false;
1584                         this.is_date_clicked = false;
1585                 }
1586                 
1587 //              // need when in windowed mode
1588 //              private void LostFocusHandler (object sender, EventArgs e) 
1589 //              {
1590 //                      if (this.owner != null) {
1591 //                              if (this.Visible) {
1592 //                                      this.owner.HideMonthCalendar ();
1593 //                              }
1594 //                      }
1595 //              }
1596                 
1597                 // occurs when mouse moves around control, used for selection
1598                 private void MouseMoveHandler (object sender, MouseEventArgs e) {
1599                         HitTestInfo hti = this.HitTest (e.X, e.Y);
1600                         // clear the last clicked item 
1601                         if (click_state [0]) {
1602                                 // register the click
1603                                 if (hti.HitArea == HitArea.PrevMonthDate ||
1604                                         hti.HitArea == HitArea.NextMonthDate ||
1605                                         hti.HitArea == HitArea.Date)
1606                                 {
1607                                         DoDateMouseDown (hti);
1608                                         if (owner == null) {
1609                                                 click_state [0] = true;
1610                                         } else {
1611                                                 click_state [0] = false;
1612                                                 click_state [1] = false;
1613                                                 click_state [2] = false;
1614                                         }
1615                                 }
1616                                 
1617                         }
1618                 }
1619                 
1620                 // to check if the mouse has come down on this control
1621                 private void MouseDownHandler (object sender, MouseEventArgs e)
1622                 {
1623                         // clear the click_state variables
1624                         click_state [0] = false;
1625                         click_state [1] = false;
1626                         click_state [2] = false;
1627
1628                         // disable the timer if it was enabled 
1629                         if (timer.Enabled) {
1630                                 timer.Stop ();
1631                                 timer.Enabled = false;
1632                         }
1633                         
1634                         Point point = new Point (e.X, e.Y);
1635                         // figure out if we are in drop down mode and a click happened outside us
1636                         if (this.owner != null) {
1637                                 if (!this.ClientRectangle.Contains (point)) {
1638                                         this.owner.HideMonthCalendar ();
1639                                         return;                                 
1640                                 }
1641                         }
1642
1643                         //establish where was hit
1644                         HitTestInfo hti = this.HitTest(point);
1645                         // hide the year numeric up down if it was clicked
1646                         if (year_updown != null && year_updown.Visible && hti.HitArea != HitArea.TitleYear)
1647                         {
1648                                 year_updown.Visible = false;
1649                         }
1650                         switch (hti.HitArea) {
1651                                 case HitArea.PrevMonthButton:
1652                                 case HitArea.NextMonthButton:
1653                                         DoButtonMouseDown (hti);
1654                                         click_state [1] = (hti.HitArea == HitArea.PrevMonthDate);
1655                                         click_state [2] = !click_state [1];                                     
1656                                         timer.Interval = 500;
1657                                         timer.Start ();
1658                                         break;
1659                                 case HitArea.Date:
1660                                 case HitArea.PrevMonthDate:
1661                                 case HitArea.NextMonthDate:
1662                                         DoDateMouseDown (hti);
1663                                         // leave clicked state blank if drop down window
1664                                         if (owner == null) {
1665                                                 click_state [0] = true;
1666                                         } else {
1667                                                 click_state [0] = false;
1668                                                 click_state [1] = false;
1669                                                 click_state [2] = false;
1670                                         }
1671                                         break;
1672                                 case HitArea.TitleMonth:
1673                                         month_title_click_location = hti.Point;
1674                                         menu.Show (this, hti.Point);            
1675                                         break;
1676                                 case HitArea.TitleYear:
1677                                         // place the numeric up down
1678                                         PrepareYearUpDown(hti.Point);
1679                                         break;
1680                                 case HitArea.TodayLink:
1681                                         this.SetSelectionRange (DateTime.Now.Date, DateTime.Now.Date);
1682                                         this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1683                                         break;
1684                                 default:
1685                                         this.is_previous_clicked = false;
1686                                         this.is_next_clicked = false;
1687                                         this.is_date_clicked = false;                           
1688                                         break;
1689                         }
1690                 }
1691
1692                 // raised by any key down events
1693                 private void KeyDownHandler (object sender, KeyEventArgs e) {
1694                         // send keys to the year_updown control, let it handle it
1695                         if(year_updown.Visible) {
1696                                 switch (e.KeyCode) {
1697                                         case Keys.Enter:
1698                                                 year_updown.Visible = false;
1699                                                 break;
1700                                         case Keys.Up:
1701                                                 year_updown.Value = year_updown.Value + 1;
1702                                                 break;
1703                                         case Keys.Down:
1704                                                 year_updown.Value = year_updown.Value - 1;
1705                                                 break;
1706                                 }
1707                         } else {
1708                                 if (!is_shift_pressed && e.Shift) {
1709                                         first_select_start_date = SelectionStart;
1710                                         is_shift_pressed = e.Shift;
1711                                 }
1712                                 switch (e.KeyCode) {
1713                                         case Keys.Home:
1714                                                 // set the date to the start of the month
1715                                                 if (is_shift_pressed) {
1716                                                         DateTime date = GetFirstDateInMonth (first_select_start_date);
1717                                                         if (date < first_select_start_date.AddDays ((MaxSelectionCount-1)*-1)) {
1718                                                                 date = first_select_start_date.AddDays ((MaxSelectionCount-1)*-1);
1719                                                         }
1720                                                         this.SetSelectionRange (date, first_select_start_date);
1721                                                 } else {
1722                                                         DateTime date = GetFirstDateInMonth (this.SelectionStart);
1723                                                         this.SetSelectionRange (date, date);
1724                                                 }
1725                                                 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1726                                                 break;
1727                                         case Keys.End:
1728                                                 // set the date to the last of the month
1729                                                 if (is_shift_pressed) {
1730                                                         DateTime date = GetLastDateInMonth (first_select_start_date);
1731                                                         if (date > first_select_start_date.AddDays (MaxSelectionCount-1)) {
1732                                                                 date = first_select_start_date.AddDays (MaxSelectionCount-1);
1733                                                         }
1734                                                         this.SetSelectionRange (date, first_select_start_date);
1735                                                 } else {
1736                                                         DateTime date = GetLastDateInMonth (this.SelectionStart);
1737                                                         this.SetSelectionRange (date, date);
1738                                                 }
1739                                                 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1740                                                 break;
1741                                         case Keys.PageUp:
1742                                                 // set the date to the last of the month
1743                                                 if (is_shift_pressed) {
1744                                                         this.AddTimeToSelection (-1, false);
1745                                                 } else {
1746                                                         DateTime date = this.SelectionStart.AddMonths (-1);
1747                                                         this.SetSelectionRange (date, date);
1748                                                 }
1749                                                 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1750                                                 break;
1751                                         case Keys.PageDown:
1752                                                 // set the date to the last of the month
1753                                                 if (is_shift_pressed) {
1754                                                         this.AddTimeToSelection (1, false);
1755                                                 } else {
1756                                                         DateTime date = this.SelectionStart.AddMonths (1);
1757                                                         this.SetSelectionRange (date, date);
1758                                                 }
1759                                                 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1760                                                 break;
1761                                         case Keys.Up:
1762                                                 // set the back 1 week
1763                                                 if (is_shift_pressed) {
1764                                                         this.AddTimeToSelection (-7, true);                                             
1765                                                 } else {
1766                                                         DateTime date = this.SelectionStart.AddDays (-7);
1767                                                         this.SetSelectionRange (date, date);
1768                                                 }
1769                                                 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1770                                                 break;
1771                                         case Keys.Down:
1772                                                 // set the date forward 1 week
1773                                                 if (is_shift_pressed) {
1774                                                         this.AddTimeToSelection (7, true);
1775                                                 } else {
1776                                                         DateTime date = this.SelectionStart.AddDays (7);
1777                                                         this.SetSelectionRange (date, date);
1778                                                 }
1779                                                 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));                                    
1780                                                 break;
1781                                         case Keys.Left:
1782                                                 // move one left
1783                                                 if (is_shift_pressed) {
1784                                                         this.AddTimeToSelection (-1, true);
1785                                                 } else {
1786                                                         DateTime date = this.SelectionStart.AddDays (-1);
1787                                                         this.SetSelectionRange (date, date);
1788                                                 }
1789                                                 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1790                                                 break;
1791                                         case Keys.Right:
1792                                                 // move one left
1793                                                 if (is_shift_pressed) {
1794                                                         this.AddTimeToSelection (1, true);
1795                                                 } else {
1796                                                         DateTime date = this.SelectionStart.AddDays (1);
1797                                                         this.SetSelectionRange (date, date);
1798                                                 }
1799                                                 this.OnDateSelected (new DateRangeEventArgs (SelectionStart, SelectionEnd));
1800                                                 break;
1801                                         default:
1802                                                 break;
1803                                 }
1804                                 e.Handled = true;
1805                         }
1806                 }
1807
1808                 // to check if the mouse has come up on this control
1809                 private void MouseUpHandler (object sender, MouseEventArgs e)
1810                 {
1811                         if (timer.Enabled) {
1812                                 timer.Stop ();
1813                         }
1814                         // clear the click state array
1815                         click_state [0] = false;
1816                         click_state [1] = false;
1817                         click_state [2] = false;
1818                         // do the regulare mouseup stuff
1819                         this.DoMouseUp ();
1820                 }
1821
1822                 // raised by any key up events
1823                 private void KeyUpHandler (object sender, KeyEventArgs e) {
1824                         is_shift_pressed = e.Shift ;
1825                         e.Handled = true;
1826                 }
1827
1828                 // paint this control now
1829                 private void PaintHandler (object sender, PaintEventArgs pe) {
1830                         if (Width <= 0 || Height <=  0 || Visible == false)
1831                                 return;
1832
1833                         Draw (pe.ClipRectangle, pe.Graphics);
1834
1835                         // fire the new paint handler
1836                         if (this.Paint != null) 
1837                         {
1838                                 this.Paint (sender, pe);
1839                         }
1840                 }
1841                 
1842                 // returns the region of the control that needs to be redrawn 
1843                 private void InvalidateDateRange (SelectionRange range) {
1844                         SelectionRange bounds = this.GetDisplayRange (false);
1845                         if (range.End < bounds.Start || range.Start > bounds.End) {
1846                                 // don't invalidate anything, as the modified date range
1847                                 // is outside the visible bounds of this control
1848                                 return;
1849                         }
1850                         // adjust the start and end to be inside the visible range
1851                         if (range.Start < bounds.Start) {
1852                                 range = new SelectionRange (bounds.Start, range.End);
1853                         }
1854                         if (range.End > bounds.End) {
1855                                 range = new SelectionRange (range.Start, bounds.End);
1856                         }
1857                         // now invalidate the date rectangles as series of rows
1858                         DateTime last_month = this.current_month.AddMonths ((CalendarDimensions.Width * CalendarDimensions.Height)).AddDays (-1);
1859                         DateTime current = range.Start;
1860                         while (current <= range.End) {
1861                                 DateTime month_end = new DateTime (current.Year, current.Month, 1).AddMonths (1).AddDays (-1);;
1862                                 Rectangle start_rect;
1863                                 Rectangle end_rect;
1864                                 // see if entire selection is in this current month
1865                                 if (range.End <= month_end && current < last_month)     {
1866                                         // the end is the last date
1867                                         if (current < this.current_month) {
1868                                                 start_rect = GetDateRowRect (current_month, current_month);
1869                                         } else {
1870                                                 start_rect = GetDateRowRect (current, current);
1871                                         }
1872                                         end_rect = GetDateRowRect (current, range.End);
1873                                 } else if (current < last_month) {
1874                                         // otherwise it simply means we have a selection spaning
1875                                         // multiple months simply set rectangle inside the current month
1876                                         start_rect = GetDateRowRect (current, current);
1877                                         end_rect = GetDateRowRect (last_month, month_end);
1878                                 } else {
1879                                         // it's outside the visible range
1880                                         start_rect = GetDateRowRect (last_month, last_month.AddDays (1));
1881                                         end_rect = GetDateRowRect (last_month, range.End);
1882                                 }
1883                                 // push to the next month
1884                                 current = month_end.AddDays (1);
1885                                 // invalidate from the start row to the end row for this month                          
1886                                 this.Invalidate (
1887                                         new Rectangle (
1888                                                 start_rect.X,
1889                                                 start_rect.Y,
1890                                                 start_rect.Width,
1891                                                 Math.Max (end_rect.Bottom - start_rect.Y, 0)));
1892                                 }
1893                 } 
1894                 
1895                 // gets the rect of the row where the specified date appears on the specified month
1896                 private Rectangle GetDateRowRect (DateTime month, DateTime date) {
1897                         // first get the general rect of the supplied month
1898                         Size month_size = SingleMonthSize;
1899                         Rectangle month_rect = Rectangle.Empty;
1900                         for (int i=0; i < CalendarDimensions.Width*CalendarDimensions.Height; i++) {
1901                                 DateTime this_month = this.current_month.AddMonths (i);
1902                                 if (month.Year == this_month.Year && month.Month == this_month.Month) {
1903                                         month_rect = new Rectangle (
1904                                                 this.ClientRectangle.X + 1 + (month_size.Width * (i%CalendarDimensions.Width)) + (this.calendar_spacing.Width * (i%CalendarDimensions.Width)),
1905                                                 this.ClientRectangle.Y + 1 + (month_size.Height * (i/CalendarDimensions.Width)) + (this.calendar_spacing.Height * (i/CalendarDimensions.Width)),
1906                                                 month_size.Width,
1907                                                 month_size.Height);
1908                                                 break;          
1909                                 }
1910                         }
1911                         // now find out where in the month the supplied date is
1912                         if (month_rect == Rectangle.Empty) {
1913                                 return Rectangle.Empty;
1914                         }
1915                         // find out which row this date is in
1916                         int row = -1;
1917                         DateTime first_date = GetFirstDateInMonthGrid (month);
1918                         DateTime end_date = first_date.AddDays (7); 
1919                         for (int i=0; i < 6; i++) {
1920                                 if (date >= first_date && date < end_date) {
1921                                         row = i;
1922                                         break;
1923                                 }
1924                                 first_date = end_date;
1925                                 end_date = end_date.AddDays (7);
1926                         }
1927                         // ensure it's a valid row
1928                         if (row < 0) {
1929                                 return Rectangle.Empty;
1930                         }
1931                         int x_offset = (this.ShowWeekNumbers) ? date_cell_size.Width : 0;
1932                         int y_offset = title_size.Height + (date_cell_size.Height * (row + 1));
1933                         return new Rectangle (
1934                                 month_rect.X + x_offset,
1935                                 month_rect.Y + y_offset,
1936                                 date_cell_size.Width * 7,
1937                                 date_cell_size.Height);
1938                 }
1939
1940                 internal void Draw (Rectangle clip_rect, Graphics dc)
1941                 {
1942                         ThemeEngine.Current.DrawMonthCalendar (dc, clip_rect, this);
1943                 }
1944
1945                 #endregion      //internal methods
1946
1947                 #region internal drawing methods
1948
1949
1950                 #endregion      // internal drawing methods
1951
1952                 #region inner classes and enumerations
1953
1954                 // enumeration about what type of area on the calendar was hit 
1955                 public enum HitArea {
1956                         Nowhere,
1957                         TitleBackground,
1958                         TitleMonth,
1959                         TitleYear,
1960                         NextMonthButton,
1961                         PrevMonthButton,
1962                         CalendarBackground,
1963                         Date,
1964                         NextMonthDate,
1965                         PrevMonthDate,
1966                         DayOfWeek,
1967                         WeekNumbers,
1968                         TodayLink
1969                 }
1970                 
1971                 // info regarding to a hit test on this calendar
1972                 public sealed class HitTestInfo {
1973
1974                         private HitArea hit_area;
1975                         private Point point;
1976                         private DateTime time;
1977
1978                         // default constructor
1979                         internal HitTestInfo () {
1980                                 hit_area = HitArea.Nowhere;
1981                                 point = new Point (0, 0);
1982                                 time = DateTime.Now;
1983                         }
1984
1985                         // overload receives all properties
1986                         internal HitTestInfo (HitArea hit_area, Point point, DateTime time) {
1987                                 this.hit_area = hit_area;
1988                                 this.point = point;
1989                                 this.time = time;
1990                         }
1991
1992                         // the type of area that was hit
1993                         public HitArea HitArea {
1994                                 get {
1995                                         return hit_area;
1996                                 }
1997                         }
1998
1999                         // the point that is being test
2000                         public Point Point {
2001                                 get {
2002                                         return point;
2003                                 }
2004                         }
2005                         
2006                         // the date under the hit test point, only valid if HitArea is Date
2007                         public DateTime Time {
2008                                 get {
2009                                         return time;
2010                                 }
2011                         }
2012                 }
2013
2014                 #endregion      // inner classes
2015         }
2016 }