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