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