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