]> git.saurik.com Git - wxWidgets.git/blame - src/generic/calctrl.cpp
use MoreFiles under Mac OS X
[wxWidgets.git] / src / generic / calctrl.cpp
CommitLineData
2ef31e80
VZ
1///////////////////////////////////////////////////////////////////////////////
2// Name: generic/calctrl.cpp
3// Purpose: implementation fo the generic wxCalendarCtrl
4// Author: Vadim Zeitlin
5// Modified by:
6// Created: 29.12.99
7// RCS-ID: $Id$
8// Copyright: (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9// Licence: wxWindows license
10///////////////////////////////////////////////////////////////////////////////
11
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
20#ifdef __GNUG__
21 #pragma implementation "calctrl.h"
22#endif
23
24// For compilers that support precompilation, includes "wx.h".
25#include "wx/wxprec.h"
26
27#ifdef __BORLANDC__
28 #pragma hdrstop
29#endif
30
31#ifndef WX_PRECOMP
9d9b7755
VZ
32 #include "wx/dcclient.h"
33 #include "wx/settings.h"
34 #include "wx/brush.h"
4f6aed9c 35 #include "wx/combobox.h"
bc385ba9 36 #include "wx/stattext.h"
2ef31e80
VZ
37#endif //WX_PRECOMP
38
1e6feb95 39#if wxUSE_CALENDARCTRL
2fa7c206 40
9d9b7755 41#include "wx/calctrl.h"
2ef31e80 42
882a8f40
VZ
43#define DEBUG_PAINT 0
44
45// ----------------------------------------------------------------------------
46// private classes
47// ----------------------------------------------------------------------------
48
49class wxMonthComboBox : public wxComboBox
50{
51public:
52 wxMonthComboBox(wxCalendarCtrl *cal);
53
54 void OnMonthChange(wxCommandEvent& event) { m_cal->OnMonthChange(event); }
55
56private:
57 wxCalendarCtrl *m_cal;
58
59 DECLARE_EVENT_TABLE()
60};
61
62class wxYearSpinCtrl : public wxSpinCtrl
63{
64public:
65 wxYearSpinCtrl(wxCalendarCtrl *cal);
66
67 void OnYearChange(wxSpinEvent& event) { m_cal->OnYearChange(event); }
68
69private:
70 wxCalendarCtrl *m_cal;
71
72 DECLARE_EVENT_TABLE()
73};
74
2ef31e80
VZ
75// ----------------------------------------------------------------------------
76// wxWin macros
77// ----------------------------------------------------------------------------
78
79BEGIN_EVENT_TABLE(wxCalendarCtrl, wxControl)
80 EVT_PAINT(wxCalendarCtrl::OnPaint)
81
9d9b7755
VZ
82 EVT_CHAR(wxCalendarCtrl::OnChar)
83
2ef31e80 84 EVT_LEFT_DOWN(wxCalendarCtrl::OnClick)
0185cd09 85 EVT_LEFT_DCLICK(wxCalendarCtrl::OnDClick)
882a8f40
VZ
86END_EVENT_TABLE()
87
88BEGIN_EVENT_TABLE(wxMonthComboBox, wxComboBox)
89 EVT_COMBOBOX(-1, wxMonthComboBox::OnMonthChange)
90END_EVENT_TABLE()
9d9b7755 91
882a8f40
VZ
92BEGIN_EVENT_TABLE(wxYearSpinCtrl, wxSpinCtrl)
93 EVT_SPINCTRL(-1, wxYearSpinCtrl::OnYearChange)
2ef31e80
VZ
94END_EVENT_TABLE()
95
96IMPLEMENT_DYNAMIC_CLASS(wxCalendarCtrl, wxControl)
f6bcfd97 97IMPLEMENT_DYNAMIC_CLASS(wxCalendarEvent, wxCommandEvent)
2ef31e80 98
2e4df4bf
VZ
99// ----------------------------------------------------------------------------
100// events
101// ----------------------------------------------------------------------------
102
103DEFINE_EVENT_TYPE(wxEVT_CALENDAR_SEL_CHANGED)
104DEFINE_EVENT_TYPE(wxEVT_CALENDAR_DAY_CHANGED)
105DEFINE_EVENT_TYPE(wxEVT_CALENDAR_MONTH_CHANGED)
106DEFINE_EVENT_TYPE(wxEVT_CALENDAR_YEAR_CHANGED)
107DEFINE_EVENT_TYPE(wxEVT_CALENDAR_DOUBLECLICKED)
108DEFINE_EVENT_TYPE(wxEVT_CALENDAR_WEEKDAY_CLICKED)
109
2ef31e80
VZ
110// ============================================================================
111// implementation
112// ============================================================================
113
882a8f40
VZ
114// ----------------------------------------------------------------------------
115// wxMonthComboBox and wxYearSpinCtrl
116// ----------------------------------------------------------------------------
117
118wxMonthComboBox::wxMonthComboBox(wxCalendarCtrl *cal)
119 : wxComboBox(cal->GetParent(), -1,
120 wxEmptyString,
121 wxDefaultPosition,
122 wxDefaultSize,
123 0, NULL,
124 wxCB_READONLY)
125{
126 m_cal = cal;
127
128 wxDateTime::Month m;
129 for ( m = wxDateTime::Jan; m < wxDateTime::Inv_Month; wxNextMonth(m) )
130 {
131 Append(wxDateTime::GetMonthName(m));
132 }
133
134 SetSelection(m_cal->GetDate().GetMonth());
f6bcfd97 135 SetSize(-1, -1, -1, -1, wxSIZE_AUTO_WIDTH|wxSIZE_AUTO_HEIGHT);
882a8f40
VZ
136}
137
138wxYearSpinCtrl::wxYearSpinCtrl(wxCalendarCtrl *cal)
139 : wxSpinCtrl(cal->GetParent(), -1,
140 cal->GetDate().Format(_T("%Y")),
141 wxDefaultPosition,
142 wxDefaultSize,
143 wxSP_ARROW_KEYS,
144 -4300, 10000, cal->GetDate().GetYear())
145{
146 m_cal = cal;
147}
148
2ef31e80
VZ
149// ----------------------------------------------------------------------------
150// wxCalendarCtrl
151// ----------------------------------------------------------------------------
152
153void wxCalendarCtrl::Init()
154{
155 m_comboMonth = NULL;
156 m_spinYear = NULL;
157
158 m_widthCol =
159 m_heightRow = 0;
9d9b7755
VZ
160
161 wxDateTime::WeekDay wd;
162 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
163 {
164 m_weekdays[wd] = wxDateTime::GetWeekDayName(wd, wxDateTime::Name_Abbr);
165 }
3965571c 166
4f6aed9c
VZ
167 for ( size_t n = 0; n < WXSIZEOF(m_attrs); n++ )
168 {
169 m_attrs[n] = NULL;
170 }
171
172 wxSystemSettings ss;
173 m_colHighlightFg = ss.GetSystemColour(wxSYS_COLOUR_HIGHLIGHTTEXT);
174 m_colHighlightBg = ss.GetSystemColour(wxSYS_COLOUR_HIGHLIGHT);
175
176 m_colHolidayFg = *wxRED;
177 // don't set m_colHolidayBg - by default, same as our bg colour
178
179 m_colHeaderFg = *wxBLUE;
180 m_colHeaderBg = *wxLIGHT_GREY;
2ef31e80
VZ
181}
182
4965c3d7
VZ
183bool wxCalendarCtrl::Create(wxWindow *parent,
184 wxWindowID id,
2ef31e80 185 const wxDateTime& date,
4965c3d7 186 const wxPoint& pos,
2ef31e80
VZ
187 const wxSize& size,
188 long style,
4965c3d7 189 const wxString& name)
2ef31e80 190{
4965c3d7
VZ
191 if ( !wxControl::Create(parent, id, pos, size,
192 style | wxWANTS_CHARS, wxDefaultValidator, name) )
193 {
194 return FALSE;
195 }
196
bc385ba9
VZ
197 // needed to get the arrow keys normally used for the dialog navigation
198 SetWindowStyle(style | wxWANTS_CHARS);
2ef31e80 199
882a8f40 200 m_date = date.IsValid() ? date : wxDateTime::Today();
9d9b7755 201
882a8f40 202 m_spinYear = new wxYearSpinCtrl(this);
bc385ba9
VZ
203 m_staticYear = new wxStaticText(GetParent(), -1, m_date.Format(_T("%Y")),
204 wxDefaultPosition, wxDefaultSize,
205 wxALIGN_CENTRE);
206
207 m_comboMonth = new wxMonthComboBox(this);
208 m_staticMonth = new wxStaticText(GetParent(), -1, m_date.Format(_T("%B")),
209 wxDefaultPosition, wxDefaultSize,
210 wxALIGN_CENTRE);
211
212 ShowCurrentControls();
9d9b7755 213
2ef31e80
VZ
214 wxSize sizeReal;
215 if ( size.x == -1 || size.y == -1 )
216 {
217 sizeReal = DoGetBestSize();
218 if ( size.x != -1 )
219 sizeReal.x = size.x;
220 if ( size.y != -1 )
221 sizeReal.y = size.y;
222 }
223 else
224 {
225 sizeReal = size;
226 }
227
228 SetSize(sizeReal);
229
230 SetBackgroundColour(*wxWHITE);
9d9b7755 231 SetFont(*wxSWISS_FONT);
2ef31e80 232
4f6aed9c
VZ
233 SetHolidayAttrs();
234
2ef31e80
VZ
235 return TRUE;
236}
237
882a8f40
VZ
238wxCalendarCtrl::~wxCalendarCtrl()
239{
4f6aed9c
VZ
240 for ( size_t n = 0; n < WXSIZEOF(m_attrs); n++ )
241 {
242 delete m_attrs[n];
243 }
882a8f40
VZ
244}
245
246// ----------------------------------------------------------------------------
247// forward wxWin functions to subcontrols
248// ----------------------------------------------------------------------------
249
250bool wxCalendarCtrl::Show(bool show)
251{
252 if ( !wxControl::Show(show) )
253 {
254 return FALSE;
255 }
256
bc385ba9
VZ
257 GetMonthControl()->Show(show);
258 GetYearControl()->Show(show);
882a8f40
VZ
259
260 return TRUE;
261}
262
263bool wxCalendarCtrl::Enable(bool enable)
264{
265 if ( !wxControl::Enable(enable) )
266 {
267 return FALSE;
268 }
269
bc385ba9
VZ
270 GetMonthControl()->Enable(enable);
271 GetYearControl()->Enable(enable);
882a8f40
VZ
272
273 return TRUE;
274}
275
bc385ba9
VZ
276// ----------------------------------------------------------------------------
277// enable/disable month/year controls
278// ----------------------------------------------------------------------------
279
280void wxCalendarCtrl::ShowCurrentControls()
281{
282 if ( AllowMonthChange() )
283 {
284 m_comboMonth->Show();
285 m_staticMonth->Hide();
286
287 if ( AllowYearChange() )
288 {
289 m_spinYear->Show();
290 m_staticYear->Hide();
291
292 // skip the rest
293 return;
294 }
295 }
296 else
297 {
298 m_comboMonth->Hide();
299 m_staticMonth->Show();
300 }
301
302 // year change not allowed here
303 m_spinYear->Hide();
304 m_staticYear->Show();
305}
306
307wxControl *wxCalendarCtrl::GetMonthControl() const
308{
380d9d62 309 return AllowMonthChange() ? (wxControl *)m_comboMonth : (wxControl *)m_staticMonth;
bc385ba9
VZ
310}
311
312wxControl *wxCalendarCtrl::GetYearControl() const
313{
380d9d62 314 return AllowYearChange() ? (wxControl *)m_spinYear : (wxControl *)m_staticYear;
bc385ba9
VZ
315}
316
317void wxCalendarCtrl::EnableYearChange(bool enable)
318{
319 if ( enable != AllowYearChange() )
320 {
321 long style = GetWindowStyle();
322 if ( enable )
323 style &= ~wxCAL_NO_YEAR_CHANGE;
324 else
325 style |= wxCAL_NO_YEAR_CHANGE;
326 SetWindowStyle(style);
327
328 ShowCurrentControls();
329 }
330}
331
332void wxCalendarCtrl::EnableMonthChange(bool enable)
333{
334 if ( enable != AllowMonthChange() )
335 {
336 long style = GetWindowStyle();
337 if ( enable )
338 style &= ~wxCAL_NO_MONTH_CHANGE;
339 else
340 style |= wxCAL_NO_MONTH_CHANGE;
341 SetWindowStyle(style);
342
343 ShowCurrentControls();
344 }
345}
346
9d9b7755
VZ
347// ----------------------------------------------------------------------------
348// changing date
349// ----------------------------------------------------------------------------
350
351void wxCalendarCtrl::SetDate(const wxDateTime& date)
352{
bc385ba9
VZ
353 bool sameMonth = m_date.GetMonth() == date.GetMonth(),
354 sameYear = m_date.GetYear() == date.GetYear();
355
356 if ( sameMonth && sameYear )
9d9b7755
VZ
357 {
358 // just change the day
359 ChangeDay(date);
360 }
361 else
362 {
bc385ba9
VZ
363 if ( !AllowMonthChange() || (!AllowYearChange() && !sameYear) )
364 {
365 // forbidden
366 return;
367 }
368
9d9b7755
VZ
369 // change everything
370 m_date = date;
371
372 // update the controls
373 m_comboMonth->SetSelection(m_date.GetMonth());
bc385ba9
VZ
374
375 if ( AllowYearChange() )
376 {
377 m_spinYear->SetValue(m_date.Format(_T("%Y")));
378 }
9d9b7755 379
0de868d9
VZ
380 // as the month changed, holidays did too
381 SetHolidayAttrs();
382
9d9b7755
VZ
383 // update the calendar
384 Refresh();
385 }
386}
387
388void wxCalendarCtrl::ChangeDay(const wxDateTime& date)
389{
390 if ( m_date != date )
391 {
392 // we need to refresh the row containing the old date and the one
393 // containing the new one
394 wxDateTime dateOld = m_date;
395 m_date = date;
396
397 RefreshDate(dateOld);
398
399 // if the date is in the same row, it was already drawn correctly
400 if ( GetWeek(m_date) != GetWeek(dateOld) )
401 {
402 RefreshDate(m_date);
403 }
404 }
405}
406
407void wxCalendarCtrl::SetDateAndNotify(const wxDateTime& date)
408{
409 wxDateTime::Tm tm1 = m_date.GetTm(),
410 tm2 = date.GetTm();
411
412 wxEventType type;
413 if ( tm1.year != tm2.year )
414 type = wxEVT_CALENDAR_YEAR_CHANGED;
415 else if ( tm1.mon != tm2.mon )
416 type = wxEVT_CALENDAR_MONTH_CHANGED;
417 else if ( tm1.mday != tm2.mday )
418 type = wxEVT_CALENDAR_DAY_CHANGED;
419 else
420 return;
421
422 SetDate(date);
423
4f6aed9c 424 GenerateEvents(type, wxEVT_CALENDAR_SEL_CHANGED);
9d9b7755
VZ
425}
426
2ef31e80
VZ
427// ----------------------------------------------------------------------------
428// date helpers
429// ----------------------------------------------------------------------------
430
431wxDateTime wxCalendarCtrl::GetStartDate() const
432{
433 wxDateTime::Tm tm = m_date.GetTm();
434
435 wxDateTime date = wxDateTime(1, tm.mon, tm.year);
9d9b7755 436
1a8557b1
VZ
437 // rewind back
438 date.SetToPrevWeekDay(GetWindowStyle() & wxCAL_MONDAY_FIRST
439 ? wxDateTime::Mon : wxDateTime::Sun);
440
2ef31e80
VZ
441 return date;
442}
443
444bool wxCalendarCtrl::IsDateShown(const wxDateTime& date) const
445{
446 return date.GetMonth() == m_date.GetMonth();
447}
448
9d9b7755
VZ
449size_t wxCalendarCtrl::GetWeek(const wxDateTime& date) const
450{
1a8557b1
VZ
451 return date.GetWeekOfMonth(GetWindowStyle() & wxCAL_MONDAY_FIRST
452 ? wxDateTime::Monday_First
453 : wxDateTime::Sunday_First);
9d9b7755
VZ
454}
455
2ef31e80
VZ
456// ----------------------------------------------------------------------------
457// size management
458// ----------------------------------------------------------------------------
459
9d9b7755
VZ
460// this is a composite control and it must arrange its parts each time its
461// size or position changes: the combobox and spinctrl are along the top of
462// the available area and the calendar takes up therest of the space
463
bc385ba9
VZ
464// the static controls are supposed to be always smaller than combo/spin so we
465// always use the latter for size calculations and position the static to take
466// the same space
467
9d9b7755
VZ
468// the constants used for the layout
469#define VERT_MARGIN 5 // distance between combo and calendar
470#define HORZ_MARGIN 15 // spin
471
2ef31e80
VZ
472wxSize wxCalendarCtrl::DoGetBestSize() const
473{
9d9b7755
VZ
474 // calc the size of the calendar
475 ((wxCalendarCtrl *)this)->RecalcGeometry(); // const_cast
476
477 wxCoord width = 7*m_widthCol,
478 height = 7*m_heightRow;
479
226ee022
VZ
480 // the combobox doesn't report its height correctly (it returns the
481 // height including the drop down list) so don't use it
482 height += VERT_MARGIN + m_spinYear->GetBestSize().y;
9d9b7755 483
bc385ba9
VZ
484 if ( GetWindowStyle() & (wxRAISED_BORDER | wxSUNKEN_BORDER) )
485 {
486 // the border would clip the last line otherwise
f41cb81e 487 height += 6;
bc385ba9
VZ
488 }
489
9d9b7755 490 return wxSize(width, height);
2ef31e80
VZ
491}
492
493void wxCalendarCtrl::DoSetSize(int x, int y,
494 int width, int height,
495 int sizeFlags)
496{
497 wxControl::DoSetSize(x, y, width, height, sizeFlags);
498}
499
500void wxCalendarCtrl::DoMoveWindow(int x, int y, int width, int height)
501{
9d9b7755 502 wxSize sizeCombo = m_comboMonth->GetSize();
bc385ba9
VZ
503 wxSize sizeStatic = m_staticMonth->GetSize();
504
505 int dy = (sizeCombo.y - sizeStatic.y) / 2;
9d9b7755 506 m_comboMonth->Move(x, y);
bc385ba9 507 m_staticMonth->SetSize(x, y + dy, sizeCombo.x, sizeStatic.y);
2ef31e80 508
9d9b7755 509 int xDiff = sizeCombo.x + HORZ_MARGIN;
882a8f40 510 m_spinYear->SetSize(x + xDiff, y, width - xDiff, sizeCombo.y);
bc385ba9 511 m_staticYear->SetSize(x + xDiff, y + dy, width - xDiff, sizeStatic.y);
2ef31e80 512
9d9b7755
VZ
513 wxSize sizeSpin = m_spinYear->GetSize();
514 int yDiff = wxMax(sizeSpin.y, sizeCombo.y) + VERT_MARGIN;
515
516 wxControl::DoMoveWindow(x, y + yDiff, width, height - yDiff);
517}
518
882a8f40
VZ
519void wxCalendarCtrl::DoGetPosition(int *x, int *y) const
520{
521 wxControl::DoGetPosition(x, y);
522
523 // our real top corner is not in this position
524 if ( y )
525 {
bc385ba9 526 *y -= GetMonthControl()->GetSize().y + VERT_MARGIN;
882a8f40
VZ
527 }
528}
529
530void wxCalendarCtrl::DoGetSize(int *width, int *height) const
531{
532 wxControl::DoGetSize(width, height);
533
534 // our real height is bigger
535 if ( height )
536 {
bc385ba9 537 *height += GetMonthControl()->GetSize().y + VERT_MARGIN;
882a8f40
VZ
538 }
539}
540
9d9b7755 541void wxCalendarCtrl::RecalcGeometry()
2ef31e80 542{
9d9b7755
VZ
543 if ( m_widthCol != 0 )
544 return;
2ef31e80 545
9d9b7755 546 wxClientDC dc(this);
3965571c 547
9d9b7755 548 dc.SetFont(m_font);
3965571c 549
2ef31e80
VZ
550 // determine the column width (we assume that the weekday names are always
551 // wider (in any language) than the numbers)
552 m_widthCol = 0;
9d9b7755 553 wxDateTime::WeekDay wd;
2ef31e80
VZ
554 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
555 {
2ef31e80 556 wxCoord width;
9d9b7755 557 dc.GetTextExtent(m_weekdays[wd], &width, &m_heightRow);
2ef31e80
VZ
558 if ( width > m_widthCol )
559 {
560 m_widthCol = width;
561 }
562 }
3965571c 563
2ef31e80
VZ
564 // leave some margins
565 m_widthCol += 2;
566 m_heightRow += 2;
9d9b7755
VZ
567}
568
569// ----------------------------------------------------------------------------
570// drawing
571// ----------------------------------------------------------------------------
572
13111b2a 573void wxCalendarCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
9d9b7755 574{
07e87221 575 wxPaintDC dc(this);
3965571c 576
9d9b7755
VZ
577 dc.SetFont(m_font);
578
3965571c 579 RecalcGeometry();
9d9b7755 580
882a8f40 581#if DEBUG_PAINT
f6bcfd97 582 wxLogDebug("--- starting to paint, selection: %s, week %u\n",
9d9b7755
VZ
583 m_date.Format("%a %d-%m-%Y %H:%M:%S").c_str(),
584 GetWeek(m_date));
882a8f40 585#endif
2ef31e80
VZ
586
587 // first draw the week days
9d9b7755 588 if ( IsExposed(0, 0, 7*m_widthCol, m_heightRow) )
2ef31e80 589 {
882a8f40 590#if DEBUG_PAINT
f6bcfd97 591 wxLogDebug("painting the header");
882a8f40 592#endif
9d9b7755 593
9d9b7755 594 dc.SetBackgroundMode(wxTRANSPARENT);
4f6aed9c
VZ
595 dc.SetTextForeground(m_colHeaderFg);
596 dc.SetBrush(wxBrush(m_colHeaderBg, wxSOLID));
597 dc.SetPen(wxPen(m_colHeaderBg, 1, wxSOLID));
9d9b7755 598 dc.DrawRectangle(0, 0, 7*m_widthCol, m_heightRow);
1a8557b1
VZ
599
600 bool startOnMonday = (GetWindowStyle() & wxCAL_MONDAY_FIRST) != 0;
601 for ( size_t wd = 0; wd < 7; wd++ )
9d9b7755 602 {
1a8557b1
VZ
603 size_t n;
604 if ( startOnMonday )
605 n = wd == 6 ? 0 : wd + 1;
606 else
607 n = wd;
608
3965571c 609 dc.DrawText(m_weekdays[n], wd*m_widthCol + 1, 0);
9d9b7755 610 }
2ef31e80
VZ
611 }
612
613 // then the calendar itself
614 dc.SetTextForeground(*wxBLACK);
615 //dc.SetFont(*wxNORMAL_FONT);
616
617 wxCoord y = m_heightRow;
618
619 wxDateTime date = GetStartDate();
882a8f40 620#if DEBUG_PAINT
f6bcfd97 621 wxLogDebug("starting calendar from %s\n",
9d9b7755 622 date.Format("%a %d-%m-%Y %H:%M:%S").c_str());
882a8f40 623#endif
9d9b7755 624
2ef31e80 625 dc.SetBackgroundMode(wxSOLID);
9d9b7755 626 for ( size_t nWeek = 1; nWeek <= 6; nWeek++, y += m_heightRow )
2ef31e80 627 {
9d9b7755 628 // if the update region doesn't intersect this row, don't paint it
15807266 629 if ( !IsExposed(0, y, 7*m_widthCol, m_heightRow - 1) )
9d9b7755
VZ
630 {
631 date += wxDateSpan::Week();
632
633 continue;
634 }
882a8f40 635
1a8557b1 636#if DEBUG_PAINT
f6bcfd97 637 wxLogDebug("painting week %d at y = %d\n", nWeek, y);
882a8f40 638#endif
9d9b7755 639
1a8557b1 640 for ( size_t wd = 0; wd < 7; wd++ )
2ef31e80
VZ
641 {
642 if ( IsDateShown(date) )
643 {
882a8f40 644 // don't use wxDate::Format() which prepends 0s
4f6aed9c
VZ
645 unsigned int day = date.GetDay();
646 wxString dayStr = wxString::Format(_T("%u"), day);
3965571c 647 wxCoord width;
4f6aed9c
VZ
648 dc.GetTextExtent(dayStr, &width, (wxCoord *)NULL);
649
650 bool changedColours = FALSE,
651 changedFont = FALSE;
652
653 wxCalendarDateAttr *attr = m_attrs[day - 1];
2ef31e80 654
f41cb81e 655 bool isSel = date.IsSameDate(m_date);
2ef31e80
VZ
656 if ( isSel )
657 {
4f6aed9c
VZ
658 dc.SetTextForeground(m_colHighlightFg);
659 dc.SetTextBackground(m_colHighlightBg);
660
661 changedColours = TRUE;
662 }
663 else if ( attr )
664 {
665 wxColour colFg, colBg;
666
667 if ( attr->IsHoliday() )
668 {
669 colFg = m_colHolidayFg;
670 colBg = m_colHolidayBg;
671 }
672 else
673 {
674 colFg = attr->GetTextColour();
675 colBg = attr->GetBackgroundColour();
676 }
677
678 if ( colFg.Ok() )
679 {
680 dc.SetTextForeground(colFg);
681 changedColours = TRUE;
682 }
683
684 if ( colBg.Ok() )
685 {
686 dc.SetTextBackground(colBg);
687 changedColours = TRUE;
688 }
689
690 if ( attr->HasFont() )
691 {
692 dc.SetFont(attr->GetFont());
693 changedFont = TRUE;
694 }
2ef31e80
VZ
695 }
696
4f6aed9c
VZ
697 wxCoord x = wd*m_widthCol + (m_widthCol - width) / 2;
698 dc.DrawText(dayStr, x, y + 1);
2ef31e80 699
4f6aed9c
VZ
700 if ( !isSel && attr && attr->HasBorder() )
701 {
702 wxColour colBorder;
703 if ( attr->HasBorderColour() )
704 {
705 colBorder = attr->GetBorderColour();
706 }
707 else
708 {
709 colBorder = m_foregroundColour;
710 }
711
712 wxPen pen(colBorder, 1, wxSOLID);
713 dc.SetPen(pen);
714 dc.SetBrush(*wxTRANSPARENT_BRUSH);
715
716 switch ( attr->GetBorder() )
717 {
718 case wxCAL_BORDER_SQUARE:
719 dc.DrawRectangle(x - 2, y,
720 width + 4, m_heightRow);
721 break;
722
723 case wxCAL_BORDER_ROUND:
724 dc.DrawEllipse(x - 2, y,
725 width + 4, m_heightRow);
726 break;
727
728 default:
729 wxFAIL_MSG(_T("unknown border type"));
730 }
731 }
732
733 if ( changedColours )
2ef31e80 734 {
9d9b7755 735 dc.SetTextForeground(m_foregroundColour);
2ef31e80
VZ
736 dc.SetTextBackground(m_backgroundColour);
737 }
4f6aed9c
VZ
738
739 if ( changedFont )
740 {
741 dc.SetFont(m_font);
742 }
2ef31e80
VZ
743 }
744 //else: just don't draw it
745
746 date += wxDateSpan::Day();
747 }
2ef31e80 748 }
882a8f40 749#if DEBUG_PAINT
f6bcfd97 750 wxLogDebug("+++ finished painting");
882a8f40 751#endif
9d9b7755
VZ
752}
753
754void wxCalendarCtrl::RefreshDate(const wxDateTime& date)
755{
756 RecalcGeometry();
757
758 wxRect rect;
759
760 // always refresh the whole row at once because our OnPaint() will draw
761 // the whole row anyhow - and this allows the small optimisation in
762 // OnClick() below to work
763 rect.x = 0;
764 rect.y = m_heightRow * GetWeek(date);
765 rect.width = 7*m_widthCol;
766 rect.height = m_heightRow;
767
f6bcfd97
BP
768#ifdef __WXMSW__
769 // VZ: for some reason, the selected date seems to occupy more space under
770 // MSW - this is probably some bug in the font size calculations, but I
771 // don't know where exactly. This fix is ugly and leads to more
772 // refreshes than really needed, but without it the selected days
773 // leaves even more ugly underscores on screen.
774 rect.Inflate(0, 1);
775#endif // MSW
776
882a8f40 777#if DEBUG_PAINT
f6bcfd97 778 wxLogDebug("*** refreshing week %d at (%d, %d)-(%d, %d)\n",
9d9b7755
VZ
779 GetWeek(date),
780 rect.x, rect.y,
781 rect.x + rect.width, rect.y + rect.height);
882a8f40 782#endif
9d9b7755
VZ
783
784 Refresh(TRUE, &rect);
2ef31e80
VZ
785}
786
787// ----------------------------------------------------------------------------
788// mouse handling
789// ----------------------------------------------------------------------------
790
0185cd09 791void wxCalendarCtrl::OnDClick(wxMouseEvent& event)
2ef31e80 792{
0185cd09 793 if ( HitTest(event.GetPosition()) != wxCAL_HITTEST_DAY )
2ef31e80
VZ
794 {
795 event.Skip();
796 }
797 else
798 {
4f6aed9c 799 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
0185cd09
VZ
800 }
801}
802
803void wxCalendarCtrl::OnClick(wxMouseEvent& event)
804{
805 wxDateTime date;
806 wxDateTime::WeekDay wday;
807 switch ( HitTest(event.GetPosition(), &date, &wday) )
808 {
809 case wxCAL_HITTEST_DAY:
810 ChangeDay(date);
2ef31e80 811
4f6aed9c
VZ
812 GenerateEvents(wxEVT_CALENDAR_DAY_CHANGED,
813 wxEVT_CALENDAR_SEL_CHANGED);
0185cd09
VZ
814 break;
815
816 case wxCAL_HITTEST_HEADER:
817 {
818 wxCalendarEvent event(this, wxEVT_CALENDAR_WEEKDAY_CLICKED);
819 event.m_wday = wday;
820 (void)GetEventHandler()->ProcessEvent(event);
821 }
822 break;
823
824 default:
825 wxFAIL_MSG(_T("unknown hittest code"));
826 // fall through
827
828 case wxCAL_HITTEST_NOWHERE:
829 event.Skip();
830 break;
2ef31e80
VZ
831 }
832}
833
0185cd09
VZ
834wxCalendarHitTestResult wxCalendarCtrl::HitTest(const wxPoint& pos,
835 wxDateTime *date,
836 wxDateTime::WeekDay *wd)
2ef31e80 837{
9d9b7755
VZ
838 RecalcGeometry();
839
0185cd09
VZ
840 int wday = pos.x / m_widthCol;
841
2ef31e80
VZ
842 wxCoord y = pos.y;
843 if ( y < m_heightRow )
0185cd09
VZ
844 {
845 if ( wd )
846 {
847 if ( GetWindowStyle() & wxCAL_MONDAY_FIRST )
848 {
849 wday = wday == 6 ? 0 : wday + 1;
850 }
851
852 *wd = (wxDateTime::WeekDay)wday;
853 }
2ef31e80 854
0185cd09
VZ
855 return wxCAL_HITTEST_HEADER;
856 }
2ef31e80 857
0185cd09 858 int week = (y - m_heightRow) / m_heightRow;
2ef31e80 859 if ( week >= 6 || wday >= 7 )
0185cd09
VZ
860 {
861 return wxCAL_HITTEST_NOWHERE;
862 }
2ef31e80 863
0185cd09 864 wxDateTime dt = GetStartDate() + wxDateSpan::Days(7*week + wday);
2ef31e80 865
0185cd09
VZ
866 if ( IsDateShown(dt) )
867 {
868 if ( date )
869 *date = dt;
9d9b7755 870
0185cd09
VZ
871 return wxCAL_HITTEST_DAY;
872 }
873 else
874 {
875 return wxCAL_HITTEST_NOWHERE;
876 }
2ef31e80 877}
9d9b7755
VZ
878
879// ----------------------------------------------------------------------------
880// subcontrols events handling
881// ----------------------------------------------------------------------------
882
883void wxCalendarCtrl::OnMonthChange(wxCommandEvent& event)
884{
885 wxDateTime::Tm tm = m_date.GetTm();
886
887 wxDateTime::Month mon = (wxDateTime::Month)event.GetInt();
888 if ( tm.mday > wxDateTime::GetNumberOfDays(mon, tm.year) )
889 {
890 tm.mday = wxDateTime::GetNumberOfDays(mon, tm.year);
891 }
892
0de868d9 893 SetDateAndNotify(wxDateTime(tm.mday, mon, tm.year));
9d9b7755
VZ
894}
895
896void wxCalendarCtrl::OnYearChange(wxSpinEvent& event)
897{
898 wxDateTime::Tm tm = m_date.GetTm();
899
13111b2a 900 int year = (int)event.GetInt();
9d9b7755
VZ
901 if ( tm.mday > wxDateTime::GetNumberOfDays(tm.mon, year) )
902 {
903 tm.mday = wxDateTime::GetNumberOfDays(tm.mon, year);
904 }
905
0de868d9 906 SetDateAndNotify(wxDateTime(tm.mday, tm.mon, year));
9d9b7755
VZ
907}
908
909// ----------------------------------------------------------------------------
910// keyboard interface
911// ----------------------------------------------------------------------------
912
913void wxCalendarCtrl::OnChar(wxKeyEvent& event)
914{
915 switch ( event.KeyCode() )
916 {
917 case _T('+'):
918 case WXK_ADD:
919 SetDateAndNotify(m_date + wxDateSpan::Year());
920 break;
921
922 case _T('-'):
923 case WXK_SUBTRACT:
924 SetDateAndNotify(m_date - wxDateSpan::Year());
925 break;
926
882a8f40
VZ
927 case WXK_PRIOR:
928 SetDateAndNotify(m_date - wxDateSpan::Month());
9d9b7755
VZ
929 break;
930
882a8f40
VZ
931 case WXK_NEXT:
932 SetDateAndNotify(m_date + wxDateSpan::Month());
9d9b7755
VZ
933 break;
934
935 case WXK_RIGHT:
1a8557b1
VZ
936 if ( event.ControlDown() )
937 SetDateAndNotify(wxDateTime(m_date).SetToNextWeekDay(
938 GetWindowStyle() & wxCAL_MONDAY_FIRST
939 ? wxDateTime::Sun : wxDateTime::Sat));
940 else
941 SetDateAndNotify(m_date + wxDateSpan::Day());
9d9b7755
VZ
942 break;
943
944 case WXK_LEFT:
1a8557b1
VZ
945 if ( event.ControlDown() )
946 SetDateAndNotify(wxDateTime(m_date).SetToPrevWeekDay(
947 GetWindowStyle() & wxCAL_MONDAY_FIRST
948 ? wxDateTime::Mon : wxDateTime::Sun));
949 else
950 SetDateAndNotify(m_date - wxDateSpan::Day());
9d9b7755
VZ
951 break;
952
953 case WXK_UP:
954 SetDateAndNotify(m_date - wxDateSpan::Week());
955 break;
956
957 case WXK_DOWN:
958 SetDateAndNotify(m_date + wxDateSpan::Week());
959 break;
960
961 case WXK_HOME:
1a8557b1
VZ
962 if ( event.ControlDown() )
963 SetDateAndNotify(wxDateTime::Today());
964 else
965 SetDateAndNotify(wxDateTime(1, m_date.GetMonth(), m_date.GetYear()));
966 break;
967
968 case WXK_END:
969 SetDateAndNotify(wxDateTime(m_date).SetToLastMonthDay());
9d9b7755
VZ
970 break;
971
4f6aed9c
VZ
972 case WXK_RETURN:
973 GenerateEvent(wxEVT_CALENDAR_DOUBLECLICKED);
974 break;
975
9d9b7755
VZ
976 default:
977 event.Skip();
978 }
979}
980
981// ----------------------------------------------------------------------------
4f6aed9c 982// holidays handling
9d9b7755
VZ
983// ----------------------------------------------------------------------------
984
4f6aed9c 985void wxCalendarCtrl::EnableHolidayDisplay(bool display)
9d9b7755 986{
4f6aed9c
VZ
987 long style = GetWindowStyle();
988 if ( display )
989 style |= wxCAL_SHOW_HOLIDAYS;
990 else
991 style &= ~wxCAL_SHOW_HOLIDAYS;
992
993 SetWindowStyle(style);
994
995 if ( display )
996 SetHolidayAttrs();
997 else
998 ResetHolidayAttrs();
999
1000 Refresh();
1001}
1002
1003void wxCalendarCtrl::SetHolidayAttrs()
1004{
1005 if ( GetWindowStyle() & wxCAL_SHOW_HOLIDAYS )
1006 {
1007 ResetHolidayAttrs();
1008
1009 wxDateTime::Tm tm = m_date.GetTm();
1010 wxDateTime dtStart(1, tm.mon, tm.year),
1011 dtEnd = dtStart.GetLastMonthDay();
1012
1013 wxDateTimeArray hol;
1014 wxDateTimeHolidayAuthority::GetHolidaysInRange(dtStart, dtEnd, hol);
1015
1016 size_t count = hol.GetCount();
1017 for ( size_t n = 0; n < count; n++ )
1018 {
1019 SetHoliday(hol[n].GetDay());
1020 }
1021 }
1022}
1023
1024void wxCalendarCtrl::SetHoliday(size_t day)
1025{
1026 wxCHECK_RET( day > 0 && day < 32, _T("invalid day in SetHoliday") );
0185cd09 1027
4f6aed9c
VZ
1028 wxCalendarDateAttr *attr = GetAttr(day);
1029 if ( !attr )
0185cd09 1030 {
4f6aed9c
VZ
1031 attr = new wxCalendarDateAttr;
1032 }
0185cd09 1033
4f6aed9c
VZ
1034 attr->SetHoliday(TRUE);
1035
1036 // can't use SetAttr() because it would delete this pointer
1037 m_attrs[day - 1] = attr;
1038}
1039
1040void wxCalendarCtrl::ResetHolidayAttrs()
1041{
1042 for ( size_t day = 0; day < 31; day++ )
1043 {
1044 if ( m_attrs[day] )
1045 {
1046 m_attrs[day]->SetHoliday(FALSE);
1047 }
0185cd09
VZ
1048 }
1049}
1050
4f6aed9c
VZ
1051// ----------------------------------------------------------------------------
1052// wxCalendarEvent
1053// ----------------------------------------------------------------------------
1054
0185cd09
VZ
1055void wxCalendarEvent::Init()
1056{
1057 m_wday = wxDateTime::Inv_WeekDay;
9d9b7755
VZ
1058}
1059
1060wxCalendarEvent::wxCalendarEvent(wxCalendarCtrl *cal, wxEventType type)
1061 : wxCommandEvent(type, cal->GetId())
1062{
1063 m_date = cal->GetDate();
1064}
2fa7c206 1065
1e6feb95 1066#endif // wxUSE_CALENDARCTRL
2fa7c206 1067