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