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