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