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