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