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