]> git.saurik.com Git - wxWidgets.git/blob - src/generic/calctrl.cpp
Fixed child positioning on window resizing.
[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 = m_rightArrowRect = wxRect(0,0,0,0);
988
989 if ( AllowMonthChange() )
990 {
991 wxDateTime ldpm = wxDateTime(1,m_date.GetMonth(), m_date.GetYear()) - wxDateSpan::Day(); // last day prev month
992 // Check if range permits change
993 if ( IsDateInRange(ldpm) && ( ( ldpm.GetYear() == m_date.GetYear() ) ? true : AllowYearChange() ) )
994 {
995 m_leftArrowRect = wxRect(larrowx - 3, arrowy - 3, (arrowheight / 2) + 8, (arrowheight + 6));
996 dc.SetBrush(wxBrush(*wxBLACK, wxSOLID));
997 dc.SetPen(wxPen(*wxBLACK, 1, wxSOLID));
998 dc.DrawPolygon(3, leftarrow, larrowx , arrowy, wxWINDING_RULE);
999 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1000 dc.DrawRectangle(m_leftArrowRect);
1001 }
1002 wxDateTime fdnm = wxDateTime(1,m_date.GetMonth(), m_date.GetYear()) + wxDateSpan::Month(); // first day next month
1003 if ( IsDateInRange(fdnm) && ( ( fdnm.GetYear() == m_date.GetYear() ) ? true : AllowYearChange() ) )
1004 {
1005 m_rightArrowRect = wxRect(rarrowx - 4, arrowy - 3, (arrowheight / 2) + 8, (arrowheight + 6));
1006 dc.SetBrush(wxBrush(*wxBLACK, wxSOLID));
1007 dc.SetPen(wxPen(*wxBLACK, 1, wxSOLID));
1008 dc.DrawPolygon(3, rightarrow, rarrowx , arrowy, wxWINDING_RULE);
1009 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1010 dc.DrawRectangle(m_rightArrowRect);
1011 }
1012 }
1013
1014 y += m_heightRow;
1015 }
1016
1017 // first draw the week days
1018 if ( IsExposed(0, y, 7*m_widthCol, m_heightRow) )
1019 {
1020 #if DEBUG_PAINT
1021 wxLogDebug("painting the header");
1022 #endif
1023
1024 dc.SetBackgroundMode(wxTRANSPARENT);
1025 dc.SetTextForeground(m_colHeaderFg);
1026 dc.SetBrush(wxBrush(m_colHeaderBg, wxSOLID));
1027 dc.SetPen(wxPen(m_colHeaderBg, 1, wxSOLID));
1028 dc.DrawRectangle(0, y, GetClientSize().x, m_heightRow);
1029
1030 bool startOnMonday = (GetWindowStyle() & wxCAL_MONDAY_FIRST) != 0;
1031 for ( size_t wd = 0; wd < 7; wd++ )
1032 {
1033 size_t n;
1034 if ( startOnMonday )
1035 n = wd == 6 ? 0 : wd + 1;
1036 else
1037 n = wd;
1038 wxCoord dayw, dayh;
1039 dc.GetTextExtent(m_weekdays[n], &dayw, &dayh);
1040 dc.DrawText(m_weekdays[n], (wd*m_widthCol) + ((m_widthCol- dayw) / 2), y); // center the day-name
1041 }
1042 }
1043
1044 // then the calendar itself
1045 dc.SetTextForeground(*wxBLACK);
1046 //dc.SetFont(*wxNORMAL_FONT);
1047
1048 y += m_heightRow;
1049 wxDateTime date = GetStartDate();
1050
1051 #if DEBUG_PAINT
1052 wxLogDebug("starting calendar from %s\n",
1053 date.Format("%a %d-%m-%Y %H:%M:%S").c_str());
1054 #endif
1055
1056 dc.SetBackgroundMode(wxSOLID);
1057 for ( size_t nWeek = 1; nWeek <= 6; nWeek++, y += m_heightRow )
1058 {
1059 // if the update region doesn't intersect this row, don't paint it
1060 if ( !IsExposed(0, y, 7*m_widthCol, m_heightRow - 1) )
1061 {
1062 date += wxDateSpan::Week();
1063
1064 continue;
1065 }
1066
1067 #if DEBUG_PAINT
1068 wxLogDebug("painting week %d at y = %d\n", nWeek, y);
1069 #endif
1070
1071 for ( size_t wd = 0; wd < 7; wd++ )
1072 {
1073 if ( IsDateShown(date) )
1074 {
1075 // don't use wxDate::Format() which prepends 0s
1076 unsigned int day = date.GetDay();
1077 wxString dayStr = wxString::Format(_T("%u"), day);
1078 wxCoord width;
1079 dc.GetTextExtent(dayStr, &width, (wxCoord *)NULL);
1080
1081 bool changedColours = false,
1082 changedFont = false;
1083
1084 bool isSel = false;
1085 wxCalendarDateAttr *attr = NULL;
1086
1087 if ( date.GetMonth() != m_date.GetMonth() || !IsDateInRange(date) )
1088 {
1089 // surrounding week or out-of-range
1090 // draw "disabled"
1091 dc.SetTextForeground(*wxLIGHT_GREY);
1092 changedColours = true;
1093 }
1094 else
1095 {
1096 isSel = date.IsSameDate(m_date);
1097 attr = m_attrs[day - 1];
1098
1099 if ( isSel )
1100 {
1101 dc.SetTextForeground(m_colHighlightFg);
1102 dc.SetTextBackground(m_colHighlightBg);
1103
1104 changedColours = true;
1105 }
1106 else if ( attr )
1107 {
1108 wxColour colFg, colBg;
1109
1110 if ( attr->IsHoliday() )
1111 {
1112 colFg = m_colHolidayFg;
1113 colBg = m_colHolidayBg;
1114 }
1115 else
1116 {
1117 colFg = attr->GetTextColour();
1118 colBg = attr->GetBackgroundColour();
1119 }
1120
1121 if ( colFg.Ok() )
1122 {
1123 dc.SetTextForeground(colFg);
1124 changedColours = true;
1125 }
1126
1127 if ( colBg.Ok() )
1128 {
1129 dc.SetTextBackground(colBg);
1130 changedColours = true;
1131 }
1132
1133 if ( attr->HasFont() )
1134 {
1135 dc.SetFont(attr->GetFont());
1136 changedFont = true;
1137 }
1138 }
1139 }
1140
1141 wxCoord x = wd*m_widthCol + (m_widthCol - width) / 2;
1142 dc.DrawText(dayStr, x, y + 1);
1143
1144 if ( !isSel && attr && attr->HasBorder() )
1145 {
1146 wxColour colBorder;
1147 if ( attr->HasBorderColour() )
1148 {
1149 colBorder = attr->GetBorderColour();
1150 }
1151 else
1152 {
1153 colBorder = GetForegroundColour();
1154 }
1155
1156 wxPen pen(colBorder, 1, wxSOLID);
1157 dc.SetPen(pen);
1158 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1159
1160 switch ( attr->GetBorder() )
1161 {
1162 case wxCAL_BORDER_SQUARE:
1163 dc.DrawRectangle(x - 2, y,
1164 width + 4, m_heightRow);
1165 break;
1166
1167 case wxCAL_BORDER_ROUND:
1168 dc.DrawEllipse(x - 2, y,
1169 width + 4, m_heightRow);
1170 break;
1171
1172 default:
1173 wxFAIL_MSG(_T("unknown border type"));
1174 }
1175 }
1176
1177 if ( changedColours )
1178 {
1179 dc.SetTextForeground(GetForegroundColour());
1180 dc.SetTextBackground(GetBackgroundColour());
1181 }
1182
1183 if ( changedFont )
1184 {
1185 dc.SetFont(GetFont());
1186 }
1187 }
1188 //else: just don't draw it
1189
1190 date += wxDateSpan::Day();
1191 }
1192 }
1193
1194 // Greying out out-of-range background
1195 bool showSurrounding = (GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS) != 0;
1196
1197 date = ( showSurrounding ) ? GetStartDate() : wxDateTime(1, m_date.GetMonth(), m_date.GetYear());
1198 if ( !IsDateInRange(date) )
1199 {
1200 wxDateTime firstOOR = GetLowerDateLimit() - wxDateSpan::Day(); // first out-of-range
1201
1202 wxBrush oorbrush = *wxLIGHT_GREY_BRUSH;
1203 oorbrush.SetStyle(wxFDIAGONAL_HATCH);
1204
1205 HighlightRange(&dc, date, firstOOR, wxTRANSPARENT_PEN, &oorbrush);
1206 }
1207
1208 date = ( showSurrounding ) ? GetStartDate() + wxDateSpan::Weeks(6) - wxDateSpan::Day() : wxDateTime().SetToLastMonthDay(m_date.GetMonth(), m_date.GetYear());
1209 if ( !IsDateInRange(date) )
1210 {
1211 wxDateTime firstOOR = GetUpperDateLimit() + wxDateSpan::Day(); // first out-of-range
1212
1213 wxBrush oorbrush = *wxLIGHT_GREY_BRUSH;
1214 oorbrush.SetStyle(wxFDIAGONAL_HATCH);
1215
1216 HighlightRange(&dc, firstOOR, date, wxTRANSPARENT_PEN, &oorbrush);
1217 }
1218
1219 #if DEBUG_PAINT
1220 wxLogDebug("+++ finished painting");
1221 #endif
1222 }
1223
1224 void wxCalendarCtrl::RefreshDate(const wxDateTime& date)
1225 {
1226 RecalcGeometry();
1227
1228 wxRect rect;
1229
1230 // always refresh the whole row at once because our OnPaint() will draw
1231 // the whole row anyhow - and this allows the small optimisation in
1232 // OnClick() below to work
1233 rect.x = 0;
1234
1235 rect.y = (m_heightRow * GetWeek(date)) + m_rowOffset;
1236
1237 rect.width = 7*m_widthCol;
1238 rect.height = m_heightRow;
1239
1240 #ifdef __WXMSW__
1241 // VZ: for some reason, the selected date seems to occupy more space under
1242 // MSW - this is probably some bug in the font size calculations, but I
1243 // don't know where exactly. This fix is ugly and leads to more
1244 // refreshes than really needed, but without it the selected days
1245 // leaves even more ugly underscores on screen.
1246 rect.Inflate(0, 1);
1247 #endif // MSW
1248
1249 #if DEBUG_PAINT
1250 wxLogDebug("*** refreshing week %d at (%d, %d)-(%d, %d)\n",
1251 GetWeek(date),
1252 rect.x, rect.y,
1253 rect.x + rect.width, rect.y + rect.height);
1254 #endif
1255
1256 Refresh(true, &rect);
1257 }
1258
1259 void wxCalendarCtrl::HighlightRange(wxPaintDC* pDC, const wxDateTime& fromdate, const wxDateTime& todate, wxPen* pPen, wxBrush* pBrush)
1260 {
1261 // Highlights the given range using pen and brush
1262 // Does nothing if todate < fromdate
1263
1264
1265 #if DEBUG_PAINT
1266 wxLogDebug("+++ HighlightRange: (%s) - (%s) +++", fromdate.Format("%d %m %Y"), todate.Format("%d %m %Y"));
1267 #endif
1268
1269 if ( todate >= fromdate )
1270 {
1271 // do stuff
1272 // date-coordinates
1273 int fd, fw;
1274 int td, tw;
1275
1276 // implicit: both dates must be currently shown - checked by GetDateCoord
1277 if ( GetDateCoord(fromdate, &fd, &fw) && GetDateCoord(todate, &td, &tw) )
1278 {
1279 #if DEBUG_PAINT
1280 wxLogDebug("Highlight range: (%i, %i) - (%i, %i)", fd, fw, td, tw);
1281 #endif
1282 if ( ( (tw - fw) == 1 ) && ( td < fd ) )
1283 {
1284 // special case: interval 7 days or less not in same week
1285 // split in two seperate intervals
1286 wxDateTime tfd = fromdate + wxDateSpan::Days(7-fd);
1287 wxDateTime ftd = tfd + wxDateSpan::Day();
1288 #if DEBUG_PAINT
1289 wxLogDebug("Highlight: Seperate segments");
1290 #endif
1291 // draw seperately
1292 HighlightRange(pDC, fromdate, tfd, pPen, pBrush);
1293 HighlightRange(pDC, ftd, todate, pPen, pBrush);
1294 }
1295 else
1296 {
1297 int numpoints;
1298 wxPoint corners[8]; // potentially 8 corners in polygon
1299
1300 if ( fw == tw )
1301 {
1302 // simple case: same week
1303 numpoints = 4;
1304 corners[0] = wxPoint((fd - 1) * m_widthCol, (fw * m_heightRow) + m_rowOffset);
1305 corners[1] = wxPoint((fd - 1) * m_widthCol, ((fw + 1 ) * m_heightRow) + m_rowOffset);
1306 corners[2] = wxPoint(td * m_widthCol, ((tw + 1) * m_heightRow) + m_rowOffset);
1307 corners[3] = wxPoint(td * m_widthCol, (tw * m_heightRow) + m_rowOffset);
1308 }
1309 else
1310 {
1311 int cidx = 0;
1312 // "complex" polygon
1313 corners[cidx] = wxPoint((fd - 1) * m_widthCol, (fw * m_heightRow) + m_rowOffset); cidx++;
1314
1315 if ( fd > 1 )
1316 {
1317 corners[cidx] = wxPoint((fd - 1) * m_widthCol, ((fw + 1) * m_heightRow) + m_rowOffset); cidx++;
1318 corners[cidx] = wxPoint(0, ((fw + 1) * m_heightRow) + m_rowOffset); cidx++;
1319 }
1320
1321 corners[cidx] = wxPoint(0, ((tw + 1) * m_heightRow) + m_rowOffset); cidx++;
1322 corners[cidx] = wxPoint(td * m_widthCol, ((tw + 1) * m_heightRow) + m_rowOffset); cidx++;
1323
1324 if ( td < 7 )
1325 {
1326 corners[cidx] = wxPoint(td * m_widthCol, (tw * m_heightRow) + m_rowOffset); cidx++;
1327 corners[cidx] = wxPoint(7 * m_widthCol, (tw * m_heightRow) + m_rowOffset); cidx++;
1328 }
1329
1330 corners[cidx] = wxPoint(7 * m_widthCol, (fw * m_heightRow) + m_rowOffset); cidx++;
1331
1332 numpoints = cidx;
1333 }
1334
1335 // draw the polygon
1336 pDC->SetBrush(*pBrush);
1337 pDC->SetPen(*pPen);
1338 pDC->DrawPolygon(numpoints, corners);
1339 }
1340 }
1341 }
1342 // else do nothing
1343 #if DEBUG_PAINT
1344 wxLogDebug("--- HighlightRange ---");
1345 #endif
1346 }
1347
1348 bool wxCalendarCtrl::GetDateCoord(const wxDateTime& date, int *day, int *week) const
1349 {
1350 bool retval = true;
1351
1352 #if DEBUG_PAINT
1353 wxLogDebug("+++ GetDateCoord: (%s) +++", date.Format("%d %m %Y"));
1354 #endif
1355
1356 if ( IsDateShown(date) )
1357 {
1358 bool startOnMonday = ( GetWindowStyle() & wxCAL_MONDAY_FIRST ) != 0;
1359
1360 // Find day
1361 *day = date.GetWeekDay();
1362
1363 if ( *day == 0 ) // sunday
1364 {
1365 *day = ( startOnMonday ) ? 7 : 1;
1366 }
1367 else
1368 {
1369 *day += ( startOnMonday ) ? 0 : 1;
1370 }
1371
1372 int targetmonth = date.GetMonth() + (12 * date.GetYear());
1373 int thismonth = m_date.GetMonth() + (12 * m_date.GetYear());
1374
1375 // Find week
1376 if ( targetmonth == thismonth )
1377 {
1378 *week = GetWeek(date);
1379 }
1380 else
1381 {
1382 if ( targetmonth < thismonth )
1383 {
1384 *week = 1; // trivial
1385 }
1386 else // targetmonth > thismonth
1387 {
1388 wxDateTime ldcm;
1389 int lastweek;
1390 int lastday;
1391
1392 // get the datecoord of the last day in the month currently shown
1393 #if DEBUG_PAINT
1394 wxLogDebug(" +++ LDOM +++");
1395 #endif
1396 GetDateCoord(ldcm.SetToLastMonthDay(m_date.GetMonth(), m_date.GetYear()), &lastday, &lastweek);
1397 #if DEBUG_PAINT
1398 wxLogDebug(" --- LDOM ---");
1399 #endif
1400
1401 wxTimeSpan span = date - ldcm;
1402
1403 int daysfromlast = span.GetDays();
1404 #if DEBUG_PAINT
1405 wxLogDebug("daysfromlast: %i", daysfromlast);
1406 #endif
1407 if ( daysfromlast + lastday > 7 ) // past week boundary
1408 {
1409 int wholeweeks = (daysfromlast / 7);
1410 *week = wholeweeks + lastweek;
1411 if ( (daysfromlast - (7 * wholeweeks) + lastday) > 7 )
1412 {
1413 *week += 1;
1414 }
1415 }
1416 else
1417 {
1418 *week = lastweek;
1419 }
1420 }
1421 }
1422 }
1423 else
1424 {
1425 *day = -1;
1426 *week = -1;
1427 retval = false;
1428 }
1429
1430 #if DEBUG_PAINT
1431 wxLogDebug("--- GetDateCoord: (%s) = (%i, %i) ---", date.Format("%d %m %Y"), *day, *week);
1432 #endif
1433
1434 return retval;
1435 }
1436
1437 // ----------------------------------------------------------------------------
1438 // mouse handling
1439 // ----------------------------------------------------------------------------
1440
1441 void wxCalendarCtrl::OnDClick(wxMouseEvent& event)
1442 {
1443 if ( HitTest(event.GetPosition()) != wxCAL_HITTEST_DAY )
1444 {
1445 event.Skip();
1446 }
1447 else
1448 {
1449 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
1450 }
1451 }
1452
1453 void wxCalendarCtrl::OnClick(wxMouseEvent& event)
1454 {
1455 wxDateTime date;
1456 wxDateTime::WeekDay wday;
1457 switch ( HitTest(event.GetPosition(), &date, &wday) )
1458 {
1459 case wxCAL_HITTEST_DAY:
1460 if ( IsDateInRange(date) )
1461 {
1462 ChangeDay(date);
1463
1464 GenerateEvents(wxEVT_CALENDAR_DAY_CHANGED,
1465 wxEVT_CALENDAR_SEL_CHANGED);
1466 }
1467 break;
1468
1469 case wxCAL_HITTEST_HEADER:
1470 {
1471 wxCalendarEvent event(this, wxEVT_CALENDAR_WEEKDAY_CLICKED);
1472 event.m_wday = wday;
1473 (void)GetEventHandler()->ProcessEvent(event);
1474 }
1475 break;
1476
1477 case wxCAL_HITTEST_DECMONTH:
1478 case wxCAL_HITTEST_INCMONTH:
1479 case wxCAL_HITTEST_SURROUNDING_WEEK:
1480 SetDateAndNotify(date); // we probably only want to refresh the control. No notification.. (maybe as an option?)
1481 break;
1482
1483 default:
1484 wxFAIL_MSG(_T("unknown hittest code"));
1485 // fall through
1486
1487 case wxCAL_HITTEST_NOWHERE:
1488 event.Skip();
1489 break;
1490 }
1491 }
1492
1493 wxCalendarHitTestResult wxCalendarCtrl::HitTest(const wxPoint& pos,
1494 wxDateTime *date,
1495 wxDateTime::WeekDay *wd)
1496 {
1497 RecalcGeometry();
1498
1499 wxCoord y = pos.y;
1500
1501 ///////////////////////////////////////////////////////////////////////////////////////////////////////
1502 if ( (GetWindowStyle() & wxCAL_SEQUENTIAL_MONTH_SELECTION) )
1503 {
1504 // Header: month
1505
1506 // we need to find out if the hit is on left arrow, on month or on right arrow
1507 // left arrow?
1508 if ( wxRegion(m_leftArrowRect).Contains(pos) == wxInRegion )
1509 {
1510 if ( date )
1511 {
1512 if ( IsDateInRange(m_date - wxDateSpan::Month()) )
1513 {
1514 *date = m_date - wxDateSpan::Month();
1515 }
1516 else
1517 {
1518 *date = GetLowerDateLimit();
1519 }
1520 }
1521
1522 return wxCAL_HITTEST_DECMONTH;
1523 }
1524
1525 if ( wxRegion(m_rightArrowRect).Contains(pos) == wxInRegion )
1526 {
1527 if ( date )
1528 {
1529 if ( IsDateInRange(m_date + wxDateSpan::Month()) )
1530 {
1531 *date = m_date + wxDateSpan::Month();
1532 }
1533 else
1534 {
1535 *date = GetUpperDateLimit();
1536 }
1537 }
1538
1539 return wxCAL_HITTEST_INCMONTH;
1540 }
1541
1542 }
1543
1544 ///////////////////////////////////////////////////////////////////////////////////////////////////////
1545 // Header: Days
1546 int wday = pos.x / m_widthCol;
1547 // if ( y < m_heightRow )
1548 if ( y < (m_heightRow + m_rowOffset) )
1549 {
1550 if ( y > m_rowOffset )
1551 {
1552 if ( wd )
1553 {
1554 if ( GetWindowStyle() & wxCAL_MONDAY_FIRST )
1555 {
1556 wday = wday == 6 ? 0 : wday + 1;
1557 }
1558
1559 *wd = (wxDateTime::WeekDay)wday;
1560 }
1561
1562 return wxCAL_HITTEST_HEADER;
1563 }
1564 else
1565 {
1566 return wxCAL_HITTEST_NOWHERE;
1567 }
1568 }
1569
1570 // int week = (y - m_heightRow) / m_heightRow;
1571 int week = (y - (m_heightRow + m_rowOffset)) / m_heightRow;
1572 if ( week >= 6 || wday >= 7 )
1573 {
1574 return wxCAL_HITTEST_NOWHERE;
1575 }
1576
1577 wxDateTime dt = GetStartDate() + wxDateSpan::Days(7*week + wday);
1578
1579 if ( IsDateShown(dt) )
1580 {
1581 if ( date )
1582 *date = dt;
1583
1584 if ( dt.GetMonth() == m_date.GetMonth() )
1585 {
1586
1587 return wxCAL_HITTEST_DAY;
1588 }
1589 else
1590 {
1591 return wxCAL_HITTEST_SURROUNDING_WEEK;
1592 }
1593 }
1594 else
1595 {
1596 return wxCAL_HITTEST_NOWHERE;
1597 }
1598 }
1599
1600 // ----------------------------------------------------------------------------
1601 // subcontrols events handling
1602 // ----------------------------------------------------------------------------
1603
1604 void wxCalendarCtrl::OnMonthChange(wxCommandEvent& event)
1605 {
1606 wxDateTime::Tm tm = m_date.GetTm();
1607
1608 wxDateTime::Month mon = (wxDateTime::Month)event.GetInt();
1609 if ( tm.mday > wxDateTime::GetNumberOfDays(mon, tm.year) )
1610 {
1611 tm.mday = wxDateTime::GetNumberOfDays(mon, tm.year);
1612 }
1613
1614 wxDateTime target = wxDateTime(tm.mday, mon, tm.year);
1615
1616 ChangeMonth(&target);
1617 SetDateAndNotify(target);
1618 }
1619
1620 void wxCalendarCtrl::OnYearChange(wxCommandEvent& event)
1621 {
1622 int year = (int)event.GetInt();
1623 if ( year == INT_MIN )
1624 {
1625 // invalid year in the spin control, ignore it
1626 return;
1627 }
1628
1629 wxDateTime::Tm tm = m_date.GetTm();
1630
1631 if ( tm.mday > wxDateTime::GetNumberOfDays(tm.mon, year) )
1632 {
1633 tm.mday = wxDateTime::GetNumberOfDays(tm.mon, year);
1634 }
1635
1636 wxDateTime target = wxDateTime(tm.mday, tm.mon, year);
1637
1638 if ( ChangeYear(&target) )
1639 {
1640 SetDateAndNotify(target);
1641 }
1642 else
1643 {
1644 // In this case we don't want to change the date. That would put us
1645 // inside the same year but a strange number of months forward/back..
1646 m_spinYear->SetValue(target.GetYear());
1647 }
1648 }
1649
1650 // ----------------------------------------------------------------------------
1651 // keyboard interface
1652 // ----------------------------------------------------------------------------
1653
1654 void wxCalendarCtrl::OnChar(wxKeyEvent& event)
1655 {
1656 wxDateTime target;
1657 switch ( event.GetKeyCode() )
1658 {
1659 case _T('+'):
1660 case WXK_ADD:
1661 target = m_date + wxDateSpan::Year();
1662 if ( ChangeYear(&target) )
1663 {
1664 SetDateAndNotify(target);
1665 }
1666 break;
1667
1668 case _T('-'):
1669 case WXK_SUBTRACT:
1670 target = m_date - wxDateSpan::Year();
1671 if ( ChangeYear(&target) )
1672 {
1673 SetDateAndNotify(target);
1674 }
1675 break;
1676
1677 case WXK_PRIOR:
1678 target = m_date - wxDateSpan::Month();
1679 ChangeMonth(&target);
1680 SetDateAndNotify(target); // always
1681 break;
1682
1683 case WXK_NEXT:
1684 target = m_date + wxDateSpan::Month();
1685 ChangeMonth(&target);
1686 SetDateAndNotify(target); // always
1687 break;
1688
1689 case WXK_RIGHT:
1690 if ( event.ControlDown() )
1691 {
1692 target = wxDateTime(m_date).SetToNextWeekDay(
1693 GetWindowStyle() & wxCAL_MONDAY_FIRST
1694 ? wxDateTime::Sun : wxDateTime::Sat);
1695 if ( !IsDateInRange(target) )
1696 {
1697 target = GetUpperDateLimit();
1698 }
1699 SetDateAndNotify(target);
1700 }
1701 else
1702 SetDateAndNotify(m_date + wxDateSpan::Day());
1703 break;
1704
1705 case WXK_LEFT:
1706 if ( event.ControlDown() )
1707 {
1708 target = wxDateTime(m_date).SetToPrevWeekDay(
1709 GetWindowStyle() & wxCAL_MONDAY_FIRST
1710 ? wxDateTime::Mon : wxDateTime::Sun);
1711 if ( !IsDateInRange(target) )
1712 {
1713 target = GetLowerDateLimit();
1714 }
1715 SetDateAndNotify(target);
1716 }
1717 else
1718 SetDateAndNotify(m_date - wxDateSpan::Day());
1719 break;
1720
1721 case WXK_UP:
1722 SetDateAndNotify(m_date - wxDateSpan::Week());
1723 break;
1724
1725 case WXK_DOWN:
1726 SetDateAndNotify(m_date + wxDateSpan::Week());
1727 break;
1728
1729 case WXK_HOME:
1730 if ( event.ControlDown() )
1731 SetDateAndNotify(wxDateTime::Today());
1732 else
1733 SetDateAndNotify(wxDateTime(1, m_date.GetMonth(), m_date.GetYear()));
1734 break;
1735
1736 case WXK_END:
1737 SetDateAndNotify(wxDateTime(m_date).SetToLastMonthDay());
1738 break;
1739
1740 case WXK_RETURN:
1741 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
1742 break;
1743
1744 default:
1745 event.Skip();
1746 }
1747 }
1748
1749 // ----------------------------------------------------------------------------
1750 // holidays handling
1751 // ----------------------------------------------------------------------------
1752
1753 void wxCalendarCtrl::EnableHolidayDisplay(bool display)
1754 {
1755 long style = GetWindowStyle();
1756 if ( display )
1757 style |= wxCAL_SHOW_HOLIDAYS;
1758 else
1759 style &= ~wxCAL_SHOW_HOLIDAYS;
1760
1761 SetWindowStyle(style);
1762
1763 if ( display )
1764 SetHolidayAttrs();
1765 else
1766 ResetHolidayAttrs();
1767
1768 Refresh();
1769 }
1770
1771 void wxCalendarCtrl::SetHolidayAttrs()
1772 {
1773 if ( GetWindowStyle() & wxCAL_SHOW_HOLIDAYS )
1774 {
1775 ResetHolidayAttrs();
1776
1777 wxDateTime::Tm tm = m_date.GetTm();
1778 wxDateTime dtStart(1, tm.mon, tm.year),
1779 dtEnd = dtStart.GetLastMonthDay();
1780
1781 wxDateTimeArray hol;
1782 wxDateTimeHolidayAuthority::GetHolidaysInRange(dtStart, dtEnd, hol);
1783
1784 size_t count = hol.GetCount();
1785 for ( size_t n = 0; n < count; n++ )
1786 {
1787 SetHoliday(hol[n].GetDay());
1788 }
1789 }
1790 }
1791
1792 void wxCalendarCtrl::SetHoliday(size_t day)
1793 {
1794 wxCHECK_RET( day > 0 && day < 32, _T("invalid day in SetHoliday") );
1795
1796 wxCalendarDateAttr *attr = GetAttr(day);
1797 if ( !attr )
1798 {
1799 attr = new wxCalendarDateAttr;
1800 }
1801
1802 attr->SetHoliday(true);
1803
1804 // can't use SetAttr() because it would delete this pointer
1805 m_attrs[day - 1] = attr;
1806 }
1807
1808 void wxCalendarCtrl::ResetHolidayAttrs()
1809 {
1810 for ( size_t day = 0; day < 31; day++ )
1811 {
1812 if ( m_attrs[day] )
1813 {
1814 m_attrs[day]->SetHoliday(false);
1815 }
1816 }
1817 }
1818
1819
1820 //static
1821 wxVisualAttributes
1822 wxCalendarCtrl::GetClassDefaultAttributes(wxWindowVariant variant)
1823 {
1824 // Use the same color scheme as wxListBox
1825 return wxListBox::GetClassDefaultAttributes(variant);
1826 }
1827
1828 #endif // wxUSE_CALENDARCTRL
1829