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