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