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