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