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