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