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