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