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