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