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