]> git.saurik.com Git - wxWidgets.git/blob - src/generic/calctrl.cpp
another attempt to fix wxPanel/wxFrame::m_winLastFocused handling
[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 // the combobox doesn't report its height correctly (it returns the
476 // height including the drop down list) so don't use it
477 height += VERT_MARGIN + m_spinYear->GetBestSize().y;
478
479 if ( GetWindowStyle() & (wxRAISED_BORDER | wxSUNKEN_BORDER) )
480 {
481 // the border would clip the last line otherwise
482 height += 6;
483 }
484
485 return wxSize(width, height);
486 }
487
488 void wxCalendarCtrl::DoSetSize(int x, int y,
489 int width, int height,
490 int sizeFlags)
491 {
492 wxControl::DoSetSize(x, y, width, height, sizeFlags);
493 }
494
495 void wxCalendarCtrl::DoMoveWindow(int x, int y, int width, int height)
496 {
497 wxSize sizeCombo = m_comboMonth->GetSize();
498 wxSize sizeStatic = m_staticMonth->GetSize();
499
500 int dy = (sizeCombo.y - sizeStatic.y) / 2;
501 m_comboMonth->Move(x, y);
502 m_staticMonth->SetSize(x, y + dy, sizeCombo.x, sizeStatic.y);
503
504 int xDiff = sizeCombo.x + HORZ_MARGIN;
505 m_spinYear->SetSize(x + xDiff, y, width - xDiff, sizeCombo.y);
506 m_staticYear->SetSize(x + xDiff, y + dy, width - xDiff, sizeStatic.y);
507
508 wxSize sizeSpin = m_spinYear->GetSize();
509 int yDiff = wxMax(sizeSpin.y, sizeCombo.y) + VERT_MARGIN;
510
511 wxControl::DoMoveWindow(x, y + yDiff, width, height - yDiff);
512 }
513
514 void wxCalendarCtrl::DoGetPosition(int *x, int *y) const
515 {
516 wxControl::DoGetPosition(x, y);
517
518 // our real top corner is not in this position
519 if ( y )
520 {
521 *y -= GetMonthControl()->GetSize().y + VERT_MARGIN;
522 }
523 }
524
525 void wxCalendarCtrl::DoGetSize(int *width, int *height) const
526 {
527 wxControl::DoGetSize(width, height);
528
529 // our real height is bigger
530 if ( height )
531 {
532 *height += GetMonthControl()->GetSize().y + VERT_MARGIN;
533 }
534 }
535
536 void wxCalendarCtrl::RecalcGeometry()
537 {
538 if ( m_widthCol != 0 )
539 return;
540
541 wxClientDC dc(this);
542
543 dc.SetFont(m_font);
544
545 // determine the column width (we assume that the weekday names are always
546 // wider (in any language) than the numbers)
547 m_widthCol = 0;
548 wxDateTime::WeekDay wd;
549 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
550 {
551 wxCoord width;
552 dc.GetTextExtent(m_weekdays[wd], &width, &m_heightRow);
553 if ( width > m_widthCol )
554 {
555 m_widthCol = width;
556 }
557 }
558
559 // leave some margins
560 m_widthCol += 2;
561 m_heightRow += 2;
562 }
563
564 // ----------------------------------------------------------------------------
565 // drawing
566 // ----------------------------------------------------------------------------
567
568 void wxCalendarCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
569 {
570 wxPaintDC dc(this);
571
572 dc.SetFont(m_font);
573
574 RecalcGeometry();
575
576 #if DEBUG_PAINT
577 wxLogDebug("--- starting to paint, selection: %s, week %u\n",
578 m_date.Format("%a %d-%m-%Y %H:%M:%S").c_str(),
579 GetWeek(m_date));
580 #endif
581
582 // first draw the week days
583 if ( IsExposed(0, 0, 7*m_widthCol, m_heightRow) )
584 {
585 #if DEBUG_PAINT
586 wxLogDebug("painting the header");
587 #endif
588
589 dc.SetBackgroundMode(wxTRANSPARENT);
590 dc.SetTextForeground(m_colHeaderFg);
591 dc.SetBrush(wxBrush(m_colHeaderBg, wxSOLID));
592 dc.SetPen(wxPen(m_colHeaderBg, 1, wxSOLID));
593 dc.DrawRectangle(0, 0, 7*m_widthCol, m_heightRow);
594
595 bool startOnMonday = (GetWindowStyle() & wxCAL_MONDAY_FIRST) != 0;
596 for ( size_t wd = 0; wd < 7; wd++ )
597 {
598 size_t n;
599 if ( startOnMonday )
600 n = wd == 6 ? 0 : wd + 1;
601 else
602 n = wd;
603
604 dc.DrawText(m_weekdays[n], wd*m_widthCol + 1, 0);
605 }
606 }
607
608 // then the calendar itself
609 dc.SetTextForeground(*wxBLACK);
610 //dc.SetFont(*wxNORMAL_FONT);
611
612 wxCoord y = m_heightRow;
613
614 wxDateTime date = GetStartDate();
615 #if DEBUG_PAINT
616 wxLogDebug("starting calendar from %s\n",
617 date.Format("%a %d-%m-%Y %H:%M:%S").c_str());
618 #endif
619
620 dc.SetBackgroundMode(wxSOLID);
621 for ( size_t nWeek = 1; nWeek <= 6; nWeek++, y += m_heightRow )
622 {
623 // if the update region doesn't intersect this row, don't paint it
624 if ( !IsExposed(0, y, 7*m_widthCol, m_heightRow - 1) )
625 {
626 date += wxDateSpan::Week();
627
628 continue;
629 }
630
631 #if DEBUG_PAINT
632 wxLogDebug("painting week %d at y = %d\n", nWeek, y);
633 #endif
634
635 for ( size_t wd = 0; wd < 7; wd++ )
636 {
637 if ( IsDateShown(date) )
638 {
639 // don't use wxDate::Format() which prepends 0s
640 unsigned int day = date.GetDay();
641 wxString dayStr = wxString::Format(_T("%u"), day);
642 wxCoord width;
643 dc.GetTextExtent(dayStr, &width, (wxCoord *)NULL);
644
645 bool changedColours = FALSE,
646 changedFont = FALSE;
647
648 wxCalendarDateAttr *attr = m_attrs[day - 1];
649
650 bool isSel = date.IsSameDate(m_date);
651 if ( isSel )
652 {
653 dc.SetTextForeground(m_colHighlightFg);
654 dc.SetTextBackground(m_colHighlightBg);
655
656 changedColours = TRUE;
657 }
658 else if ( attr )
659 {
660 wxColour colFg, colBg;
661
662 if ( attr->IsHoliday() )
663 {
664 colFg = m_colHolidayFg;
665 colBg = m_colHolidayBg;
666 }
667 else
668 {
669 colFg = attr->GetTextColour();
670 colBg = attr->GetBackgroundColour();
671 }
672
673 if ( colFg.Ok() )
674 {
675 dc.SetTextForeground(colFg);
676 changedColours = TRUE;
677 }
678
679 if ( colBg.Ok() )
680 {
681 dc.SetTextBackground(colBg);
682 changedColours = TRUE;
683 }
684
685 if ( attr->HasFont() )
686 {
687 dc.SetFont(attr->GetFont());
688 changedFont = TRUE;
689 }
690 }
691
692 wxCoord x = wd*m_widthCol + (m_widthCol - width) / 2;
693 dc.DrawText(dayStr, x, y + 1);
694
695 if ( !isSel && attr && attr->HasBorder() )
696 {
697 wxColour colBorder;
698 if ( attr->HasBorderColour() )
699 {
700 colBorder = attr->GetBorderColour();
701 }
702 else
703 {
704 colBorder = m_foregroundColour;
705 }
706
707 wxPen pen(colBorder, 1, wxSOLID);
708 dc.SetPen(pen);
709 dc.SetBrush(*wxTRANSPARENT_BRUSH);
710
711 switch ( attr->GetBorder() )
712 {
713 case wxCAL_BORDER_SQUARE:
714 dc.DrawRectangle(x - 2, y,
715 width + 4, m_heightRow);
716 break;
717
718 case wxCAL_BORDER_ROUND:
719 dc.DrawEllipse(x - 2, y,
720 width + 4, m_heightRow);
721 break;
722
723 default:
724 wxFAIL_MSG(_T("unknown border type"));
725 }
726 }
727
728 if ( changedColours )
729 {
730 dc.SetTextForeground(m_foregroundColour);
731 dc.SetTextBackground(m_backgroundColour);
732 }
733
734 if ( changedFont )
735 {
736 dc.SetFont(m_font);
737 }
738 }
739 //else: just don't draw it
740
741 date += wxDateSpan::Day();
742 }
743 }
744 #if DEBUG_PAINT
745 wxLogDebug("+++ finished painting");
746 #endif
747 }
748
749 void wxCalendarCtrl::RefreshDate(const wxDateTime& date)
750 {
751 RecalcGeometry();
752
753 wxRect rect;
754
755 // always refresh the whole row at once because our OnPaint() will draw
756 // the whole row anyhow - and this allows the small optimisation in
757 // OnClick() below to work
758 rect.x = 0;
759 rect.y = m_heightRow * GetWeek(date);
760 rect.width = 7*m_widthCol;
761 rect.height = m_heightRow;
762
763 #ifdef __WXMSW__
764 // VZ: for some reason, the selected date seems to occupy more space under
765 // MSW - this is probably some bug in the font size calculations, but I
766 // don't know where exactly. This fix is ugly and leads to more
767 // refreshes than really needed, but without it the selected days
768 // leaves even more ugly underscores on screen.
769 rect.Inflate(0, 1);
770 #endif // MSW
771
772 #if DEBUG_PAINT
773 wxLogDebug("*** refreshing week %d at (%d, %d)-(%d, %d)\n",
774 GetWeek(date),
775 rect.x, rect.y,
776 rect.x + rect.width, rect.y + rect.height);
777 #endif
778
779 Refresh(TRUE, &rect);
780 }
781
782 // ----------------------------------------------------------------------------
783 // mouse handling
784 // ----------------------------------------------------------------------------
785
786 void wxCalendarCtrl::OnDClick(wxMouseEvent& event)
787 {
788 if ( HitTest(event.GetPosition()) != wxCAL_HITTEST_DAY )
789 {
790 event.Skip();
791 }
792 else
793 {
794 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
795 }
796 }
797
798 void wxCalendarCtrl::OnClick(wxMouseEvent& event)
799 {
800 wxDateTime date;
801 wxDateTime::WeekDay wday;
802 switch ( HitTest(event.GetPosition(), &date, &wday) )
803 {
804 case wxCAL_HITTEST_DAY:
805 ChangeDay(date);
806
807 GenerateEvents(wxEVT_CALENDAR_DAY_CHANGED,
808 wxEVT_CALENDAR_SEL_CHANGED);
809 break;
810
811 case wxCAL_HITTEST_HEADER:
812 {
813 wxCalendarEvent event(this, wxEVT_CALENDAR_WEEKDAY_CLICKED);
814 event.m_wday = wday;
815 (void)GetEventHandler()->ProcessEvent(event);
816 }
817 break;
818
819 default:
820 wxFAIL_MSG(_T("unknown hittest code"));
821 // fall through
822
823 case wxCAL_HITTEST_NOWHERE:
824 event.Skip();
825 break;
826 }
827 }
828
829 wxCalendarHitTestResult wxCalendarCtrl::HitTest(const wxPoint& pos,
830 wxDateTime *date,
831 wxDateTime::WeekDay *wd)
832 {
833 RecalcGeometry();
834
835 int wday = pos.x / m_widthCol;
836
837 wxCoord y = pos.y;
838 if ( y < m_heightRow )
839 {
840 if ( wd )
841 {
842 if ( GetWindowStyle() & wxCAL_MONDAY_FIRST )
843 {
844 wday = wday == 6 ? 0 : wday + 1;
845 }
846
847 *wd = (wxDateTime::WeekDay)wday;
848 }
849
850 return wxCAL_HITTEST_HEADER;
851 }
852
853 int week = (y - m_heightRow) / m_heightRow;
854 if ( week >= 6 || wday >= 7 )
855 {
856 return wxCAL_HITTEST_NOWHERE;
857 }
858
859 wxDateTime dt = GetStartDate() + wxDateSpan::Days(7*week + wday);
860
861 if ( IsDateShown(dt) )
862 {
863 if ( date )
864 *date = dt;
865
866 return wxCAL_HITTEST_DAY;
867 }
868 else
869 {
870 return wxCAL_HITTEST_NOWHERE;
871 }
872 }
873
874 // ----------------------------------------------------------------------------
875 // subcontrols events handling
876 // ----------------------------------------------------------------------------
877
878 void wxCalendarCtrl::OnMonthChange(wxCommandEvent& event)
879 {
880 wxDateTime::Tm tm = m_date.GetTm();
881
882 wxDateTime::Month mon = (wxDateTime::Month)event.GetInt();
883 if ( tm.mday > wxDateTime::GetNumberOfDays(mon, tm.year) )
884 {
885 tm.mday = wxDateTime::GetNumberOfDays(mon, tm.year);
886 }
887
888 SetDateAndNotify(wxDateTime(tm.mday, mon, tm.year));
889 }
890
891 void wxCalendarCtrl::OnYearChange(wxSpinEvent& event)
892 {
893 wxDateTime::Tm tm = m_date.GetTm();
894
895 int year = (int)event.GetInt();
896 if ( tm.mday > wxDateTime::GetNumberOfDays(tm.mon, year) )
897 {
898 tm.mday = wxDateTime::GetNumberOfDays(tm.mon, year);
899 }
900
901 SetDateAndNotify(wxDateTime(tm.mday, tm.mon, year));
902 }
903
904 // ----------------------------------------------------------------------------
905 // keyboard interface
906 // ----------------------------------------------------------------------------
907
908 void wxCalendarCtrl::OnChar(wxKeyEvent& event)
909 {
910 switch ( event.KeyCode() )
911 {
912 case _T('+'):
913 case WXK_ADD:
914 SetDateAndNotify(m_date + wxDateSpan::Year());
915 break;
916
917 case _T('-'):
918 case WXK_SUBTRACT:
919 SetDateAndNotify(m_date - wxDateSpan::Year());
920 break;
921
922 case WXK_PRIOR:
923 SetDateAndNotify(m_date - wxDateSpan::Month());
924 break;
925
926 case WXK_NEXT:
927 SetDateAndNotify(m_date + wxDateSpan::Month());
928 break;
929
930 case WXK_RIGHT:
931 if ( event.ControlDown() )
932 SetDateAndNotify(wxDateTime(m_date).SetToNextWeekDay(
933 GetWindowStyle() & wxCAL_MONDAY_FIRST
934 ? wxDateTime::Sun : wxDateTime::Sat));
935 else
936 SetDateAndNotify(m_date + wxDateSpan::Day());
937 break;
938
939 case WXK_LEFT:
940 if ( event.ControlDown() )
941 SetDateAndNotify(wxDateTime(m_date).SetToPrevWeekDay(
942 GetWindowStyle() & wxCAL_MONDAY_FIRST
943 ? wxDateTime::Mon : wxDateTime::Sun));
944 else
945 SetDateAndNotify(m_date - wxDateSpan::Day());
946 break;
947
948 case WXK_UP:
949 SetDateAndNotify(m_date - wxDateSpan::Week());
950 break;
951
952 case WXK_DOWN:
953 SetDateAndNotify(m_date + wxDateSpan::Week());
954 break;
955
956 case WXK_HOME:
957 if ( event.ControlDown() )
958 SetDateAndNotify(wxDateTime::Today());
959 else
960 SetDateAndNotify(wxDateTime(1, m_date.GetMonth(), m_date.GetYear()));
961 break;
962
963 case WXK_END:
964 SetDateAndNotify(wxDateTime(m_date).SetToLastMonthDay());
965 break;
966
967 case WXK_RETURN:
968 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
969 break;
970
971 default:
972 event.Skip();
973 }
974 }
975
976 // ----------------------------------------------------------------------------
977 // holidays handling
978 // ----------------------------------------------------------------------------
979
980 void wxCalendarCtrl::EnableHolidayDisplay(bool display)
981 {
982 long style = GetWindowStyle();
983 if ( display )
984 style |= wxCAL_SHOW_HOLIDAYS;
985 else
986 style &= ~wxCAL_SHOW_HOLIDAYS;
987
988 SetWindowStyle(style);
989
990 if ( display )
991 SetHolidayAttrs();
992 else
993 ResetHolidayAttrs();
994
995 Refresh();
996 }
997
998 void wxCalendarCtrl::SetHolidayAttrs()
999 {
1000 if ( GetWindowStyle() & wxCAL_SHOW_HOLIDAYS )
1001 {
1002 ResetHolidayAttrs();
1003
1004 wxDateTime::Tm tm = m_date.GetTm();
1005 wxDateTime dtStart(1, tm.mon, tm.year),
1006 dtEnd = dtStart.GetLastMonthDay();
1007
1008 wxDateTimeArray hol;
1009 wxDateTimeHolidayAuthority::GetHolidaysInRange(dtStart, dtEnd, hol);
1010
1011 size_t count = hol.GetCount();
1012 for ( size_t n = 0; n < count; n++ )
1013 {
1014 SetHoliday(hol[n].GetDay());
1015 }
1016 }
1017 }
1018
1019 void wxCalendarCtrl::SetHoliday(size_t day)
1020 {
1021 wxCHECK_RET( day > 0 && day < 32, _T("invalid day in SetHoliday") );
1022
1023 wxCalendarDateAttr *attr = GetAttr(day);
1024 if ( !attr )
1025 {
1026 attr = new wxCalendarDateAttr;
1027 }
1028
1029 attr->SetHoliday(TRUE);
1030
1031 // can't use SetAttr() because it would delete this pointer
1032 m_attrs[day - 1] = attr;
1033 }
1034
1035 void wxCalendarCtrl::ResetHolidayAttrs()
1036 {
1037 for ( size_t day = 0; day < 31; day++ )
1038 {
1039 if ( m_attrs[day] )
1040 {
1041 m_attrs[day]->SetHoliday(FALSE);
1042 }
1043 }
1044 }
1045
1046 // ----------------------------------------------------------------------------
1047 // wxCalendarEvent
1048 // ----------------------------------------------------------------------------
1049
1050 void wxCalendarEvent::Init()
1051 {
1052 m_wday = wxDateTime::Inv_WeekDay;
1053 }
1054
1055 wxCalendarEvent::wxCalendarEvent(wxCalendarCtrl *cal, wxEventType type)
1056 : wxCommandEvent(type, cal->GetId())
1057 {
1058 m_date = cal->GetDate();
1059 }
1060
1061 #endif // wxUSE_SPINBTN
1062