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