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