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