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