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"
37 #include "wx/calctrl.h"
41 // ----------------------------------------------------------------------------
43 // ----------------------------------------------------------------------------
45 class wxMonthComboBox
: public wxComboBox
48 wxMonthComboBox(wxCalendarCtrl
*cal
);
50 void OnMonthChange(wxCommandEvent
& event
) { m_cal
->OnMonthChange(event
); }
53 wxCalendarCtrl
*m_cal
;
58 class wxYearSpinCtrl
: public wxSpinCtrl
61 wxYearSpinCtrl(wxCalendarCtrl
*cal
);
63 void OnYearChange(wxSpinEvent
& event
) { m_cal
->OnYearChange(event
); }
66 wxCalendarCtrl
*m_cal
;
71 // ----------------------------------------------------------------------------
73 // ----------------------------------------------------------------------------
75 BEGIN_EVENT_TABLE(wxCalendarCtrl
, wxControl
)
76 EVT_PAINT(wxCalendarCtrl::OnPaint
)
78 EVT_CHAR(wxCalendarCtrl::OnChar
)
80 EVT_LEFT_DOWN(wxCalendarCtrl::OnClick
)
83 BEGIN_EVENT_TABLE(wxMonthComboBox
, wxComboBox
)
84 EVT_COMBOBOX(-1, wxMonthComboBox::OnMonthChange
)
87 BEGIN_EVENT_TABLE(wxYearSpinCtrl
, wxSpinCtrl
)
88 EVT_SPINCTRL(-1, wxYearSpinCtrl::OnYearChange
)
91 IMPLEMENT_DYNAMIC_CLASS(wxCalendarCtrl
, wxControl
)
93 // ============================================================================
95 // ============================================================================
97 // ----------------------------------------------------------------------------
98 // wxMonthComboBox and wxYearSpinCtrl
99 // ----------------------------------------------------------------------------
101 wxMonthComboBox::wxMonthComboBox(wxCalendarCtrl
*cal
)
102 : wxComboBox(cal
->GetParent(), -1,
112 for ( m
= wxDateTime::Jan
; m
< wxDateTime::Inv_Month
; wxNextMonth(m
) )
114 Append(wxDateTime::GetMonthName(m
));
117 SetSelection(m_cal
->GetDate().GetMonth());
120 wxYearSpinCtrl::wxYearSpinCtrl(wxCalendarCtrl
*cal
)
121 : wxSpinCtrl(cal
->GetParent(), -1,
122 cal
->GetDate().Format(_T("%Y")),
126 -4300, 10000, cal
->GetDate().GetYear())
131 // ----------------------------------------------------------------------------
133 // ----------------------------------------------------------------------------
135 void wxCalendarCtrl::Init()
143 wxDateTime::WeekDay wd
;
144 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
146 m_weekdays
[wd
] = wxDateTime::GetWeekDayName(wd
, wxDateTime::Name_Abbr
);
150 bool wxCalendarCtrl::Create(wxWindow
*parent
,
152 const wxDateTime
& date
,
156 const wxString
& name
)
158 SetWindowStyle(style
| (wxBORDER
| wxWANTS_CHARS
));
160 m_date
= date
.IsValid() ? date
: wxDateTime::Today();
162 m_comboMonth
= new wxMonthComboBox(this);
163 m_spinYear
= new wxYearSpinCtrl(this);
166 if ( size
.x
== -1 || size
.y
== -1 )
168 sizeReal
= DoGetBestSize();
181 SetBackgroundColour(*wxWHITE
);
182 SetFont(*wxSWISS_FONT
);
187 wxCalendarCtrl::~wxCalendarCtrl()
190 m_comboMonth
->PopEventHandler();
191 m_spinYear
->PopEventHandler();
195 // ----------------------------------------------------------------------------
196 // forward wxWin functions to subcontrols
197 // ----------------------------------------------------------------------------
199 bool wxCalendarCtrl::Show(bool show
)
201 if ( !wxControl::Show(show
) )
206 m_comboMonth
->Show(show
);
207 m_spinYear
->Show(show
);
212 bool wxCalendarCtrl::Enable(bool enable
)
214 if ( !wxControl::Enable(enable
) )
219 m_comboMonth
->Enable(enable
);
220 m_spinYear
->Enable(enable
);
225 // ----------------------------------------------------------------------------
227 // ----------------------------------------------------------------------------
229 void wxCalendarCtrl::SetDate(const wxDateTime
& date
)
231 if ( m_date
.GetMonth() == date
.GetMonth() &&
232 m_date
.GetYear() == date
.GetYear() )
234 // just change the day
242 // update the controls
243 m_comboMonth
->SetSelection(m_date
.GetMonth());
244 m_spinYear
->SetValue(m_date
.Format(_T("%Y")));
246 // update the calendar
251 void wxCalendarCtrl::ChangeDay(const wxDateTime
& date
)
253 if ( m_date
!= date
)
255 // we need to refresh the row containing the old date and the one
256 // containing the new one
257 wxDateTime dateOld
= m_date
;
260 RefreshDate(dateOld
);
262 // if the date is in the same row, it was already drawn correctly
263 if ( GetWeek(m_date
) != GetWeek(dateOld
) )
270 void wxCalendarCtrl::SetDateAndNotify(const wxDateTime
& date
)
272 wxDateTime::Tm tm1
= m_date
.GetTm(),
276 if ( tm1
.year
!= tm2
.year
)
277 type
= wxEVT_CALENDAR_YEAR_CHANGED
;
278 else if ( tm1
.mon
!= tm2
.mon
)
279 type
= wxEVT_CALENDAR_MONTH_CHANGED
;
280 else if ( tm1
.mday
!= tm2
.mday
)
281 type
= wxEVT_CALENDAR_DAY_CHANGED
;
290 // ----------------------------------------------------------------------------
292 // ----------------------------------------------------------------------------
294 wxDateTime
wxCalendarCtrl::GetStartDate() const
296 wxDateTime::Tm tm
= m_date
.GetTm();
298 wxDateTime date
= wxDateTime(1, tm
.mon
, tm
.year
);
299 if ( date
.GetWeekDay() != wxDateTime::Sun
)
301 date
.SetToPrevWeekDay(wxDateTime::Sun
);
303 // be sure to do it or it might gain 1 hour if DST changed in between
306 //else: we already have it
311 bool wxCalendarCtrl::IsDateShown(const wxDateTime
& date
) const
313 return date
.GetMonth() == m_date
.GetMonth();
316 size_t wxCalendarCtrl::GetWeek(const wxDateTime
& date
) const
318 return date
.GetWeekOfMonth(wxDateTime::Sunday_First
);
321 // ----------------------------------------------------------------------------
323 // ----------------------------------------------------------------------------
325 // this is a composite control and it must arrange its parts each time its
326 // size or position changes: the combobox and spinctrl are along the top of
327 // the available area and the calendar takes up therest of the space
329 // the constants used for the layout
330 #define VERT_MARGIN 5 // distance between combo and calendar
331 #define HORZ_MARGIN 15 // spin
333 wxSize
wxCalendarCtrl::DoGetBestSize() const
335 // calc the size of the calendar
336 ((wxCalendarCtrl
*)this)->RecalcGeometry(); // const_cast
338 wxCoord width
= 7*m_widthCol
,
339 height
= 7*m_heightRow
;
341 wxSize sizeCombo
= m_comboMonth
->GetBestSize(),
342 sizeSpin
= m_spinYear
->GetBestSize();
344 height
+= VERT_MARGIN
+ wxMax(sizeCombo
.y
, sizeSpin
.y
);
346 return wxSize(width
, height
);
349 void wxCalendarCtrl::DoSetSize(int x
, int y
,
350 int width
, int height
,
353 wxControl::DoSetSize(x
, y
, width
, height
, sizeFlags
);
356 void wxCalendarCtrl::DoMoveWindow(int x
, int y
, int width
, int height
)
358 wxSize sizeCombo
= m_comboMonth
->GetSize();
359 m_comboMonth
->Move(x
, y
);
361 int xDiff
= sizeCombo
.x
+ HORZ_MARGIN
;
362 m_spinYear
->SetSize(x
+ xDiff
, y
, width
- xDiff
, sizeCombo
.y
);
364 wxSize sizeSpin
= m_spinYear
->GetSize();
365 int yDiff
= wxMax(sizeSpin
.y
, sizeCombo
.y
) + VERT_MARGIN
;
367 wxControl::DoMoveWindow(x
, y
+ yDiff
, width
, height
- yDiff
);
370 void wxCalendarCtrl::DoGetPosition(int *x
, int *y
) const
372 wxControl::DoGetPosition(x
, y
);
374 // our real top corner is not in this position
377 *y
-= m_comboMonth
->GetSize().y
+ VERT_MARGIN
;
381 void wxCalendarCtrl::DoGetSize(int *width
, int *height
) const
383 wxControl::DoGetSize(width
, height
);
385 // our real height is bigger
388 *height
+= m_comboMonth
->GetSize().y
+ VERT_MARGIN
;
392 void wxCalendarCtrl::RecalcGeometry()
394 if ( m_widthCol
!= 0 )
401 // determine the column width (we assume that the weekday names are always
402 // wider (in any language) than the numbers)
404 wxDateTime::WeekDay wd
;
405 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
408 dc
.GetTextExtent(m_weekdays
[wd
], &width
, &m_heightRow
);
409 if ( width
> m_widthCol
)
415 // leave some margins
420 // ----------------------------------------------------------------------------
422 // ----------------------------------------------------------------------------
424 void wxCalendarCtrl::OnPaint(wxPaintEvent
& event
)
428 wxDateTime::WeekDay wd
;
435 printf("--- starting to paint, selection: %s, week %u\n",
436 m_date
.Format("%a %d-%m-%Y %H:%M:%S").c_str(),
440 // first draw the week days
441 if ( IsExposed(0, 0, 7*m_widthCol
, m_heightRow
) )
444 puts("painting the header");
447 dc
.SetTextForeground(*wxBLUE
);
448 dc
.SetBrush(wxBrush(*wxLIGHT_GREY
, wxSOLID
));
449 dc
.SetBackgroundMode(wxTRANSPARENT
);
450 dc
.SetPen(*wxLIGHT_GREY_PEN
);
451 dc
.DrawRectangle(0, 0, 7*m_widthCol
, m_heightRow
);
452 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
454 dc
.DrawText(m_weekdays
[wd
], wd
*m_widthCol
+ 1, 0);
458 // then the calendar itself
459 dc
.SetTextForeground(*wxBLACK
);
460 //dc.SetFont(*wxNORMAL_FONT);
462 wxCoord y
= m_heightRow
;
464 wxDateTime date
= GetStartDate();
466 printf("starting calendar from %s\n",
467 date
.Format("%a %d-%m-%Y %H:%M:%S").c_str());
470 dc
.SetBackgroundMode(wxSOLID
);
471 for ( size_t nWeek
= 1; nWeek
<= 6; nWeek
++, y
+= m_heightRow
)
473 // if the update region doesn't intersect this row, don't paint it
474 if ( !IsExposed(0, y
, 7*m_widthCol
, m_heightRow
- 1) )
476 date
+= wxDateSpan::Week();
482 printf("painting week %d at y = %d\n", nWeek
, y
);
485 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
487 if ( IsDateShown(date
) )
489 // don't use wxDate::Format() which prepends 0s
490 wxString day
= wxString::Format(_T("%u"), date
.GetDay());
492 dc
.GetTextExtent(day
, &width
, (wxCoord
*)NULL
);
494 bool isSel
= m_date
== date
;
497 dc
.SetTextForeground(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHTTEXT
));
498 dc
.SetTextBackground(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHT
));
501 dc
.DrawText(day
, wd
*m_widthCol
+ (m_widthCol
- width
) / 2, y
);
505 dc
.SetTextForeground(m_foregroundColour
);
506 dc
.SetTextBackground(m_backgroundColour
);
509 //else: just don't draw it
511 date
+= wxDateSpan::Day();
515 puts("+++ finished painting");
519 void wxCalendarCtrl::RefreshDate(const wxDateTime
& date
)
525 // always refresh the whole row at once because our OnPaint() will draw
526 // the whole row anyhow - and this allows the small optimisation in
527 // OnClick() below to work
529 rect
.y
= m_heightRow
* GetWeek(date
);
530 rect
.width
= 7*m_widthCol
;
531 rect
.height
= m_heightRow
;
534 printf("*** refreshing week %d at (%d, %d)-(%d, %d)\n",
537 rect
.x
+ rect
.width
, rect
.y
+ rect
.height
);
540 Refresh(TRUE
, &rect
);
543 // ----------------------------------------------------------------------------
545 // ----------------------------------------------------------------------------
547 void wxCalendarCtrl::OnClick(wxMouseEvent
& event
)
552 if ( !HitTest(event
.GetPosition(), &date
) )
560 GenerateEvent(wxEVT_CALENDAR_DAY_CHANGED
);
564 bool wxCalendarCtrl::HitTest(const wxPoint
& pos
, wxDateTime
*date
)
569 if ( y
< m_heightRow
)
573 int week
= y
/ m_heightRow
,
574 wday
= pos
.x
/ m_widthCol
;
576 if ( week
>= 6 || wday
>= 7 )
579 wxCHECK_MSG( date
, FALSE
, _T("bad pointer in wxCalendarCtrl::HitTest") );
581 *date
= GetStartDate();
582 *date
+= wxDateSpan::Days(7*week
+ wday
);
584 return IsDateShown(*date
);
587 // ----------------------------------------------------------------------------
588 // subcontrols events handling
589 // ----------------------------------------------------------------------------
591 void wxCalendarCtrl::OnMonthChange(wxCommandEvent
& event
)
593 wxDateTime::Tm tm
= m_date
.GetTm();
595 wxDateTime::Month mon
= (wxDateTime::Month
)event
.GetInt();
596 if ( tm
.mday
> wxDateTime::GetNumberOfDays(mon
, tm
.year
) )
598 tm
.mday
= wxDateTime::GetNumberOfDays(mon
, tm
.year
);
601 SetDate(wxDateTime(tm
.mday
, mon
, tm
.year
));
603 GenerateEvent(wxEVT_CALENDAR_MONTH_CHANGED
);
606 void wxCalendarCtrl::OnYearChange(wxSpinEvent
& event
)
608 wxDateTime::Tm tm
= m_date
.GetTm();
610 int year
= event
.GetInt();
611 if ( tm
.mday
> wxDateTime::GetNumberOfDays(tm
.mon
, year
) )
613 tm
.mday
= wxDateTime::GetNumberOfDays(tm
.mon
, year
);
616 SetDate(wxDateTime(tm
.mday
, tm
.mon
, year
));
618 GenerateEvent(wxEVT_CALENDAR_YEAR_CHANGED
);
621 // ----------------------------------------------------------------------------
622 // keyboard interface
623 // ----------------------------------------------------------------------------
625 void wxCalendarCtrl::OnChar(wxKeyEvent
& event
)
627 switch ( event
.KeyCode() )
631 SetDateAndNotify(m_date
+ wxDateSpan::Year());
636 SetDateAndNotify(m_date
- wxDateSpan::Year());
640 SetDateAndNotify(m_date
- wxDateSpan::Month());
644 SetDateAndNotify(m_date
+ wxDateSpan::Month());
648 SetDateAndNotify(m_date
+ wxDateSpan::Day());
652 SetDateAndNotify(m_date
- wxDateSpan::Day());
656 SetDateAndNotify(m_date
- wxDateSpan::Week());
660 SetDateAndNotify(m_date
+ wxDateSpan::Week());
664 SetDateAndNotify(wxDateTime::Today());
672 // ----------------------------------------------------------------------------
674 // ----------------------------------------------------------------------------
676 void wxCalendarCtrl::GenerateEvent(wxEventType type
)
678 // we're called for a change in some particular date field but we always
679 // also generate a generic "changed" event
680 wxCalendarEvent
event(this, type
);
681 wxCalendarEvent
event2(this, wxEVT_CALENDAR_SEL_CHANGED
);
683 (void)GetEventHandler()->ProcessEvent(event
);
684 (void)GetEventHandler()->ProcessEvent(event2
);
687 wxCalendarEvent::wxCalendarEvent(wxCalendarCtrl
*cal
, wxEventType type
)
688 : wxCommandEvent(type
, cal
->GetId())
690 m_date
= cal
->GetDate();