1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: generic/calctrl.cpp
3 // Purpose: implementation fo the generic wxCalendarCtrl
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license
10 ///////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
21 #pragma implementation "calctrl.h"
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
32 #include "wx/dcclient.h"
33 #include "wx/settings.h"
35 #include "wx/combobox.h"
38 #include "wx/calctrl.h"
42 // ----------------------------------------------------------------------------
44 // ----------------------------------------------------------------------------
46 class wxMonthComboBox
: public wxComboBox
49 wxMonthComboBox(wxCalendarCtrl
*cal
);
51 void OnMonthChange(wxCommandEvent
& event
) { m_cal
->OnMonthChange(event
); }
54 wxCalendarCtrl
*m_cal
;
59 class wxYearSpinCtrl
: public wxSpinCtrl
62 wxYearSpinCtrl(wxCalendarCtrl
*cal
);
64 void OnYearChange(wxSpinEvent
& event
) { m_cal
->OnYearChange(event
); }
67 wxCalendarCtrl
*m_cal
;
72 // ----------------------------------------------------------------------------
74 // ----------------------------------------------------------------------------
76 BEGIN_EVENT_TABLE(wxCalendarCtrl
, wxControl
)
77 EVT_PAINT(wxCalendarCtrl::OnPaint
)
79 EVT_CHAR(wxCalendarCtrl::OnChar
)
81 EVT_LEFT_DOWN(wxCalendarCtrl::OnClick
)
82 EVT_LEFT_DCLICK(wxCalendarCtrl::OnDClick
)
84 EVT_CALENDAR_MONTH(-1, wxCalendarCtrl::OnCalMonthChange
)
85 EVT_CALENDAR_YEAR(-1, wxCalendarCtrl::OnCalMonthChange
)
88 BEGIN_EVENT_TABLE(wxMonthComboBox
, wxComboBox
)
89 EVT_COMBOBOX(-1, wxMonthComboBox::OnMonthChange
)
92 BEGIN_EVENT_TABLE(wxYearSpinCtrl
, wxSpinCtrl
)
93 EVT_SPINCTRL(-1, wxYearSpinCtrl::OnYearChange
)
96 IMPLEMENT_DYNAMIC_CLASS(wxCalendarCtrl
, wxControl
)
98 // ============================================================================
100 // ============================================================================
102 // ----------------------------------------------------------------------------
103 // wxMonthComboBox and wxYearSpinCtrl
104 // ----------------------------------------------------------------------------
106 wxMonthComboBox::wxMonthComboBox(wxCalendarCtrl
*cal
)
107 : wxComboBox(cal
->GetParent(), -1,
117 for ( m
= wxDateTime::Jan
; m
< wxDateTime::Inv_Month
; wxNextMonth(m
) )
119 Append(wxDateTime::GetMonthName(m
));
122 SetSelection(m_cal
->GetDate().GetMonth());
125 wxYearSpinCtrl::wxYearSpinCtrl(wxCalendarCtrl
*cal
)
126 : wxSpinCtrl(cal
->GetParent(), -1,
127 cal
->GetDate().Format(_T("%Y")),
131 -4300, 10000, cal
->GetDate().GetYear())
136 // ----------------------------------------------------------------------------
138 // ----------------------------------------------------------------------------
140 void wxCalendarCtrl::Init()
148 wxDateTime::WeekDay wd
;
149 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
151 m_weekdays
[wd
] = wxDateTime::GetWeekDayName(wd
, wxDateTime::Name_Abbr
);
154 for ( size_t n
= 0; n
< WXSIZEOF(m_attrs
); n
++ )
160 m_colHighlightFg
= ss
.GetSystemColour(wxSYS_COLOUR_HIGHLIGHTTEXT
);
161 m_colHighlightBg
= ss
.GetSystemColour(wxSYS_COLOUR_HIGHLIGHT
);
163 m_colHolidayFg
= *wxRED
;
164 // don't set m_colHolidayBg - by default, same as our bg colour
166 m_colHeaderFg
= *wxBLUE
;
167 m_colHeaderBg
= *wxLIGHT_GREY
;
170 bool wxCalendarCtrl::Create(wxWindow
* WXUNUSED(parent
),
171 wxWindowID
WXUNUSED(id
),
172 const wxDateTime
& date
,
173 const wxPoint
& WXUNUSED(pos
),
176 const wxString
& WXUNUSED(name
))
178 SetWindowStyle(style
| (wxRAISED_BORDER
| wxWANTS_CHARS
));
180 m_date
= date
.IsValid() ? date
: wxDateTime::Today();
182 m_comboMonth
= new wxMonthComboBox(this);
183 m_spinYear
= new wxYearSpinCtrl(this);
186 if ( size
.x
== -1 || size
.y
== -1 )
188 sizeReal
= DoGetBestSize();
201 SetBackgroundColour(*wxWHITE
);
202 SetFont(*wxSWISS_FONT
);
209 wxCalendarCtrl::~wxCalendarCtrl()
211 for ( size_t n
= 0; n
< WXSIZEOF(m_attrs
); n
++ )
217 // ----------------------------------------------------------------------------
218 // forward wxWin functions to subcontrols
219 // ----------------------------------------------------------------------------
221 bool wxCalendarCtrl::Show(bool show
)
223 if ( !wxControl::Show(show
) )
228 m_comboMonth
->Show(show
);
229 m_spinYear
->Show(show
);
234 bool wxCalendarCtrl::Enable(bool enable
)
236 if ( !wxControl::Enable(enable
) )
241 m_comboMonth
->Enable(enable
);
242 m_spinYear
->Enable(enable
);
247 // ----------------------------------------------------------------------------
249 // ----------------------------------------------------------------------------
251 void wxCalendarCtrl::SetDate(const wxDateTime
& date
)
253 if ( m_date
.GetMonth() == date
.GetMonth() &&
254 m_date
.GetYear() == date
.GetYear() )
256 // just change the day
264 // update the controls
265 m_comboMonth
->SetSelection(m_date
.GetMonth());
266 m_spinYear
->SetValue(m_date
.Format(_T("%Y")));
268 // update the calendar
273 void wxCalendarCtrl::ChangeDay(const wxDateTime
& date
)
275 if ( m_date
!= date
)
277 // we need to refresh the row containing the old date and the one
278 // containing the new one
279 wxDateTime dateOld
= m_date
;
282 RefreshDate(dateOld
);
284 // if the date is in the same row, it was already drawn correctly
285 if ( GetWeek(m_date
) != GetWeek(dateOld
) )
292 void wxCalendarCtrl::SetDateAndNotify(const wxDateTime
& date
)
294 wxDateTime::Tm tm1
= m_date
.GetTm(),
298 if ( tm1
.year
!= tm2
.year
)
299 type
= wxEVT_CALENDAR_YEAR_CHANGED
;
300 else if ( tm1
.mon
!= tm2
.mon
)
301 type
= wxEVT_CALENDAR_MONTH_CHANGED
;
302 else if ( tm1
.mday
!= tm2
.mday
)
303 type
= wxEVT_CALENDAR_DAY_CHANGED
;
309 GenerateEvents(type
, wxEVT_CALENDAR_SEL_CHANGED
);
312 // ----------------------------------------------------------------------------
314 // ----------------------------------------------------------------------------
316 wxDateTime
wxCalendarCtrl::GetStartDate() const
318 wxDateTime::Tm tm
= m_date
.GetTm();
320 wxDateTime date
= wxDateTime(1, tm
.mon
, tm
.year
);
323 date
.SetToPrevWeekDay(GetWindowStyle() & wxCAL_MONDAY_FIRST
324 ? wxDateTime::Mon
: wxDateTime::Sun
);
329 bool wxCalendarCtrl::IsDateShown(const wxDateTime
& date
) const
331 return date
.GetMonth() == m_date
.GetMonth();
334 size_t wxCalendarCtrl::GetWeek(const wxDateTime
& date
) const
336 return date
.GetWeekOfMonth(GetWindowStyle() & wxCAL_MONDAY_FIRST
337 ? wxDateTime::Monday_First
338 : wxDateTime::Sunday_First
);
341 // ----------------------------------------------------------------------------
343 // ----------------------------------------------------------------------------
345 // this is a composite control and it must arrange its parts each time its
346 // size or position changes: the combobox and spinctrl are along the top of
347 // the available area and the calendar takes up therest of the space
349 // the constants used for the layout
350 #define VERT_MARGIN 5 // distance between combo and calendar
351 #define HORZ_MARGIN 15 // spin
353 wxSize
wxCalendarCtrl::DoGetBestSize() const
355 // calc the size of the calendar
356 ((wxCalendarCtrl
*)this)->RecalcGeometry(); // const_cast
358 wxCoord width
= 7*m_widthCol
,
359 height
= 7*m_heightRow
;
361 wxSize sizeCombo
= m_comboMonth
->GetBestSize(),
362 sizeSpin
= m_spinYear
->GetBestSize();
364 height
+= VERT_MARGIN
+ wxMax(sizeCombo
.y
, sizeSpin
.y
);
366 return wxSize(width
, height
);
369 void wxCalendarCtrl::DoSetSize(int x
, int y
,
370 int width
, int height
,
373 wxControl::DoSetSize(x
, y
, width
, height
, sizeFlags
);
376 void wxCalendarCtrl::DoMoveWindow(int x
, int y
, int width
, int height
)
378 wxSize sizeCombo
= m_comboMonth
->GetSize();
379 m_comboMonth
->Move(x
, y
);
381 int xDiff
= sizeCombo
.x
+ HORZ_MARGIN
;
382 m_spinYear
->SetSize(x
+ xDiff
, y
, width
- xDiff
, sizeCombo
.y
);
384 wxSize sizeSpin
= m_spinYear
->GetSize();
385 int yDiff
= wxMax(sizeSpin
.y
, sizeCombo
.y
) + VERT_MARGIN
;
387 wxControl::DoMoveWindow(x
, y
+ yDiff
, width
, height
- yDiff
);
390 void wxCalendarCtrl::DoGetPosition(int *x
, int *y
) const
392 wxControl::DoGetPosition(x
, y
);
394 // our real top corner is not in this position
397 *y
-= m_comboMonth
->GetSize().y
+ VERT_MARGIN
;
401 void wxCalendarCtrl::DoGetSize(int *width
, int *height
) const
403 wxControl::DoGetSize(width
, height
);
405 // our real height is bigger
408 *height
+= m_comboMonth
->GetSize().y
+ VERT_MARGIN
;
412 void wxCalendarCtrl::RecalcGeometry()
414 if ( m_widthCol
!= 0 )
421 // determine the column width (we assume that the weekday names are always
422 // wider (in any language) than the numbers)
424 wxDateTime::WeekDay wd
;
425 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
428 dc
.GetTextExtent(m_weekdays
[wd
], &width
, &m_heightRow
);
429 if ( width
> m_widthCol
)
435 // leave some margins
440 // ----------------------------------------------------------------------------
442 // ----------------------------------------------------------------------------
444 void wxCalendarCtrl::OnPaint(wxPaintEvent
& WXUNUSED(event
))
453 printf("--- starting to paint, selection: %s, week %u\n",
454 m_date
.Format("%a %d-%m-%Y %H:%M:%S").c_str(),
458 // first draw the week days
459 if ( IsExposed(0, 0, 7*m_widthCol
, m_heightRow
) )
462 puts("painting the header");
465 dc
.SetBackgroundMode(wxTRANSPARENT
);
466 dc
.SetTextForeground(m_colHeaderFg
);
467 dc
.SetBrush(wxBrush(m_colHeaderBg
, wxSOLID
));
468 dc
.SetPen(wxPen(m_colHeaderBg
, 1, wxSOLID
));
469 dc
.DrawRectangle(0, 0, 7*m_widthCol
, m_heightRow
);
471 bool startOnMonday
= (GetWindowStyle() & wxCAL_MONDAY_FIRST
) != 0;
472 for ( size_t wd
= 0; wd
< 7; wd
++ )
476 n
= wd
== 6 ? 0 : wd
+ 1;
480 dc
.DrawText(m_weekdays
[n
], wd
*m_widthCol
+ 1, 0);
484 // then the calendar itself
485 dc
.SetTextForeground(*wxBLACK
);
486 //dc.SetFont(*wxNORMAL_FONT);
488 wxCoord y
= m_heightRow
;
490 wxDateTime date
= GetStartDate();
492 printf("starting calendar from %s\n",
493 date
.Format("%a %d-%m-%Y %H:%M:%S").c_str());
496 dc
.SetBackgroundMode(wxSOLID
);
497 for ( size_t nWeek
= 1; nWeek
<= 6; nWeek
++, y
+= m_heightRow
)
499 // if the update region doesn't intersect this row, don't paint it
500 if ( !IsExposed(0, y
, 7*m_widthCol
, m_heightRow
- 1) )
502 date
+= wxDateSpan::Week();
508 printf("painting week %d at y = %d\n", nWeek
, y
);
511 for ( size_t wd
= 0; wd
< 7; wd
++ )
513 if ( IsDateShown(date
) )
515 // don't use wxDate::Format() which prepends 0s
516 unsigned int day
= date
.GetDay();
517 wxString dayStr
= wxString::Format(_T("%u"), day
);
519 dc
.GetTextExtent(dayStr
, &width
, (wxCoord
*)NULL
);
521 bool changedColours
= FALSE
,
524 wxCalendarDateAttr
*attr
= m_attrs
[day
- 1];
526 bool isSel
= m_date
== date
;
529 dc
.SetTextForeground(m_colHighlightFg
);
530 dc
.SetTextBackground(m_colHighlightBg
);
532 changedColours
= TRUE
;
536 wxColour colFg
, colBg
;
538 if ( attr
->IsHoliday() )
540 colFg
= m_colHolidayFg
;
541 colBg
= m_colHolidayBg
;
545 colFg
= attr
->GetTextColour();
546 colBg
= attr
->GetBackgroundColour();
551 dc
.SetTextForeground(colFg
);
552 changedColours
= TRUE
;
557 dc
.SetTextBackground(colBg
);
558 changedColours
= TRUE
;
561 if ( attr
->HasFont() )
563 dc
.SetFont(attr
->GetFont());
568 wxCoord x
= wd
*m_widthCol
+ (m_widthCol
- width
) / 2;
569 dc
.DrawText(dayStr
, x
, y
+ 1);
571 if ( !isSel
&& attr
&& attr
->HasBorder() )
574 if ( attr
->HasBorderColour() )
576 colBorder
= attr
->GetBorderColour();
580 colBorder
= m_foregroundColour
;
583 wxPen
pen(colBorder
, 1, wxSOLID
);
585 dc
.SetBrush(*wxTRANSPARENT_BRUSH
);
587 switch ( attr
->GetBorder() )
589 case wxCAL_BORDER_SQUARE
:
590 dc
.DrawRectangle(x
- 2, y
,
591 width
+ 4, m_heightRow
);
594 case wxCAL_BORDER_ROUND
:
595 dc
.DrawEllipse(x
- 2, y
,
596 width
+ 4, m_heightRow
);
600 wxFAIL_MSG(_T("unknown border type"));
604 if ( changedColours
)
606 dc
.SetTextForeground(m_foregroundColour
);
607 dc
.SetTextBackground(m_backgroundColour
);
615 //else: just don't draw it
617 date
+= wxDateSpan::Day();
621 puts("+++ finished painting");
625 void wxCalendarCtrl::RefreshDate(const wxDateTime
& date
)
631 // always refresh the whole row at once because our OnPaint() will draw
632 // the whole row anyhow - and this allows the small optimisation in
633 // OnClick() below to work
635 rect
.y
= m_heightRow
* GetWeek(date
);
636 rect
.width
= 7*m_widthCol
;
637 rect
.height
= m_heightRow
;
640 printf("*** refreshing week %d at (%d, %d)-(%d, %d)\n",
643 rect
.x
+ rect
.width
, rect
.y
+ rect
.height
);
646 Refresh(TRUE
, &rect
);
649 // ----------------------------------------------------------------------------
651 // ----------------------------------------------------------------------------
653 void wxCalendarCtrl::OnDClick(wxMouseEvent
& event
)
655 if ( HitTest(event
.GetPosition()) != wxCAL_HITTEST_DAY
)
661 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED
);
665 void wxCalendarCtrl::OnClick(wxMouseEvent
& event
)
668 wxDateTime::WeekDay wday
;
669 switch ( HitTest(event
.GetPosition(), &date
, &wday
) )
671 case wxCAL_HITTEST_DAY
:
674 GenerateEvents(wxEVT_CALENDAR_DAY_CHANGED
,
675 wxEVT_CALENDAR_SEL_CHANGED
);
678 case wxCAL_HITTEST_HEADER
:
680 wxCalendarEvent
event(this, wxEVT_CALENDAR_WEEKDAY_CLICKED
);
682 (void)GetEventHandler()->ProcessEvent(event
);
687 wxFAIL_MSG(_T("unknown hittest code"));
690 case wxCAL_HITTEST_NOWHERE
:
696 wxCalendarHitTestResult
wxCalendarCtrl::HitTest(const wxPoint
& pos
,
698 wxDateTime::WeekDay
*wd
)
702 int wday
= pos
.x
/ m_widthCol
;
705 if ( y
< m_heightRow
)
709 if ( GetWindowStyle() & wxCAL_MONDAY_FIRST
)
711 wday
= wday
== 6 ? 0 : wday
+ 1;
714 *wd
= (wxDateTime::WeekDay
)wday
;
717 return wxCAL_HITTEST_HEADER
;
720 int week
= (y
- m_heightRow
) / m_heightRow
;
721 if ( week
>= 6 || wday
>= 7 )
723 return wxCAL_HITTEST_NOWHERE
;
726 wxDateTime dt
= GetStartDate() + wxDateSpan::Days(7*week
+ wday
);
728 if ( IsDateShown(dt
) )
733 return wxCAL_HITTEST_DAY
;
737 return wxCAL_HITTEST_NOWHERE
;
741 // ----------------------------------------------------------------------------
742 // subcontrols events handling
743 // ----------------------------------------------------------------------------
745 void wxCalendarCtrl::OnMonthChange(wxCommandEvent
& event
)
747 wxDateTime::Tm tm
= m_date
.GetTm();
749 wxDateTime::Month mon
= (wxDateTime::Month
)event
.GetInt();
750 if ( tm
.mday
> wxDateTime::GetNumberOfDays(mon
, tm
.year
) )
752 tm
.mday
= wxDateTime::GetNumberOfDays(mon
, tm
.year
);
755 SetDate(wxDateTime(tm
.mday
, mon
, tm
.year
));
757 GenerateEvents(wxEVT_CALENDAR_MONTH_CHANGED
, wxEVT_CALENDAR_SEL_CHANGED
);
760 void wxCalendarCtrl::OnYearChange(wxSpinEvent
& event
)
762 wxDateTime::Tm tm
= m_date
.GetTm();
764 int year
= (int)event
.GetInt();
765 if ( tm
.mday
> wxDateTime::GetNumberOfDays(tm
.mon
, year
) )
767 tm
.mday
= wxDateTime::GetNumberOfDays(tm
.mon
, year
);
770 SetDate(wxDateTime(tm
.mday
, tm
.mon
, year
));
772 GenerateEvents(wxEVT_CALENDAR_YEAR_CHANGED
, wxEVT_CALENDAR_SEL_CHANGED
);
775 // ----------------------------------------------------------------------------
776 // keyboard interface
777 // ----------------------------------------------------------------------------
779 void wxCalendarCtrl::OnChar(wxKeyEvent
& event
)
781 switch ( event
.KeyCode() )
785 SetDateAndNotify(m_date
+ wxDateSpan::Year());
790 SetDateAndNotify(m_date
- wxDateSpan::Year());
794 SetDateAndNotify(m_date
- wxDateSpan::Month());
798 SetDateAndNotify(m_date
+ wxDateSpan::Month());
802 if ( event
.ControlDown() )
803 SetDateAndNotify(wxDateTime(m_date
).SetToNextWeekDay(
804 GetWindowStyle() & wxCAL_MONDAY_FIRST
805 ? wxDateTime::Sun
: wxDateTime::Sat
));
807 SetDateAndNotify(m_date
+ wxDateSpan::Day());
811 if ( event
.ControlDown() )
812 SetDateAndNotify(wxDateTime(m_date
).SetToPrevWeekDay(
813 GetWindowStyle() & wxCAL_MONDAY_FIRST
814 ? wxDateTime::Mon
: wxDateTime::Sun
));
816 SetDateAndNotify(m_date
- wxDateSpan::Day());
820 SetDateAndNotify(m_date
- wxDateSpan::Week());
824 SetDateAndNotify(m_date
+ wxDateSpan::Week());
828 if ( event
.ControlDown() )
829 SetDateAndNotify(wxDateTime::Today());
831 SetDateAndNotify(wxDateTime(1, m_date
.GetMonth(), m_date
.GetYear()));
835 SetDateAndNotify(wxDateTime(m_date
).SetToLastMonthDay());
839 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED
);
847 // ----------------------------------------------------------------------------
849 // ----------------------------------------------------------------------------
851 void wxCalendarCtrl::OnCalMonthChange(wxCalendarEvent
& event
)
858 void wxCalendarCtrl::EnableHolidayDisplay(bool display
)
860 long style
= GetWindowStyle();
862 style
|= wxCAL_SHOW_HOLIDAYS
;
864 style
&= ~wxCAL_SHOW_HOLIDAYS
;
866 SetWindowStyle(style
);
876 void wxCalendarCtrl::SetHolidayAttrs()
878 if ( GetWindowStyle() & wxCAL_SHOW_HOLIDAYS
)
882 wxDateTime::Tm tm
= m_date
.GetTm();
883 wxDateTime
dtStart(1, tm
.mon
, tm
.year
),
884 dtEnd
= dtStart
.GetLastMonthDay();
887 wxDateTimeHolidayAuthority::GetHolidaysInRange(dtStart
, dtEnd
, hol
);
889 size_t count
= hol
.GetCount();
890 for ( size_t n
= 0; n
< count
; n
++ )
892 SetHoliday(hol
[n
].GetDay());
897 void wxCalendarCtrl::SetHoliday(size_t day
)
899 wxCHECK_RET( day
> 0 && day
< 32, _T("invalid day in SetHoliday") );
901 wxCalendarDateAttr
*attr
= GetAttr(day
);
904 attr
= new wxCalendarDateAttr
;
907 attr
->SetHoliday(TRUE
);
909 // can't use SetAttr() because it would delete this pointer
910 m_attrs
[day
- 1] = attr
;
913 void wxCalendarCtrl::ResetHolidayAttrs()
915 for ( size_t day
= 0; day
< 31; day
++ )
919 m_attrs
[day
]->SetHoliday(FALSE
);
924 // ----------------------------------------------------------------------------
926 // ----------------------------------------------------------------------------
928 void wxCalendarEvent::Init()
930 m_wday
= wxDateTime::Inv_WeekDay
;
933 wxCalendarEvent::wxCalendarEvent(wxCalendarCtrl
*cal
, wxEventType type
)
934 : wxCommandEvent(type
, cal
->GetId())
936 m_date
= cal
->GetDate();