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