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