1
///////////////////////////////////////////////////////////////////////////////
2
// Name: src/generic/calctrlg.cpp
3
// Purpose: implementation of the wxGenericCalendarCtrl
4
// Author: Vadim Zeitlin
7
// Copyright: (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
8
// Licence: wxWindows licence
9
///////////////////////////////////////////////////////////////////////////////
11
// ============================================================================
13
// ============================================================================
15
// ----------------------------------------------------------------------------
17
// ----------------------------------------------------------------------------
19
// For compilers that support precompilation, includes "wx.h".
20
#include "wx/wxprec.h"
27
#include "wx/dcclient.h"
28
#include "wx/settings.h"
30
#include "wx/combobox.h"
31
#include "wx/listbox.h"
32
#include "wx/stattext.h"
33
#include "wx/textctrl.h"
37
#if wxUSE_CALENDARCTRL
39
#include "wx/spinctrl.h"
40
#include "wx/calctrl.h"
41
#include "wx/generic/calctrlg.h"
45
// ----------------------------------------------------------------------------
47
// ----------------------------------------------------------------------------
49
#ifdef wxHAS_NATIVE_CALENDARCTRL
51
wxIMPLEMENT_DYNAMIC_CLASS_XTI(wxGenericCalendarCtrl, wxControl,"wx/calctrl.h")
55
BEGIN_EVENT_TABLE(wxGenericCalendarCtrl, wxControl)
56
EVT_PAINT(wxGenericCalendarCtrl::OnPaint)
58
EVT_CHAR(wxGenericCalendarCtrl::OnChar)
60
EVT_LEFT_DOWN(wxGenericCalendarCtrl::OnClick)
61
EVT_LEFT_DCLICK(wxGenericCalendarCtrl::OnDClick)
63
EVT_SYS_COLOUR_CHANGED(wxGenericCalendarCtrl::OnSysColourChanged)
66
// ============================================================================
68
// ============================================================================
70
// ----------------------------------------------------------------------------
72
// ----------------------------------------------------------------------------
77
// add attributes that are set in attr
78
void AddAttr(wxCalendarDateAttr *self, const wxCalendarDateAttr& attr)
80
if (attr.HasTextColour())
81
self->SetTextColour(attr.GetTextColour());
82
if (attr.HasBackgroundColour())
83
self->SetBackgroundColour(attr.GetBackgroundColour());
84
if (attr.HasBorderColour())
85
self->SetBorderColour(attr.GetBorderColour());
87
self->SetFont(attr.GetFont());
89
self->SetBorder(attr.GetBorder());
91
self->SetHoliday(true);
94
// remove attributes that are set in attr
95
void DelAttr(wxCalendarDateAttr *self, const wxCalendarDateAttr &attr)
97
if (attr.HasTextColour())
98
self->SetTextColour(wxNullColour);
99
if (attr.HasBackgroundColour())
100
self->SetBackgroundColour(wxNullColour);
101
if (attr.HasBorderColour())
102
self->SetBorderColour(wxNullColour);
104
self->SetFont(wxNullFont);
105
if (attr.HasBorder())
106
self->SetBorder(wxCAL_BORDER_NONE);
107
if (attr.IsHoliday())
108
self->SetHoliday(false);
111
} // anonymous namespace
113
// ----------------------------------------------------------------------------
114
// wxGenericCalendarCtrl
115
// ----------------------------------------------------------------------------
117
wxGenericCalendarCtrl::wxGenericCalendarCtrl(wxWindow *parent,
119
const wxDateTime& date,
123
const wxString& name)
127
(void)Create(parent, id, date, pos, size, style, name);
130
void wxGenericCalendarCtrl::Init()
135
m_staticMonth = NULL;
137
m_userChangedYear = false;
141
m_calendarWeekWidth = 0;
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);
149
for ( size_t n = 0; n < WXSIZEOF(m_attrs); n++ )
157
void wxGenericCalendarCtrl::InitColours()
159
m_colHighlightFg = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT);
160
m_colHighlightBg = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT);
161
m_colBackground = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
162
m_colSurrounding = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT);
164
m_colHolidayFg = *wxRED;
165
// don't set m_colHolidayBg - by default, same as our bg colour
167
m_colHeaderFg = *wxBLUE;
168
m_colHeaderBg = *wxLIGHT_GREY;
171
bool wxGenericCalendarCtrl::Create(wxWindow *parent,
173
const wxDateTime& date,
177
const wxString& name)
179
if ( !wxControl::Create(parent, id, pos, size,
180
style | wxCLIP_CHILDREN | wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE,
181
wxDefaultValidator, name) )
186
// needed to get the arrow keys normally used for the dialog navigation
187
SetWindowStyle(style | wxWANTS_CHARS);
189
m_date = date.IsValid() ? date : wxDateTime::Today();
191
m_lowdate = wxDefaultDateTime;
192
m_highdate = wxDefaultDateTime;
194
if ( !HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
196
CreateYearSpinCtrl();
197
m_staticYear = new wxStaticText(GetParent(), wxID_ANY, m_date.Format(wxT("%Y")),
198
wxDefaultPosition, wxDefaultSize,
200
CreateMonthComboBox();
201
m_staticMonth = new wxStaticText(GetParent(), wxID_ANY, m_date.Format(wxT("%B")),
202
wxDefaultPosition, wxDefaultSize,
206
ShowCurrentControls();
208
// we need to set the position as well because the main control position
209
// is not the same as the one specified in pos if we have the controls
211
SetInitialSize(size);
214
// Since we don't paint the whole background make sure that the platform
215
// will use the right one.
216
SetBackgroundColour(m_colBackground);
223
wxGenericCalendarCtrl::~wxGenericCalendarCtrl()
225
for ( size_t n = 0; n < WXSIZEOF(m_attrs); n++ )
230
if ( !HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
233
delete m_staticMonth;
239
void wxGenericCalendarCtrl::SetWindowStyleFlag(long style)
241
// changing this style doesn't work because the controls are not
242
// created/shown/hidden accordingly
243
wxASSERT_MSG( (style & wxCAL_SEQUENTIAL_MONTH_SELECTION) ==
244
(m_windowStyle & wxCAL_SEQUENTIAL_MONTH_SELECTION),
245
wxT("wxCAL_SEQUENTIAL_MONTH_SELECTION can't be changed after creation") );
247
wxControl::SetWindowStyleFlag(style);
250
// ----------------------------------------------------------------------------
251
// Create the wxComboBox and wxSpinCtrl
252
// ----------------------------------------------------------------------------
254
void wxGenericCalendarCtrl::CreateMonthComboBox()
256
m_comboMonth = new wxComboBox(GetParent(), wxID_ANY,
261
wxCB_READONLY | wxCLIP_SIBLINGS);
264
for ( m = wxDateTime::Jan; m < wxDateTime::Inv_Month; wxNextMonth(m) )
266
m_comboMonth->Append(wxDateTime::GetMonthName(m));
269
m_comboMonth->SetSelection(GetDate().GetMonth());
270
m_comboMonth->SetSize(wxDefaultCoord,
274
wxSIZE_AUTO_WIDTH|wxSIZE_AUTO_HEIGHT);
276
m_comboMonth->Connect(m_comboMonth->GetId(), wxEVT_COMBOBOX,
277
wxCommandEventHandler(wxGenericCalendarCtrl::OnMonthChange),
281
void wxGenericCalendarCtrl::CreateYearSpinCtrl()
283
m_spinYear = new wxSpinCtrl(GetParent(), wxID_ANY,
284
GetDate().Format(wxT("%Y")),
287
wxSP_ARROW_KEYS | wxCLIP_SIBLINGS,
288
-4300, 10000, GetDate().GetYear());
290
m_spinYear->Connect(m_spinYear->GetId(), wxEVT_TEXT,
291
wxCommandEventHandler(wxGenericCalendarCtrl::OnYearTextChange),
294
m_spinYear->Connect(m_spinYear->GetId(), wxEVT_SPINCTRL,
295
wxSpinEventHandler(wxGenericCalendarCtrl::OnYearChange),
299
// ----------------------------------------------------------------------------
300
// forward wxWin functions to subcontrols
301
// ----------------------------------------------------------------------------
303
bool wxGenericCalendarCtrl::Destroy()
306
m_staticYear->Destroy();
308
m_spinYear->Destroy();
310
m_comboMonth->Destroy();
312
m_staticMonth->Destroy();
317
m_staticMonth = NULL;
319
return wxControl::Destroy();
322
bool wxGenericCalendarCtrl::Show(bool show)
324
if ( !wxControl::Show(show) )
329
if ( !(GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) )
331
if ( GetMonthControl() )
333
GetMonthControl()->Show(show);
334
GetYearControl()->Show(show);
341
bool wxGenericCalendarCtrl::Enable(bool enable)
343
if ( !wxControl::Enable(enable) )
348
if ( !(GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) )
350
GetMonthControl()->Enable(enable);
351
GetYearControl()->Enable(enable);
357
// ----------------------------------------------------------------------------
358
// enable/disable month/year controls
359
// ----------------------------------------------------------------------------
361
void wxGenericCalendarCtrl::ShowCurrentControls()
363
if ( !HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
365
if ( AllowMonthChange() )
367
m_comboMonth->Show();
368
m_staticMonth->Hide();
370
if ( AllowYearChange() )
373
m_staticYear->Hide();
381
m_comboMonth->Hide();
382
m_staticMonth->Show();
385
// year change not allowed here
387
m_staticYear->Show();
389
//else: these controls are not even created, don't show/hide them
392
wxControl *wxGenericCalendarCtrl::GetMonthControl() const
394
return AllowMonthChange() ? (wxControl *)m_comboMonth : (wxControl *)m_staticMonth;
397
wxControl *wxGenericCalendarCtrl::GetYearControl() const
399
return AllowYearChange() ? (wxControl *)m_spinYear : (wxControl *)m_staticYear;
402
void wxGenericCalendarCtrl::EnableYearChange(bool enable)
404
if ( enable != AllowYearChange() )
406
long style = GetWindowStyle();
408
style &= ~wxCAL_NO_YEAR_CHANGE;
410
style |= wxCAL_NO_YEAR_CHANGE;
411
SetWindowStyle(style);
413
ShowCurrentControls();
414
if ( GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION )
421
bool wxGenericCalendarCtrl::EnableMonthChange(bool enable)
423
if ( !wxCalendarCtrlBase::EnableMonthChange(enable) )
426
ShowCurrentControls();
427
if ( GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION )
433
// ----------------------------------------------------------------------------
435
// ----------------------------------------------------------------------------
437
bool wxGenericCalendarCtrl::SetDate(const wxDateTime& date)
441
bool sameMonth = m_date.GetMonth() == date.GetMonth(),
442
sameYear = m_date.GetYear() == date.GetYear();
444
if ( IsDateInRange(date) )
446
if ( sameMonth && sameYear )
448
// just change the day
453
if ( AllowMonthChange() && (AllowYearChange() || sameYear) )
458
if ( !(GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) )
460
// update the controls
461
m_comboMonth->SetSelection(m_date.GetMonth());
463
if ( AllowYearChange() )
465
if ( !m_userChangedYear )
466
m_spinYear->SetValue(m_date.Format(wxT("%Y")));
470
// as the month changed, holidays did too
473
// update the calendar
484
m_userChangedYear = false;
489
void wxGenericCalendarCtrl::ChangeDay(const wxDateTime& date)
491
if ( m_date != date )
493
// we need to refresh the row containing the old date and the one
494
// containing the new one
495
wxDateTime dateOld = m_date;
498
RefreshDate(dateOld);
500
// if the date is in the same row, it was already drawn correctly
501
if ( GetWeek(m_date) != GetWeek(dateOld) )
508
void wxGenericCalendarCtrl::SetDateAndNotify(const wxDateTime& date)
510
const wxDateTime dateOld = GetDate();
511
if ( date != dateOld && SetDate(date) )
513
GenerateAllChangeEvents(dateOld);
517
// ----------------------------------------------------------------------------
519
// ----------------------------------------------------------------------------
521
bool wxGenericCalendarCtrl::SetLowerDateLimit(const wxDateTime& date /* = wxDefaultDateTime */)
525
if ( !(date.IsValid()) || ( ( m_highdate.IsValid() ) ? ( date <= m_highdate ) : true ) )
537
bool wxGenericCalendarCtrl::SetUpperDateLimit(const wxDateTime& date /* = wxDefaultDateTime */)
541
if ( !(date.IsValid()) || ( ( m_lowdate.IsValid() ) ? ( date >= m_lowdate ) : true ) )
553
bool wxGenericCalendarCtrl::SetDateRange(const wxDateTime& lowerdate /* = wxDefaultDateTime */, const wxDateTime& upperdate /* = wxDefaultDateTime */)
558
( !( lowerdate.IsValid() ) || ( ( upperdate.IsValid() ) ? ( lowerdate <= upperdate ) : true ) ) &&
559
( !( upperdate.IsValid() ) || ( ( lowerdate.IsValid() ) ? ( upperdate >= lowerdate ) : true ) ) )
561
m_lowdate = lowerdate;
562
m_highdate = upperdate;
572
bool wxGenericCalendarCtrl::GetDateRange(wxDateTime *lowerdate,
573
wxDateTime *upperdate) const
576
*lowerdate = m_lowdate;
578
*upperdate = m_highdate;
580
return m_lowdate.IsValid() || m_highdate.IsValid();
583
// ----------------------------------------------------------------------------
585
// ----------------------------------------------------------------------------
587
wxDateTime wxGenericCalendarCtrl::GetStartDate() const
589
wxDateTime::Tm tm = m_date.GetTm();
591
wxDateTime date = wxDateTime(1, tm.mon, tm.year);
594
date.SetToPrevWeekDay(GetWeekStart());
596
if ( GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS )
598
// We want to offset the calendar if we start on the first..
599
if ( date.GetDay() == 1 )
601
date -= wxDateSpan::Week();
608
bool wxGenericCalendarCtrl::IsDateShown(const wxDateTime& date) const
610
if ( !(GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS) )
612
return date.GetMonth() == m_date.GetMonth();
620
bool wxGenericCalendarCtrl::IsDateInRange(const wxDateTime& date) const
622
// Check if the given date is in the range specified
623
return ( ( ( m_lowdate.IsValid() ) ? ( date >= m_lowdate ) : true )
624
&& ( ( m_highdate.IsValid() ) ? ( date <= m_highdate ) : true ) );
627
bool wxGenericCalendarCtrl::AdjustDateToRange(wxDateTime *date) const
629
if ( m_lowdate.IsValid() && *date < m_lowdate )
635
if ( m_highdate.IsValid() && *date > m_highdate )
644
size_t wxGenericCalendarCtrl::GetWeek(const wxDateTime& date) const
646
size_t retval = date.GetWeekOfMonth(HasFlag(wxCAL_MONDAY_FIRST)
647
? wxDateTime::Monday_First
648
: wxDateTime::Sunday_First);
650
if ( (GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS) )
652
// we need to offset an extra week if we "start" on the 1st of the month
653
wxDateTime::Tm tm = date.GetTm();
655
wxDateTime datetest = wxDateTime(1, tm.mon, tm.year);
658
datetest.SetToPrevWeekDay(GetWeekStart());
660
if ( datetest.GetDay() == 1 )
669
// ----------------------------------------------------------------------------
671
// ----------------------------------------------------------------------------
673
// this is a composite control and it must arrange its parts each time its
674
// size or position changes: the combobox and spinctrl are along the top of
675
// the available area and the calendar takes up the rest of the space
677
// the static controls are supposed to be always smaller than combo/spin so we
678
// always use the latter for size calculations and position the static to take
681
// the constants used for the layout
682
#define VERT_MARGIN 5 // distance between combo and calendar
683
#define HORZ_MARGIN 5 // spin
685
wxSize wxGenericCalendarCtrl::DoGetBestSize() const
687
// calc the size of the calendar
688
const_cast<wxGenericCalendarCtrl *>(this)->RecalcGeometry();
690
wxCoord width = 7*m_widthCol + m_calendarWeekWidth,
691
height = 7*m_heightRow + m_rowOffset + VERT_MARGIN;
693
if ( !HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
695
const wxSize bestSizeCombo = m_comboMonth->GetBestSize();
697
height += wxMax(bestSizeCombo.y, m_spinYear->GetBestSize().y)
700
wxCoord w2 = bestSizeCombo.x + HORZ_MARGIN + GetCharWidth()*8;
705
wxSize best(width, height);
706
if ( !HasFlag(wxBORDER_NONE) )
708
best += GetWindowBorderSize();
716
void wxGenericCalendarCtrl::DoMoveWindow(int x, int y, int width, int height)
720
if ( !HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) && m_staticMonth )
722
wxSize sizeCombo = m_comboMonth->GetEffectiveMinSize();
723
wxSize sizeStatic = m_staticMonth->GetSize();
724
wxSize sizeSpin = m_spinYear->GetSize();
726
int maxHeight = wxMax(sizeSpin.y, sizeCombo.y);
727
int dy = (maxHeight - sizeStatic.y) / 2;
728
m_comboMonth->Move(x, y + (maxHeight - sizeCombo.y)/2);
729
m_staticMonth->SetSize(x, y + dy, sizeCombo.x, -1);
731
int xDiff = sizeCombo.x + HORZ_MARGIN;
733
m_spinYear->SetSize(x + xDiff, y + (maxHeight - sizeSpin.y)/2, width - xDiff, maxHeight);
734
m_staticYear->SetSize(x + xDiff, y + dy, width - xDiff, sizeStatic.y);
736
yDiff = maxHeight + VERT_MARGIN;
738
else // no controls on the top
743
wxControl::DoMoveWindow(x, y + yDiff, width, height - yDiff);
746
void wxGenericCalendarCtrl::DoGetSize(int *width, int *height) const
748
wxControl::DoGetSize( width, height );
751
void wxGenericCalendarCtrl::RecalcGeometry()
755
dc.SetFont(GetFont());
757
// determine the column width (weekday names are not necessarily wider
758
// than the numbers (in some languages), so let's not assume that they are)
760
for ( int day = 10; day <= 31; day++)
763
dc.GetTextExtent(wxString::Format(wxT("%d"), day), &width, &m_heightRow);
764
if ( width > m_widthCol )
766
// 1.5 times the width gives nice margins even if the weekday
768
m_widthCol = width+width/2;
771
wxDateTime::WeekDay wd;
772
for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
775
dc.GetTextExtent(m_weekdays[wd], &width, &m_heightRow);
776
if ( width > m_widthCol )
782
m_calendarWeekWidth = HasFlag( wxCAL_SHOW_WEEK_NUMBERS )
783
? dc.GetTextExtent( wxString::Format( wxT( "%d" ), 42 )).GetWidth() + 4 : 0;
785
// leave some margins
789
m_rowOffset = HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) ? m_heightRow : 0; // conditional in relation to style
792
// ----------------------------------------------------------------------------
794
// ----------------------------------------------------------------------------
796
void wxGenericCalendarCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
800
dc.SetFont(GetFont());
805
wxLogDebug("--- starting to paint, selection: %s, week %u\n",
806
m_date.Format("%a %d-%m-%Y %H:%M:%S").c_str(),
811
wxCoord x0 = m_calendarWeekWidth;
813
if ( HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
815
// draw the sequential month-selector
817
dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
818
dc.SetTextForeground(*wxBLACK);
819
dc.SetBrush(wxBrush(m_colHeaderBg, wxBRUSHSTYLE_SOLID));
820
dc.SetPen(wxPen(m_colHeaderBg, 1, wxPENSTYLE_SOLID));
821
dc.DrawRectangle(0, y, GetClientSize().x, m_heightRow);
823
// Get extent of month-name + year
824
wxCoord monthw, monthh;
825
wxString headertext = m_date.Format(wxT("%B %Y"));
826
dc.GetTextExtent(headertext, &monthw, &monthh);
828
// draw month-name centered above weekdays
829
wxCoord monthx = ((m_widthCol * 7) - monthw) / 2 + x0;
830
wxCoord monthy = ((m_heightRow - monthh) / 2) + y;
831
dc.DrawText(headertext, monthx, monthy);
833
// calculate the "month-arrows"
834
wxPoint leftarrow[3];
835
wxPoint rightarrow[3];
837
int arrowheight = monthh / 2;
839
leftarrow[0] = wxPoint(0, arrowheight / 2);
840
leftarrow[1] = wxPoint(arrowheight / 2, 0);
841
leftarrow[2] = wxPoint(arrowheight / 2, arrowheight - 1);
843
rightarrow[0] = wxPoint(0,0);
844
rightarrow[1] = wxPoint(arrowheight / 2, arrowheight / 2);
845
rightarrow[2] = wxPoint(0, arrowheight - 1);
847
// draw the "month-arrows"
849
wxCoord arrowy = (m_heightRow - arrowheight) / 2;
850
wxCoord larrowx = (m_widthCol - (arrowheight / 2)) / 2 + x0;
851
wxCoord rarrowx = ((m_widthCol - (arrowheight / 2)) / 2) + m_widthCol*6 + x0;
852
m_leftArrowRect = m_rightArrowRect = wxRect(0,0,0,0);
854
if ( AllowMonthChange() )
856
wxDateTime ldpm = wxDateTime(1,m_date.GetMonth(), m_date.GetYear()) - wxDateSpan::Day(); // last day prev month
857
// Check if range permits change
858
if ( IsDateInRange(ldpm) && ( ( ldpm.GetYear() == m_date.GetYear() ) ? true : AllowYearChange() ) )
860
m_leftArrowRect = wxRect(larrowx - 3, arrowy - 3, (arrowheight / 2) + 8, (arrowheight + 6));
861
dc.SetBrush(*wxBLACK_BRUSH);
862
dc.SetPen(*wxBLACK_PEN);
863
dc.DrawPolygon(3, leftarrow, larrowx , arrowy, wxWINDING_RULE);
864
dc.SetBrush(*wxTRANSPARENT_BRUSH);
865
dc.DrawRectangle(m_leftArrowRect);
867
wxDateTime fdnm = wxDateTime(1,m_date.GetMonth(), m_date.GetYear()) + wxDateSpan::Month(); // first day next month
868
if ( IsDateInRange(fdnm) && ( ( fdnm.GetYear() == m_date.GetYear() ) ? true : AllowYearChange() ) )
870
m_rightArrowRect = wxRect(rarrowx - 4, arrowy - 3, (arrowheight / 2) + 8, (arrowheight + 6));
871
dc.SetBrush(*wxBLACK_BRUSH);
872
dc.SetPen(*wxBLACK_PEN);
873
dc.DrawPolygon(3, rightarrow, rarrowx , arrowy, wxWINDING_RULE);
874
dc.SetBrush(*wxTRANSPARENT_BRUSH);
875
dc.DrawRectangle(m_rightArrowRect);
882
// first draw the week days
883
if ( IsExposed(x0, y, x0 + 7*m_widthCol, m_heightRow) )
886
wxLogDebug("painting the header");
889
dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
890
dc.SetTextForeground(m_colHeaderFg);
891
dc.SetBrush(wxBrush(m_colHeaderBg, wxBRUSHSTYLE_SOLID));
892
dc.SetPen(wxPen(m_colHeaderBg, 1, wxPENSTYLE_SOLID));
893
dc.DrawRectangle(0, y, GetClientSize().x, m_heightRow);
895
bool startOnMonday = HasFlag(wxCAL_MONDAY_FIRST);
896
for ( int wd = 0; wd < 7; wd++ )
900
n = wd == 6 ? 0 : wd + 1;
904
dc.GetTextExtent(m_weekdays[n], &dayw, &dayh);
905
dc.DrawText(m_weekdays[n], x0 + (wd*m_widthCol) + ((m_widthCol- dayw) / 2), y); // center the day-name
909
// then the calendar itself
910
dc.SetTextForeground(*wxBLACK);
911
//dc.SetFont(*wxNORMAL_FONT);
915
// draw column with calendar week nr
916
if ( HasFlag( wxCAL_SHOW_WEEK_NUMBERS ) && IsExposed( 0, y, m_calendarWeekWidth, m_heightRow * 6 ))
918
dc.SetBackgroundMode(wxTRANSPARENT);
919
dc.SetBrush(wxBrush(m_colHeaderBg, wxSOLID));
920
dc.SetPen(wxPen(m_colHeaderBg, 1, wxSOLID));
921
dc.DrawRectangle( 0, y, m_calendarWeekWidth, m_heightRow * 6 );
922
wxDateTime date = GetStartDate();
923
for ( size_t i = 0; i < 6; ++i )
925
const int weekNr = date.GetWeekOfYear();
926
wxString text = wxString::Format( wxT( "%d" ), weekNr );
927
dc.DrawText( text, m_calendarWeekWidth - dc.GetTextExtent( text ).GetWidth() - 2, y + m_heightRow * i );
928
date += wxDateSpan::Week();
932
wxDateTime date = GetStartDate();
935
wxLogDebug("starting calendar from %s\n",
936
date.Format("%a %d-%m-%Y %H:%M:%S").c_str());
939
dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID);
940
for ( size_t nWeek = 1; nWeek <= 6; nWeek++, y += m_heightRow )
942
// if the update region doesn't intersect this row, don't paint it
943
if ( !IsExposed(x0, y, x0 + 7*m_widthCol, m_heightRow - 1) )
945
date += wxDateSpan::Week();
951
wxLogDebug("painting week %d at y = %d\n", nWeek, y);
954
for ( int wd = 0; wd < 7; wd++ )
956
dc.SetTextBackground(m_colBackground);
957
if ( IsDateShown(date) )
959
// don't use wxDate::Format() which prepends 0s
960
unsigned int day = date.GetDay();
961
wxString dayStr = wxString::Format(wxT("%u"), day);
963
dc.GetTextExtent(dayStr, &width, NULL);
965
bool changedColours = false,
969
wxCalendarDateAttr *attr = NULL;
971
if ( date.GetMonth() != m_date.GetMonth() || !IsDateInRange(date) )
973
// draw the days of adjacent months in different colour
974
dc.SetTextForeground(m_colSurrounding);
975
changedColours = true;
979
isSel = date.IsSameDate(m_date);
980
attr = m_attrs[day - 1];
984
dc.SetTextForeground(m_colHighlightFg);
985
dc.SetTextBackground(m_colHighlightBg);
987
changedColours = true;
991
wxColour colFg, colBg;
993
if ( attr->IsHoliday() )
995
colFg = m_colHolidayFg;
996
colBg = m_colHolidayBg;
1000
colFg = attr->GetTextColour();
1001
colBg = attr->GetBackgroundColour();
1006
dc.SetTextForeground(colFg);
1007
changedColours = true;
1012
dc.SetTextBackground(colBg);
1013
changedColours = true;
1016
if ( attr->HasFont() )
1018
dc.SetFont(attr->GetFont());
1024
wxCoord x = wd*m_widthCol + (m_widthCol - width) / 2 + x0;
1025
dc.DrawText(dayStr, x, y + 1);
1027
if ( !isSel && attr && attr->HasBorder() )
1030
if ( attr->HasBorderColour() )
1032
colBorder = attr->GetBorderColour();
1036
colBorder = GetForegroundColour();
1039
wxPen pen(colBorder, 1, wxPENSTYLE_SOLID);
1041
dc.SetBrush(*wxTRANSPARENT_BRUSH);
1043
switch ( attr->GetBorder() )
1045
case wxCAL_BORDER_SQUARE:
1046
dc.DrawRectangle(x - 2, y,
1047
width + 4, m_heightRow);
1050
case wxCAL_BORDER_ROUND:
1051
dc.DrawEllipse(x - 2, y,
1052
width + 4, m_heightRow);
1056
wxFAIL_MSG(wxT("unknown border type"));
1060
if ( changedColours )
1062
dc.SetTextForeground(GetForegroundColour());
1063
dc.SetTextBackground(GetBackgroundColour());
1068
dc.SetFont(GetFont());
1071
//else: just don't draw it
1073
date += wxDateSpan::Day();
1077
// Greying out out-of-range background
1078
bool showSurrounding = (GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS) != 0;
1080
date = ( showSurrounding ) ? GetStartDate() : wxDateTime(1, m_date.GetMonth(), m_date.GetYear());
1081
if ( !IsDateInRange(date) )
1083
wxDateTime firstOOR = GetLowerDateLimit() - wxDateSpan::Day(); // first out-of-range
1085
wxBrush oorbrush = *wxLIGHT_GREY_BRUSH;
1086
oorbrush.SetStyle(wxBRUSHSTYLE_FDIAGONAL_HATCH);
1088
HighlightRange(&dc, date, firstOOR, wxTRANSPARENT_PEN, &oorbrush);
1091
date = ( showSurrounding ) ? GetStartDate() + wxDateSpan::Weeks(6) - wxDateSpan::Day() : wxDateTime().SetToLastMonthDay(m_date.GetMonth(), m_date.GetYear());
1092
if ( !IsDateInRange(date) )
1094
wxDateTime firstOOR = GetUpperDateLimit() + wxDateSpan::Day(); // first out-of-range
1096
wxBrush oorbrush = *wxLIGHT_GREY_BRUSH;
1097
oorbrush.SetStyle(wxBRUSHSTYLE_FDIAGONAL_HATCH);
1099
HighlightRange(&dc, firstOOR, date, wxTRANSPARENT_PEN, &oorbrush);
1103
wxLogDebug("+++ finished painting");
1107
void wxGenericCalendarCtrl::RefreshDate(const wxDateTime& date)
1113
// always refresh the whole row at once because our OnPaint() will draw
1114
// the whole row anyhow - and this allows the small optimization in
1115
// OnClick() below to work
1116
rect.x = m_calendarWeekWidth;
1118
rect.y = (m_heightRow * GetWeek(date)) + m_rowOffset;
1120
rect.width = 7*m_widthCol;
1121
rect.height = m_heightRow;
1124
// VZ: for some reason, the selected date seems to occupy more space under
1125
// MSW - this is probably some bug in the font size calculations, but I
1126
// don't know where exactly. This fix is ugly and leads to more
1127
// refreshes than really needed, but without it the selected days
1128
// leaves even more ugly underscores on screen.
1133
wxLogDebug("*** refreshing week %d at (%d, %d)-(%d, %d)\n",
1136
rect.x + rect.width, rect.y + rect.height);
1139
Refresh(true, &rect);
1142
void wxGenericCalendarCtrl::HighlightRange(wxPaintDC* pDC, const wxDateTime& fromdate, const wxDateTime& todate, const wxPen* pPen, const wxBrush* pBrush)
1144
// Highlights the given range using pen and brush
1145
// Does nothing if todate < fromdate
1149
wxLogDebug("+++ HighlightRange: (%s) - (%s) +++", fromdate.Format("%d %m %Y"), todate.Format("%d %m %Y"));
1152
if ( todate >= fromdate )
1159
// implicit: both dates must be currently shown - checked by GetDateCoord
1160
if ( GetDateCoord(fromdate, &fd, &fw) && GetDateCoord(todate, &td, &tw) )
1163
wxLogDebug("Highlight range: (%i, %i) - (%i, %i)", fd, fw, td, tw);
1165
if ( ( (tw - fw) == 1 ) && ( td < fd ) )
1167
// special case: interval 7 days or less not in same week
1168
// split in two separate intervals
1169
wxDateTime tfd = fromdate + wxDateSpan::Days(7-fd);
1170
wxDateTime ftd = tfd + wxDateSpan::Day();
1172
wxLogDebug("Highlight: Separate segments");
1175
HighlightRange(pDC, fromdate, tfd, pPen, pBrush);
1176
HighlightRange(pDC, ftd, todate, pPen, pBrush);
1181
wxPoint corners[8]; // potentially 8 corners in polygon
1182
wxCoord x0 = m_calendarWeekWidth;
1186
// simple case: same week
1188
corners[0] = wxPoint(x0 + (fd - 1) * m_widthCol, (fw * m_heightRow) + m_rowOffset);
1189
corners[1] = wxPoint(x0 + (fd - 1) * m_widthCol, ((fw + 1 ) * m_heightRow) + m_rowOffset);
1190
corners[2] = wxPoint(x0 + td * m_widthCol, ((tw + 1) * m_heightRow) + m_rowOffset);
1191
corners[3] = wxPoint(x0 + td * m_widthCol, (tw * m_heightRow) + m_rowOffset);
1196
// "complex" polygon
1197
corners[cidx] = wxPoint(x0 + (fd - 1) * m_widthCol, (fw * m_heightRow) + m_rowOffset); cidx++;
1201
corners[cidx] = wxPoint(x0 + (fd - 1) * m_widthCol, ((fw + 1) * m_heightRow) + m_rowOffset); cidx++;
1202
corners[cidx] = wxPoint(x0, ((fw + 1) * m_heightRow) + m_rowOffset); cidx++;
1205
corners[cidx] = wxPoint(x0, ((tw + 1) * m_heightRow) + m_rowOffset); cidx++;
1206
corners[cidx] = wxPoint(x0 + td * m_widthCol, ((tw + 1) * m_heightRow) + m_rowOffset); cidx++;
1210
corners[cidx] = wxPoint(x0 + td * m_widthCol, (tw * m_heightRow) + m_rowOffset); cidx++;
1211
corners[cidx] = wxPoint(x0 + 7 * m_widthCol, (tw * m_heightRow) + m_rowOffset); cidx++;
1214
corners[cidx] = wxPoint(x0 + 7 * m_widthCol, (fw * m_heightRow) + m_rowOffset); cidx++;
1220
pDC->SetBrush(*pBrush);
1222
pDC->DrawPolygon(numpoints, corners);
1228
wxLogDebug("--- HighlightRange ---");
1232
bool wxGenericCalendarCtrl::GetDateCoord(const wxDateTime& date, int *day, int *week) const
1237
wxLogDebug("+++ GetDateCoord: (%s) +++", date.Format("%d %m %Y"));
1240
if ( IsDateShown(date) )
1242
bool startOnMonday = HasFlag(wxCAL_MONDAY_FIRST);
1245
*day = date.GetWeekDay();
1247
if ( *day == 0 ) // sunday
1249
*day = ( startOnMonday ) ? 7 : 1;
1253
*day += ( startOnMonday ) ? 0 : 1;
1256
int targetmonth = date.GetMonth() + (12 * date.GetYear());
1257
int thismonth = m_date.GetMonth() + (12 * m_date.GetYear());
1260
if ( targetmonth == thismonth )
1262
*week = GetWeek(date);
1266
if ( targetmonth < thismonth )
1268
*week = 1; // trivial
1270
else // targetmonth > thismonth
1276
// get the datecoord of the last day in the month currently shown
1278
wxLogDebug(" +++ LDOM +++");
1280
GetDateCoord(ldcm.SetToLastMonthDay(m_date.GetMonth(), m_date.GetYear()), &lastday, &lastweek);
1282
wxLogDebug(" --- LDOM ---");
1285
wxTimeSpan span = date - ldcm;
1287
int daysfromlast = span.GetDays();
1289
wxLogDebug("daysfromlast: %i", daysfromlast);
1291
if ( daysfromlast + lastday > 7 ) // past week boundary
1293
int wholeweeks = (daysfromlast / 7);
1294
*week = wholeweeks + lastweek;
1295
if ( (daysfromlast - (7 * wholeweeks) + lastday) > 7 )
1315
wxLogDebug("--- GetDateCoord: (%s) = (%i, %i) ---", date.Format("%d %m %Y"), *day, *week);
1321
// ----------------------------------------------------------------------------
1323
// ----------------------------------------------------------------------------
1325
void wxGenericCalendarCtrl::OnDClick(wxMouseEvent& event)
1327
if ( HitTest(event.GetPosition()) != wxCAL_HITTEST_DAY )
1333
GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
1337
void wxGenericCalendarCtrl::OnClick(wxMouseEvent& event)
1340
wxDateTime::WeekDay wday;
1341
switch ( HitTest(event.GetPosition(), &date, &wday) )
1343
case wxCAL_HITTEST_DAY:
1344
if ( IsDateInRange(date) )
1348
GenerateEvent(wxEVT_CALENDAR_SEL_CHANGED);
1350
// we know that the month/year never change when the user
1351
// clicks on the control so there is no need to call
1352
// GenerateAllChangeEvents() here, we know which event to send
1353
GenerateEvent(wxEVT_CALENDAR_DAY_CHANGED);
1357
case wxCAL_HITTEST_WEEK:
1359
wxCalendarEvent send( this, date, wxEVT_CALENDAR_WEEK_CLICKED );
1360
HandleWindowEvent( send );
1364
case wxCAL_HITTEST_HEADER:
1366
wxCalendarEvent eventWd(this, GetDate(),
1367
wxEVT_CALENDAR_WEEKDAY_CLICKED);
1368
eventWd.SetWeekDay(wday);
1369
(void)GetEventHandler()->ProcessEvent(eventWd);
1373
case wxCAL_HITTEST_DECMONTH:
1374
case wxCAL_HITTEST_INCMONTH:
1375
case wxCAL_HITTEST_SURROUNDING_WEEK:
1376
SetDateAndNotify(date); // we probably only want to refresh the control. No notification.. (maybe as an option?)
1380
wxFAIL_MSG(wxT("unknown hittest code"));
1383
case wxCAL_HITTEST_NOWHERE:
1388
// as we don't (always) skip the message, we're not going to receive the
1389
// focus on click by default if we don't do it ourselves
1393
wxCalendarHitTestResult wxGenericCalendarCtrl::HitTest(const wxPoint& pos,
1395
wxDateTime::WeekDay *wd)
1399
// the position where the calendar really begins
1400
wxCoord x0 = m_calendarWeekWidth;
1402
if ( HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
1406
// we need to find out if the hit is on left arrow, on month or on right arrow
1408
if ( m_leftArrowRect.Contains(pos) )
1412
if ( IsDateInRange(m_date - wxDateSpan::Month()) )
1414
*date = m_date - wxDateSpan::Month();
1418
*date = GetLowerDateLimit();
1422
return wxCAL_HITTEST_DECMONTH;
1425
if ( m_rightArrowRect.Contains(pos) )
1429
if ( IsDateInRange(m_date + wxDateSpan::Month()) )
1431
*date = m_date + wxDateSpan::Month();
1435
*date = GetUpperDateLimit();
1439
return wxCAL_HITTEST_INCMONTH;
1443
if ( pos.x - x0 < 0 )
1445
if ( pos.x >= 0 && pos.y > m_rowOffset + m_heightRow && pos.y <= m_rowOffset + m_heightRow * 7 )
1449
*date = GetStartDate();
1450
*date += wxDateSpan::Week() * (( pos.y - m_rowOffset ) / m_heightRow - 1 );
1453
*wd = GetWeekStart();
1454
return wxCAL_HITTEST_WEEK;
1456
else // early exit -> the rest of the function checks for clicks on days
1457
return wxCAL_HITTEST_NOWHERE;
1460
// header: week days
1461
int wday = (pos.x - x0) / m_widthCol;
1463
return wxCAL_HITTEST_NOWHERE;
1464
if ( pos.y < (m_heightRow + m_rowOffset))
1466
if ( pos.y > m_rowOffset )
1470
if ( HasFlag(wxCAL_MONDAY_FIRST) )
1472
wday = wday == 6 ? 0 : wday + 1;
1475
*wd = (wxDateTime::WeekDay)wday;
1478
return wxCAL_HITTEST_HEADER;
1482
return wxCAL_HITTEST_NOWHERE;
1486
int week = (pos.y - (m_heightRow + m_rowOffset)) / m_heightRow;
1487
if ( week >= 6 || wday >= 7 )
1489
return wxCAL_HITTEST_NOWHERE;
1492
wxDateTime dt = GetStartDate() + wxDateSpan::Days(7*week + wday);
1494
if ( IsDateShown(dt) )
1499
if ( dt.GetMonth() == m_date.GetMonth() )
1502
return wxCAL_HITTEST_DAY;
1506
return wxCAL_HITTEST_SURROUNDING_WEEK;
1511
return wxCAL_HITTEST_NOWHERE;
1515
// ----------------------------------------------------------------------------
1516
// subcontrols events handling
1517
// ----------------------------------------------------------------------------
1519
void wxGenericCalendarCtrl::OnMonthChange(wxCommandEvent& event)
1521
wxDateTime::Tm tm = m_date.GetTm();
1523
wxDateTime::Month mon = (wxDateTime::Month)event.GetInt();
1524
if ( tm.mday > wxDateTime::GetNumberOfDays(mon, tm.year) )
1526
tm.mday = wxDateTime::GetNumberOfDays(mon, tm.year);
1529
wxDateTime dt(tm.mday, mon, tm.year);
1530
if ( AdjustDateToRange(&dt) )
1532
// The date must have been changed to ensure it's in valid range,
1533
// reflect this in the month choice control.
1534
m_comboMonth->SetSelection(dt.GetMonth());
1537
SetDateAndNotify(dt);
1540
void wxGenericCalendarCtrl::HandleYearChange(wxCommandEvent& event)
1542
int year = (int)event.GetInt();
1543
if ( year == INT_MIN )
1545
// invalid year in the spin control, ignore it
1549
wxDateTime::Tm tm = m_date.GetTm();
1551
if ( tm.mday > wxDateTime::GetNumberOfDays(tm.mon, year) )
1553
tm.mday = wxDateTime::GetNumberOfDays(tm.mon, year);
1556
wxDateTime dt(tm.mday, tm.mon, year);
1557
if ( AdjustDateToRange(&dt) )
1559
// As above, if the date was changed to keep it in valid range, its
1560
// possibly changed year must be shown in the GUI.
1561
m_spinYear->SetValue(dt.GetYear());
1564
SetDateAndNotify(dt);
1567
void wxGenericCalendarCtrl::OnYearChange(wxSpinEvent& event)
1569
HandleYearChange( event );
1572
void wxGenericCalendarCtrl::OnYearTextChange(wxCommandEvent& event)
1574
SetUserChangedYear();
1575
HandleYearChange(event);
1578
// Responds to colour changes, and passes event on to children.
1579
void wxGenericCalendarCtrl::OnSysColourChanged(wxSysColourChangedEvent& event)
1584
// Propagate the event to the children
1585
wxControl::OnSysColourChanged(event);
1587
// Redraw control area
1588
SetBackgroundColour(m_colBackground);
1592
// ----------------------------------------------------------------------------
1593
// keyboard interface
1594
// ----------------------------------------------------------------------------
1596
void wxGenericCalendarCtrl::OnChar(wxKeyEvent& event)
1598
switch ( event.GetKeyCode() )
1602
SetDateAndNotify(m_date + wxDateSpan::Year());
1607
SetDateAndNotify(m_date - wxDateSpan::Year());
1611
SetDateAndNotify(m_date - wxDateSpan::Month());
1615
SetDateAndNotify(m_date + wxDateSpan::Month());
1619
if ( event.ControlDown() )
1621
wxDateTime target = m_date.SetToNextWeekDay(GetWeekEnd());
1622
AdjustDateToRange(&target);
1623
SetDateAndNotify(target);
1626
SetDateAndNotify(m_date + wxDateSpan::Day());
1630
if ( event.ControlDown() )
1632
wxDateTime target = m_date.SetToPrevWeekDay(GetWeekStart());
1633
AdjustDateToRange(&target);
1634
SetDateAndNotify(target);
1637
SetDateAndNotify(m_date - wxDateSpan::Day());
1641
SetDateAndNotify(m_date - wxDateSpan::Week());
1645
SetDateAndNotify(m_date + wxDateSpan::Week());
1649
if ( event.ControlDown() )
1650
SetDateAndNotify(wxDateTime::Today());
1652
SetDateAndNotify(wxDateTime(1, m_date.GetMonth(), m_date.GetYear()));
1656
SetDateAndNotify(wxDateTime(m_date).SetToLastMonthDay());
1660
GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
1668
// ----------------------------------------------------------------------------
1669
// holidays handling
1670
// ----------------------------------------------------------------------------
1672
void wxGenericCalendarCtrl::SetHoliday(size_t day)
1674
wxCHECK_RET( day > 0 && day < 32, wxT("invalid day in SetHoliday") );
1676
wxCalendarDateAttr *attr = GetAttr(day);
1679
attr = new wxCalendarDateAttr;
1682
attr->SetHoliday(true);
1684
// can't use SetAttr() because it would delete this pointer
1685
m_attrs[day - 1] = attr;
1688
void wxGenericCalendarCtrl::ResetHolidayAttrs()
1690
for ( size_t day = 0; day < 31; day++ )
1694
m_attrs[day]->SetHoliday(false);
1699
void wxGenericCalendarCtrl::Mark(size_t day, bool mark)
1701
wxCHECK_RET( day > 0 && day < 32, wxT("invalid day in Mark") );
1703
const wxCalendarDateAttr& m = wxCalendarDateAttr::GetMark();
1705
if ( m_attrs[day - 1] )
1706
AddAttr(m_attrs[day - 1], m);
1708
SetAttr(day, new wxCalendarDateAttr(m));
1711
if ( m_attrs[day - 1] )
1712
DelAttr(m_attrs[day - 1], m);
1718
wxGenericCalendarCtrl::GetClassDefaultAttributes(wxWindowVariant variant)
1720
// Use the same color scheme as wxListBox
1721
return wxListBox::GetClassDefaultAttributes(variant);
1724
#endif // wxUSE_CALENDARCTRL