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