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