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