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
* WXUNUSED(parent
),
151 wxWindowID
WXUNUSED(id
),
152 const wxDateTime
& date
,
153 const wxPoint
& WXUNUSED(pos
),
156 const wxString
& WXUNUSED(name
))
158 SetWindowStyle(style
| (wxRAISED_BORDER
| 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
);
301 date
.SetToPrevWeekDay(GetWindowStyle() & wxCAL_MONDAY_FIRST
302 ? wxDateTime::Mon
: wxDateTime::Sun
);
304 // be sure to do it or it might gain 1 hour if DST changed in between
310 bool wxCalendarCtrl::IsDateShown(const wxDateTime
& date
) const
312 return date
.GetMonth() == m_date
.GetMonth();
315 size_t wxCalendarCtrl::GetWeek(const wxDateTime
& date
) const
317 return date
.GetWeekOfMonth(GetWindowStyle() & wxCAL_MONDAY_FIRST
318 ? wxDateTime::Monday_First
319 : wxDateTime::Sunday_First
);
322 // ----------------------------------------------------------------------------
324 // ----------------------------------------------------------------------------
326 // this is a composite control and it must arrange its parts each time its
327 // size or position changes: the combobox and spinctrl are along the top of
328 // the available area and the calendar takes up therest of the space
330 // the constants used for the layout
331 #define VERT_MARGIN 5 // distance between combo and calendar
332 #define HORZ_MARGIN 15 // spin
334 wxSize
wxCalendarCtrl::DoGetBestSize() const
336 // calc the size of the calendar
337 ((wxCalendarCtrl
*)this)->RecalcGeometry(); // const_cast
339 wxCoord width
= 7*m_widthCol
,
340 height
= 7*m_heightRow
;
342 wxSize sizeCombo
= m_comboMonth
->GetBestSize(),
343 sizeSpin
= m_spinYear
->GetBestSize();
345 height
+= VERT_MARGIN
+ wxMax(sizeCombo
.y
, sizeSpin
.y
);
347 return wxSize(width
, height
);
350 void wxCalendarCtrl::DoSetSize(int x
, int y
,
351 int width
, int height
,
354 wxControl::DoSetSize(x
, y
, width
, height
, sizeFlags
);
357 void wxCalendarCtrl::DoMoveWindow(int x
, int y
, int width
, int height
)
359 wxSize sizeCombo
= m_comboMonth
->GetSize();
360 m_comboMonth
->Move(x
, y
);
362 int xDiff
= sizeCombo
.x
+ HORZ_MARGIN
;
363 m_spinYear
->SetSize(x
+ xDiff
, y
, width
- xDiff
, sizeCombo
.y
);
365 wxSize sizeSpin
= m_spinYear
->GetSize();
366 int yDiff
= wxMax(sizeSpin
.y
, sizeCombo
.y
) + VERT_MARGIN
;
368 wxControl::DoMoveWindow(x
, y
+ yDiff
, width
, height
- yDiff
);
371 void wxCalendarCtrl::DoGetPosition(int *x
, int *y
) const
373 wxControl::DoGetPosition(x
, y
);
375 // our real top corner is not in this position
378 *y
-= m_comboMonth
->GetSize().y
+ VERT_MARGIN
;
382 void wxCalendarCtrl::DoGetSize(int *width
, int *height
) const
384 wxControl::DoGetSize(width
, height
);
386 // our real height is bigger
389 *height
+= m_comboMonth
->GetSize().y
+ VERT_MARGIN
;
393 void wxCalendarCtrl::RecalcGeometry()
395 if ( m_widthCol
!= 0 )
402 // determine the column width (we assume that the weekday names are always
403 // wider (in any language) than the numbers)
405 wxDateTime::WeekDay wd
;
406 for ( wd
= wxDateTime::Sun
; wd
< wxDateTime::Inv_WeekDay
; wxNextWDay(wd
) )
409 dc
.GetTextExtent(m_weekdays
[wd
], &width
, &m_heightRow
);
410 if ( width
> m_widthCol
)
416 // leave some margins
421 // ----------------------------------------------------------------------------
423 // ----------------------------------------------------------------------------
425 void wxCalendarCtrl::OnPaint(wxPaintEvent
& WXUNUSED(event
))
434 printf("--- starting to paint, selection: %s, week %u\n",
435 m_date
.Format("%a %d-%m-%Y %H:%M:%S").c_str(),
439 // first draw the week days
440 if ( IsExposed(0, 0, 7*m_widthCol
, m_heightRow
) )
443 puts("painting the header");
446 dc
.SetTextForeground(*wxBLUE
);
447 dc
.SetBrush(wxBrush(*wxLIGHT_GREY
, wxSOLID
));
448 dc
.SetBackgroundMode(wxTRANSPARENT
);
449 dc
.SetPen(*wxLIGHT_GREY_PEN
);
450 dc
.DrawRectangle(0, 0, 7*m_widthCol
, m_heightRow
);
452 bool startOnMonday
= (GetWindowStyle() & wxCAL_MONDAY_FIRST
) != 0;
453 for ( size_t wd
= 0; wd
< 7; wd
++ )
457 n
= wd
== 6 ? 0 : wd
+ 1;
461 dc
.DrawText(m_weekdays
[n
], wd
*m_widthCol
+ 1, 0);
465 // then the calendar itself
466 dc
.SetTextForeground(*wxBLACK
);
467 //dc.SetFont(*wxNORMAL_FONT);
469 wxCoord y
= m_heightRow
;
471 wxDateTime date
= GetStartDate();
473 printf("starting calendar from %s\n",
474 date
.Format("%a %d-%m-%Y %H:%M:%S").c_str());
477 dc
.SetBackgroundMode(wxSOLID
);
478 for ( size_t nWeek
= 1; nWeek
<= 6; nWeek
++, y
+= m_heightRow
)
480 // if the update region doesn't intersect this row, don't paint it
481 if ( !IsExposed(0, y
, 7*m_widthCol
, m_heightRow
- 1) )
483 date
+= wxDateSpan::Week();
489 printf("painting week %d at y = %d\n", nWeek
, y
);
492 for ( size_t wd
= 0; wd
< 7; wd
++ )
494 if ( IsDateShown(date
) )
496 // don't use wxDate::Format() which prepends 0s
497 wxString day
= wxString::Format(_T("%u"), date
.GetDay());
499 dc
.GetTextExtent(day
, &width
, (wxCoord
*)NULL
);
501 bool isSel
= m_date
== date
;
504 dc
.SetTextForeground(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHTTEXT
));
505 dc
.SetTextBackground(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHT
));
508 dc
.DrawText(day
, wd
*m_widthCol
+ (m_widthCol
- width
) / 2, y
);
512 dc
.SetTextForeground(m_foregroundColour
);
513 dc
.SetTextBackground(m_backgroundColour
);
516 //else: just don't draw it
518 date
+= wxDateSpan::Day();
522 puts("+++ finished painting");
526 void wxCalendarCtrl::RefreshDate(const wxDateTime
& date
)
532 // always refresh the whole row at once because our OnPaint() will draw
533 // the whole row anyhow - and this allows the small optimisation in
534 // OnClick() below to work
536 rect
.y
= m_heightRow
* GetWeek(date
);
537 rect
.width
= 7*m_widthCol
;
538 rect
.height
= m_heightRow
;
541 printf("*** refreshing week %d at (%d, %d)-(%d, %d)\n",
544 rect
.x
+ rect
.width
, rect
.y
+ rect
.height
);
547 Refresh(TRUE
, &rect
);
550 // ----------------------------------------------------------------------------
552 // ----------------------------------------------------------------------------
554 void wxCalendarCtrl::OnClick(wxMouseEvent
& event
)
559 if ( !HitTest(event
.GetPosition(), &date
) )
567 GenerateEvent(wxEVT_CALENDAR_DAY_CHANGED
);
571 bool wxCalendarCtrl::HitTest(const wxPoint
& pos
, wxDateTime
*date
)
576 if ( y
< m_heightRow
)
580 int week
= y
/ m_heightRow
,
581 wday
= pos
.x
/ m_widthCol
;
583 if ( week
>= 6 || wday
>= 7 )
586 wxCHECK_MSG( date
, FALSE
, _T("bad pointer in wxCalendarCtrl::HitTest") );
588 *date
= GetStartDate();
589 *date
+= wxDateSpan::Days(7*week
+ wday
);
591 return IsDateShown(*date
);
594 // ----------------------------------------------------------------------------
595 // subcontrols events handling
596 // ----------------------------------------------------------------------------
598 void wxCalendarCtrl::OnMonthChange(wxCommandEvent
& event
)
600 wxDateTime::Tm tm
= m_date
.GetTm();
602 wxDateTime::Month mon
= (wxDateTime::Month
)event
.GetInt();
603 if ( tm
.mday
> wxDateTime::GetNumberOfDays(mon
, tm
.year
) )
605 tm
.mday
= wxDateTime::GetNumberOfDays(mon
, tm
.year
);
608 SetDate(wxDateTime(tm
.mday
, mon
, tm
.year
));
610 GenerateEvent(wxEVT_CALENDAR_MONTH_CHANGED
);
613 void wxCalendarCtrl::OnYearChange(wxSpinEvent
& event
)
615 wxDateTime::Tm tm
= m_date
.GetTm();
617 int year
= (int)event
.GetInt();
618 if ( tm
.mday
> wxDateTime::GetNumberOfDays(tm
.mon
, year
) )
620 tm
.mday
= wxDateTime::GetNumberOfDays(tm
.mon
, year
);
623 SetDate(wxDateTime(tm
.mday
, tm
.mon
, year
));
625 GenerateEvent(wxEVT_CALENDAR_YEAR_CHANGED
);
628 // ----------------------------------------------------------------------------
629 // keyboard interface
630 // ----------------------------------------------------------------------------
632 void wxCalendarCtrl::OnChar(wxKeyEvent
& event
)
634 switch ( event
.KeyCode() )
638 SetDateAndNotify(m_date
+ wxDateSpan::Year());
643 SetDateAndNotify(m_date
- wxDateSpan::Year());
647 SetDateAndNotify(m_date
- wxDateSpan::Month());
651 SetDateAndNotify(m_date
+ wxDateSpan::Month());
655 if ( event
.ControlDown() )
656 SetDateAndNotify(wxDateTime(m_date
).SetToNextWeekDay(
657 GetWindowStyle() & wxCAL_MONDAY_FIRST
658 ? wxDateTime::Sun
: wxDateTime::Sat
));
660 SetDateAndNotify(m_date
+ wxDateSpan::Day());
664 if ( event
.ControlDown() )
665 SetDateAndNotify(wxDateTime(m_date
).SetToPrevWeekDay(
666 GetWindowStyle() & wxCAL_MONDAY_FIRST
667 ? wxDateTime::Mon
: wxDateTime::Sun
));
669 SetDateAndNotify(m_date
- wxDateSpan::Day());
673 SetDateAndNotify(m_date
- wxDateSpan::Week());
677 SetDateAndNotify(m_date
+ wxDateSpan::Week());
681 if ( event
.ControlDown() )
682 SetDateAndNotify(wxDateTime::Today());
684 SetDateAndNotify(wxDateTime(1, m_date
.GetMonth(), m_date
.GetYear()));
688 SetDateAndNotify(wxDateTime(m_date
).SetToLastMonthDay());
696 // ----------------------------------------------------------------------------
698 // ----------------------------------------------------------------------------
700 void wxCalendarCtrl::GenerateEvent(wxEventType type
)
702 // we're called for a change in some particular date field but we always
703 // also generate a generic "changed" event
704 wxCalendarEvent
event(this, type
);
705 wxCalendarEvent
event2(this, wxEVT_CALENDAR_SEL_CHANGED
);
707 (void)GetEventHandler()->ProcessEvent(event
);
708 (void)GetEventHandler()->ProcessEvent(event2
);
711 wxCalendarEvent::wxCalendarEvent(wxCalendarCtrl
*cal
, wxEventType type
)
712 : wxCommandEvent(type
, cal
->GetId())
714 m_date
= cal
->GetDate();