]> git.saurik.com Git - wxWidgets.git/blob - src/generic/calctrl.cpp
Added possibility of 2-step initialisation, and wxWIZARD_EX_HELPBUTTON style.
[wxWidgets.git] / src / generic / calctrl.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: generic/calctrl.cpp
3 // Purpose: implementation fo the generic wxCalendarCtrl
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 29.12.99
7 // RCS-ID: $Id$
8 // Copyright: (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "calctrl.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #ifndef WX_PRECOMP
32 #include "wx/dcclient.h"
33 #include "wx/settings.h"
34 #include "wx/brush.h"
35 #include "wx/combobox.h"
36 #include "wx/stattext.h"
37 #endif //WX_PRECOMP
38
39 #if wxUSE_CALENDARCTRL
40
41 #include "wx/calctrl.h"
42
43 #define DEBUG_PAINT 0
44
45 // ----------------------------------------------------------------------------
46 // private classes
47 // ----------------------------------------------------------------------------
48
49 class wxMonthComboBox : public wxComboBox
50 {
51 public:
52 wxMonthComboBox(wxCalendarCtrl *cal);
53
54 void OnMonthChange(wxCommandEvent& event) { m_cal->OnMonthChange(event); }
55
56 private:
57 wxCalendarCtrl *m_cal;
58
59 DECLARE_EVENT_TABLE()
60 };
61
62 class wxYearSpinCtrl : public wxSpinCtrl
63 {
64 public:
65 wxYearSpinCtrl(wxCalendarCtrl *cal);
66
67 void OnYearChange(wxSpinEvent& event) { m_cal->OnYearChange(event); }
68
69 private:
70 wxCalendarCtrl *m_cal;
71
72 DECLARE_EVENT_TABLE()
73 };
74
75 // ----------------------------------------------------------------------------
76 // wxWin macros
77 // ----------------------------------------------------------------------------
78
79 BEGIN_EVENT_TABLE(wxCalendarCtrl, wxControl)
80 EVT_PAINT(wxCalendarCtrl::OnPaint)
81
82 EVT_CHAR(wxCalendarCtrl::OnChar)
83
84 EVT_LEFT_DOWN(wxCalendarCtrl::OnClick)
85 EVT_LEFT_DCLICK(wxCalendarCtrl::OnDClick)
86 END_EVENT_TABLE()
87
88 BEGIN_EVENT_TABLE(wxMonthComboBox, wxComboBox)
89 EVT_COMBOBOX(-1, wxMonthComboBox::OnMonthChange)
90 END_EVENT_TABLE()
91
92 BEGIN_EVENT_TABLE(wxYearSpinCtrl, wxSpinCtrl)
93 EVT_SPINCTRL(-1, wxYearSpinCtrl::OnYearChange)
94 END_EVENT_TABLE()
95
96 IMPLEMENT_DYNAMIC_CLASS(wxCalendarCtrl, wxControl)
97 IMPLEMENT_DYNAMIC_CLASS(wxCalendarEvent, wxCommandEvent)
98
99 // ----------------------------------------------------------------------------
100 // events
101 // ----------------------------------------------------------------------------
102
103 DEFINE_EVENT_TYPE(wxEVT_CALENDAR_SEL_CHANGED)
104 DEFINE_EVENT_TYPE(wxEVT_CALENDAR_DAY_CHANGED)
105 DEFINE_EVENT_TYPE(wxEVT_CALENDAR_MONTH_CHANGED)
106 DEFINE_EVENT_TYPE(wxEVT_CALENDAR_YEAR_CHANGED)
107 DEFINE_EVENT_TYPE(wxEVT_CALENDAR_DOUBLECLICKED)
108 DEFINE_EVENT_TYPE(wxEVT_CALENDAR_WEEKDAY_CLICKED)
109
110 // ============================================================================
111 // implementation
112 // ============================================================================
113
114 // ----------------------------------------------------------------------------
115 // wxMonthComboBox and wxYearSpinCtrl
116 // ----------------------------------------------------------------------------
117
118 wxMonthComboBox::wxMonthComboBox(wxCalendarCtrl *cal)
119 : wxComboBox(cal->GetParent(), -1,
120 wxEmptyString,
121 wxDefaultPosition,
122 wxDefaultSize,
123 0, NULL,
124 wxCB_READONLY)
125 {
126 m_cal = cal;
127
128 wxDateTime::Month m;
129 for ( m = wxDateTime::Jan; m < wxDateTime::Inv_Month; wxNextMonth(m) )
130 {
131 Append(wxDateTime::GetMonthName(m));
132 }
133
134 SetSelection(m_cal->GetDate().GetMonth());
135 SetSize(-1, -1, -1, -1, wxSIZE_AUTO_WIDTH|wxSIZE_AUTO_HEIGHT);
136 }
137
138 wxYearSpinCtrl::wxYearSpinCtrl(wxCalendarCtrl *cal)
139 : wxSpinCtrl(cal->GetParent(), -1,
140 cal->GetDate().Format(_T("%Y")),
141 wxDefaultPosition,
142 wxDefaultSize,
143 wxSP_ARROW_KEYS,
144 -4300, 10000, cal->GetDate().GetYear())
145 {
146 m_cal = cal;
147 }
148
149 // ----------------------------------------------------------------------------
150 // wxCalendarCtrl
151 // ----------------------------------------------------------------------------
152
153 void wxCalendarCtrl::Init()
154 {
155 m_comboMonth = NULL;
156 m_spinYear = NULL;
157
158 m_widthCol =
159 m_heightRow = 0;
160
161 wxDateTime::WeekDay wd;
162 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
163 {
164 m_weekdays[wd] = wxDateTime::GetWeekDayName(wd, wxDateTime::Name_Abbr);
165 }
166
167 for ( size_t n = 0; n < WXSIZEOF(m_attrs); n++ )
168 {
169 m_attrs[n] = NULL;
170 }
171
172 wxSystemSettings ss;
173 m_colHighlightFg = ss.GetSystemColour(wxSYS_COLOUR_HIGHLIGHTTEXT);
174 m_colHighlightBg = ss.GetSystemColour(wxSYS_COLOUR_HIGHLIGHT);
175
176 m_colHolidayFg = *wxRED;
177 // don't set m_colHolidayBg - by default, same as our bg colour
178
179 m_colHeaderFg = *wxBLUE;
180 m_colHeaderBg = *wxLIGHT_GREY;
181 }
182
183 bool wxCalendarCtrl::Create(wxWindow * WXUNUSED(parent),
184 wxWindowID WXUNUSED(id),
185 const wxDateTime& date,
186 const wxPoint& WXUNUSED(pos),
187 const wxSize& size,
188 long style,
189 const wxString& WXUNUSED(name))
190 {
191 // needed to get the arrow keys normally used for the dialog navigation
192 SetWindowStyle(style | wxWANTS_CHARS);
193
194 m_date = date.IsValid() ? date : wxDateTime::Today();
195
196 m_spinYear = new wxYearSpinCtrl(this);
197 m_staticYear = new wxStaticText(GetParent(), -1, m_date.Format(_T("%Y")),
198 wxDefaultPosition, wxDefaultSize,
199 wxALIGN_CENTRE);
200
201 m_comboMonth = new wxMonthComboBox(this);
202 m_staticMonth = new wxStaticText(GetParent(), -1, m_date.Format(_T("%B")),
203 wxDefaultPosition, wxDefaultSize,
204 wxALIGN_CENTRE);
205
206 ShowCurrentControls();
207
208 wxSize sizeReal;
209 if ( size.x == -1 || size.y == -1 )
210 {
211 sizeReal = DoGetBestSize();
212 if ( size.x != -1 )
213 sizeReal.x = size.x;
214 if ( size.y != -1 )
215 sizeReal.y = size.y;
216 }
217 else
218 {
219 sizeReal = size;
220 }
221
222 SetSize(sizeReal);
223
224 SetBackgroundColour(*wxWHITE);
225 SetFont(*wxSWISS_FONT);
226
227 SetHolidayAttrs();
228
229 return TRUE;
230 }
231
232 wxCalendarCtrl::~wxCalendarCtrl()
233 {
234 for ( size_t n = 0; n < WXSIZEOF(m_attrs); n++ )
235 {
236 delete m_attrs[n];
237 }
238 }
239
240 // ----------------------------------------------------------------------------
241 // forward wxWin functions to subcontrols
242 // ----------------------------------------------------------------------------
243
244 bool wxCalendarCtrl::Show(bool show)
245 {
246 if ( !wxControl::Show(show) )
247 {
248 return FALSE;
249 }
250
251 GetMonthControl()->Show(show);
252 GetYearControl()->Show(show);
253
254 return TRUE;
255 }
256
257 bool wxCalendarCtrl::Enable(bool enable)
258 {
259 if ( !wxControl::Enable(enable) )
260 {
261 return FALSE;
262 }
263
264 GetMonthControl()->Enable(enable);
265 GetYearControl()->Enable(enable);
266
267 return TRUE;
268 }
269
270 // ----------------------------------------------------------------------------
271 // enable/disable month/year controls
272 // ----------------------------------------------------------------------------
273
274 void wxCalendarCtrl::ShowCurrentControls()
275 {
276 if ( AllowMonthChange() )
277 {
278 m_comboMonth->Show();
279 m_staticMonth->Hide();
280
281 if ( AllowYearChange() )
282 {
283 m_spinYear->Show();
284 m_staticYear->Hide();
285
286 // skip the rest
287 return;
288 }
289 }
290 else
291 {
292 m_comboMonth->Hide();
293 m_staticMonth->Show();
294 }
295
296 // year change not allowed here
297 m_spinYear->Hide();
298 m_staticYear->Show();
299 }
300
301 wxControl *wxCalendarCtrl::GetMonthControl() const
302 {
303 return AllowMonthChange() ? (wxControl *)m_comboMonth : (wxControl *)m_staticMonth;
304 }
305
306 wxControl *wxCalendarCtrl::GetYearControl() const
307 {
308 return AllowYearChange() ? (wxControl *)m_spinYear : (wxControl *)m_staticYear;
309 }
310
311 void wxCalendarCtrl::EnableYearChange(bool enable)
312 {
313 if ( enable != AllowYearChange() )
314 {
315 long style = GetWindowStyle();
316 if ( enable )
317 style &= ~wxCAL_NO_YEAR_CHANGE;
318 else
319 style |= wxCAL_NO_YEAR_CHANGE;
320 SetWindowStyle(style);
321
322 ShowCurrentControls();
323 }
324 }
325
326 void wxCalendarCtrl::EnableMonthChange(bool enable)
327 {
328 if ( enable != AllowMonthChange() )
329 {
330 long style = GetWindowStyle();
331 if ( enable )
332 style &= ~wxCAL_NO_MONTH_CHANGE;
333 else
334 style |= wxCAL_NO_MONTH_CHANGE;
335 SetWindowStyle(style);
336
337 ShowCurrentControls();
338 }
339 }
340
341 // ----------------------------------------------------------------------------
342 // changing date
343 // ----------------------------------------------------------------------------
344
345 void wxCalendarCtrl::SetDate(const wxDateTime& date)
346 {
347 bool sameMonth = m_date.GetMonth() == date.GetMonth(),
348 sameYear = m_date.GetYear() == date.GetYear();
349
350 if ( sameMonth && sameYear )
351 {
352 // just change the day
353 ChangeDay(date);
354 }
355 else
356 {
357 if ( !AllowMonthChange() || (!AllowYearChange() && !sameYear) )
358 {
359 // forbidden
360 return;
361 }
362
363 // change everything
364 m_date = date;
365
366 // update the controls
367 m_comboMonth->SetSelection(m_date.GetMonth());
368
369 if ( AllowYearChange() )
370 {
371 m_spinYear->SetValue(m_date.Format(_T("%Y")));
372 }
373
374 // as the month changed, holidays did too
375 SetHolidayAttrs();
376
377 // update the calendar
378 Refresh();
379 }
380 }
381
382 void wxCalendarCtrl::ChangeDay(const wxDateTime& date)
383 {
384 if ( m_date != date )
385 {
386 // we need to refresh the row containing the old date and the one
387 // containing the new one
388 wxDateTime dateOld = m_date;
389 m_date = date;
390
391 RefreshDate(dateOld);
392
393 // if the date is in the same row, it was already drawn correctly
394 if ( GetWeek(m_date) != GetWeek(dateOld) )
395 {
396 RefreshDate(m_date);
397 }
398 }
399 }
400
401 void wxCalendarCtrl::SetDateAndNotify(const wxDateTime& date)
402 {
403 wxDateTime::Tm tm1 = m_date.GetTm(),
404 tm2 = date.GetTm();
405
406 wxEventType type;
407 if ( tm1.year != tm2.year )
408 type = wxEVT_CALENDAR_YEAR_CHANGED;
409 else if ( tm1.mon != tm2.mon )
410 type = wxEVT_CALENDAR_MONTH_CHANGED;
411 else if ( tm1.mday != tm2.mday )
412 type = wxEVT_CALENDAR_DAY_CHANGED;
413 else
414 return;
415
416 SetDate(date);
417
418 GenerateEvents(type, wxEVT_CALENDAR_SEL_CHANGED);
419 }
420
421 // ----------------------------------------------------------------------------
422 // date helpers
423 // ----------------------------------------------------------------------------
424
425 wxDateTime wxCalendarCtrl::GetStartDate() const
426 {
427 wxDateTime::Tm tm = m_date.GetTm();
428
429 wxDateTime date = wxDateTime(1, tm.mon, tm.year);
430
431 // rewind back
432 date.SetToPrevWeekDay(GetWindowStyle() & wxCAL_MONDAY_FIRST
433 ? wxDateTime::Mon : wxDateTime::Sun);
434
435 return date;
436 }
437
438 bool wxCalendarCtrl::IsDateShown(const wxDateTime& date) const
439 {
440 return date.GetMonth() == m_date.GetMonth();
441 }
442
443 size_t wxCalendarCtrl::GetWeek(const wxDateTime& date) const
444 {
445 return date.GetWeekOfMonth(GetWindowStyle() & wxCAL_MONDAY_FIRST
446 ? wxDateTime::Monday_First
447 : wxDateTime::Sunday_First);
448 }
449
450 // ----------------------------------------------------------------------------
451 // size management
452 // ----------------------------------------------------------------------------
453
454 // this is a composite control and it must arrange its parts each time its
455 // size or position changes: the combobox and spinctrl are along the top of
456 // the available area and the calendar takes up therest of the space
457
458 // the static controls are supposed to be always smaller than combo/spin so we
459 // always use the latter for size calculations and position the static to take
460 // the same space
461
462 // the constants used for the layout
463 #define VERT_MARGIN 5 // distance between combo and calendar
464 #define HORZ_MARGIN 15 // spin
465
466 wxSize wxCalendarCtrl::DoGetBestSize() const
467 {
468 // calc the size of the calendar
469 ((wxCalendarCtrl *)this)->RecalcGeometry(); // const_cast
470
471 wxCoord width = 7*m_widthCol,
472 height = 7*m_heightRow;
473
474 // the combobox doesn't report its height correctly (it returns the
475 // height including the drop down list) so don't use it
476 height += VERT_MARGIN + m_spinYear->GetBestSize().y;
477
478 if ( GetWindowStyle() & (wxRAISED_BORDER | wxSUNKEN_BORDER) )
479 {
480 // the border would clip the last line otherwise
481 height += 6;
482 }
483
484 return wxSize(width, height);
485 }
486
487 void wxCalendarCtrl::DoSetSize(int x, int y,
488 int width, int height,
489 int sizeFlags)
490 {
491 wxControl::DoSetSize(x, y, width, height, sizeFlags);
492 }
493
494 void wxCalendarCtrl::DoMoveWindow(int x, int y, int width, int height)
495 {
496 wxSize sizeCombo = m_comboMonth->GetSize();
497 wxSize sizeStatic = m_staticMonth->GetSize();
498
499 int dy = (sizeCombo.y - sizeStatic.y) / 2;
500 m_comboMonth->Move(x, y);
501 m_staticMonth->SetSize(x, y + dy, sizeCombo.x, sizeStatic.y);
502
503 int xDiff = sizeCombo.x + HORZ_MARGIN;
504 m_spinYear->SetSize(x + xDiff, y, width - xDiff, sizeCombo.y);
505 m_staticYear->SetSize(x + xDiff, y + dy, width - xDiff, sizeStatic.y);
506
507 wxSize sizeSpin = m_spinYear->GetSize();
508 int yDiff = wxMax(sizeSpin.y, sizeCombo.y) + VERT_MARGIN;
509
510 wxControl::DoMoveWindow(x, y + yDiff, width, height - yDiff);
511 }
512
513 void wxCalendarCtrl::DoGetPosition(int *x, int *y) const
514 {
515 wxControl::DoGetPosition(x, y);
516
517 // our real top corner is not in this position
518 if ( y )
519 {
520 *y -= GetMonthControl()->GetSize().y + VERT_MARGIN;
521 }
522 }
523
524 void wxCalendarCtrl::DoGetSize(int *width, int *height) const
525 {
526 wxControl::DoGetSize(width, height);
527
528 // our real height is bigger
529 if ( height )
530 {
531 *height += GetMonthControl()->GetSize().y + VERT_MARGIN;
532 }
533 }
534
535 void wxCalendarCtrl::RecalcGeometry()
536 {
537 if ( m_widthCol != 0 )
538 return;
539
540 wxClientDC dc(this);
541
542 dc.SetFont(m_font);
543
544 // determine the column width (we assume that the weekday names are always
545 // wider (in any language) than the numbers)
546 m_widthCol = 0;
547 wxDateTime::WeekDay wd;
548 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
549 {
550 wxCoord width;
551 dc.GetTextExtent(m_weekdays[wd], &width, &m_heightRow);
552 if ( width > m_widthCol )
553 {
554 m_widthCol = width;
555 }
556 }
557
558 // leave some margins
559 m_widthCol += 2;
560 m_heightRow += 2;
561 }
562
563 // ----------------------------------------------------------------------------
564 // drawing
565 // ----------------------------------------------------------------------------
566
567 void wxCalendarCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
568 {
569 wxPaintDC dc(this);
570
571 dc.SetFont(m_font);
572
573 RecalcGeometry();
574
575 #if DEBUG_PAINT
576 wxLogDebug("--- starting to paint, selection: %s, week %u\n",
577 m_date.Format("%a %d-%m-%Y %H:%M:%S").c_str(),
578 GetWeek(m_date));
579 #endif
580
581 // first draw the week days
582 if ( IsExposed(0, 0, 7*m_widthCol, m_heightRow) )
583 {
584 #if DEBUG_PAINT
585 wxLogDebug("painting the header");
586 #endif
587
588 dc.SetBackgroundMode(wxTRANSPARENT);
589 dc.SetTextForeground(m_colHeaderFg);
590 dc.SetBrush(wxBrush(m_colHeaderBg, wxSOLID));
591 dc.SetPen(wxPen(m_colHeaderBg, 1, wxSOLID));
592 dc.DrawRectangle(0, 0, 7*m_widthCol, m_heightRow);
593
594 bool startOnMonday = (GetWindowStyle() & wxCAL_MONDAY_FIRST) != 0;
595 for ( size_t wd = 0; wd < 7; wd++ )
596 {
597 size_t n;
598 if ( startOnMonday )
599 n = wd == 6 ? 0 : wd + 1;
600 else
601 n = wd;
602
603 dc.DrawText(m_weekdays[n], wd*m_widthCol + 1, 0);
604 }
605 }
606
607 // then the calendar itself
608 dc.SetTextForeground(*wxBLACK);
609 //dc.SetFont(*wxNORMAL_FONT);
610
611 wxCoord y = m_heightRow;
612
613 wxDateTime date = GetStartDate();
614 #if DEBUG_PAINT
615 wxLogDebug("starting calendar from %s\n",
616 date.Format("%a %d-%m-%Y %H:%M:%S").c_str());
617 #endif
618
619 dc.SetBackgroundMode(wxSOLID);
620 for ( size_t nWeek = 1; nWeek <= 6; nWeek++, y += m_heightRow )
621 {
622 // if the update region doesn't intersect this row, don't paint it
623 if ( !IsExposed(0, y, 7*m_widthCol, m_heightRow - 1) )
624 {
625 date += wxDateSpan::Week();
626
627 continue;
628 }
629
630 #if DEBUG_PAINT
631 wxLogDebug("painting week %d at y = %d\n", nWeek, y);
632 #endif
633
634 for ( size_t wd = 0; wd < 7; wd++ )
635 {
636 if ( IsDateShown(date) )
637 {
638 // don't use wxDate::Format() which prepends 0s
639 unsigned int day = date.GetDay();
640 wxString dayStr = wxString::Format(_T("%u"), day);
641 wxCoord width;
642 dc.GetTextExtent(dayStr, &width, (wxCoord *)NULL);
643
644 bool changedColours = FALSE,
645 changedFont = FALSE;
646
647 wxCalendarDateAttr *attr = m_attrs[day - 1];
648
649 bool isSel = date.IsSameDate(m_date);
650 if ( isSel )
651 {
652 dc.SetTextForeground(m_colHighlightFg);
653 dc.SetTextBackground(m_colHighlightBg);
654
655 changedColours = TRUE;
656 }
657 else if ( attr )
658 {
659 wxColour colFg, colBg;
660
661 if ( attr->IsHoliday() )
662 {
663 colFg = m_colHolidayFg;
664 colBg = m_colHolidayBg;
665 }
666 else
667 {
668 colFg = attr->GetTextColour();
669 colBg = attr->GetBackgroundColour();
670 }
671
672 if ( colFg.Ok() )
673 {
674 dc.SetTextForeground(colFg);
675 changedColours = TRUE;
676 }
677
678 if ( colBg.Ok() )
679 {
680 dc.SetTextBackground(colBg);
681 changedColours = TRUE;
682 }
683
684 if ( attr->HasFont() )
685 {
686 dc.SetFont(attr->GetFont());
687 changedFont = TRUE;
688 }
689 }
690
691 wxCoord x = wd*m_widthCol + (m_widthCol - width) / 2;
692 dc.DrawText(dayStr, x, y + 1);
693
694 if ( !isSel && attr && attr->HasBorder() )
695 {
696 wxColour colBorder;
697 if ( attr->HasBorderColour() )
698 {
699 colBorder = attr->GetBorderColour();
700 }
701 else
702 {
703 colBorder = m_foregroundColour;
704 }
705
706 wxPen pen(colBorder, 1, wxSOLID);
707 dc.SetPen(pen);
708 dc.SetBrush(*wxTRANSPARENT_BRUSH);
709
710 switch ( attr->GetBorder() )
711 {
712 case wxCAL_BORDER_SQUARE:
713 dc.DrawRectangle(x - 2, y,
714 width + 4, m_heightRow);
715 break;
716
717 case wxCAL_BORDER_ROUND:
718 dc.DrawEllipse(x - 2, y,
719 width + 4, m_heightRow);
720 break;
721
722 default:
723 wxFAIL_MSG(_T("unknown border type"));
724 }
725 }
726
727 if ( changedColours )
728 {
729 dc.SetTextForeground(m_foregroundColour);
730 dc.SetTextBackground(m_backgroundColour);
731 }
732
733 if ( changedFont )
734 {
735 dc.SetFont(m_font);
736 }
737 }
738 //else: just don't draw it
739
740 date += wxDateSpan::Day();
741 }
742 }
743 #if DEBUG_PAINT
744 wxLogDebug("+++ finished painting");
745 #endif
746 }
747
748 void wxCalendarCtrl::RefreshDate(const wxDateTime& date)
749 {
750 RecalcGeometry();
751
752 wxRect rect;
753
754 // always refresh the whole row at once because our OnPaint() will draw
755 // the whole row anyhow - and this allows the small optimisation in
756 // OnClick() below to work
757 rect.x = 0;
758 rect.y = m_heightRow * GetWeek(date);
759 rect.width = 7*m_widthCol;
760 rect.height = m_heightRow;
761
762 #ifdef __WXMSW__
763 // VZ: for some reason, the selected date seems to occupy more space under
764 // MSW - this is probably some bug in the font size calculations, but I
765 // don't know where exactly. This fix is ugly and leads to more
766 // refreshes than really needed, but without it the selected days
767 // leaves even more ugly underscores on screen.
768 rect.Inflate(0, 1);
769 #endif // MSW
770
771 #if DEBUG_PAINT
772 wxLogDebug("*** refreshing week %d at (%d, %d)-(%d, %d)\n",
773 GetWeek(date),
774 rect.x, rect.y,
775 rect.x + rect.width, rect.y + rect.height);
776 #endif
777
778 Refresh(TRUE, &rect);
779 }
780
781 // ----------------------------------------------------------------------------
782 // mouse handling
783 // ----------------------------------------------------------------------------
784
785 void wxCalendarCtrl::OnDClick(wxMouseEvent& event)
786 {
787 if ( HitTest(event.GetPosition()) != wxCAL_HITTEST_DAY )
788 {
789 event.Skip();
790 }
791 else
792 {
793 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
794 }
795 }
796
797 void wxCalendarCtrl::OnClick(wxMouseEvent& event)
798 {
799 wxDateTime date;
800 wxDateTime::WeekDay wday;
801 switch ( HitTest(event.GetPosition(), &date, &wday) )
802 {
803 case wxCAL_HITTEST_DAY:
804 ChangeDay(date);
805
806 GenerateEvents(wxEVT_CALENDAR_DAY_CHANGED,
807 wxEVT_CALENDAR_SEL_CHANGED);
808 break;
809
810 case wxCAL_HITTEST_HEADER:
811 {
812 wxCalendarEvent event(this, wxEVT_CALENDAR_WEEKDAY_CLICKED);
813 event.m_wday = wday;
814 (void)GetEventHandler()->ProcessEvent(event);
815 }
816 break;
817
818 default:
819 wxFAIL_MSG(_T("unknown hittest code"));
820 // fall through
821
822 case wxCAL_HITTEST_NOWHERE:
823 event.Skip();
824 break;
825 }
826 }
827
828 wxCalendarHitTestResult wxCalendarCtrl::HitTest(const wxPoint& pos,
829 wxDateTime *date,
830 wxDateTime::WeekDay *wd)
831 {
832 RecalcGeometry();
833
834 int wday = pos.x / m_widthCol;
835
836 wxCoord y = pos.y;
837 if ( y < m_heightRow )
838 {
839 if ( wd )
840 {
841 if ( GetWindowStyle() & wxCAL_MONDAY_FIRST )
842 {
843 wday = wday == 6 ? 0 : wday + 1;
844 }
845
846 *wd = (wxDateTime::WeekDay)wday;
847 }
848
849 return wxCAL_HITTEST_HEADER;
850 }
851
852 int week = (y - m_heightRow) / m_heightRow;
853 if ( week >= 6 || wday >= 7 )
854 {
855 return wxCAL_HITTEST_NOWHERE;
856 }
857
858 wxDateTime dt = GetStartDate() + wxDateSpan::Days(7*week + wday);
859
860 if ( IsDateShown(dt) )
861 {
862 if ( date )
863 *date = dt;
864
865 return wxCAL_HITTEST_DAY;
866 }
867 else
868 {
869 return wxCAL_HITTEST_NOWHERE;
870 }
871 }
872
873 // ----------------------------------------------------------------------------
874 // subcontrols events handling
875 // ----------------------------------------------------------------------------
876
877 void wxCalendarCtrl::OnMonthChange(wxCommandEvent& event)
878 {
879 wxDateTime::Tm tm = m_date.GetTm();
880
881 wxDateTime::Month mon = (wxDateTime::Month)event.GetInt();
882 if ( tm.mday > wxDateTime::GetNumberOfDays(mon, tm.year) )
883 {
884 tm.mday = wxDateTime::GetNumberOfDays(mon, tm.year);
885 }
886
887 SetDateAndNotify(wxDateTime(tm.mday, mon, tm.year));
888 }
889
890 void wxCalendarCtrl::OnYearChange(wxSpinEvent& event)
891 {
892 wxDateTime::Tm tm = m_date.GetTm();
893
894 int year = (int)event.GetInt();
895 if ( tm.mday > wxDateTime::GetNumberOfDays(tm.mon, year) )
896 {
897 tm.mday = wxDateTime::GetNumberOfDays(tm.mon, year);
898 }
899
900 SetDateAndNotify(wxDateTime(tm.mday, tm.mon, year));
901 }
902
903 // ----------------------------------------------------------------------------
904 // keyboard interface
905 // ----------------------------------------------------------------------------
906
907 void wxCalendarCtrl::OnChar(wxKeyEvent& event)
908 {
909 switch ( event.KeyCode() )
910 {
911 case _T('+'):
912 case WXK_ADD:
913 SetDateAndNotify(m_date + wxDateSpan::Year());
914 break;
915
916 case _T('-'):
917 case WXK_SUBTRACT:
918 SetDateAndNotify(m_date - wxDateSpan::Year());
919 break;
920
921 case WXK_PRIOR:
922 SetDateAndNotify(m_date - wxDateSpan::Month());
923 break;
924
925 case WXK_NEXT:
926 SetDateAndNotify(m_date + wxDateSpan::Month());
927 break;
928
929 case WXK_RIGHT:
930 if ( event.ControlDown() )
931 SetDateAndNotify(wxDateTime(m_date).SetToNextWeekDay(
932 GetWindowStyle() & wxCAL_MONDAY_FIRST
933 ? wxDateTime::Sun : wxDateTime::Sat));
934 else
935 SetDateAndNotify(m_date + wxDateSpan::Day());
936 break;
937
938 case WXK_LEFT:
939 if ( event.ControlDown() )
940 SetDateAndNotify(wxDateTime(m_date).SetToPrevWeekDay(
941 GetWindowStyle() & wxCAL_MONDAY_FIRST
942 ? wxDateTime::Mon : wxDateTime::Sun));
943 else
944 SetDateAndNotify(m_date - wxDateSpan::Day());
945 break;
946
947 case WXK_UP:
948 SetDateAndNotify(m_date - wxDateSpan::Week());
949 break;
950
951 case WXK_DOWN:
952 SetDateAndNotify(m_date + wxDateSpan::Week());
953 break;
954
955 case WXK_HOME:
956 if ( event.ControlDown() )
957 SetDateAndNotify(wxDateTime::Today());
958 else
959 SetDateAndNotify(wxDateTime(1, m_date.GetMonth(), m_date.GetYear()));
960 break;
961
962 case WXK_END:
963 SetDateAndNotify(wxDateTime(m_date).SetToLastMonthDay());
964 break;
965
966 case WXK_RETURN:
967 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
968 break;
969
970 default:
971 event.Skip();
972 }
973 }
974
975 // ----------------------------------------------------------------------------
976 // holidays handling
977 // ----------------------------------------------------------------------------
978
979 void wxCalendarCtrl::EnableHolidayDisplay(bool display)
980 {
981 long style = GetWindowStyle();
982 if ( display )
983 style |= wxCAL_SHOW_HOLIDAYS;
984 else
985 style &= ~wxCAL_SHOW_HOLIDAYS;
986
987 SetWindowStyle(style);
988
989 if ( display )
990 SetHolidayAttrs();
991 else
992 ResetHolidayAttrs();
993
994 Refresh();
995 }
996
997 void wxCalendarCtrl::SetHolidayAttrs()
998 {
999 if ( GetWindowStyle() & wxCAL_SHOW_HOLIDAYS )
1000 {
1001 ResetHolidayAttrs();
1002
1003 wxDateTime::Tm tm = m_date.GetTm();
1004 wxDateTime dtStart(1, tm.mon, tm.year),
1005 dtEnd = dtStart.GetLastMonthDay();
1006
1007 wxDateTimeArray hol;
1008 wxDateTimeHolidayAuthority::GetHolidaysInRange(dtStart, dtEnd, hol);
1009
1010 size_t count = hol.GetCount();
1011 for ( size_t n = 0; n < count; n++ )
1012 {
1013 SetHoliday(hol[n].GetDay());
1014 }
1015 }
1016 }
1017
1018 void wxCalendarCtrl::SetHoliday(size_t day)
1019 {
1020 wxCHECK_RET( day > 0 && day < 32, _T("invalid day in SetHoliday") );
1021
1022 wxCalendarDateAttr *attr = GetAttr(day);
1023 if ( !attr )
1024 {
1025 attr = new wxCalendarDateAttr;
1026 }
1027
1028 attr->SetHoliday(TRUE);
1029
1030 // can't use SetAttr() because it would delete this pointer
1031 m_attrs[day - 1] = attr;
1032 }
1033
1034 void wxCalendarCtrl::ResetHolidayAttrs()
1035 {
1036 for ( size_t day = 0; day < 31; day++ )
1037 {
1038 if ( m_attrs[day] )
1039 {
1040 m_attrs[day]->SetHoliday(FALSE);
1041 }
1042 }
1043 }
1044
1045 // ----------------------------------------------------------------------------
1046 // wxCalendarEvent
1047 // ----------------------------------------------------------------------------
1048
1049 void wxCalendarEvent::Init()
1050 {
1051 m_wday = wxDateTime::Inv_WeekDay;
1052 }
1053
1054 wxCalendarEvent::wxCalendarEvent(wxCalendarCtrl *cal, wxEventType type)
1055 : wxCommandEvent(type, cal->GetId())
1056 {
1057 m_date = cal->GetDate();
1058 }
1059
1060 #endif // wxUSE_CALENDARCTRL
1061