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