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