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