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