]> git.saurik.com Git - wxWidgets.git/blob - src/generic/calctrl.cpp
Applied patch [ 735304 ] wxDisplay::ChangeMode - does not resize full-screen windows
[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) { 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 int dy = (sizeCombo.y - sizeStatic.y) / 2;
763 /*
764 In the calender the size of the combobox for the year
765 is just defined by a margin from the month combobox to
766 the left border. While in wxUniv the year control can't
767 show all 4 digits, in wxMsw it show almost twice as
768 much. Instead the year should use it's best size and be
769 left aligned to the calendar. Just in case the month in
770 any language is longer than it has space in the
771 calendar it is shortend.This way the year always can
772 show the 4 digits.
773
774 This patch relies on the fact that a combobox has a
775 good best size implementation. This is not the case
776 with wxMSW but I don't know why.
777
778 Otto Wyss
779 */
780
781 #ifdef __WXUNIVERSAL__
782 if (sizeCombo.x + HORZ_MARGIN - sizeSpin.x > width)
783 {
784 m_comboMonth->SetSize(x, y, width - HORZ_MARGIN - sizeSpin.x, sizeCombo.y);
785 }
786 else
787 {
788 m_comboMonth->Move(x, y);
789 }
790 m_staticMonth->Move(x, y + dy);
791 m_spinYear->Move(x + width - sizeSpin.x, y);
792 m_staticYear->Move(x + width - sizeSpin.x, y + dy);
793 #else
794 m_comboMonth->Move(x, y);
795 m_staticMonth->SetSize(x, y + dy, sizeCombo.x, sizeStatic.y);
796
797 int xDiff = sizeCombo.x + HORZ_MARGIN;
798
799 m_spinYear->SetSize(x + xDiff, y, width - xDiff, sizeCombo.y);
800 m_staticYear->SetSize(x + xDiff, y + dy, width - xDiff, sizeStatic.y);
801 #endif
802 yDiff = wxMax(sizeSpin.y, sizeCombo.y) + VERT_MARGIN;
803 }
804 else // no controls on the top
805 {
806 yDiff = 0;
807 }
808
809 wxControl::DoMoveWindow(x, y + yDiff, width, height - yDiff);
810 }
811
812 void wxCalendarCtrl::DoGetPosition(int *x, int *y) const
813 {
814 wxControl::DoGetPosition(x, y);
815
816 if ( !(GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) )
817 {
818 // our real top corner is not in this position
819 if ( y )
820 {
821 *y -= GetMonthControl()->GetSize().y + VERT_MARGIN;
822 }
823 }
824 }
825
826 void wxCalendarCtrl::DoGetSize(int *width, int *height) const
827 {
828 wxControl::DoGetSize(width, height);
829
830 if ( !(GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) )
831 {
832 // our real height is bigger
833 if ( height && GetMonthControl())
834 {
835 *height += GetMonthControl()->GetSize().y + VERT_MARGIN;
836 }
837 }
838 }
839
840 void wxCalendarCtrl::RecalcGeometry()
841 {
842 if ( m_widthCol != 0 )
843 return;
844
845 wxClientDC dc(this);
846
847 dc.SetFont(m_font);
848
849 // determine the column width (we assume that the weekday names are always
850 // wider (in any language) than the numbers)
851 m_widthCol = 0;
852 wxDateTime::WeekDay wd;
853 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
854 {
855 wxCoord width;
856 dc.GetTextExtent(m_weekdays[wd], &width, &m_heightRow);
857 if ( width > m_widthCol )
858 {
859 m_widthCol = width;
860 }
861 }
862
863 // leave some margins
864 m_widthCol += 2;
865 m_heightRow += 2;
866
867 m_rowOffset = (GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) ? m_heightRow : 0; // conditional in relation to style
868 }
869
870 // ----------------------------------------------------------------------------
871 // drawing
872 // ----------------------------------------------------------------------------
873
874 void wxCalendarCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
875 {
876 wxPaintDC dc(this);
877
878 dc.SetFont(m_font);
879
880 RecalcGeometry();
881
882 #if DEBUG_PAINT
883 wxLogDebug("--- starting to paint, selection: %s, week %u\n",
884 m_date.Format("%a %d-%m-%Y %H:%M:%S").c_str(),
885 GetWeek(m_date));
886 #endif
887
888 wxCoord y = 0;
889
890 if ( HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
891 {
892 // draw the sequential month-selector
893
894 dc.SetBackgroundMode(wxTRANSPARENT);
895 dc.SetTextForeground(*wxBLACK);
896 dc.SetBrush(wxBrush(m_colHeaderBg, wxSOLID));
897 dc.SetPen(wxPen(m_colHeaderBg, 1, wxSOLID));
898 dc.DrawRectangle(0, y, GetClientSize().x, m_heightRow);
899
900 // Get extent of month-name + year
901 wxCoord monthw, monthh;
902 wxString headertext = m_date.Format(wxT("%B %Y"));
903 dc.GetTextExtent(headertext, &monthw, &monthh);
904
905 // draw month-name centered above weekdays
906 wxCoord monthx = ((m_widthCol * 7) - monthw) / 2;
907 wxCoord monthy = ((m_heightRow - monthh) / 2) + y;
908 dc.DrawText(headertext, monthx, monthy);
909
910 // calculate the "month-arrows"
911 wxPoint leftarrow[3];
912 wxPoint rightarrow[3];
913
914 int arrowheight = monthh / 2;
915
916 leftarrow[0] = wxPoint(0, arrowheight / 2);
917 leftarrow[1] = wxPoint(arrowheight / 2, 0);
918 leftarrow[2] = wxPoint(arrowheight / 2, arrowheight - 1);
919
920 rightarrow[0] = wxPoint(0, 0);
921 rightarrow[1] = wxPoint(arrowheight / 2, arrowheight / 2);
922 rightarrow[2] = wxPoint(0, arrowheight - 1);
923
924 // draw the "month-arrows"
925
926 wxCoord arrowy = (m_heightRow - arrowheight) / 2;
927 wxCoord larrowx = (m_widthCol - (arrowheight / 2)) / 2;
928 wxCoord rarrowx = ((m_widthCol - (arrowheight / 2)) / 2) + m_widthCol*6;
929 m_leftArrowRect = wxRect(0, 0, 0, 0);
930 m_rightArrowRect = wxRect(0, 0, 0, 0);
931
932 if ( AllowMonthChange() )
933 {
934 wxDateTime ldpm = wxDateTime(1,m_date.GetMonth(), m_date.GetYear()) - wxDateSpan::Day(); // last day prev month
935 // Check if range permits change
936 if ( IsDateInRange(ldpm) && ( ( ldpm.GetYear() == m_date.GetYear() ) ? TRUE : AllowYearChange() ) )
937 {
938 m_leftArrowRect = wxRect(larrowx - 3, arrowy - 3, (arrowheight / 2) + 8, (arrowheight + 6));
939 dc.SetBrush(wxBrush(*wxBLACK, wxSOLID));
940 dc.SetPen(wxPen(*wxBLACK, 1, wxSOLID));
941 dc.DrawPolygon(3, leftarrow, larrowx , arrowy, wxWINDING_RULE);
942 dc.SetBrush(*wxTRANSPARENT_BRUSH);
943 dc.DrawRectangle(m_leftArrowRect);
944 }
945 wxDateTime fdnm = wxDateTime(1,m_date.GetMonth(), m_date.GetYear()) + wxDateSpan::Month(); // first day next month
946 if ( IsDateInRange(fdnm) && ( ( fdnm.GetYear() == m_date.GetYear() ) ? TRUE : AllowYearChange() ) )
947 {
948 m_rightArrowRect = wxRect(rarrowx - 4, arrowy - 3, (arrowheight / 2) + 8, (arrowheight + 6));
949 dc.SetBrush(wxBrush(*wxBLACK, wxSOLID));
950 dc.SetPen(wxPen(*wxBLACK, 1, wxSOLID));
951 dc.DrawPolygon(3, rightarrow, rarrowx , arrowy, wxWINDING_RULE);
952 dc.SetBrush(*wxTRANSPARENT_BRUSH);
953 dc.DrawRectangle(m_rightArrowRect);
954 }
955 }
956
957 y += m_heightRow;
958 }
959
960 // first draw the week days
961 if ( IsExposed(0, y, 7*m_widthCol, m_heightRow) )
962 {
963 #if DEBUG_PAINT
964 wxLogDebug("painting the header");
965 #endif
966
967 dc.SetBackgroundMode(wxTRANSPARENT);
968 dc.SetTextForeground(m_colHeaderFg);
969 dc.SetBrush(wxBrush(m_colHeaderBg, wxSOLID));
970 dc.SetPen(wxPen(m_colHeaderBg, 1, wxSOLID));
971 dc.DrawRectangle(0, y, GetClientSize().x, m_heightRow);
972
973 bool startOnMonday = (GetWindowStyle() & wxCAL_MONDAY_FIRST) != 0;
974 for ( size_t wd = 0; wd < 7; wd++ )
975 {
976 size_t n;
977 if ( startOnMonday )
978 n = wd == 6 ? 0 : wd + 1;
979 else
980 n = wd;
981 wxCoord dayw, dayh;
982 dc.GetTextExtent(m_weekdays[n], &dayw, &dayh);
983 dc.DrawText(m_weekdays[n], (wd*m_widthCol) + ((m_widthCol- dayw) / 2), y); // center the day-name
984 }
985 }
986
987 // then the calendar itself
988 dc.SetTextForeground(*wxBLACK);
989 //dc.SetFont(*wxNORMAL_FONT);
990
991 y += m_heightRow;
992 wxDateTime date = GetStartDate();
993
994 #if DEBUG_PAINT
995 wxLogDebug("starting calendar from %s\n",
996 date.Format("%a %d-%m-%Y %H:%M:%S").c_str());
997 #endif
998
999 dc.SetBackgroundMode(wxSOLID);
1000 for ( size_t nWeek = 1; nWeek <= 6; nWeek++, y += m_heightRow )
1001 {
1002 // if the update region doesn't intersect this row, don't paint it
1003 if ( !IsExposed(0, y, 7*m_widthCol, m_heightRow - 1) )
1004 {
1005 date += wxDateSpan::Week();
1006
1007 continue;
1008 }
1009
1010 #if DEBUG_PAINT
1011 wxLogDebug("painting week %d at y = %d\n", nWeek, y);
1012 #endif
1013
1014 for ( size_t wd = 0; wd < 7; wd++ )
1015 {
1016 if ( IsDateShown(date) )
1017 {
1018 // don't use wxDate::Format() which prepends 0s
1019 unsigned int day = date.GetDay();
1020 wxString dayStr = wxString::Format(_T("%u"), day);
1021 wxCoord width;
1022 dc.GetTextExtent(dayStr, &width, (wxCoord *)NULL);
1023
1024 bool changedColours = FALSE,
1025 changedFont = FALSE;
1026
1027 bool isSel = FALSE;
1028 wxCalendarDateAttr *attr = NULL;
1029
1030 if ( date.GetMonth() != m_date.GetMonth() || !IsDateInRange(date) )
1031 {
1032 // surrounding week or out-of-range
1033 // draw "disabled"
1034 dc.SetTextForeground(*wxLIGHT_GREY);
1035 changedColours = TRUE;
1036 }
1037 else
1038 {
1039 isSel = date.IsSameDate(m_date);
1040 attr = m_attrs[day - 1];
1041
1042 if ( isSel )
1043 {
1044 dc.SetTextForeground(m_colHighlightFg);
1045 dc.SetTextBackground(m_colHighlightBg);
1046
1047 changedColours = TRUE;
1048 }
1049 else if ( attr )
1050 {
1051 wxColour colFg, colBg;
1052
1053 if ( attr->IsHoliday() )
1054 {
1055 colFg = m_colHolidayFg;
1056 colBg = m_colHolidayBg;
1057 }
1058 else
1059 {
1060 colFg = attr->GetTextColour();
1061 colBg = attr->GetBackgroundColour();
1062 }
1063
1064 if ( colFg.Ok() )
1065 {
1066 dc.SetTextForeground(colFg);
1067 changedColours = TRUE;
1068 }
1069
1070 if ( colBg.Ok() )
1071 {
1072 dc.SetTextBackground(colBg);
1073 changedColours = TRUE;
1074 }
1075
1076 if ( attr->HasFont() )
1077 {
1078 dc.SetFont(attr->GetFont());
1079 changedFont = TRUE;
1080 }
1081 }
1082 }
1083
1084 wxCoord x = wd*m_widthCol + (m_widthCol - width) / 2;
1085 dc.DrawText(dayStr, x, y + 1);
1086
1087 if ( !isSel && attr && attr->HasBorder() )
1088 {
1089 wxColour colBorder;
1090 if ( attr->HasBorderColour() )
1091 {
1092 colBorder = attr->GetBorderColour();
1093 }
1094 else
1095 {
1096 colBorder = m_foregroundColour;
1097 }
1098
1099 wxPen pen(colBorder, 1, wxSOLID);
1100 dc.SetPen(pen);
1101 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1102
1103 switch ( attr->GetBorder() )
1104 {
1105 case wxCAL_BORDER_SQUARE:
1106 dc.DrawRectangle(x - 2, y,
1107 width + 4, m_heightRow);
1108 break;
1109
1110 case wxCAL_BORDER_ROUND:
1111 dc.DrawEllipse(x - 2, y,
1112 width + 4, m_heightRow);
1113 break;
1114
1115 default:
1116 wxFAIL_MSG(_T("unknown border type"));
1117 }
1118 }
1119
1120 if ( changedColours )
1121 {
1122 dc.SetTextForeground(m_foregroundColour);
1123 dc.SetTextBackground(m_backgroundColour);
1124 }
1125
1126 if ( changedFont )
1127 {
1128 dc.SetFont(m_font);
1129 }
1130 }
1131 //else: just don't draw it
1132
1133 date += wxDateSpan::Day();
1134 }
1135 }
1136
1137 // Greying out out-of-range background
1138 bool showSurrounding = (GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS) != 0;
1139
1140 date = ( showSurrounding ) ? GetStartDate() : wxDateTime(1, m_date.GetMonth(), m_date.GetYear());
1141 if ( !IsDateInRange(date) )
1142 {
1143 wxDateTime firstOOR = GetLowerDateLimit() - wxDateSpan::Day(); // first out-of-range
1144
1145 wxBrush oorbrush = *wxLIGHT_GREY_BRUSH;
1146 oorbrush.SetStyle(wxFDIAGONAL_HATCH);
1147
1148 HighlightRange(&dc, date, firstOOR, wxTRANSPARENT_PEN, &oorbrush);
1149 }
1150
1151 date = ( showSurrounding ) ? GetStartDate() + wxDateSpan::Weeks(6) - wxDateSpan::Day() : wxDateTime().SetToLastMonthDay(m_date.GetMonth(), m_date.GetYear());
1152 if ( !IsDateInRange(date) )
1153 {
1154 wxDateTime firstOOR = GetUpperDateLimit() + wxDateSpan::Day(); // first out-of-range
1155
1156 wxBrush oorbrush = *wxLIGHT_GREY_BRUSH;
1157 oorbrush.SetStyle(wxFDIAGONAL_HATCH);
1158
1159 HighlightRange(&dc, firstOOR, date, wxTRANSPARENT_PEN, &oorbrush);
1160 }
1161
1162 #if DEBUG_PAINT
1163 wxLogDebug("+++ finished painting");
1164 #endif
1165 }
1166
1167 void wxCalendarCtrl::RefreshDate(const wxDateTime& date)
1168 {
1169 RecalcGeometry();
1170
1171 wxRect rect;
1172
1173 // always refresh the whole row at once because our OnPaint() will draw
1174 // the whole row anyhow - and this allows the small optimisation in
1175 // OnClick() below to work
1176 rect.x = 0;
1177
1178 rect.y = (m_heightRow * GetWeek(date)) + m_rowOffset;
1179
1180 rect.width = 7*m_widthCol;
1181 rect.height = m_heightRow;
1182
1183 #ifdef __WXMSW__
1184 // VZ: for some reason, the selected date seems to occupy more space under
1185 // MSW - this is probably some bug in the font size calculations, but I
1186 // don't know where exactly. This fix is ugly and leads to more
1187 // refreshes than really needed, but without it the selected days
1188 // leaves even more ugly underscores on screen.
1189 rect.Inflate(0, 1);
1190 #endif // MSW
1191
1192 #if DEBUG_PAINT
1193 wxLogDebug("*** refreshing week %d at (%d, %d)-(%d, %d)\n",
1194 GetWeek(date),
1195 rect.x, rect.y,
1196 rect.x + rect.width, rect.y + rect.height);
1197 #endif
1198
1199 Refresh(TRUE, &rect);
1200 }
1201
1202 void wxCalendarCtrl::HighlightRange(wxPaintDC* pDC, const wxDateTime& fromdate, const wxDateTime& todate, wxPen* pPen, wxBrush* pBrush)
1203 {
1204 // Highlights the given range using pen and brush
1205 // Does nothing if todate < fromdate
1206
1207
1208 #if DEBUG_PAINT
1209 wxLogDebug("+++ HighlightRange: (%s) - (%s) +++", fromdate.Format("%d %m %Y"), todate.Format("%d %m %Y"));
1210 #endif
1211
1212 if ( todate >= fromdate )
1213 {
1214 // do stuff
1215 // date-coordinates
1216 int fd, fw;
1217 int td, tw;
1218
1219 // implicit: both dates must be currently shown - checked by GetDateCoord
1220 if ( GetDateCoord(fromdate, &fd, &fw) && GetDateCoord(todate, &td, &tw) )
1221 {
1222 #if DEBUG_PAINT
1223 wxLogDebug("Highlight range: (%i, %i) - (%i, %i)", fd, fw, td, tw);
1224 #endif
1225 if ( ( (tw - fw) == 1 ) && ( td < fd ) )
1226 {
1227 // special case: interval 7 days or less not in same week
1228 // split in two seperate intervals
1229 wxDateTime tfd = fromdate + wxDateSpan::Days(7-fd);
1230 wxDateTime ftd = tfd + wxDateSpan::Day();
1231 #if DEBUG_PAINT
1232 wxLogDebug("Highlight: Seperate segments");
1233 #endif
1234 // draw seperately
1235 HighlightRange(pDC, fromdate, tfd, pPen, pBrush);
1236 HighlightRange(pDC, ftd, todate, pPen, pBrush);
1237 }
1238 else
1239 {
1240 int numpoints;
1241 wxPoint corners[8]; // potentially 8 corners in polygon
1242
1243 if ( fw == tw )
1244 {
1245 // simple case: same week
1246 numpoints = 4;
1247 corners[0] = wxPoint((fd - 1) * m_widthCol, (fw * m_heightRow) + m_rowOffset);
1248 corners[1] = wxPoint((fd - 1) * m_widthCol, ((fw + 1 ) * m_heightRow) + m_rowOffset);
1249 corners[2] = wxPoint(td * m_widthCol, ((tw + 1) * m_heightRow) + m_rowOffset);
1250 corners[3] = wxPoint(td * m_widthCol, (tw * m_heightRow) + m_rowOffset);
1251 }
1252 else
1253 {
1254 int cidx = 0;
1255 // "complex" polygon
1256 corners[cidx] = wxPoint((fd - 1) * m_widthCol, (fw * m_heightRow) + m_rowOffset); cidx++;
1257
1258 if ( fd > 1 )
1259 {
1260 corners[cidx] = wxPoint((fd - 1) * m_widthCol, ((fw + 1) * m_heightRow) + m_rowOffset); cidx++;
1261 corners[cidx] = wxPoint(0, ((fw + 1) * m_heightRow) + m_rowOffset); cidx++;
1262 }
1263
1264 corners[cidx] = wxPoint(0, ((tw + 1) * m_heightRow) + m_rowOffset); cidx++;
1265 corners[cidx] = wxPoint(td * m_widthCol, ((tw + 1) * m_heightRow) + m_rowOffset); cidx++;
1266
1267 if ( td < 7 )
1268 {
1269 corners[cidx] = wxPoint(td * m_widthCol, (tw * m_heightRow) + m_rowOffset); cidx++;
1270 corners[cidx] = wxPoint(7 * m_widthCol, (tw * m_heightRow) + m_rowOffset); cidx++;
1271 }
1272
1273 corners[cidx] = wxPoint(7 * m_widthCol, (fw * m_heightRow) + m_rowOffset); cidx++;
1274
1275 numpoints = cidx;
1276 }
1277
1278 // draw the polygon
1279 pDC->SetBrush(*pBrush);
1280 pDC->SetPen(*pPen);
1281 pDC->DrawPolygon(numpoints, corners);
1282 }
1283 }
1284 }
1285 // else do nothing
1286 #if DEBUG_PAINT
1287 wxLogDebug("--- HighlightRange ---");
1288 #endif
1289 }
1290
1291 bool wxCalendarCtrl::GetDateCoord(const wxDateTime& date, int *day, int *week) const
1292 {
1293 bool retval = TRUE;
1294
1295 #if DEBUG_PAINT
1296 wxLogDebug("+++ GetDateCoord: (%s) +++", date.Format("%d %m %Y"));
1297 #endif
1298
1299 if ( IsDateShown(date) )
1300 {
1301 bool startOnMonday = ( GetWindowStyle() & wxCAL_MONDAY_FIRST ) != 0;
1302
1303 // Find day
1304 *day = date.GetWeekDay();
1305
1306 if ( *day == 0 ) // sunday
1307 {
1308 *day = ( startOnMonday ) ? 7 : 1;
1309 }
1310 else
1311 {
1312 day += ( startOnMonday ) ? 0 : 1;
1313 }
1314
1315 int targetmonth = date.GetMonth() + (12 * date.GetYear());
1316 int thismonth = m_date.GetMonth() + (12 * m_date.GetYear());
1317
1318 // Find week
1319 if ( targetmonth == thismonth )
1320 {
1321 *week = GetWeek(date);
1322 }
1323 else
1324 {
1325 if ( targetmonth < thismonth )
1326 {
1327 *week = 1; // trivial
1328 }
1329 else // targetmonth > thismonth
1330 {
1331 wxDateTime ldcm;
1332 int lastweek;
1333 int lastday;
1334
1335 // get the datecoord of the last day in the month currently shown
1336 #if DEBUG_PAINT
1337 wxLogDebug(" +++ LDOM +++");
1338 #endif
1339 GetDateCoord(ldcm.SetToLastMonthDay(m_date.GetMonth(), m_date.GetYear()), &lastday, &lastweek);
1340 #if DEBUG_PAINT
1341 wxLogDebug(" --- LDOM ---");
1342 #endif
1343
1344 wxTimeSpan span = date - ldcm;
1345
1346 int daysfromlast = span.GetDays();
1347 #if DEBUG_PAINT
1348 wxLogDebug("daysfromlast: %i", daysfromlast);
1349 #endif
1350 if ( daysfromlast + lastday > 7 ) // past week boundary
1351 {
1352 int wholeweeks = (daysfromlast / 7);
1353 *week = wholeweeks + lastweek;
1354 if ( (daysfromlast - (7 * wholeweeks) + lastday) > 7 )
1355 {
1356 *week += 1;
1357 }
1358 }
1359 else
1360 {
1361 *week = lastweek;
1362 }
1363 }
1364 }
1365 }
1366 else
1367 {
1368 *day = -1;
1369 *week = -1;
1370 retval = FALSE;
1371 }
1372
1373 #if DEBUG_PAINT
1374 wxLogDebug("--- GetDateCoord: (%s) = (%i, %i) ---", date.Format("%d %m %Y"), *day, *week);
1375 #endif
1376
1377 return retval;
1378 }
1379
1380 // ----------------------------------------------------------------------------
1381 // mouse handling
1382 // ----------------------------------------------------------------------------
1383
1384 void wxCalendarCtrl::OnDClick(wxMouseEvent& event)
1385 {
1386 if ( HitTest(event.GetPosition()) != wxCAL_HITTEST_DAY )
1387 {
1388 event.Skip();
1389 }
1390 else
1391 {
1392 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
1393 }
1394 }
1395
1396 void wxCalendarCtrl::OnClick(wxMouseEvent& event)
1397 {
1398 wxDateTime date;
1399 wxDateTime::WeekDay wday;
1400 switch ( HitTest(event.GetPosition(), &date, &wday) )
1401 {
1402 case wxCAL_HITTEST_DAY:
1403 if ( IsDateInRange(date) )
1404 {
1405 ChangeDay(date);
1406
1407 GenerateEvents(wxEVT_CALENDAR_DAY_CHANGED,
1408 wxEVT_CALENDAR_SEL_CHANGED);
1409 }
1410 break;
1411
1412 case wxCAL_HITTEST_HEADER:
1413 {
1414 wxCalendarEvent event(this, wxEVT_CALENDAR_WEEKDAY_CLICKED);
1415 event.m_wday = wday;
1416 (void)GetEventHandler()->ProcessEvent(event);
1417 }
1418 break;
1419
1420 case wxCAL_HITTEST_DECMONTH:
1421 case wxCAL_HITTEST_INCMONTH:
1422 case wxCAL_HITTEST_SURROUNDING_WEEK:
1423 SetDateAndNotify(date); // we probably only want to refresh the control. No notification.. (maybe as an option?)
1424 break;
1425
1426 default:
1427 wxFAIL_MSG(_T("unknown hittest code"));
1428 // fall through
1429
1430 case wxCAL_HITTEST_NOWHERE:
1431 event.Skip();
1432 break;
1433 }
1434 }
1435
1436 wxCalendarHitTestResult wxCalendarCtrl::HitTest(const wxPoint& pos,
1437 wxDateTime *date,
1438 wxDateTime::WeekDay *wd)
1439 {
1440 RecalcGeometry();
1441
1442 wxCoord y = pos.y;
1443
1444 ///////////////////////////////////////////////////////////////////////////////////////////////////////
1445 if ( (GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) )
1446 {
1447 // Header: month
1448
1449 // we need to find out if the hit is on left arrow, on month or on right arrow
1450 // left arrow?
1451 if ( wxRegion(m_leftArrowRect).Contains(pos) == wxInRegion )
1452 {
1453 if ( date )
1454 {
1455 if ( IsDateInRange(m_date - wxDateSpan::Month()) )
1456 {
1457 *date = m_date - wxDateSpan::Month();
1458 }
1459 else
1460 {
1461 *date = GetLowerDateLimit();
1462 }
1463 }
1464
1465 return wxCAL_HITTEST_DECMONTH;
1466 }
1467
1468 if ( wxRegion(m_rightArrowRect).Contains(pos) == wxInRegion )
1469 {
1470 if ( date )
1471 {
1472 if ( IsDateInRange(m_date + wxDateSpan::Month()) )
1473 {
1474 *date = m_date + wxDateSpan::Month();
1475 }
1476 else
1477 {
1478 *date = GetUpperDateLimit();
1479 }
1480 }
1481
1482 return wxCAL_HITTEST_INCMONTH;
1483 }
1484
1485 }
1486
1487 ///////////////////////////////////////////////////////////////////////////////////////////////////////
1488 // Header: Days
1489 int wday = pos.x / m_widthCol;
1490 // if ( y < m_heightRow )
1491 if ( y < (m_heightRow + m_rowOffset) )
1492 {
1493 if ( y > m_rowOffset )
1494 {
1495 if ( wd )
1496 {
1497 if ( GetWindowStyle() & wxCAL_MONDAY_FIRST )
1498 {
1499 wday = wday == 6 ? 0 : wday + 1;
1500 }
1501
1502 *wd = (wxDateTime::WeekDay)wday;
1503 }
1504
1505 return wxCAL_HITTEST_HEADER;
1506 }
1507 else
1508 {
1509 return wxCAL_HITTEST_NOWHERE;
1510 }
1511 }
1512
1513 // int week = (y - m_heightRow) / m_heightRow;
1514 int week = (y - (m_heightRow + m_rowOffset)) / m_heightRow;
1515 if ( week >= 6 || wday >= 7 )
1516 {
1517 return wxCAL_HITTEST_NOWHERE;
1518 }
1519
1520 wxDateTime dt = GetStartDate() + wxDateSpan::Days(7*week + wday);
1521
1522 if ( IsDateShown(dt) )
1523 {
1524 if ( date )
1525 *date = dt;
1526
1527 if ( dt.GetMonth() == m_date.GetMonth() )
1528 {
1529
1530 return wxCAL_HITTEST_DAY;
1531 }
1532 else
1533 {
1534 return wxCAL_HITTEST_SURROUNDING_WEEK;
1535 }
1536 }
1537 else
1538 {
1539 return wxCAL_HITTEST_NOWHERE;
1540 }
1541 }
1542
1543 // ----------------------------------------------------------------------------
1544 // subcontrols events handling
1545 // ----------------------------------------------------------------------------
1546
1547 void wxCalendarCtrl::OnMonthChange(wxCommandEvent& event)
1548 {
1549 wxDateTime::Tm tm = m_date.GetTm();
1550
1551 wxDateTime::Month mon = (wxDateTime::Month)event.GetInt();
1552 if ( tm.mday > wxDateTime::GetNumberOfDays(mon, tm.year) )
1553 {
1554 tm.mday = wxDateTime::GetNumberOfDays(mon, tm.year);
1555 }
1556
1557 wxDateTime target = wxDateTime(tm.mday, mon, tm.year);
1558
1559 ChangeMonth(&target);
1560 SetDateAndNotify(target);
1561 }
1562
1563 void wxCalendarCtrl::OnYearChange(wxCommandEvent& event)
1564 {
1565 int year = (int)event.GetInt();
1566 if ( year == INT_MIN )
1567 {
1568 // invalid year in the spin control, ignore it
1569 return;
1570 }
1571
1572 // set the flag for SetDate(): otherwise it would overwrite the year
1573 // typed in by the user
1574 m_userChangedYear = TRUE;
1575
1576 wxDateTime::Tm tm = m_date.GetTm();
1577
1578 if ( tm.mday > wxDateTime::GetNumberOfDays(tm.mon, year) )
1579 {
1580 tm.mday = wxDateTime::GetNumberOfDays(tm.mon, year);
1581 }
1582
1583 wxDateTime target = wxDateTime(tm.mday, tm.mon, year);
1584
1585 if ( ChangeYear(&target) )
1586 {
1587 SetDateAndNotify(target);
1588 }
1589 else
1590 {
1591 // In this case we don't want to change the date. That would put us
1592 // inside the same year but a strange number of months forward/back..
1593 m_spinYear->SetValue(target.GetYear());
1594 }
1595 }
1596
1597 // ----------------------------------------------------------------------------
1598 // keyboard interface
1599 // ----------------------------------------------------------------------------
1600
1601 void wxCalendarCtrl::OnChar(wxKeyEvent& event)
1602 {
1603 wxDateTime target;
1604 switch ( event.GetKeyCode() )
1605 {
1606 case _T('+'):
1607 case WXK_ADD:
1608 target = m_date + wxDateSpan::Year();
1609 if ( ChangeYear(&target) )
1610 {
1611 SetDateAndNotify(target);
1612 }
1613 break;
1614
1615 case _T('-'):
1616 case WXK_SUBTRACT:
1617 target = m_date - wxDateSpan::Year();
1618 if ( ChangeYear(&target) )
1619 {
1620 SetDateAndNotify(target);
1621 }
1622 break;
1623
1624 case WXK_PRIOR:
1625 target = m_date - wxDateSpan::Month();
1626 ChangeMonth(&target);
1627 SetDateAndNotify(target); // always
1628 break;
1629
1630 case WXK_NEXT:
1631 target = m_date + wxDateSpan::Month();
1632 ChangeMonth(&target);
1633 SetDateAndNotify(target); // always
1634 break;
1635
1636 case WXK_RIGHT:
1637 if ( event.ControlDown() )
1638 {
1639 target = wxDateTime(m_date).SetToNextWeekDay(
1640 GetWindowStyle() & wxCAL_MONDAY_FIRST
1641 ? wxDateTime::Sun : wxDateTime::Sat);
1642 if ( !IsDateInRange(target) )
1643 {
1644 target = GetUpperDateLimit();
1645 }
1646 SetDateAndNotify(target);
1647 }
1648 else
1649 SetDateAndNotify(m_date + wxDateSpan::Day());
1650 break;
1651
1652 case WXK_LEFT:
1653 if ( event.ControlDown() )
1654 {
1655 target = wxDateTime(m_date).SetToPrevWeekDay(
1656 GetWindowStyle() & wxCAL_MONDAY_FIRST
1657 ? wxDateTime::Mon : wxDateTime::Sun);
1658 if ( !IsDateInRange(target) )
1659 {
1660 target = GetLowerDateLimit();
1661 }
1662 SetDateAndNotify(target);
1663 }
1664 else
1665 SetDateAndNotify(m_date - wxDateSpan::Day());
1666 break;
1667
1668 case WXK_UP:
1669 SetDateAndNotify(m_date - wxDateSpan::Week());
1670 break;
1671
1672 case WXK_DOWN:
1673 SetDateAndNotify(m_date + wxDateSpan::Week());
1674 break;
1675
1676 case WXK_HOME:
1677 if ( event.ControlDown() )
1678 SetDateAndNotify(wxDateTime::Today());
1679 else
1680 SetDateAndNotify(wxDateTime(1, m_date.GetMonth(), m_date.GetYear()));
1681 break;
1682
1683 case WXK_END:
1684 SetDateAndNotify(wxDateTime(m_date).SetToLastMonthDay());
1685 break;
1686
1687 case WXK_RETURN:
1688 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
1689 break;
1690
1691 default:
1692 event.Skip();
1693 }
1694 }
1695
1696 // ----------------------------------------------------------------------------
1697 // holidays handling
1698 // ----------------------------------------------------------------------------
1699
1700 void wxCalendarCtrl::EnableHolidayDisplay(bool display)
1701 {
1702 long style = GetWindowStyle();
1703 if ( display )
1704 style |= wxCAL_SHOW_HOLIDAYS;
1705 else
1706 style &= ~wxCAL_SHOW_HOLIDAYS;
1707
1708 SetWindowStyle(style);
1709
1710 if ( display )
1711 SetHolidayAttrs();
1712 else
1713 ResetHolidayAttrs();
1714
1715 Refresh();
1716 }
1717
1718 void wxCalendarCtrl::SetHolidayAttrs()
1719 {
1720 if ( GetWindowStyle() & wxCAL_SHOW_HOLIDAYS )
1721 {
1722 ResetHolidayAttrs();
1723
1724 wxDateTime::Tm tm = m_date.GetTm();
1725 wxDateTime dtStart(1, tm.mon, tm.year),
1726 dtEnd = dtStart.GetLastMonthDay();
1727
1728 wxDateTimeArray hol;
1729 wxDateTimeHolidayAuthority::GetHolidaysInRange(dtStart, dtEnd, hol);
1730
1731 size_t count = hol.GetCount();
1732 for ( size_t n = 0; n < count; n++ )
1733 {
1734 SetHoliday(hol[n].GetDay());
1735 }
1736 }
1737 }
1738
1739 void wxCalendarCtrl::SetHoliday(size_t day)
1740 {
1741 wxCHECK_RET( day > 0 && day < 32, _T("invalid day in SetHoliday") );
1742
1743 wxCalendarDateAttr *attr = GetAttr(day);
1744 if ( !attr )
1745 {
1746 attr = new wxCalendarDateAttr;
1747 }
1748
1749 attr->SetHoliday(TRUE);
1750
1751 // can't use SetAttr() because it would delete this pointer
1752 m_attrs[day - 1] = attr;
1753 }
1754
1755 void wxCalendarCtrl::ResetHolidayAttrs()
1756 {
1757 for ( size_t day = 0; day < 31; day++ )
1758 {
1759 if ( m_attrs[day] )
1760 {
1761 m_attrs[day]->SetHoliday(FALSE);
1762 }
1763 }
1764 }
1765
1766 // ----------------------------------------------------------------------------
1767 // wxCalendarEvent
1768 // ----------------------------------------------------------------------------
1769
1770 void wxCalendarEvent::Init()
1771 {
1772 m_wday = wxDateTime::Inv_WeekDay;
1773 }
1774
1775 wxCalendarEvent::wxCalendarEvent(wxCalendarCtrl *cal, wxEventType type)
1776 : wxCommandEvent(type, cal->GetId())
1777 {
1778 m_date = cal->GetDate();
1779 SetEventObject(cal);
1780 }
1781
1782 #endif // wxUSE_CALENDARCTRL
1783