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