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