]> git.saurik.com Git - wxWidgets.git/blob - src/generic/calctrl.cpp
call InitDialog() before showing the dialog, not after; also TRUE -> true
[wxWidgets.git] / src / generic / calctrl.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name:        generic/calctrl.cpp
3 // Purpose:     implementation fo the generic wxCalendarCtrl
4 // Author:      Vadim Zeitlin
5 // Modified by:
6 // Created:     29.12.99
7 // RCS-ID:      $Id$
8 // Copyright:   (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence:     wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
21     #pragma implementation "calctrl.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28     #pragma hdrstop
29 #endif
30
31 #ifndef WX_PRECOMP
32     #include "wx/dcclient.h"
33     #include "wx/settings.h"
34     #include "wx/brush.h"
35     #include "wx/combobox.h"
36     #include "wx/stattext.h"
37     #include "wx/textctrl.h"
38 #endif //WX_PRECOMP
39
40 #if wxUSE_CALENDARCTRL
41
42 #include "wx/spinctrl.h"
43
44 #include "wx/calctrl.h"
45
46 #define DEBUG_PAINT 0
47
48 // ----------------------------------------------------------------------------
49 // private classes
50 // ----------------------------------------------------------------------------
51
52 class wxMonthComboBox : public wxComboBox
53 {
54 public:
55     wxMonthComboBox(wxCalendarCtrl *cal);
56
57     void OnMonthChange(wxCommandEvent& event) { m_cal->OnMonthChange(event); }
58
59 private:
60     wxCalendarCtrl *m_cal;
61
62     DECLARE_EVENT_TABLE()
63     DECLARE_NO_COPY_CLASS(wxMonthComboBox)
64 };
65
66 class wxYearSpinCtrl : public wxSpinCtrl
67 {
68 public:
69     wxYearSpinCtrl(wxCalendarCtrl *cal);
70
71     void OnYearTextChange(wxCommandEvent& event) 
72     {
73         m_cal->SetUserChangedYear();
74         m_cal->OnYearChange(event);
75     }
76     void OnYearChange(wxSpinEvent& event) { m_cal->OnYearChange(event); }
77
78 private:
79     wxCalendarCtrl *m_cal;
80
81     DECLARE_EVENT_TABLE()
82     DECLARE_NO_COPY_CLASS(wxYearSpinCtrl)
83 };
84
85 // ----------------------------------------------------------------------------
86 // wxWin macros
87 // ----------------------------------------------------------------------------
88
89 BEGIN_EVENT_TABLE(wxCalendarCtrl, wxControl)
90     EVT_PAINT(wxCalendarCtrl::OnPaint)
91
92     EVT_CHAR(wxCalendarCtrl::OnChar)
93
94     EVT_LEFT_DOWN(wxCalendarCtrl::OnClick)
95     EVT_LEFT_DCLICK(wxCalendarCtrl::OnDClick)
96 END_EVENT_TABLE()
97
98 BEGIN_EVENT_TABLE(wxMonthComboBox, wxComboBox)
99     EVT_COMBOBOX(-1, wxMonthComboBox::OnMonthChange)
100 END_EVENT_TABLE()
101
102 BEGIN_EVENT_TABLE(wxYearSpinCtrl, wxSpinCtrl)
103     EVT_TEXT(-1, wxYearSpinCtrl::OnYearTextChange)
104     EVT_SPINCTRL(-1, wxYearSpinCtrl::OnYearChange)
105 END_EVENT_TABLE()
106
107 #if wxUSE_EXTENDED_RTTI
108 WX_DEFINE_FLAGS( wxCalendarCtrlStyle )
109
110 wxBEGIN_FLAGS( wxCalendarCtrlStyle )
111     // new style border flags, we put them first to
112     // use them for streaming out
113     wxFLAGS_MEMBER(wxBORDER_SIMPLE)
114     wxFLAGS_MEMBER(wxBORDER_SUNKEN)
115     wxFLAGS_MEMBER(wxBORDER_DOUBLE)
116     wxFLAGS_MEMBER(wxBORDER_RAISED)
117     wxFLAGS_MEMBER(wxBORDER_STATIC)
118     wxFLAGS_MEMBER(wxBORDER_NONE)
119     
120     // old style border flags
121     wxFLAGS_MEMBER(wxSIMPLE_BORDER)
122     wxFLAGS_MEMBER(wxSUNKEN_BORDER)
123     wxFLAGS_MEMBER(wxDOUBLE_BORDER)
124     wxFLAGS_MEMBER(wxRAISED_BORDER)
125     wxFLAGS_MEMBER(wxSTATIC_BORDER)
126     wxFLAGS_MEMBER(wxBORDER)
127
128     // standard window styles
129     wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
130     wxFLAGS_MEMBER(wxCLIP_CHILDREN)
131     wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
132     wxFLAGS_MEMBER(wxWANTS_CHARS)
133     wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
134     wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
135     wxFLAGS_MEMBER(wxVSCROLL)
136     wxFLAGS_MEMBER(wxHSCROLL)
137
138     wxFLAGS_MEMBER(wxCAL_SUNDAY_FIRST)
139     wxFLAGS_MEMBER(wxCAL_MONDAY_FIRST)
140     wxFLAGS_MEMBER(wxCAL_SHOW_HOLIDAYS)
141     wxFLAGS_MEMBER(wxCAL_NO_YEAR_CHANGE)
142     wxFLAGS_MEMBER(wxCAL_NO_MONTH_CHANGE)
143     wxFLAGS_MEMBER(wxCAL_SEQUENTIAL_MONTH_SELECTION)
144     wxFLAGS_MEMBER(wxCAL_SHOW_SURROUNDING_WEEKS)
145
146 wxEND_FLAGS( wxCalendarCtrlStyle )
147
148 IMPLEMENT_DYNAMIC_CLASS_XTI(wxCalendarCtrl, wxControl,"wx/calctrl.h")
149
150 wxBEGIN_PROPERTIES_TABLE(wxCalendarCtrl)
151     wxEVENT_RANGE_PROPERTY( Updated , wxEVT_CALENDAR_SEL_CHANGED , wxEVT_CALENDAR_WEEKDAY_CLICKED , wxCalendarEvent )
152     wxHIDE_PROPERTY( Children )
153         wxPROPERTY( Date,wxDateTime, SetDate , GetDate, , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
154     wxPROPERTY_FLAGS( WindowStyle , wxCalendarCtrlStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
155 wxEND_PROPERTIES_TABLE()
156
157 wxBEGIN_HANDLERS_TABLE(wxCalendarCtrl)
158 wxEND_HANDLERS_TABLE()
159
160 wxCONSTRUCTOR_6( wxCalendarCtrl , wxWindow* , Parent , wxWindowID , Id , wxDateTime , Date , wxPoint , Position , wxSize , Size , long , WindowStyle ) 
161 #else
162 IMPLEMENT_DYNAMIC_CLASS(wxCalendarCtrl, wxControl)
163 #endif
164 IMPLEMENT_DYNAMIC_CLASS(wxCalendarEvent, wxCommandEvent)
165
166 // ----------------------------------------------------------------------------
167 // events
168 // ----------------------------------------------------------------------------
169
170 DEFINE_EVENT_TYPE(wxEVT_CALENDAR_SEL_CHANGED)
171 DEFINE_EVENT_TYPE(wxEVT_CALENDAR_DAY_CHANGED)
172 DEFINE_EVENT_TYPE(wxEVT_CALENDAR_MONTH_CHANGED)
173 DEFINE_EVENT_TYPE(wxEVT_CALENDAR_YEAR_CHANGED)
174 DEFINE_EVENT_TYPE(wxEVT_CALENDAR_DOUBLECLICKED)
175 DEFINE_EVENT_TYPE(wxEVT_CALENDAR_WEEKDAY_CLICKED)
176
177 // ============================================================================
178 // implementation
179 // ============================================================================
180
181 // ----------------------------------------------------------------------------
182 // wxMonthComboBox and wxYearSpinCtrl
183 // ----------------------------------------------------------------------------
184
185 wxMonthComboBox::wxMonthComboBox(wxCalendarCtrl *cal)
186                : wxComboBox(cal->GetParent(), -1,
187                             wxEmptyString,
188                             wxDefaultPosition,
189                             wxDefaultSize,
190                             0, NULL,
191                             wxCB_READONLY | wxCLIP_SIBLINGS)
192 {
193     m_cal = cal;
194
195     wxDateTime::Month m;
196     for ( m = wxDateTime::Jan; m < wxDateTime::Inv_Month; wxNextMonth(m) )
197     {
198         Append(wxDateTime::GetMonthName(m));
199     }
200
201     SetSelection(m_cal->GetDate().GetMonth());
202     SetSize(-1, -1, -1, -1, wxSIZE_AUTO_WIDTH|wxSIZE_AUTO_HEIGHT);
203 }
204
205 wxYearSpinCtrl::wxYearSpinCtrl(wxCalendarCtrl *cal)
206               : wxSpinCtrl(cal->GetParent(), -1,
207                            cal->GetDate().Format(_T("%Y")),
208                            wxDefaultPosition,
209                            wxDefaultSize,
210                            wxSP_ARROW_KEYS | wxCLIP_SIBLINGS,
211                            -4300, 10000, cal->GetDate().GetYear())
212
213 {
214     m_cal = cal;
215 }
216
217 // ----------------------------------------------------------------------------
218 // wxCalendarCtrl
219 // ----------------------------------------------------------------------------
220
221 wxCalendarCtrl::wxCalendarCtrl(wxWindow *parent,
222                    wxWindowID id,
223                    const wxDateTime& date,
224                    const wxPoint& pos,
225                    const wxSize& size,
226                    long style,
227                    const wxString& name)
228 {
229     Init();
230     
231     (void)Create(parent, id, date, pos, size, style, name);
232 }
233
234 void wxCalendarCtrl::Init()
235 {
236     m_comboMonth = NULL;
237     m_spinYear = NULL;
238     m_staticYear = NULL;
239     m_staticMonth = NULL;
240
241     m_userChangedYear = FALSE;
242
243     m_widthCol =
244     m_heightRow = 0;
245
246     wxDateTime::WeekDay wd;
247     for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
248     {
249         m_weekdays[wd] = wxDateTime::GetWeekDayName(wd, wxDateTime::Name_Abbr);
250     }
251
252     for ( size_t n = 0; n < WXSIZEOF(m_attrs); n++ )
253     {
254         m_attrs[n] = NULL;
255     }
256
257     m_colHighlightFg = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT);
258     m_colHighlightBg = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
259
260     m_colHolidayFg = *wxRED;
261     // don't set m_colHolidayBg - by default, same as our bg colour
262
263     m_colHeaderFg = *wxBLUE;
264     m_colHeaderBg = *wxLIGHT_GREY;
265 }
266
267 bool wxCalendarCtrl::Create(wxWindow *parent,
268                             wxWindowID id,
269                             const wxDateTime& date,
270                             const wxPoint& pos,
271                             const wxSize& size,
272                             long style,
273                             const wxString& name)
274 {
275     if ( !wxControl::Create(parent, id, pos, size,
276                             style | wxCLIP_CHILDREN | wxWANTS_CHARS,
277                             wxDefaultValidator, name) )
278     {
279         return FALSE;
280     }
281
282     // needed to get the arrow keys normally used for the dialog navigation
283     SetWindowStyle(style | wxWANTS_CHARS);
284
285     m_date = date.IsValid() ? date : wxDateTime::Today();
286
287     m_lowdate = wxDefaultDateTime;
288     m_highdate = wxDefaultDateTime;
289
290     if ( !HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
291     {
292         m_spinYear = new wxYearSpinCtrl(this);
293         m_staticYear = new wxStaticText(GetParent(), -1, m_date.Format(_T("%Y")),
294                                         wxDefaultPosition, wxDefaultSize,
295                                         wxALIGN_CENTRE);
296
297         m_comboMonth = new wxMonthComboBox(this);
298         m_staticMonth = new wxStaticText(GetParent(), -1, m_date.Format(_T("%B")),
299                                          wxDefaultPosition, wxDefaultSize,
300                                          wxALIGN_CENTRE);
301     }
302
303     ShowCurrentControls();
304
305     wxSize sizeReal;
306     if ( size.x == -1 || size.y == -1 )
307     {
308         sizeReal = DoGetBestSize();
309         if ( size.x != -1 )
310             sizeReal.x = size.x;
311         if ( size.y != -1 )
312             sizeReal.y = size.y;
313     }
314     else
315     {
316         sizeReal = size;
317     }
318
319     // we need to set the position as well because the main control position
320     // is not the same as the one specified in pos if we have the controls
321     // above it
322     SetSize(pos.x, pos.y, sizeReal.x, sizeReal.y);
323
324     SetBackgroundColour(*wxWHITE);
325     SetFont(*wxSWISS_FONT);
326
327     SetHolidayAttrs();
328
329     return TRUE;
330 }
331
332 wxCalendarCtrl::~wxCalendarCtrl()
333 {
334     for ( size_t n = 0; n < WXSIZEOF(m_attrs); n++ )
335     {
336         delete m_attrs[n];
337     }
338 }
339
340 // ----------------------------------------------------------------------------
341 // forward wxWin functions to subcontrols
342 // ----------------------------------------------------------------------------
343
344 bool wxCalendarCtrl::Destroy()
345 {
346     if ( m_staticYear )
347         m_staticYear->Destroy();
348     if ( m_spinYear )
349         m_spinYear->Destroy();
350     if ( m_comboMonth )
351         m_comboMonth->Destroy();
352     if ( m_staticMonth )
353         m_staticMonth->Destroy();
354
355     m_staticYear = NULL;
356     m_spinYear = NULL;
357     m_comboMonth = NULL;
358     m_staticMonth = NULL;
359
360     return wxControl::Destroy();
361 }
362
363 bool wxCalendarCtrl::Show(bool show)
364 {
365     if ( !wxControl::Show(show) )
366     {
367         return FALSE;
368     }
369
370     if ( !(GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) )
371     {
372         if ( GetMonthControl() )
373         {
374             GetMonthControl()->Show(show);
375             GetYearControl()->Show(show);
376         }
377     }
378
379     return TRUE;
380 }
381
382 bool wxCalendarCtrl::Enable(bool enable)
383 {
384     if ( !wxControl::Enable(enable) )
385     {
386         return FALSE;
387     }
388
389     if ( !(GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) )
390     {
391         GetMonthControl()->Enable(enable);
392         GetYearControl()->Enable(enable);
393     }
394
395     return TRUE;
396 }
397
398 // ----------------------------------------------------------------------------
399 // enable/disable month/year controls
400 // ----------------------------------------------------------------------------
401
402 void wxCalendarCtrl::ShowCurrentControls()
403 {
404     if ( !HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
405     {
406         if ( AllowMonthChange() )
407         {
408             m_comboMonth->Show();
409             m_staticMonth->Hide();
410
411             if ( AllowYearChange() )
412             {
413                 m_spinYear->Show();
414                 m_staticYear->Hide();
415
416                 // skip the rest
417                 return;
418             }
419         }
420         else
421         {
422             m_comboMonth->Hide();
423             m_staticMonth->Show();
424         }
425
426         // year change not allowed here
427         m_spinYear->Hide();
428         m_staticYear->Show();
429     }
430 }
431
432 wxControl *wxCalendarCtrl::GetMonthControl() const
433 {
434     return AllowMonthChange() ? (wxControl *)m_comboMonth : (wxControl *)m_staticMonth;
435 }
436
437 wxControl *wxCalendarCtrl::GetYearControl() const
438 {
439     return AllowYearChange() ? (wxControl *)m_spinYear : (wxControl *)m_staticYear;
440 }
441
442 void wxCalendarCtrl::EnableYearChange(bool enable)
443 {
444     if ( enable != AllowYearChange() )
445     {
446         long style = GetWindowStyle();
447         if ( enable )
448             style &= ~wxCAL_NO_YEAR_CHANGE;
449         else
450             style |= wxCAL_NO_YEAR_CHANGE;
451         SetWindowStyle(style);
452
453         ShowCurrentControls();
454         if ( GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION )
455         {
456             Refresh();
457         }
458     }
459 }
460
461 void wxCalendarCtrl::EnableMonthChange(bool enable)
462 {
463     if ( enable != AllowMonthChange() )
464     {
465         long style = GetWindowStyle();
466         if ( enable )
467             style &= ~wxCAL_NO_MONTH_CHANGE;
468         else
469             style |= wxCAL_NO_MONTH_CHANGE;
470         SetWindowStyle(style);
471
472         ShowCurrentControls();
473         if ( GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION )
474         {
475             Refresh();
476         }
477     }
478 }
479
480 // ----------------------------------------------------------------------------
481 // changing date
482 // ----------------------------------------------------------------------------
483
484 bool wxCalendarCtrl::SetDate(const wxDateTime& date)
485 {
486     bool retval = TRUE;
487
488     bool sameMonth = m_date.GetMonth() == date.GetMonth(),
489          sameYear = m_date.GetYear() == date.GetYear();
490
491     if ( IsDateInRange(date) )
492     {
493         if ( sameMonth && sameYear )
494         {
495             // just change the day
496             ChangeDay(date);
497         }
498         else
499         {
500             if ( AllowMonthChange() && (AllowYearChange() || sameYear) )
501             {
502                 // change everything
503                 m_date = date;
504
505                 if ( !(GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) )
506                 {
507                     // update the controls
508                     m_comboMonth->SetSelection(m_date.GetMonth());
509
510                     if ( AllowYearChange() )
511                     {
512                         if ( !m_userChangedYear )
513                             m_spinYear->SetValue(m_date.Format(_T("%Y")));
514                     }
515                 }
516
517                 // as the month changed, holidays did too
518                 SetHolidayAttrs();
519
520                 // update the calendar
521                 Refresh();
522             }
523             else
524             {
525                 // forbidden
526                 retval = FALSE;
527             }
528         }
529     }
530
531     m_userChangedYear = FALSE;
532     
533     return retval;
534 }
535
536 void wxCalendarCtrl::ChangeDay(const wxDateTime& date)
537 {
538     if ( m_date != date )
539     {
540         // we need to refresh the row containing the old date and the one
541         // containing the new one
542         wxDateTime dateOld = m_date;
543         m_date = date;
544
545         RefreshDate(dateOld);
546
547         // if the date is in the same row, it was already drawn correctly
548         if ( GetWeek(m_date) != GetWeek(dateOld) )
549         {
550             RefreshDate(m_date);
551         }
552     }
553 }
554
555 void wxCalendarCtrl::SetDateAndNotify(const wxDateTime& date)
556 {
557     wxDateTime::Tm tm1 = m_date.GetTm(),
558                    tm2 = date.GetTm();
559
560     wxEventType type;
561     if ( tm1.year != tm2.year )
562         type = wxEVT_CALENDAR_YEAR_CHANGED;
563     else if ( tm1.mon != tm2.mon )
564         type = wxEVT_CALENDAR_MONTH_CHANGED;
565     else if ( tm1.mday != tm2.mday )
566         type = wxEVT_CALENDAR_DAY_CHANGED;
567     else
568         return;
569
570     if ( SetDate(date) )
571     {
572         GenerateEvents(type, wxEVT_CALENDAR_SEL_CHANGED);
573     }
574 }
575
576 // ----------------------------------------------------------------------------
577 // date range
578 // ----------------------------------------------------------------------------
579
580 bool wxCalendarCtrl::SetLowerDateLimit(const wxDateTime& date /* = wxDefaultDateTime */)
581 {
582     bool retval = TRUE;
583
584     if ( !(date.IsValid()) || ( ( m_highdate.IsValid() ) ? ( date <= m_highdate ) : TRUE ) )
585     {
586         m_lowdate = date;
587     }
588     else
589     {
590         retval = FALSE;
591     }
592
593     return retval;
594 }
595
596 bool wxCalendarCtrl::SetUpperDateLimit(const wxDateTime& date /* = wxDefaultDateTime */)
597 {
598     bool retval = TRUE;
599
600     if ( !(date.IsValid()) || ( ( m_lowdate.IsValid() ) ? ( date >= m_lowdate ) : TRUE ) )
601     {
602         m_highdate = date;
603     }
604     else
605     {
606         retval = FALSE;
607     }
608
609     return retval;
610 }
611
612 bool wxCalendarCtrl::SetDateRange(const wxDateTime& lowerdate /* = wxDefaultDateTime */, const wxDateTime& upperdate /* = wxDefaultDateTime */)
613 {
614     bool retval = TRUE;
615
616     if (
617         ( !( lowerdate.IsValid() ) || ( ( upperdate.IsValid() ) ? ( lowerdate <= upperdate ) : TRUE ) ) &&
618         ( !( upperdate.IsValid() ) || ( ( lowerdate.IsValid() ) ? ( upperdate >= lowerdate ) : TRUE ) ) )
619     {
620         m_lowdate = lowerdate;
621         m_highdate = upperdate;
622     }
623     else
624     {
625         retval = FALSE;
626     }
627
628     return retval;
629 }
630
631 // ----------------------------------------------------------------------------
632 // date helpers
633 // ----------------------------------------------------------------------------
634
635 wxDateTime wxCalendarCtrl::GetStartDate() const
636 {
637     wxDateTime::Tm tm = m_date.GetTm();
638
639     wxDateTime date = wxDateTime(1, tm.mon, tm.year);
640
641     // rewind back
642     date.SetToPrevWeekDay(GetWindowStyle() & wxCAL_MONDAY_FIRST
643                           ? wxDateTime::Mon : wxDateTime::Sun);
644
645     if ( GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS )
646     {
647         // We want to offset the calendar if we start on the first..
648         if ( date.GetDay() == 1 )
649         {
650             date -= wxDateSpan::Week();
651         }
652     }
653
654     return date;
655 }
656
657 bool wxCalendarCtrl::IsDateShown(const wxDateTime& date) const
658 {
659     if ( !(GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS) )
660     {
661         return date.GetMonth() == m_date.GetMonth();
662     }
663     else
664     {
665         return TRUE;
666     }
667 }
668
669 bool wxCalendarCtrl::IsDateInRange(const wxDateTime& date) const
670 {
671     // Check if the given date is in the range specified
672     return ( ( ( m_lowdate.IsValid() ) ? ( date >= m_lowdate ) : TRUE )
673         && ( ( m_highdate.IsValid() ) ? ( date <= m_highdate ) : TRUE ) );
674 }
675
676 bool wxCalendarCtrl::ChangeYear(wxDateTime* target) const
677 {
678     bool retval = FALSE;
679
680     if ( !(IsDateInRange(*target)) )
681     {
682         if ( target->GetYear() < m_date.GetYear() )
683         {
684             if ( target->GetYear() >= GetLowerDateLimit().GetYear() )
685             {
686                 *target = GetLowerDateLimit();
687                 retval = TRUE;
688             }
689             else
690             {
691                 *target = m_date;
692             }
693         }
694         else
695         {
696             if ( target->GetYear() <= GetUpperDateLimit().GetYear() )
697             {
698                 *target = GetUpperDateLimit();
699                 retval = TRUE;
700             }
701             else
702             {
703                 *target = m_date;
704             }
705         }
706     }
707     else
708     {
709         retval = TRUE;
710     }
711
712     return retval;
713 }
714
715 bool wxCalendarCtrl::ChangeMonth(wxDateTime* target) const
716 {
717     bool retval = TRUE;
718
719     if ( !(IsDateInRange(*target)) )
720     {
721         retval = FALSE;
722
723         if ( target->GetMonth() < m_date.GetMonth() )
724         {
725             *target = GetLowerDateLimit();
726         }
727         else
728         {
729             *target = GetUpperDateLimit();
730         }
731     }
732
733     return retval;
734 }
735
736 size_t wxCalendarCtrl::GetWeek(const wxDateTime& date) const
737 {
738     size_t retval = date.GetWeekOfMonth(GetWindowStyle() & wxCAL_MONDAY_FIRST
739                                    ? wxDateTime::Monday_First
740                                    : wxDateTime::Sunday_First);
741
742     if ( (GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS) )
743     {
744         // we need to offset an extra week if we "start" on the 1st of the month
745         wxDateTime::Tm tm = date.GetTm();
746
747         wxDateTime datetest = wxDateTime(1, tm.mon, tm.year);
748
749         // rewind back
750         datetest.SetToPrevWeekDay(GetWindowStyle() & wxCAL_MONDAY_FIRST
751                               ? wxDateTime::Mon : wxDateTime::Sun);
752
753         if ( datetest.GetDay() == 1 )
754         {
755             retval += 1;
756         }
757     }
758
759     return retval;
760 }
761
762 // ----------------------------------------------------------------------------
763 // size management
764 // ----------------------------------------------------------------------------
765
766 // this is a composite control and it must arrange its parts each time its
767 // size or position changes: the combobox and spinctrl are along the top of
768 // the available area and the calendar takes up therest of the space
769
770 // the static controls are supposed to be always smaller than combo/spin so we
771 // always use the latter for size calculations and position the static to take
772 // the same space
773
774 // the constants used for the layout
775 #define VERT_MARGIN     5           // distance between combo and calendar
776 #ifdef __WXMAC__
777 #define HORZ_MARGIN    5           //                            spin
778 #else
779 #define HORZ_MARGIN    15           //                            spin
780 #endif
781 wxSize wxCalendarCtrl::DoGetBestSize() const
782 {
783     // calc the size of the calendar
784     ((wxCalendarCtrl *)this)->RecalcGeometry(); // const_cast
785
786     wxCoord width = 7*m_widthCol,
787             height = 7*m_heightRow + m_rowOffset + VERT_MARGIN;
788
789     if ( !HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
790     {
791         // the combobox doesn't report its height correctly (it returns the
792         // height including the drop down list) so don't use it
793         height += m_spinYear->GetBestSize().y;
794     }
795
796     if ( !HasFlag(wxBORDER_NONE) )
797     {
798         // the border would clip the last line otherwise
799         height += 6;
800         width += 4;
801     }
802
803     return wxSize(width, height);
804 }
805
806 void wxCalendarCtrl::DoSetSize(int x, int y,
807                                int width, int height,
808                                int sizeFlags)
809 {
810     wxControl::DoSetSize(x, y, width, height, sizeFlags);
811 }
812
813 void wxCalendarCtrl::DoMoveWindow(int x, int y, int width, int height)
814 {
815     int yDiff;
816
817     if ( !HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
818     {
819         wxSize sizeCombo = m_comboMonth->GetSize();
820         wxSize sizeStatic = m_staticMonth->GetSize();
821         wxSize sizeSpin = m_spinYear->GetSize();
822         int dy = (sizeCombo.y - sizeStatic.y) / 2;
823 /*
824 In the calender the size of the combobox for the year 
825 is just defined by a margin from the month combobox to 
826 the left border. While in wxUniv the year control can't 
827 show all 4 digits, in wxMsw it show almost twice as 
828 much. Instead the year should use it's best size and be 
829 left aligned to the calendar. Just in case the month in 
830 any language is longer than it has space in the 
831 calendar it is shortend.This way the year always can 
832 show the 4 digits. 
833
834 This patch relies on the fact that a combobox has a 
835 good best size implementation. This is not the case 
836 with wxMSW but I don't know why.
837
838 Otto Wyss
839 */
840
841 #ifdef __WXUNIVERSAL__
842         if (sizeCombo.x + HORZ_MARGIN - sizeSpin.x > width)
843         {
844             m_comboMonth->SetSize(x, y, width - HORZ_MARGIN - sizeSpin.x, sizeCombo.y);
845         }
846         else
847         {
848             m_comboMonth->Move(x, y);
849         }
850         m_staticMonth->Move(x, y + dy);
851         m_spinYear->Move(x + width - sizeSpin.x, y);
852         m_staticYear->Move(x + width - sizeSpin.x, y + dy);
853 #else
854         m_comboMonth->Move(x, y);
855         m_staticMonth->SetSize(x, y + dy, sizeCombo.x, sizeStatic.y);
856
857         int xDiff = sizeCombo.x + HORZ_MARGIN;
858
859         m_spinYear->SetSize(x + xDiff, y, width - xDiff, sizeCombo.y);
860         m_staticYear->SetSize(x + xDiff, y + dy, width - xDiff, sizeStatic.y);
861 #endif
862         yDiff = wxMax(sizeSpin.y, sizeCombo.y) + VERT_MARGIN;
863     }
864     else // no controls on the top
865     {
866         yDiff = 0;
867     }
868
869     wxControl::DoMoveWindow(x, y + yDiff, width, height - yDiff);
870 }
871
872 void wxCalendarCtrl::DoGetPosition(int *x, int *y) const
873 {
874     wxControl::DoGetPosition(x, y);
875
876     if ( !(GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) )
877     {
878         // our real top corner is not in this position
879         if ( y )
880         {
881             *y -= GetMonthControl()->GetSize().y + VERT_MARGIN;
882         }
883     }
884 }
885
886 void wxCalendarCtrl::DoGetSize(int *width, int *height) const
887 {
888     wxControl::DoGetSize(width, height);
889
890     if ( !(GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) )
891     {
892         // our real height is bigger
893         if ( height && GetMonthControl())
894         {
895             *height += GetMonthControl()->GetSize().y + VERT_MARGIN;
896         }
897     }
898 }
899
900 void wxCalendarCtrl::RecalcGeometry()
901 {
902     if ( m_widthCol != 0 )
903         return;
904
905     wxClientDC dc(this);
906
907     dc.SetFont(m_font);
908
909     // determine the column width (we assume that the weekday names are always
910     // wider (in any language) than the numbers)
911     m_widthCol = 0;
912     wxDateTime::WeekDay wd;
913     for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
914     {
915         wxCoord width;
916         dc.GetTextExtent(m_weekdays[wd], &width, &m_heightRow);
917         if ( width > m_widthCol )
918         {
919             m_widthCol = width;
920         }
921     }
922
923     // leave some margins
924     m_widthCol += 2;
925     m_heightRow += 2;
926
927     m_rowOffset = (GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) ? m_heightRow : 0; // conditional in relation to style
928 }
929
930 // ----------------------------------------------------------------------------
931 // drawing
932 // ----------------------------------------------------------------------------
933
934 void wxCalendarCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
935 {
936     wxPaintDC dc(this);
937
938     dc.SetFont(m_font);
939
940     RecalcGeometry();
941
942 #if DEBUG_PAINT
943     wxLogDebug("--- starting to paint, selection: %s, week %u\n",
944            m_date.Format("%a %d-%m-%Y %H:%M:%S").c_str(),
945            GetWeek(m_date));
946 #endif
947
948     wxCoord y = 0;
949
950     if ( HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
951     {
952         // draw the sequential month-selector
953
954         dc.SetBackgroundMode(wxTRANSPARENT);
955         dc.SetTextForeground(*wxBLACK);
956         dc.SetBrush(wxBrush(m_colHeaderBg, wxSOLID));
957         dc.SetPen(wxPen(m_colHeaderBg, 1, wxSOLID));
958         dc.DrawRectangle(0, y, GetClientSize().x, m_heightRow);
959
960         // Get extent of month-name + year
961         wxCoord monthw, monthh;
962         wxString headertext = m_date.Format(wxT("%B %Y"));
963         dc.GetTextExtent(headertext, &monthw, &monthh);
964
965         // draw month-name centered above weekdays
966         wxCoord monthx = ((m_widthCol * 7) - monthw) / 2;
967         wxCoord monthy = ((m_heightRow - monthh) / 2) + y;
968         dc.DrawText(headertext, monthx,  monthy);
969
970         // calculate the "month-arrows"
971         wxPoint leftarrow[3];
972         wxPoint rightarrow[3];
973
974         int arrowheight = monthh / 2;
975
976         leftarrow[0] = wxPoint(0, arrowheight / 2);
977         leftarrow[1] = wxPoint(arrowheight / 2, 0);
978         leftarrow[2] = wxPoint(arrowheight / 2, arrowheight - 1);
979
980         rightarrow[0] = wxPoint(0, 0);
981         rightarrow[1] = wxPoint(arrowheight / 2, arrowheight / 2);
982         rightarrow[2] = wxPoint(0, arrowheight - 1);
983
984         // draw the "month-arrows"
985
986         wxCoord arrowy = (m_heightRow - arrowheight) / 2;
987         wxCoord larrowx = (m_widthCol - (arrowheight / 2)) / 2;
988         wxCoord rarrowx = ((m_widthCol - (arrowheight / 2)) / 2) + m_widthCol*6;
989         m_leftArrowRect = wxRect(0, 0, 0, 0);
990         m_rightArrowRect = wxRect(0, 0, 0, 0);
991
992         if ( AllowMonthChange() )
993         {
994             wxDateTime ldpm = wxDateTime(1,m_date.GetMonth(), m_date.GetYear()) - wxDateSpan::Day(); // last day prev month
995             // Check if range permits change
996             if ( IsDateInRange(ldpm) && ( ( ldpm.GetYear() == m_date.GetYear() ) ? TRUE : AllowYearChange() ) )
997             {
998                 m_leftArrowRect = wxRect(larrowx - 3, arrowy - 3, (arrowheight / 2) + 8, (arrowheight + 6));
999                 dc.SetBrush(wxBrush(*wxBLACK, wxSOLID));
1000                 dc.SetPen(wxPen(*wxBLACK, 1, wxSOLID));
1001                 dc.DrawPolygon(3, leftarrow, larrowx , arrowy, wxWINDING_RULE);
1002                 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1003                 dc.DrawRectangle(m_leftArrowRect);
1004             }
1005             wxDateTime fdnm = wxDateTime(1,m_date.GetMonth(), m_date.GetYear()) + wxDateSpan::Month(); // first day next month
1006             if ( IsDateInRange(fdnm) && ( ( fdnm.GetYear() == m_date.GetYear() ) ? TRUE : AllowYearChange() ) )
1007             {
1008                 m_rightArrowRect = wxRect(rarrowx - 4, arrowy - 3, (arrowheight / 2) + 8, (arrowheight + 6));
1009                 dc.SetBrush(wxBrush(*wxBLACK, wxSOLID));
1010                 dc.SetPen(wxPen(*wxBLACK, 1, wxSOLID));
1011                 dc.DrawPolygon(3, rightarrow, rarrowx , arrowy, wxWINDING_RULE);
1012                 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1013                 dc.DrawRectangle(m_rightArrowRect);
1014             }
1015         }
1016
1017         y += m_heightRow;
1018     }
1019
1020     // first draw the week days
1021     if ( IsExposed(0, y, 7*m_widthCol, m_heightRow) )
1022     {
1023 #if DEBUG_PAINT
1024         wxLogDebug("painting the header");
1025 #endif
1026
1027         dc.SetBackgroundMode(wxTRANSPARENT);
1028         dc.SetTextForeground(m_colHeaderFg);
1029         dc.SetBrush(wxBrush(m_colHeaderBg, wxSOLID));
1030         dc.SetPen(wxPen(m_colHeaderBg, 1, wxSOLID));
1031         dc.DrawRectangle(0, y, GetClientSize().x, m_heightRow);
1032
1033         bool startOnMonday = (GetWindowStyle() & wxCAL_MONDAY_FIRST) != 0;
1034         for ( size_t wd = 0; wd < 7; wd++ )
1035         {
1036             size_t n;
1037             if ( startOnMonday )
1038                 n = wd == 6 ? 0 : wd + 1;
1039             else
1040                 n = wd;
1041             wxCoord dayw, dayh;
1042             dc.GetTextExtent(m_weekdays[n], &dayw, &dayh);
1043             dc.DrawText(m_weekdays[n], (wd*m_widthCol) + ((m_widthCol- dayw) / 2), y); // center the day-name
1044         }
1045     }
1046
1047     // then the calendar itself
1048     dc.SetTextForeground(*wxBLACK);
1049     //dc.SetFont(*wxNORMAL_FONT);
1050
1051     y += m_heightRow;
1052     wxDateTime date = GetStartDate();
1053
1054 #if DEBUG_PAINT
1055     wxLogDebug("starting calendar from %s\n",
1056             date.Format("%a %d-%m-%Y %H:%M:%S").c_str());
1057 #endif
1058
1059     dc.SetBackgroundMode(wxSOLID);
1060     for ( size_t nWeek = 1; nWeek <= 6; nWeek++, y += m_heightRow )
1061     {
1062         // if the update region doesn't intersect this row, don't paint it
1063         if ( !IsExposed(0, y, 7*m_widthCol, m_heightRow - 1) )
1064         {
1065             date += wxDateSpan::Week();
1066
1067             continue;
1068         }
1069
1070 #if DEBUG_PAINT
1071         wxLogDebug("painting week %d at y = %d\n", nWeek, y);
1072 #endif
1073
1074         for ( size_t wd = 0; wd < 7; wd++ )
1075         {
1076             if ( IsDateShown(date) )
1077             {
1078                 // don't use wxDate::Format() which prepends 0s
1079                 unsigned int day = date.GetDay();
1080                 wxString dayStr = wxString::Format(_T("%u"), day);
1081                 wxCoord width;
1082                 dc.GetTextExtent(dayStr, &width, (wxCoord *)NULL);
1083
1084                 bool changedColours = FALSE,
1085                      changedFont = FALSE;
1086
1087                 bool isSel = FALSE;
1088                 wxCalendarDateAttr *attr = NULL;
1089
1090                 if ( date.GetMonth() != m_date.GetMonth() || !IsDateInRange(date) )
1091                 {
1092                     // surrounding week or out-of-range
1093                     // draw "disabled"
1094                     dc.SetTextForeground(*wxLIGHT_GREY);
1095                     changedColours = TRUE;
1096                 }
1097                 else
1098                 {
1099                     isSel = date.IsSameDate(m_date);
1100                     attr = m_attrs[day - 1];
1101
1102                     if ( isSel )
1103                     {
1104                         dc.SetTextForeground(m_colHighlightFg);
1105                         dc.SetTextBackground(m_colHighlightBg);
1106
1107                         changedColours = TRUE;
1108                     }
1109                     else if ( attr )
1110                     {
1111                         wxColour colFg, colBg;
1112
1113                         if ( attr->IsHoliday() )
1114                         {
1115                             colFg = m_colHolidayFg;
1116                             colBg = m_colHolidayBg;
1117                         }
1118                         else
1119                         {
1120                             colFg = attr->GetTextColour();
1121                             colBg = attr->GetBackgroundColour();
1122                         }
1123
1124                         if ( colFg.Ok() )
1125                         {
1126                             dc.SetTextForeground(colFg);
1127                             changedColours = TRUE;
1128                         }
1129
1130                         if ( colBg.Ok() )
1131                         {
1132                             dc.SetTextBackground(colBg);
1133                             changedColours = TRUE;
1134                         }
1135
1136                         if ( attr->HasFont() )
1137                         {
1138                             dc.SetFont(attr->GetFont());
1139                             changedFont = TRUE;
1140                         }
1141                     }
1142                 }
1143
1144                 wxCoord x = wd*m_widthCol + (m_widthCol - width) / 2;
1145                 dc.DrawText(dayStr, x, y + 1);
1146
1147                 if ( !isSel && attr && attr->HasBorder() )
1148                 {
1149                     wxColour colBorder;
1150                     if ( attr->HasBorderColour() )
1151                     {
1152                         colBorder = attr->GetBorderColour();
1153                     }
1154                     else
1155                     {
1156                         colBorder = m_foregroundColour;
1157                     }
1158
1159                     wxPen pen(colBorder, 1, wxSOLID);
1160                     dc.SetPen(pen);
1161                     dc.SetBrush(*wxTRANSPARENT_BRUSH);
1162
1163                     switch ( attr->GetBorder() )
1164                     {
1165                         case wxCAL_BORDER_SQUARE:
1166                             dc.DrawRectangle(x - 2, y,
1167                                              width + 4, m_heightRow);
1168                             break;
1169
1170                         case wxCAL_BORDER_ROUND:
1171                             dc.DrawEllipse(x - 2, y,
1172                                            width + 4, m_heightRow);
1173                             break;
1174
1175                         default:
1176                             wxFAIL_MSG(_T("unknown border type"));
1177                     }
1178                 }
1179
1180                 if ( changedColours )
1181                 {
1182                     dc.SetTextForeground(m_foregroundColour);
1183                     dc.SetTextBackground(m_backgroundColour);
1184                 }
1185
1186                 if ( changedFont )
1187                 {
1188                     dc.SetFont(m_font);
1189                 }
1190             }
1191             //else: just don't draw it
1192
1193             date += wxDateSpan::Day();
1194         }
1195     }
1196
1197     // Greying out out-of-range background
1198     bool showSurrounding = (GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS) != 0;
1199
1200     date = ( showSurrounding ) ? GetStartDate() : wxDateTime(1, m_date.GetMonth(), m_date.GetYear());
1201     if ( !IsDateInRange(date) )
1202     {
1203         wxDateTime firstOOR = GetLowerDateLimit() - wxDateSpan::Day(); // first out-of-range
1204
1205         wxBrush oorbrush = *wxLIGHT_GREY_BRUSH;
1206         oorbrush.SetStyle(wxFDIAGONAL_HATCH);
1207
1208         HighlightRange(&dc, date, firstOOR, wxTRANSPARENT_PEN, &oorbrush);
1209     }
1210
1211     date = ( showSurrounding ) ? GetStartDate() + wxDateSpan::Weeks(6) - wxDateSpan::Day() : wxDateTime().SetToLastMonthDay(m_date.GetMonth(), m_date.GetYear());
1212     if ( !IsDateInRange(date) )
1213     {
1214         wxDateTime firstOOR = GetUpperDateLimit() + wxDateSpan::Day(); // first out-of-range
1215
1216         wxBrush oorbrush = *wxLIGHT_GREY_BRUSH;
1217         oorbrush.SetStyle(wxFDIAGONAL_HATCH);
1218
1219         HighlightRange(&dc, firstOOR, date, wxTRANSPARENT_PEN, &oorbrush);
1220     }
1221
1222 #if DEBUG_PAINT
1223     wxLogDebug("+++ finished painting");
1224 #endif
1225 }
1226
1227 void wxCalendarCtrl::RefreshDate(const wxDateTime& date)
1228 {
1229     RecalcGeometry();
1230
1231     wxRect rect;
1232
1233     // always refresh the whole row at once because our OnPaint() will draw
1234     // the whole row anyhow - and this allows the small optimisation in
1235     // OnClick() below to work
1236     rect.x = 0;
1237
1238     rect.y = (m_heightRow * GetWeek(date)) + m_rowOffset;
1239
1240     rect.width = 7*m_widthCol;
1241     rect.height = m_heightRow;
1242
1243 #ifdef __WXMSW__
1244     // VZ: for some reason, the selected date seems to occupy more space under
1245     //     MSW - this is probably some bug in the font size calculations, but I
1246     //     don't know where exactly. This fix is ugly and leads to more
1247     //     refreshes than really needed, but without it the selected days
1248     //     leaves even more ugly underscores on screen.
1249     rect.Inflate(0, 1);
1250 #endif // MSW
1251
1252 #if DEBUG_PAINT
1253     wxLogDebug("*** refreshing week %d at (%d, %d)-(%d, %d)\n",
1254            GetWeek(date),
1255            rect.x, rect.y,
1256            rect.x + rect.width, rect.y + rect.height);
1257 #endif
1258
1259     Refresh(TRUE, &rect);
1260 }
1261
1262 void wxCalendarCtrl::HighlightRange(wxPaintDC* pDC, const wxDateTime& fromdate, const wxDateTime& todate, wxPen* pPen, wxBrush* pBrush)
1263 {
1264     // Highlights the given range using pen and brush
1265     // Does nothing if todate < fromdate
1266
1267
1268 #if DEBUG_PAINT
1269     wxLogDebug("+++ HighlightRange: (%s) - (%s) +++", fromdate.Format("%d %m %Y"), todate.Format("%d %m %Y"));
1270 #endif
1271
1272     if ( todate >= fromdate )
1273     {
1274         // do stuff
1275         // date-coordinates
1276         int fd, fw;
1277         int td, tw;
1278
1279         // implicit: both dates must be currently shown - checked by GetDateCoord
1280         if ( GetDateCoord(fromdate, &fd, &fw) && GetDateCoord(todate, &td, &tw) )
1281         {
1282 #if DEBUG_PAINT
1283             wxLogDebug("Highlight range: (%i, %i) - (%i, %i)", fd, fw, td, tw);
1284 #endif
1285             if ( ( (tw - fw) == 1 ) && ( td < fd ) )
1286             {
1287                 // special case: interval 7 days or less not in same week
1288                 // split in two seperate intervals
1289                 wxDateTime tfd = fromdate + wxDateSpan::Days(7-fd);
1290                 wxDateTime ftd = tfd + wxDateSpan::Day();
1291 #if DEBUG_PAINT
1292                 wxLogDebug("Highlight: Seperate segments");
1293 #endif
1294                 // draw seperately
1295                 HighlightRange(pDC, fromdate, tfd, pPen, pBrush);
1296                 HighlightRange(pDC, ftd, todate, pPen, pBrush);
1297             }
1298             else
1299             {
1300                 int numpoints;
1301                 wxPoint corners[8]; // potentially 8 corners in polygon
1302
1303                 if ( fw == tw )
1304                 {
1305                     // simple case: same week
1306                     numpoints = 4;
1307                     corners[0] = wxPoint((fd - 1) * m_widthCol, (fw * m_heightRow) + m_rowOffset);
1308                     corners[1] = wxPoint((fd - 1) * m_widthCol, ((fw + 1 ) * m_heightRow) + m_rowOffset);
1309                     corners[2] = wxPoint(td * m_widthCol, ((tw + 1) * m_heightRow) + m_rowOffset);
1310                     corners[3] = wxPoint(td * m_widthCol, (tw * m_heightRow) + m_rowOffset);
1311                 }
1312                 else
1313                 {
1314                     int cidx = 0;
1315                     // "complex" polygon
1316                     corners[cidx] = wxPoint((fd - 1) * m_widthCol, (fw * m_heightRow) + m_rowOffset); cidx++;
1317
1318                     if ( fd > 1 )
1319                     {
1320                         corners[cidx] = wxPoint((fd - 1) * m_widthCol, ((fw + 1) * m_heightRow) + m_rowOffset); cidx++;
1321                         corners[cidx] = wxPoint(0, ((fw + 1) * m_heightRow) + m_rowOffset); cidx++;
1322                     }
1323
1324                     corners[cidx] = wxPoint(0, ((tw + 1) * m_heightRow) + m_rowOffset); cidx++;
1325                     corners[cidx] = wxPoint(td * m_widthCol, ((tw + 1) * m_heightRow) + m_rowOffset); cidx++;
1326
1327                     if ( td < 7 )
1328                     {
1329                         corners[cidx] = wxPoint(td * m_widthCol, (tw * m_heightRow) + m_rowOffset); cidx++;
1330                         corners[cidx] = wxPoint(7 * m_widthCol, (tw * m_heightRow) + m_rowOffset); cidx++;
1331                     }
1332
1333                     corners[cidx] = wxPoint(7 * m_widthCol, (fw * m_heightRow) + m_rowOffset); cidx++;
1334
1335                     numpoints = cidx;
1336                 }
1337
1338                 // draw the polygon
1339                 pDC->SetBrush(*pBrush);
1340                 pDC->SetPen(*pPen);
1341                 pDC->DrawPolygon(numpoints, corners);
1342             }
1343         }
1344     }
1345     // else do nothing
1346 #if DEBUG_PAINT
1347     wxLogDebug("--- HighlightRange ---");
1348 #endif
1349 }
1350
1351 bool wxCalendarCtrl::GetDateCoord(const wxDateTime& date, int *day, int *week) const
1352 {
1353     bool retval = TRUE;
1354
1355 #if DEBUG_PAINT
1356     wxLogDebug("+++ GetDateCoord: (%s) +++", date.Format("%d %m %Y"));
1357 #endif
1358
1359     if ( IsDateShown(date) )
1360     {
1361         bool startOnMonday = ( GetWindowStyle() & wxCAL_MONDAY_FIRST ) != 0;
1362
1363         // Find day
1364         *day = date.GetWeekDay();
1365
1366         if ( *day == 0 ) // sunday
1367         {
1368             *day = ( startOnMonday ) ? 7 : 1;
1369         }
1370         else
1371         {
1372             *day += ( startOnMonday ) ? 0 : 1;
1373         }
1374
1375         int targetmonth = date.GetMonth() + (12 * date.GetYear());
1376         int thismonth = m_date.GetMonth() + (12 * m_date.GetYear());
1377
1378         // Find week
1379         if ( targetmonth == thismonth )
1380         {
1381             *week = GetWeek(date);
1382         }
1383         else
1384         {
1385             if ( targetmonth < thismonth )
1386             {
1387                 *week = 1; // trivial
1388             }
1389             else // targetmonth > thismonth
1390             {
1391                 wxDateTime ldcm;
1392                 int lastweek;
1393                 int lastday;
1394
1395                 // get the datecoord of the last day in the month currently shown
1396 #if DEBUG_PAINT
1397                 wxLogDebug("     +++ LDOM +++");
1398 #endif
1399                 GetDateCoord(ldcm.SetToLastMonthDay(m_date.GetMonth(), m_date.GetYear()), &lastday, &lastweek);
1400 #if DEBUG_PAINT
1401                 wxLogDebug("     --- LDOM ---");
1402 #endif
1403
1404                 wxTimeSpan span = date - ldcm;
1405
1406                 int daysfromlast = span.GetDays();
1407 #if DEBUG_PAINT
1408                 wxLogDebug("daysfromlast: %i", daysfromlast);
1409 #endif
1410                 if ( daysfromlast + lastday > 7 ) // past week boundary
1411                 {
1412                     int wholeweeks = (daysfromlast / 7);
1413                     *week = wholeweeks + lastweek;
1414                     if ( (daysfromlast - (7 * wholeweeks) + lastday) > 7 )
1415                     {
1416                         *week += 1;
1417                     }
1418                 }
1419                 else
1420                 {
1421                     *week = lastweek;
1422                 }
1423             }
1424         }
1425     }
1426     else
1427     {
1428         *day = -1;
1429         *week = -1;
1430         retval = FALSE;
1431     }
1432
1433 #if DEBUG_PAINT
1434     wxLogDebug("--- GetDateCoord: (%s) = (%i, %i) ---", date.Format("%d %m %Y"), *day, *week);
1435 #endif
1436
1437     return retval;
1438 }
1439
1440 // ----------------------------------------------------------------------------
1441 // mouse handling
1442 // ----------------------------------------------------------------------------
1443
1444 void wxCalendarCtrl::OnDClick(wxMouseEvent& event)
1445 {
1446     if ( HitTest(event.GetPosition()) != wxCAL_HITTEST_DAY )
1447     {
1448         event.Skip();
1449     }
1450     else
1451     {
1452         GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
1453     }
1454 }
1455
1456 void wxCalendarCtrl::OnClick(wxMouseEvent& event)
1457 {
1458     wxDateTime date;
1459     wxDateTime::WeekDay wday;
1460     switch ( HitTest(event.GetPosition(), &date, &wday) )
1461     {
1462         case wxCAL_HITTEST_DAY:
1463             if ( IsDateInRange(date) )
1464             {
1465                 ChangeDay(date);
1466
1467                 GenerateEvents(wxEVT_CALENDAR_DAY_CHANGED,
1468                                wxEVT_CALENDAR_SEL_CHANGED);
1469             }
1470             break;
1471
1472         case wxCAL_HITTEST_HEADER:
1473             {
1474                 wxCalendarEvent event(this, wxEVT_CALENDAR_WEEKDAY_CLICKED);
1475                 event.m_wday = wday;
1476                 (void)GetEventHandler()->ProcessEvent(event);
1477             }
1478             break;
1479
1480         case wxCAL_HITTEST_DECMONTH:
1481         case wxCAL_HITTEST_INCMONTH:
1482         case wxCAL_HITTEST_SURROUNDING_WEEK:
1483             SetDateAndNotify(date); // we probably only want to refresh the control. No notification.. (maybe as an option?)
1484             break;
1485
1486         default:
1487             wxFAIL_MSG(_T("unknown hittest code"));
1488             // fall through
1489
1490         case wxCAL_HITTEST_NOWHERE:
1491             event.Skip();
1492             break;
1493     }
1494 }
1495
1496 wxCalendarHitTestResult wxCalendarCtrl::HitTest(const wxPoint& pos,
1497                                                 wxDateTime *date,
1498                                                 wxDateTime::WeekDay *wd)
1499 {
1500     RecalcGeometry();
1501
1502     wxCoord y = pos.y;
1503
1504 ///////////////////////////////////////////////////////////////////////////////////////////////////////
1505     if ( (GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) )
1506     {
1507         // Header: month
1508
1509         // we need to find out if the hit is on left arrow, on month or on right arrow
1510         // left arrow?
1511         if ( wxRegion(m_leftArrowRect).Contains(pos) == wxInRegion )
1512         {
1513             if ( date )
1514             {
1515                 if ( IsDateInRange(m_date - wxDateSpan::Month()) )
1516                 {
1517                     *date = m_date - wxDateSpan::Month();
1518                 }
1519                 else
1520                 {
1521                     *date = GetLowerDateLimit();
1522                 }
1523             }
1524
1525             return wxCAL_HITTEST_DECMONTH;
1526         }
1527
1528         if ( wxRegion(m_rightArrowRect).Contains(pos) == wxInRegion )
1529         {
1530             if ( date )
1531             {
1532                 if ( IsDateInRange(m_date + wxDateSpan::Month()) )
1533                 {
1534                     *date = m_date + wxDateSpan::Month();
1535                 }
1536                 else
1537                 {
1538                     *date = GetUpperDateLimit();
1539                 }
1540             }
1541
1542             return wxCAL_HITTEST_INCMONTH;
1543         }
1544
1545     }
1546
1547 ///////////////////////////////////////////////////////////////////////////////////////////////////////
1548     // Header: Days
1549     int wday = pos.x / m_widthCol;
1550 //    if ( y < m_heightRow )
1551     if ( y < (m_heightRow + m_rowOffset) )
1552     {
1553         if ( y > m_rowOffset )
1554         {
1555             if ( wd )
1556             {
1557                 if ( GetWindowStyle() & wxCAL_MONDAY_FIRST )
1558                 {
1559                     wday = wday == 6 ? 0 : wday + 1;
1560                 }
1561
1562                 *wd = (wxDateTime::WeekDay)wday;
1563             }
1564
1565             return wxCAL_HITTEST_HEADER;
1566         }
1567         else
1568         {
1569             return wxCAL_HITTEST_NOWHERE;
1570         }
1571     }
1572
1573 //    int week = (y - m_heightRow) / m_heightRow;
1574     int week = (y - (m_heightRow + m_rowOffset)) / m_heightRow;
1575     if ( week >= 6 || wday >= 7 )
1576     {
1577         return wxCAL_HITTEST_NOWHERE;
1578     }
1579
1580     wxDateTime dt = GetStartDate() + wxDateSpan::Days(7*week + wday);
1581
1582     if ( IsDateShown(dt) )
1583     {
1584         if ( date )
1585             *date = dt;
1586
1587         if ( dt.GetMonth() == m_date.GetMonth() )
1588         {
1589
1590             return wxCAL_HITTEST_DAY;
1591         }
1592         else
1593         {
1594             return wxCAL_HITTEST_SURROUNDING_WEEK;
1595         }
1596     }
1597     else
1598     {
1599         return wxCAL_HITTEST_NOWHERE;
1600     }
1601 }
1602
1603 // ----------------------------------------------------------------------------
1604 // subcontrols events handling
1605 // ----------------------------------------------------------------------------
1606
1607 void wxCalendarCtrl::OnMonthChange(wxCommandEvent& event)
1608 {
1609     wxDateTime::Tm tm = m_date.GetTm();
1610
1611     wxDateTime::Month mon = (wxDateTime::Month)event.GetInt();
1612     if ( tm.mday > wxDateTime::GetNumberOfDays(mon, tm.year) )
1613     {
1614         tm.mday = wxDateTime::GetNumberOfDays(mon, tm.year);
1615     }
1616
1617     wxDateTime target = wxDateTime(tm.mday, mon, tm.year);
1618
1619     ChangeMonth(&target);
1620     SetDateAndNotify(target);
1621 }
1622
1623 void wxCalendarCtrl::OnYearChange(wxCommandEvent& event)
1624 {
1625     int year = (int)event.GetInt();
1626     if ( year == INT_MIN )
1627     {
1628         // invalid year in the spin control, ignore it
1629         return;
1630     }
1631
1632     wxDateTime::Tm tm = m_date.GetTm();
1633
1634     if ( tm.mday > wxDateTime::GetNumberOfDays(tm.mon, year) )
1635     {
1636         tm.mday = wxDateTime::GetNumberOfDays(tm.mon, year);
1637     }
1638
1639     wxDateTime target = wxDateTime(tm.mday, tm.mon, year);
1640
1641     if ( ChangeYear(&target) )
1642     {
1643         SetDateAndNotify(target);
1644     }
1645     else
1646     {
1647         // In this case we don't want to change the date. That would put us
1648         // inside the same year but a strange number of months forward/back..
1649         m_spinYear->SetValue(target.GetYear());
1650     }
1651 }
1652
1653 // ----------------------------------------------------------------------------
1654 // keyboard interface
1655 // ----------------------------------------------------------------------------
1656
1657 void wxCalendarCtrl::OnChar(wxKeyEvent& event)
1658 {
1659     wxDateTime target;
1660     switch ( event.GetKeyCode() )
1661     {
1662         case _T('+'):
1663         case WXK_ADD:
1664             target = m_date + wxDateSpan::Year();
1665             if ( ChangeYear(&target) )
1666             {
1667                 SetDateAndNotify(target);
1668             }
1669             break;
1670
1671         case _T('-'):
1672         case WXK_SUBTRACT:
1673             target = m_date - wxDateSpan::Year();
1674             if ( ChangeYear(&target) )
1675             {
1676                 SetDateAndNotify(target);
1677             }
1678             break;
1679
1680         case WXK_PRIOR:
1681             target = m_date - wxDateSpan::Month();
1682             ChangeMonth(&target);
1683             SetDateAndNotify(target); // always
1684             break;
1685
1686         case WXK_NEXT:
1687             target = m_date + wxDateSpan::Month();
1688             ChangeMonth(&target);
1689             SetDateAndNotify(target); // always
1690             break;
1691
1692         case WXK_RIGHT:
1693             if ( event.ControlDown() )
1694             {
1695                 target = wxDateTime(m_date).SetToNextWeekDay(
1696                                  GetWindowStyle() & wxCAL_MONDAY_FIRST
1697                                  ? wxDateTime::Sun : wxDateTime::Sat);
1698                 if ( !IsDateInRange(target) )
1699                 {
1700                     target = GetUpperDateLimit();
1701                 }
1702                 SetDateAndNotify(target);
1703             }
1704             else
1705                 SetDateAndNotify(m_date + wxDateSpan::Day());
1706             break;
1707
1708         case WXK_LEFT:
1709             if ( event.ControlDown() )
1710             {
1711                 target = wxDateTime(m_date).SetToPrevWeekDay(
1712                                  GetWindowStyle() & wxCAL_MONDAY_FIRST
1713                                  ? wxDateTime::Mon : wxDateTime::Sun);
1714                 if ( !IsDateInRange(target) )
1715                 {
1716                     target = GetLowerDateLimit();
1717                 }
1718                 SetDateAndNotify(target);
1719             }
1720             else
1721                 SetDateAndNotify(m_date - wxDateSpan::Day());
1722             break;
1723
1724         case WXK_UP:
1725             SetDateAndNotify(m_date - wxDateSpan::Week());
1726             break;
1727
1728         case WXK_DOWN:
1729             SetDateAndNotify(m_date + wxDateSpan::Week());
1730             break;
1731
1732         case WXK_HOME:
1733             if ( event.ControlDown() )
1734                 SetDateAndNotify(wxDateTime::Today());
1735             else
1736                 SetDateAndNotify(wxDateTime(1, m_date.GetMonth(), m_date.GetYear()));
1737             break;
1738
1739         case WXK_END:
1740             SetDateAndNotify(wxDateTime(m_date).SetToLastMonthDay());
1741             break;
1742
1743         case WXK_RETURN:
1744             GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
1745             break;
1746
1747         default:
1748             event.Skip();
1749     }
1750 }
1751
1752 // ----------------------------------------------------------------------------
1753 // holidays handling
1754 // ----------------------------------------------------------------------------
1755
1756 void wxCalendarCtrl::EnableHolidayDisplay(bool display)
1757 {
1758     long style = GetWindowStyle();
1759     if ( display )
1760         style |= wxCAL_SHOW_HOLIDAYS;
1761     else
1762         style &= ~wxCAL_SHOW_HOLIDAYS;
1763
1764     SetWindowStyle(style);
1765
1766     if ( display )
1767         SetHolidayAttrs();
1768     else
1769         ResetHolidayAttrs();
1770
1771     Refresh();
1772 }
1773
1774 void wxCalendarCtrl::SetHolidayAttrs()
1775 {
1776     if ( GetWindowStyle() & wxCAL_SHOW_HOLIDAYS )
1777     {
1778         ResetHolidayAttrs();
1779
1780         wxDateTime::Tm tm = m_date.GetTm();
1781         wxDateTime dtStart(1, tm.mon, tm.year),
1782                    dtEnd = dtStart.GetLastMonthDay();
1783
1784         wxDateTimeArray hol;
1785         wxDateTimeHolidayAuthority::GetHolidaysInRange(dtStart, dtEnd, hol);
1786
1787         size_t count = hol.GetCount();
1788         for ( size_t n = 0; n < count; n++ )
1789         {
1790             SetHoliday(hol[n].GetDay());
1791         }
1792     }
1793 }
1794
1795 void wxCalendarCtrl::SetHoliday(size_t day)
1796 {
1797     wxCHECK_RET( day > 0 && day < 32, _T("invalid day in SetHoliday") );
1798
1799     wxCalendarDateAttr *attr = GetAttr(day);
1800     if ( !attr )
1801     {
1802         attr = new wxCalendarDateAttr;
1803     }
1804
1805     attr->SetHoliday(TRUE);
1806
1807     // can't use SetAttr() because it would delete this pointer
1808     m_attrs[day - 1] = attr;
1809 }
1810
1811 void wxCalendarCtrl::ResetHolidayAttrs()
1812 {
1813     for ( size_t day = 0; day < 31; day++ )
1814     {
1815         if ( m_attrs[day] )
1816         {
1817             m_attrs[day]->SetHoliday(FALSE);
1818         }
1819     }
1820 }
1821
1822 // ----------------------------------------------------------------------------
1823 // wxCalendarEvent
1824 // ----------------------------------------------------------------------------
1825
1826 void wxCalendarEvent::Init()
1827 {
1828     m_wday = wxDateTime::Inv_WeekDay;
1829 }
1830
1831 wxCalendarEvent::wxCalendarEvent(wxCalendarCtrl *cal, wxEventType type)
1832                : wxCommandEvent(type, cal->GetId())
1833 {
1834     m_date = cal->GetDate();
1835     SetEventObject(cal);
1836 }
1837
1838 #endif // wxUSE_CALENDARCTRL
1839