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