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