added native GTK implementation of wxCalendarCtrl (modified patch 1925439)
[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 // ----------------------------------------------------------------------------
643 // date helpers
644 // ----------------------------------------------------------------------------
645
646 wxDateTime wxGenericCalendarCtrl::GetStartDate() const
647 {
648 wxDateTime::Tm tm = m_date.GetTm();
649
650 wxDateTime date = wxDateTime(1, tm.mon, tm.year);
651
652 // rewind back
653 date.SetToPrevWeekDay(GetWindowStyle() & wxCAL_MONDAY_FIRST
654 ? wxDateTime::Mon : wxDateTime::Sun);
655
656 if ( GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS )
657 {
658 // We want to offset the calendar if we start on the first..
659 if ( date.GetDay() == 1 )
660 {
661 date -= wxDateSpan::Week();
662 }
663 }
664
665 return date;
666 }
667
668 bool wxGenericCalendarCtrl::IsDateShown(const wxDateTime& date) const
669 {
670 if ( !(GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS) )
671 {
672 return date.GetMonth() == m_date.GetMonth();
673 }
674 else
675 {
676 return true;
677 }
678 }
679
680 bool wxGenericCalendarCtrl::IsDateInRange(const wxDateTime& date) const
681 {
682 // Check if the given date is in the range specified
683 return ( ( ( m_lowdate.IsValid() ) ? ( date >= m_lowdate ) : true )
684 && ( ( m_highdate.IsValid() ) ? ( date <= m_highdate ) : true ) );
685 }
686
687 bool wxGenericCalendarCtrl::ChangeYear(wxDateTime* target) const
688 {
689 bool retval = false;
690
691 if ( !(IsDateInRange(*target)) )
692 {
693 if ( target->GetYear() < m_date.GetYear() )
694 {
695 if ( target->GetYear() >= GetLowerDateLimit().GetYear() )
696 {
697 *target = GetLowerDateLimit();
698 retval = true;
699 }
700 else
701 {
702 *target = m_date;
703 }
704 }
705 else
706 {
707 if ( target->GetYear() <= GetUpperDateLimit().GetYear() )
708 {
709 *target = GetUpperDateLimit();
710 retval = true;
711 }
712 else
713 {
714 *target = m_date;
715 }
716 }
717 }
718 else
719 {
720 retval = true;
721 }
722
723 return retval;
724 }
725
726 bool wxGenericCalendarCtrl::ChangeMonth(wxDateTime* target) const
727 {
728 bool retval = true;
729
730 if ( !(IsDateInRange(*target)) )
731 {
732 retval = false;
733
734 if ( target->GetMonth() < m_date.GetMonth() )
735 {
736 *target = GetLowerDateLimit();
737 }
738 else
739 {
740 *target = GetUpperDateLimit();
741 }
742 }
743
744 return retval;
745 }
746
747 size_t wxGenericCalendarCtrl::GetWeek(const wxDateTime& date) const
748 {
749 size_t retval = date.GetWeekOfMonth(GetWindowStyle() & wxCAL_MONDAY_FIRST
750 ? wxDateTime::Monday_First
751 : wxDateTime::Sunday_First);
752
753 if ( (GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS) )
754 {
755 // we need to offset an extra week if we "start" on the 1st of the month
756 wxDateTime::Tm tm = date.GetTm();
757
758 wxDateTime datetest = wxDateTime(1, tm.mon, tm.year);
759
760 // rewind back
761 datetest.SetToPrevWeekDay(GetWindowStyle() & wxCAL_MONDAY_FIRST
762 ? wxDateTime::Mon : wxDateTime::Sun);
763
764 if ( datetest.GetDay() == 1 )
765 {
766 retval += 1;
767 }
768 }
769
770 return retval;
771 }
772
773 // ----------------------------------------------------------------------------
774 // size management
775 // ----------------------------------------------------------------------------
776
777 // this is a composite control and it must arrange its parts each time its
778 // size or position changes: the combobox and spinctrl are along the top of
779 // the available area and the calendar takes up therest of the space
780
781 // the static controls are supposed to be always smaller than combo/spin so we
782 // always use the latter for size calculations and position the static to take
783 // the same space
784
785 // the constants used for the layout
786 #define VERT_MARGIN 5 // distance between combo and calendar
787 #ifdef __WXMAC__
788 #define HORZ_MARGIN 5 // spin
789 #else
790 #define HORZ_MARGIN 15 // spin
791 #endif
792 wxSize wxGenericCalendarCtrl::DoGetBestSize() const
793 {
794 // calc the size of the calendar
795 wx_const_cast(wxGenericCalendarCtrl *, this)->RecalcGeometry();
796
797 wxCoord width = 7*m_widthCol,
798 height = 7*m_heightRow + m_rowOffset + VERT_MARGIN;
799
800 if ( !HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
801 {
802 // the combobox doesn't report its height correctly (it returns the
803 // height including the drop down list) so don't use it
804 height += m_spinYear->GetBestSize().y;
805
806
807 wxCoord w2 = m_comboMonth->GetBestSize().x + HORZ_MARGIN + GetCharWidth()*6;
808 if (width < w2)
809 width = w2;
810 }
811
812 if ( !HasFlag(wxBORDER_NONE) )
813 {
814 // the border would clip the last line otherwise
815 height += 6;
816 width += 4;
817 }
818
819 wxSize best(width, height);
820 CacheBestSize(best);
821 return best;
822 }
823
824 void wxGenericCalendarCtrl::DoSetSize(int x, int y,
825 int width, int height,
826 int sizeFlags)
827 {
828 wxControl::DoSetSize(x, y, width, height, sizeFlags);
829 }
830
831 void wxGenericCalendarCtrl::DoMoveWindow(int x, int y, int width, int height)
832 {
833 int yDiff;
834
835 if ( !HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) && m_staticMonth )
836 {
837 wxSize sizeCombo = m_comboMonth->GetEffectiveMinSize();
838 wxSize sizeStatic = m_staticMonth->GetSize();
839 wxSize sizeSpin = m_spinYear->GetSize();
840
841 // wxMSW sometimes reports the wrong combo height,
842 // so on this platform we'll use the spin control
843 // height instead.
844 #ifdef __WXMSW__
845 int maxHeight = sizeSpin.y;
846 int requiredSpinHeight = -1;
847 #else
848 int maxHeight = sizeCombo.y;
849 int requiredSpinHeight = sizeCombo.y;
850 #endif
851 int dy = (maxHeight - sizeStatic.y) / 2;
852 m_comboMonth->Move(x, y);
853 m_staticMonth->SetSize(x, y + dy, sizeCombo.x, -1, sizeStatic.y);
854
855 int xDiff = sizeCombo.x + HORZ_MARGIN;
856
857 m_spinYear->SetSize(x + xDiff, y, width - xDiff, requiredSpinHeight);
858 m_staticYear->SetSize(x + xDiff, y + dy, width - xDiff, sizeStatic.y);
859
860 yDiff = wxMax(sizeSpin.y, maxHeight) + VERT_MARGIN;
861 }
862 else // no controls on the top
863 {
864 yDiff = 0;
865 }
866
867 wxControl::DoMoveWindow(x, y + yDiff, width, height - yDiff);
868 }
869
870 void wxGenericCalendarCtrl::DoGetPosition(int *x, int *y) const
871 {
872 wxControl::DoGetPosition(x, y);
873 #ifndef __WXPM__
874 if ( !HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) && GetMonthControl() )
875 {
876 // our real top corner is not in this position
877 if ( y )
878 {
879 *y -= GetMonthControl()->GetSize().y + VERT_MARGIN;
880 }
881 }
882 #endif
883 }
884
885 void wxGenericCalendarCtrl::DoGetSize(int *width, int *height) const
886 {
887 wxControl::DoGetSize(width, height);
888 #ifndef __WXPM__
889 if ( !HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
890 {
891 // our real height is bigger
892 if ( height && GetMonthControl())
893 {
894 *height += GetMonthControl()->GetSize().y + VERT_MARGIN;
895 }
896 }
897 #endif
898 }
899
900 void wxGenericCalendarCtrl::RecalcGeometry()
901 {
902 wxClientDC dc(this);
903
904 dc.SetFont(GetFont());
905
906 // determine the column width (weekday names are not necessarily wider
907 // than the numbers (in some languages), so let's not assume that they are)
908 m_widthCol = 0;
909 for ( int day = 10; day <= 31; day++)
910 {
911 wxCoord width;
912 dc.GetTextExtent(wxString::Format(wxT("%d"), day), &width, &m_heightRow);
913 if ( width > m_widthCol )
914 {
915 // 1.5 times the width gives nice margins even if the weekday
916 // names are short
917 m_widthCol = width+width/2;
918 }
919 }
920 wxDateTime::WeekDay wd;
921 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
922 {
923 wxCoord width;
924 dc.GetTextExtent(m_weekdays[wd], &width, &m_heightRow);
925 if ( width > m_widthCol )
926 {
927 m_widthCol = width;
928 }
929 }
930
931 // leave some margins
932 m_widthCol += 2;
933 m_heightRow += 2;
934
935 m_rowOffset = HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) ? m_heightRow : 0; // conditional in relation to style
936 }
937
938 // ----------------------------------------------------------------------------
939 // drawing
940 // ----------------------------------------------------------------------------
941
942 void wxGenericCalendarCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
943 {
944 wxPaintDC dc(this);
945
946 dc.SetFont(GetFont());
947
948 RecalcGeometry();
949
950 #if DEBUG_PAINT
951 wxLogDebug("--- starting to paint, selection: %s, week %u\n",
952 m_date.Format("%a %d-%m-%Y %H:%M:%S").c_str(),
953 GetWeek(m_date));
954 #endif
955
956 wxCoord y = 0;
957 wxCoord x0 = wxMax( (GetSize().x - m_widthCol*7) /2 , 0 );
958
959 if ( HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
960 {
961 // draw the sequential month-selector
962
963 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
964 dc.SetTextForeground(*wxBLACK);
965 dc.SetBrush(wxBrush(m_colHeaderBg, wxBRUSHSTYLE_SOLID));
966 dc.SetPen(wxPen(m_colHeaderBg, 1, wxPENSTYLE_SOLID));
967 dc.DrawRectangle(0, y, GetClientSize().x, m_heightRow);
968
969 // Get extent of month-name + year
970 wxCoord monthw, monthh;
971 wxString headertext = m_date.Format(wxT("%B %Y"));
972 dc.GetTextExtent(headertext, &monthw, &monthh);
973
974 // draw month-name centered above weekdays
975 wxCoord monthx = ((m_widthCol * 7) - monthw) / 2 + x0;
976 wxCoord monthy = ((m_heightRow - monthh) / 2) + y;
977 dc.DrawText(headertext, monthx, monthy);
978
979 // calculate the "month-arrows"
980 wxPoint leftarrow[3];
981 wxPoint rightarrow[3];
982
983 int arrowheight = monthh / 2;
984
985 leftarrow[0] = wxPoint(0, arrowheight / 2);
986 leftarrow[1] = wxPoint(arrowheight / 2, 0);
987 leftarrow[2] = wxPoint(arrowheight / 2, arrowheight - 1);
988
989 rightarrow[0] = wxPoint(0,0);
990 rightarrow[1] = wxPoint(arrowheight / 2, arrowheight / 2);
991 rightarrow[2] = wxPoint(0, arrowheight - 1);
992
993 // draw the "month-arrows"
994
995 wxCoord arrowy = (m_heightRow - arrowheight) / 2;
996 wxCoord larrowx = (m_widthCol - (arrowheight / 2)) / 2 + x0;
997 wxCoord rarrowx = ((m_widthCol - (arrowheight / 2)) / 2) + m_widthCol*6 + x0;
998 m_leftArrowRect = m_rightArrowRect = wxRect(0,0,0,0);
999
1000 if ( AllowMonthChange() )
1001 {
1002 wxDateTime ldpm = wxDateTime(1,m_date.GetMonth(), m_date.GetYear()) - wxDateSpan::Day(); // last day prev month
1003 // Check if range permits change
1004 if ( IsDateInRange(ldpm) && ( ( ldpm.GetYear() == m_date.GetYear() ) ? true : AllowYearChange() ) )
1005 {
1006 m_leftArrowRect = wxRect(larrowx - 3, arrowy - 3, (arrowheight / 2) + 8, (arrowheight + 6));
1007 dc.SetBrush(*wxBLACK_BRUSH);
1008 dc.SetPen(*wxBLACK_PEN);
1009 dc.DrawPolygon(3, leftarrow, larrowx , arrowy, wxWINDING_RULE);
1010 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1011 dc.DrawRectangle(m_leftArrowRect);
1012 }
1013 wxDateTime fdnm = wxDateTime(1,m_date.GetMonth(), m_date.GetYear()) + wxDateSpan::Month(); // first day next month
1014 if ( IsDateInRange(fdnm) && ( ( fdnm.GetYear() == m_date.GetYear() ) ? true : AllowYearChange() ) )
1015 {
1016 m_rightArrowRect = wxRect(rarrowx - 4, arrowy - 3, (arrowheight / 2) + 8, (arrowheight + 6));
1017 dc.SetBrush(*wxBLACK_BRUSH);
1018 dc.SetPen(*wxBLACK_PEN);
1019 dc.DrawPolygon(3, rightarrow, rarrowx , arrowy, wxWINDING_RULE);
1020 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1021 dc.DrawRectangle(m_rightArrowRect);
1022 }
1023 }
1024
1025 y += m_heightRow;
1026 }
1027
1028 // first draw the week days
1029 if ( IsExposed(x0, y, x0 + 7*m_widthCol, m_heightRow) )
1030 {
1031 #if DEBUG_PAINT
1032 wxLogDebug("painting the header");
1033 #endif
1034
1035 dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
1036 dc.SetTextForeground(m_colHeaderFg);
1037 dc.SetBrush(wxBrush(m_colHeaderBg, wxBRUSHSTYLE_SOLID));
1038 dc.SetPen(wxPen(m_colHeaderBg, 1, wxPENSTYLE_SOLID));
1039 dc.DrawRectangle(0, y, GetClientSize().x, m_heightRow);
1040
1041 bool startOnMonday = (GetWindowStyle() & wxCAL_MONDAY_FIRST) != 0;
1042 for ( int wd = 0; wd < 7; wd++ )
1043 {
1044 size_t n;
1045 if ( startOnMonday )
1046 n = wd == 6 ? 0 : wd + 1;
1047 else
1048 n = wd;
1049 wxCoord dayw, dayh;
1050 dc.GetTextExtent(m_weekdays[n], &dayw, &dayh);
1051 dc.DrawText(m_weekdays[n], x0 + (wd*m_widthCol) + ((m_widthCol- dayw) / 2), y); // center the day-name
1052 }
1053 }
1054
1055 // then the calendar itself
1056 dc.SetTextForeground(*wxBLACK);
1057 //dc.SetFont(*wxNORMAL_FONT);
1058
1059 y += m_heightRow;
1060 wxDateTime date = GetStartDate();
1061
1062 #if DEBUG_PAINT
1063 wxLogDebug("starting calendar from %s\n",
1064 date.Format("%a %d-%m-%Y %H:%M:%S").c_str());
1065 #endif
1066
1067 dc.SetBackgroundMode(wxBRUSHSTYLE_SOLID);
1068 for ( size_t nWeek = 1; nWeek <= 6; nWeek++, y += m_heightRow )
1069 {
1070 // if the update region doesn't intersect this row, don't paint it
1071 if ( !IsExposed(x0, y, x0 + 7*m_widthCol, m_heightRow - 1) )
1072 {
1073 date += wxDateSpan::Week();
1074
1075 continue;
1076 }
1077
1078 #if DEBUG_PAINT
1079 wxLogDebug("painting week %d at y = %d\n", nWeek, y);
1080 #endif
1081
1082 for ( int wd = 0; wd < 7; wd++ )
1083 {
1084 dc.SetTextBackground(m_colBackground);
1085 if ( IsDateShown(date) )
1086 {
1087 // don't use wxDate::Format() which prepends 0s
1088 unsigned int day = date.GetDay();
1089 wxString dayStr = wxString::Format(_T("%u"), day);
1090 wxCoord width;
1091 dc.GetTextExtent(dayStr, &width, (wxCoord *)NULL);
1092
1093 bool changedColours = false,
1094 changedFont = false;
1095
1096 bool isSel = false;
1097 wxCalendarDateAttr *attr = NULL;
1098
1099 if ( date.GetMonth() != m_date.GetMonth() || !IsDateInRange(date) )
1100 {
1101 // surrounding week or out-of-range
1102 // draw "disabled"
1103 dc.SetTextForeground(m_colSorrounding);
1104 changedColours = true;
1105 }
1106 else
1107 {
1108 isSel = date.IsSameDate(m_date);
1109 attr = m_attrs[day - 1];
1110
1111 if ( isSel )
1112 {
1113 dc.SetTextForeground(m_colHighlightFg);
1114 dc.SetTextBackground(m_colHighlightBg);
1115
1116 changedColours = true;
1117 }
1118 else if ( attr )
1119 {
1120 wxColour colFg, colBg;
1121
1122 if ( attr->IsHoliday() )
1123 {
1124 colFg = m_colHolidayFg;
1125 colBg = m_colHolidayBg;
1126 }
1127 else
1128 {
1129 colFg = attr->GetTextColour();
1130 colBg = attr->GetBackgroundColour();
1131 }
1132
1133 if ( colFg.Ok() )
1134 {
1135 dc.SetTextForeground(colFg);
1136 changedColours = true;
1137 }
1138
1139 if ( colBg.Ok() )
1140 {
1141 dc.SetTextBackground(colBg);
1142 changedColours = true;
1143 }
1144
1145 if ( attr->HasFont() )
1146 {
1147 dc.SetFont(attr->GetFont());
1148 changedFont = true;
1149 }
1150 }
1151 }
1152
1153 wxCoord x = wd*m_widthCol + (m_widthCol - width) / 2 + x0;
1154 dc.DrawText(dayStr, x, y + 1);
1155
1156 if ( !isSel && attr && attr->HasBorder() )
1157 {
1158 wxColour colBorder;
1159 if ( attr->HasBorderColour() )
1160 {
1161 colBorder = attr->GetBorderColour();
1162 }
1163 else
1164 {
1165 colBorder = GetForegroundColour();
1166 }
1167
1168 wxPen pen(colBorder, 1, wxPENSTYLE_SOLID);
1169 dc.SetPen(pen);
1170 dc.SetBrush(*wxTRANSPARENT_BRUSH);
1171
1172 switch ( attr->GetBorder() )
1173 {
1174 case wxCAL_BORDER_SQUARE:
1175 dc.DrawRectangle(x - 2, y,
1176 width + 4, m_heightRow);
1177 break;
1178
1179 case wxCAL_BORDER_ROUND:
1180 dc.DrawEllipse(x - 2, y,
1181 width + 4, m_heightRow);
1182 break;
1183
1184 default:
1185 wxFAIL_MSG(_T("unknown border type"));
1186 }
1187 }
1188
1189 if ( changedColours )
1190 {
1191 dc.SetTextForeground(GetForegroundColour());
1192 dc.SetTextBackground(GetBackgroundColour());
1193 }
1194
1195 if ( changedFont )
1196 {
1197 dc.SetFont(GetFont());
1198 }
1199 }
1200 //else: just don't draw it
1201
1202 date += wxDateSpan::Day();
1203 }
1204 }
1205
1206 // Greying out out-of-range background
1207 bool showSurrounding = (GetWindowStyle() & wxCAL_SHOW_SURROUNDING_WEEKS) != 0;
1208
1209 date = ( showSurrounding ) ? GetStartDate() : wxDateTime(1, m_date.GetMonth(), m_date.GetYear());
1210 if ( !IsDateInRange(date) )
1211 {
1212 wxDateTime firstOOR = GetLowerDateLimit() - wxDateSpan::Day(); // first out-of-range
1213
1214 wxBrush oorbrush = *wxLIGHT_GREY_BRUSH;
1215 oorbrush.SetStyle(wxBRUSHSTYLE_FDIAGONAL_HATCH);
1216
1217 HighlightRange(&dc, date, firstOOR, wxTRANSPARENT_PEN, &oorbrush);
1218 }
1219
1220 date = ( showSurrounding ) ? GetStartDate() + wxDateSpan::Weeks(6) - wxDateSpan::Day() : wxDateTime().SetToLastMonthDay(m_date.GetMonth(), m_date.GetYear());
1221 if ( !IsDateInRange(date) )
1222 {
1223 wxDateTime firstOOR = GetUpperDateLimit() + wxDateSpan::Day(); // first out-of-range
1224
1225 wxBrush oorbrush = *wxLIGHT_GREY_BRUSH;
1226 oorbrush.SetStyle(wxBRUSHSTYLE_FDIAGONAL_HATCH);
1227
1228 HighlightRange(&dc, firstOOR, date, wxTRANSPARENT_PEN, &oorbrush);
1229 }
1230
1231 #if DEBUG_PAINT
1232 wxLogDebug("+++ finished painting");
1233 #endif
1234 }
1235
1236 void wxGenericCalendarCtrl::RefreshDate(const wxDateTime& date)
1237 {
1238 RecalcGeometry();
1239
1240 wxRect rect;
1241
1242 // always refresh the whole row at once because our OnPaint() will draw
1243 // the whole row anyhow - and this allows the small optimisation in
1244 // OnClick() below to work
1245 rect.x = wxMax( (GetSize().x - m_widthCol*7) /2 , 0 );
1246
1247 rect.y = (m_heightRow * GetWeek(date)) + m_rowOffset;
1248
1249 rect.width = 7*m_widthCol;
1250 rect.height = m_heightRow;
1251
1252 #ifdef __WXMSW__
1253 // VZ: for some reason, the selected date seems to occupy more space under
1254 // MSW - this is probably some bug in the font size calculations, but I
1255 // don't know where exactly. This fix is ugly and leads to more
1256 // refreshes than really needed, but without it the selected days
1257 // leaves even more ugly underscores on screen.
1258 rect.Inflate(0, 1);
1259 #endif // MSW
1260
1261 #if DEBUG_PAINT
1262 wxLogDebug("*** refreshing week %d at (%d, %d)-(%d, %d)\n",
1263 GetWeek(date),
1264 rect.x, rect.y,
1265 rect.x + rect.width, rect.y + rect.height);
1266 #endif
1267
1268 Refresh(true, &rect);
1269 }
1270
1271 void wxGenericCalendarCtrl::HighlightRange(wxPaintDC* pDC, const wxDateTime& fromdate, const wxDateTime& todate, const wxPen* pPen, const wxBrush* pBrush)
1272 {
1273 // Highlights the given range using pen and brush
1274 // Does nothing if todate < fromdate
1275
1276
1277 #if DEBUG_PAINT
1278 wxLogDebug("+++ HighlightRange: (%s) - (%s) +++", fromdate.Format("%d %m %Y"), todate.Format("%d %m %Y"));
1279 #endif
1280
1281 if ( todate >= fromdate )
1282 {
1283 // do stuff
1284 // date-coordinates
1285 int fd, fw;
1286 int td, tw;
1287
1288 // implicit: both dates must be currently shown - checked by GetDateCoord
1289 if ( GetDateCoord(fromdate, &fd, &fw) && GetDateCoord(todate, &td, &tw) )
1290 {
1291 #if DEBUG_PAINT
1292 wxLogDebug("Highlight range: (%i, %i) - (%i, %i)", fd, fw, td, tw);
1293 #endif
1294 if ( ( (tw - fw) == 1 ) && ( td < fd ) )
1295 {
1296 // special case: interval 7 days or less not in same week
1297 // split in two separate intervals
1298 wxDateTime tfd = fromdate + wxDateSpan::Days(7-fd);
1299 wxDateTime ftd = tfd + wxDateSpan::Day();
1300 #if DEBUG_PAINT
1301 wxLogDebug("Highlight: Separate segments");
1302 #endif
1303 // draw separately
1304 HighlightRange(pDC, fromdate, tfd, pPen, pBrush);
1305 HighlightRange(pDC, ftd, todate, pPen, pBrush);
1306 }
1307 else
1308 {
1309 int numpoints;
1310 wxPoint corners[8]; // potentially 8 corners in polygon
1311 wxCoord x0 = wxMax( (GetSize().x - m_widthCol*7) /2 , 0 );
1312
1313 if ( fw == tw )
1314 {
1315 // simple case: same week
1316 numpoints = 4;
1317 corners[0] = wxPoint(x0 + (fd - 1) * m_widthCol, (fw * m_heightRow) + m_rowOffset);
1318 corners[1] = wxPoint(x0 + (fd - 1) * m_widthCol, ((fw + 1 ) * m_heightRow) + m_rowOffset);
1319 corners[2] = wxPoint(x0 + td * m_widthCol, ((tw + 1) * m_heightRow) + m_rowOffset);
1320 corners[3] = wxPoint(x0 + td * m_widthCol, (tw * m_heightRow) + m_rowOffset);
1321 }
1322 else
1323 {
1324 int cidx = 0;
1325 // "complex" polygon
1326 corners[cidx] = wxPoint(x0 + (fd - 1) * m_widthCol, (fw * m_heightRow) + m_rowOffset); cidx++;
1327
1328 if ( fd > 1 )
1329 {
1330 corners[cidx] = wxPoint(x0 + (fd - 1) * m_widthCol, ((fw + 1) * m_heightRow) + m_rowOffset); cidx++;
1331 corners[cidx] = wxPoint(x0, ((fw + 1) * m_heightRow) + m_rowOffset); cidx++;
1332 }
1333
1334 corners[cidx] = wxPoint(x0, ((tw + 1) * m_heightRow) + m_rowOffset); cidx++;
1335 corners[cidx] = wxPoint(x0 + td * m_widthCol, ((tw + 1) * m_heightRow) + m_rowOffset); cidx++;
1336
1337 if ( td < 7 )
1338 {
1339 corners[cidx] = wxPoint(x0 + td * m_widthCol, (tw * m_heightRow) + m_rowOffset); cidx++;
1340 corners[cidx] = wxPoint(x0 + 7 * m_widthCol, (tw * m_heightRow) + m_rowOffset); cidx++;
1341 }
1342
1343 corners[cidx] = wxPoint(x0 + 7 * m_widthCol, (fw * m_heightRow) + m_rowOffset); cidx++;
1344
1345 numpoints = cidx;
1346 }
1347
1348 // draw the polygon
1349 pDC->SetBrush(*pBrush);
1350 pDC->SetPen(*pPen);
1351 pDC->DrawPolygon(numpoints, corners);
1352 }
1353 }
1354 }
1355 // else do nothing
1356 #if DEBUG_PAINT
1357 wxLogDebug("--- HighlightRange ---");
1358 #endif
1359 }
1360
1361 bool wxGenericCalendarCtrl::GetDateCoord(const wxDateTime& date, int *day, int *week) const
1362 {
1363 bool retval = true;
1364
1365 #if DEBUG_PAINT
1366 wxLogDebug("+++ GetDateCoord: (%s) +++", date.Format("%d %m %Y"));
1367 #endif
1368
1369 if ( IsDateShown(date) )
1370 {
1371 bool startOnMonday = ( GetWindowStyle() & wxCAL_MONDAY_FIRST ) != 0;
1372
1373 // Find day
1374 *day = date.GetWeekDay();
1375
1376 if ( *day == 0 ) // sunday
1377 {
1378 *day = ( startOnMonday ) ? 7 : 1;
1379 }
1380 else
1381 {
1382 *day += ( startOnMonday ) ? 0 : 1;
1383 }
1384
1385 int targetmonth = date.GetMonth() + (12 * date.GetYear());
1386 int thismonth = m_date.GetMonth() + (12 * m_date.GetYear());
1387
1388 // Find week
1389 if ( targetmonth == thismonth )
1390 {
1391 *week = GetWeek(date);
1392 }
1393 else
1394 {
1395 if ( targetmonth < thismonth )
1396 {
1397 *week = 1; // trivial
1398 }
1399 else // targetmonth > thismonth
1400 {
1401 wxDateTime ldcm;
1402 int lastweek;
1403 int lastday;
1404
1405 // get the datecoord of the last day in the month currently shown
1406 #if DEBUG_PAINT
1407 wxLogDebug(" +++ LDOM +++");
1408 #endif
1409 GetDateCoord(ldcm.SetToLastMonthDay(m_date.GetMonth(), m_date.GetYear()), &lastday, &lastweek);
1410 #if DEBUG_PAINT
1411 wxLogDebug(" --- LDOM ---");
1412 #endif
1413
1414 wxTimeSpan span = date - ldcm;
1415
1416 int daysfromlast = span.GetDays();
1417 #if DEBUG_PAINT
1418 wxLogDebug("daysfromlast: %i", daysfromlast);
1419 #endif
1420 if ( daysfromlast + lastday > 7 ) // past week boundary
1421 {
1422 int wholeweeks = (daysfromlast / 7);
1423 *week = wholeweeks + lastweek;
1424 if ( (daysfromlast - (7 * wholeweeks) + lastday) > 7 )
1425 {
1426 *week += 1;
1427 }
1428 }
1429 else
1430 {
1431 *week = lastweek;
1432 }
1433 }
1434 }
1435 }
1436 else
1437 {
1438 *day = -1;
1439 *week = -1;
1440 retval = false;
1441 }
1442
1443 #if DEBUG_PAINT
1444 wxLogDebug("--- GetDateCoord: (%s) = (%i, %i) ---", date.Format("%d %m %Y"), *day, *week);
1445 #endif
1446
1447 return retval;
1448 }
1449
1450 // ----------------------------------------------------------------------------
1451 // mouse handling
1452 // ----------------------------------------------------------------------------
1453
1454 void wxGenericCalendarCtrl::OnDClick(wxMouseEvent& event)
1455 {
1456 if ( HitTest(event.GetPosition()) != wxCAL_HITTEST_DAY )
1457 {
1458 event.Skip();
1459 }
1460 else
1461 {
1462 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
1463 }
1464 }
1465
1466 void wxGenericCalendarCtrl::OnClick(wxMouseEvent& event)
1467 {
1468 wxDateTime date;
1469 wxDateTime::WeekDay wday;
1470 switch ( HitTest(event.GetPosition(), &date, &wday) )
1471 {
1472 case wxCAL_HITTEST_DAY:
1473 if ( IsDateInRange(date) )
1474 {
1475 ChangeDay(date);
1476
1477 GenerateEvent(wxEVT_CALENDAR_SEL_CHANGED);
1478 GenerateEvent(wxEVT_CALENDAR_DAY_CHANGED);
1479 }
1480 break;
1481
1482 case wxCAL_HITTEST_HEADER:
1483 {
1484 wxCalendarEvent eventWd(this, GetDate(),
1485 wxEVT_CALENDAR_WEEKDAY_CLICKED);
1486 eventWd.SetWeekDay(wday);
1487 (void)GetEventHandler()->ProcessEvent(eventWd);
1488 }
1489 break;
1490
1491 case wxCAL_HITTEST_DECMONTH:
1492 case wxCAL_HITTEST_INCMONTH:
1493 case wxCAL_HITTEST_SURROUNDING_WEEK:
1494 SetDateAndNotify(date); // we probably only want to refresh the control. No notification.. (maybe as an option?)
1495 break;
1496
1497 default:
1498 wxFAIL_MSG(_T("unknown hittest code"));
1499 // fall through
1500
1501 case wxCAL_HITTEST_NOWHERE:
1502 event.Skip();
1503 break;
1504 }
1505 }
1506
1507 wxCalendarHitTestResult wxGenericCalendarCtrl::HitTest(const wxPoint& pos,
1508 wxDateTime *date,
1509 wxDateTime::WeekDay *wd)
1510 {
1511 RecalcGeometry();
1512
1513 // the position where the calendar really begins
1514 wxCoord x0 = wxMax((GetSize().x - m_widthCol*7)/2, 0);
1515
1516 if ( HasFlag(wxCAL_SEQUENTIAL_MONTH_SELECTION) )
1517 {
1518 // Header: month
1519
1520 // we need to find out if the hit is on left arrow, on month or on right arrow
1521 // left arrow?
1522 if ( m_leftArrowRect.Contains(pos) )
1523 {
1524 if ( date )
1525 {
1526 if ( IsDateInRange(m_date - wxDateSpan::Month()) )
1527 {
1528 *date = m_date - wxDateSpan::Month();
1529 }
1530 else
1531 {
1532 *date = GetLowerDateLimit();
1533 }
1534 }
1535
1536 return wxCAL_HITTEST_DECMONTH;
1537 }
1538
1539 if ( m_rightArrowRect.Contains(pos) )
1540 {
1541 if ( date )
1542 {
1543 if ( IsDateInRange(m_date + wxDateSpan::Month()) )
1544 {
1545 *date = m_date + wxDateSpan::Month();
1546 }
1547 else
1548 {
1549 *date = GetUpperDateLimit();
1550 }
1551 }
1552
1553 return wxCAL_HITTEST_INCMONTH;
1554 }
1555
1556 }
1557
1558 // header: week days
1559 int wday = (pos.x - x0) / m_widthCol;
1560 if ( pos.y < (m_heightRow + m_rowOffset) )
1561 {
1562 if ( pos.y > m_rowOffset )
1563 {
1564 if ( wd )
1565 {
1566 if ( GetWindowStyle() & wxCAL_MONDAY_FIRST )
1567 {
1568 wday = wday == 6 ? 0 : wday + 1;
1569 }
1570
1571 *wd = (wxDateTime::WeekDay)wday;
1572 }
1573
1574 return wxCAL_HITTEST_HEADER;
1575 }
1576 else
1577 {
1578 return wxCAL_HITTEST_NOWHERE;
1579 }
1580 }
1581
1582 int week = (pos.y - (m_heightRow + m_rowOffset)) / m_heightRow;
1583 if ( week >= 6 || wday >= 7 )
1584 {
1585 return wxCAL_HITTEST_NOWHERE;
1586 }
1587
1588 wxDateTime dt = GetStartDate() + wxDateSpan::Days(7*week + wday);
1589
1590 if ( IsDateShown(dt) )
1591 {
1592 if ( date )
1593 *date = dt;
1594
1595 if ( dt.GetMonth() == m_date.GetMonth() )
1596 {
1597
1598 return wxCAL_HITTEST_DAY;
1599 }
1600 else
1601 {
1602 return wxCAL_HITTEST_SURROUNDING_WEEK;
1603 }
1604 }
1605 else
1606 {
1607 return wxCAL_HITTEST_NOWHERE;
1608 }
1609 }
1610
1611 // ----------------------------------------------------------------------------
1612 // subcontrols events handling
1613 // ----------------------------------------------------------------------------
1614
1615 void wxGenericCalendarCtrl::OnMonthChange(wxCommandEvent& event)
1616 {
1617 wxDateTime::Tm tm = m_date.GetTm();
1618
1619 wxDateTime::Month mon = (wxDateTime::Month)event.GetInt();
1620 if ( tm.mday > wxDateTime::GetNumberOfDays(mon, tm.year) )
1621 {
1622 tm.mday = wxDateTime::GetNumberOfDays(mon, tm.year);
1623 }
1624
1625 wxDateTime target = wxDateTime(tm.mday, mon, tm.year);
1626
1627 ChangeMonth(&target);
1628 SetDateAndNotify(target);
1629 }
1630
1631 void wxGenericCalendarCtrl::OnYearChange(wxCommandEvent& event)
1632 {
1633 int year = (int)event.GetInt();
1634 if ( year == INT_MIN )
1635 {
1636 // invalid year in the spin control, ignore it
1637 return;
1638 }
1639
1640 wxDateTime::Tm tm = m_date.GetTm();
1641
1642 if ( tm.mday > wxDateTime::GetNumberOfDays(tm.mon, year) )
1643 {
1644 tm.mday = wxDateTime::GetNumberOfDays(tm.mon, year);
1645 }
1646
1647 wxDateTime target = wxDateTime(tm.mday, tm.mon, year);
1648
1649 if ( ChangeYear(&target) )
1650 {
1651 SetDateAndNotify(target);
1652 }
1653 else
1654 {
1655 // In this case we don't want to change the date. That would put us
1656 // inside the same year but a strange number of months forward/back..
1657 m_spinYear->SetValue(target.GetYear());
1658 }
1659 }
1660
1661 void wxGenericCalendarCtrl::OnYearTextChange(wxCommandEvent& event)
1662 {
1663 SetUserChangedYear();
1664 OnYearChange(event);
1665 }
1666
1667 // Responds to colour changes, and passes event on to children.
1668 void wxGenericCalendarCtrl::OnSysColourChanged(wxSysColourChangedEvent& event)
1669 {
1670 // reinit colours
1671 InitColours();
1672
1673 // Propagate the event to the children
1674 wxControl::OnSysColourChanged(event);
1675
1676 // Redraw control area
1677 SetBackgroundColour(m_colBackground);
1678 Refresh();
1679 }
1680
1681 // ----------------------------------------------------------------------------
1682 // keyboard interface
1683 // ----------------------------------------------------------------------------
1684
1685 void wxGenericCalendarCtrl::OnChar(wxKeyEvent& event)
1686 {
1687 wxDateTime target;
1688 switch ( event.GetKeyCode() )
1689 {
1690 case _T('+'):
1691 case WXK_ADD:
1692 target = m_date + wxDateSpan::Year();
1693 if ( ChangeYear(&target) )
1694 {
1695 SetDateAndNotify(target);
1696 }
1697 break;
1698
1699 case _T('-'):
1700 case WXK_SUBTRACT:
1701 target = m_date - wxDateSpan::Year();
1702 if ( ChangeYear(&target) )
1703 {
1704 SetDateAndNotify(target);
1705 }
1706 break;
1707
1708 case WXK_PAGEUP:
1709 target = m_date - wxDateSpan::Month();
1710 ChangeMonth(&target);
1711 SetDateAndNotify(target); // always
1712 break;
1713
1714 case WXK_PAGEDOWN:
1715 target = m_date + wxDateSpan::Month();
1716 ChangeMonth(&target);
1717 SetDateAndNotify(target); // always
1718 break;
1719
1720 case WXK_RIGHT:
1721 if ( event.ControlDown() )
1722 {
1723 target = wxDateTime(m_date).SetToNextWeekDay(
1724 GetWindowStyle() & wxCAL_MONDAY_FIRST
1725 ? wxDateTime::Sun : wxDateTime::Sat);
1726 if ( !IsDateInRange(target) )
1727 {
1728 target = GetUpperDateLimit();
1729 }
1730 SetDateAndNotify(target);
1731 }
1732 else
1733 SetDateAndNotify(m_date + wxDateSpan::Day());
1734 break;
1735
1736 case WXK_LEFT:
1737 if ( event.ControlDown() )
1738 {
1739 target = wxDateTime(m_date).SetToPrevWeekDay(
1740 GetWindowStyle() & wxCAL_MONDAY_FIRST
1741 ? wxDateTime::Mon : wxDateTime::Sun);
1742 if ( !IsDateInRange(target) )
1743 {
1744 target = GetLowerDateLimit();
1745 }
1746 SetDateAndNotify(target);
1747 }
1748 else
1749 SetDateAndNotify(m_date - wxDateSpan::Day());
1750 break;
1751
1752 case WXK_UP:
1753 SetDateAndNotify(m_date - wxDateSpan::Week());
1754 break;
1755
1756 case WXK_DOWN:
1757 SetDateAndNotify(m_date + wxDateSpan::Week());
1758 break;
1759
1760 case WXK_HOME:
1761 if ( event.ControlDown() )
1762 SetDateAndNotify(wxDateTime::Today());
1763 else
1764 SetDateAndNotify(wxDateTime(1, m_date.GetMonth(), m_date.GetYear()));
1765 break;
1766
1767 case WXK_END:
1768 SetDateAndNotify(wxDateTime(m_date).SetToLastMonthDay());
1769 break;
1770
1771 case WXK_RETURN:
1772 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
1773 break;
1774
1775 default:
1776 event.Skip();
1777 }
1778 }
1779
1780 // ----------------------------------------------------------------------------
1781 // holidays handling
1782 // ----------------------------------------------------------------------------
1783
1784 void wxGenericCalendarCtrl::EnableHolidayDisplay(bool display)
1785 {
1786 long style = GetWindowStyle();
1787 if ( display )
1788 style |= wxCAL_SHOW_HOLIDAYS;
1789 else
1790 style &= ~wxCAL_SHOW_HOLIDAYS;
1791
1792 SetWindowStyle(style);
1793
1794 if ( display )
1795 SetHolidayAttrs();
1796 else
1797 ResetHolidayAttrs();
1798
1799 Refresh();
1800 }
1801
1802 void wxGenericCalendarCtrl::SetHolidayAttrs()
1803 {
1804 if ( GetWindowStyle() & wxCAL_SHOW_HOLIDAYS )
1805 {
1806 ResetHolidayAttrs();
1807
1808 wxDateTime::Tm tm = m_date.GetTm();
1809 wxDateTime dtStart(1, tm.mon, tm.year),
1810 dtEnd = dtStart.GetLastMonthDay();
1811
1812 wxDateTimeArray hol;
1813 wxDateTimeHolidayAuthority::GetHolidaysInRange(dtStart, dtEnd, hol);
1814
1815 size_t count = hol.GetCount();
1816 for ( size_t n = 0; n < count; n++ )
1817 {
1818 SetHoliday(hol[n].GetDay());
1819 }
1820 }
1821 }
1822
1823 void wxGenericCalendarCtrl::SetHoliday(size_t day)
1824 {
1825 wxCHECK_RET( day > 0 && day < 32, _T("invalid day in SetHoliday") );
1826
1827 wxCalendarDateAttr *attr = GetAttr(day);
1828 if ( !attr )
1829 {
1830 attr = new wxCalendarDateAttr;
1831 }
1832
1833 attr->SetHoliday(true);
1834
1835 // can't use SetAttr() because it would delete this pointer
1836 m_attrs[day - 1] = attr;
1837 }
1838
1839 void wxGenericCalendarCtrl::ResetHolidayAttrs()
1840 {
1841 for ( size_t day = 0; day < 31; day++ )
1842 {
1843 if ( m_attrs[day] )
1844 {
1845 m_attrs[day]->SetHoliday(false);
1846 }
1847 }
1848 }
1849
1850 void wxGenericCalendarCtrl::Mark(size_t day, bool mark)
1851 {
1852 wxCHECK_RET( day > 0 && day < 32, _T("invalid day in Mark") );
1853
1854 const wxCalendarDateAttr& m = wxCalendarDateAttr::GetMark();
1855 if (mark) {
1856 if ( m_attrs[day - 1] )
1857 AddAttr(m_attrs[day - 1], m);
1858 else
1859 SetAttr(day, new wxCalendarDateAttr(m));
1860 }
1861 else {
1862 if ( m_attrs[day - 1] )
1863 DelAttr(m_attrs[day - 1], m);
1864 }
1865 }
1866
1867 //static
1868 wxVisualAttributes
1869 wxGenericCalendarCtrl::GetClassDefaultAttributes(wxWindowVariant variant)
1870 {
1871 // Use the same color scheme as wxListBox
1872 return wxListBox::GetClassDefaultAttributes(variant);
1873 }
1874
1875 #endif // wxUSE_CALENDARCTRL