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