]> git.saurik.com Git - wxWidgets.git/blob - src/generic/calctrl.cpp
ed540c9dd43c6af015c1f6af992b826924ac95b5
[wxWidgets.git] / src / generic / calctrl.cpp
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
32 #include "wx/dcclient.h"
33 #include "wx/settings.h"
34 #include "wx/brush.h"
35 #endif //WX_PRECOMP
36
37 #include "wx/calctrl.h"
38
39 #define DEBUG_PAINT 0
40
41 // ----------------------------------------------------------------------------
42 // private classes
43 // ----------------------------------------------------------------------------
44
45 class wxMonthComboBox : public wxComboBox
46 {
47 public:
48 wxMonthComboBox(wxCalendarCtrl *cal);
49
50 void OnMonthChange(wxCommandEvent& event) { m_cal->OnMonthChange(event); }
51
52 private:
53 wxCalendarCtrl *m_cal;
54
55 DECLARE_EVENT_TABLE()
56 };
57
58 class wxYearSpinCtrl : public wxSpinCtrl
59 {
60 public:
61 wxYearSpinCtrl(wxCalendarCtrl *cal);
62
63 void OnYearChange(wxSpinEvent& event) { m_cal->OnYearChange(event); }
64
65 private:
66 wxCalendarCtrl *m_cal;
67
68 DECLARE_EVENT_TABLE()
69 };
70
71 // ----------------------------------------------------------------------------
72 // wxWin macros
73 // ----------------------------------------------------------------------------
74
75 BEGIN_EVENT_TABLE(wxCalendarCtrl, wxControl)
76 EVT_PAINT(wxCalendarCtrl::OnPaint)
77
78 EVT_CHAR(wxCalendarCtrl::OnChar)
79
80 EVT_LEFT_DOWN(wxCalendarCtrl::OnClick)
81 END_EVENT_TABLE()
82
83 BEGIN_EVENT_TABLE(wxMonthComboBox, wxComboBox)
84 EVT_COMBOBOX(-1, wxMonthComboBox::OnMonthChange)
85 END_EVENT_TABLE()
86
87 BEGIN_EVENT_TABLE(wxYearSpinCtrl, wxSpinCtrl)
88 EVT_SPINCTRL(-1, wxYearSpinCtrl::OnYearChange)
89 END_EVENT_TABLE()
90
91 IMPLEMENT_DYNAMIC_CLASS(wxCalendarCtrl, wxControl)
92
93 // ============================================================================
94 // implementation
95 // ============================================================================
96
97 // ----------------------------------------------------------------------------
98 // wxMonthComboBox and wxYearSpinCtrl
99 // ----------------------------------------------------------------------------
100
101 wxMonthComboBox::wxMonthComboBox(wxCalendarCtrl *cal)
102 : wxComboBox(cal->GetParent(), -1,
103 wxEmptyString,
104 wxDefaultPosition,
105 wxDefaultSize,
106 0, NULL,
107 wxCB_READONLY)
108 {
109 m_cal = cal;
110
111 wxDateTime::Month m;
112 for ( m = wxDateTime::Jan; m < wxDateTime::Inv_Month; wxNextMonth(m) )
113 {
114 Append(wxDateTime::GetMonthName(m));
115 }
116
117 SetSelection(m_cal->GetDate().GetMonth());
118 }
119
120 wxYearSpinCtrl::wxYearSpinCtrl(wxCalendarCtrl *cal)
121 : wxSpinCtrl(cal->GetParent(), -1,
122 cal->GetDate().Format(_T("%Y")),
123 wxDefaultPosition,
124 wxDefaultSize,
125 wxSP_ARROW_KEYS,
126 -4300, 10000, cal->GetDate().GetYear())
127 {
128 m_cal = cal;
129 }
130
131 // ----------------------------------------------------------------------------
132 // wxCalendarCtrl
133 // ----------------------------------------------------------------------------
134
135 void wxCalendarCtrl::Init()
136 {
137 m_comboMonth = NULL;
138 m_spinYear = NULL;
139
140 m_widthCol =
141 m_heightRow = 0;
142
143 wxDateTime::WeekDay wd;
144 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
145 {
146 m_weekdays[wd] = wxDateTime::GetWeekDayName(wd, wxDateTime::Name_Abbr);
147 }
148 }
149
150 bool wxCalendarCtrl::Create(wxWindow *parent,
151 wxWindowID id,
152 const wxDateTime& date,
153 const wxPoint& pos,
154 const wxSize& size,
155 long style,
156 const wxString& name)
157 {
158 SetWindowStyle(style | (wxBORDER | wxWANTS_CHARS));
159
160 m_date = date.IsValid() ? date : wxDateTime::Today();
161
162 m_comboMonth = new wxMonthComboBox(this);
163 m_spinYear = new wxYearSpinCtrl(this);
164
165 wxSize sizeReal;
166 if ( size.x == -1 || size.y == -1 )
167 {
168 sizeReal = DoGetBestSize();
169 if ( size.x != -1 )
170 sizeReal.x = size.x;
171 if ( size.y != -1 )
172 sizeReal.y = size.y;
173 }
174 else
175 {
176 sizeReal = size;
177 }
178
179 SetSize(sizeReal);
180
181 SetBackgroundColour(*wxWHITE);
182 SetFont(*wxSWISS_FONT);
183
184 return TRUE;
185 }
186
187 wxCalendarCtrl::~wxCalendarCtrl()
188 {
189 #if 0
190 m_comboMonth->PopEventHandler();
191 m_spinYear->PopEventHandler();
192 #endif // 0
193 }
194
195 // ----------------------------------------------------------------------------
196 // forward wxWin functions to subcontrols
197 // ----------------------------------------------------------------------------
198
199 bool wxCalendarCtrl::Show(bool show)
200 {
201 if ( !wxControl::Show(show) )
202 {
203 return FALSE;
204 }
205
206 m_comboMonth->Show(show);
207 m_spinYear->Show(show);
208
209 return TRUE;
210 }
211
212 bool wxCalendarCtrl::Enable(bool enable)
213 {
214 if ( !wxControl::Enable(enable) )
215 {
216 return FALSE;
217 }
218
219 m_comboMonth->Enable(enable);
220 m_spinYear->Enable(enable);
221
222 return TRUE;
223 }
224
225 // ----------------------------------------------------------------------------
226 // changing date
227 // ----------------------------------------------------------------------------
228
229 void wxCalendarCtrl::SetDate(const wxDateTime& date)
230 {
231 if ( m_date.GetMonth() == date.GetMonth() &&
232 m_date.GetYear() == date.GetYear() )
233 {
234 // just change the day
235 ChangeDay(date);
236 }
237 else
238 {
239 // change everything
240 m_date = date;
241
242 // update the controls
243 m_comboMonth->SetSelection(m_date.GetMonth());
244 m_spinYear->SetValue(m_date.Format(_T("%Y")));
245
246 // update the calendar
247 Refresh();
248 }
249 }
250
251 void wxCalendarCtrl::ChangeDay(const wxDateTime& date)
252 {
253 if ( m_date != date )
254 {
255 // we need to refresh the row containing the old date and the one
256 // containing the new one
257 wxDateTime dateOld = m_date;
258 m_date = date;
259
260 RefreshDate(dateOld);
261
262 // if the date is in the same row, it was already drawn correctly
263 if ( GetWeek(m_date) != GetWeek(dateOld) )
264 {
265 RefreshDate(m_date);
266 }
267 }
268 }
269
270 void wxCalendarCtrl::SetDateAndNotify(const wxDateTime& date)
271 {
272 wxDateTime::Tm tm1 = m_date.GetTm(),
273 tm2 = date.GetTm();
274
275 wxEventType type;
276 if ( tm1.year != tm2.year )
277 type = wxEVT_CALENDAR_YEAR_CHANGED;
278 else if ( tm1.mon != tm2.mon )
279 type = wxEVT_CALENDAR_MONTH_CHANGED;
280 else if ( tm1.mday != tm2.mday )
281 type = wxEVT_CALENDAR_DAY_CHANGED;
282 else
283 return;
284
285 SetDate(date);
286
287 GenerateEvent(type);
288 }
289
290 // ----------------------------------------------------------------------------
291 // date helpers
292 // ----------------------------------------------------------------------------
293
294 wxDateTime wxCalendarCtrl::GetStartDate() const
295 {
296 wxDateTime::Tm tm = m_date.GetTm();
297
298 wxDateTime date = wxDateTime(1, tm.mon, tm.year);
299 if ( date.GetWeekDay() != wxDateTime::Sun )
300 {
301 date.SetToPrevWeekDay(wxDateTime::Sun);
302
303 // be sure to do it or it might gain 1 hour if DST changed in between
304 date.ResetTime();
305 }
306 //else: we already have it
307
308 return date;
309 }
310
311 bool wxCalendarCtrl::IsDateShown(const wxDateTime& date) const
312 {
313 return date.GetMonth() == m_date.GetMonth();
314 }
315
316 size_t wxCalendarCtrl::GetWeek(const wxDateTime& date) const
317 {
318 return date.GetWeekOfMonth(wxDateTime::Sunday_First);
319 }
320
321 // ----------------------------------------------------------------------------
322 // size management
323 // ----------------------------------------------------------------------------
324
325 // this is a composite control and it must arrange its parts each time its
326 // size or position changes: the combobox and spinctrl are along the top of
327 // the available area and the calendar takes up therest of the space
328
329 // the constants used for the layout
330 #define VERT_MARGIN 5 // distance between combo and calendar
331 #define HORZ_MARGIN 15 // spin
332
333 wxSize wxCalendarCtrl::DoGetBestSize() const
334 {
335 // calc the size of the calendar
336 ((wxCalendarCtrl *)this)->RecalcGeometry(); // const_cast
337
338 wxCoord width = 7*m_widthCol,
339 height = 7*m_heightRow;
340
341 wxSize sizeCombo = m_comboMonth->GetBestSize(),
342 sizeSpin = m_spinYear->GetBestSize();
343
344 height += VERT_MARGIN + wxMax(sizeCombo.y, sizeSpin.y);
345
346 return wxSize(width, height);
347 }
348
349 void wxCalendarCtrl::DoSetSize(int x, int y,
350 int width, int height,
351 int sizeFlags)
352 {
353 wxControl::DoSetSize(x, y, width, height, sizeFlags);
354 }
355
356 void wxCalendarCtrl::DoMoveWindow(int x, int y, int width, int height)
357 {
358 wxSize sizeCombo = m_comboMonth->GetSize();
359 m_comboMonth->Move(x, y);
360
361 int xDiff = sizeCombo.x + HORZ_MARGIN;
362 m_spinYear->SetSize(x + xDiff, y, width - xDiff, sizeCombo.y);
363
364 wxSize sizeSpin = m_spinYear->GetSize();
365 int yDiff = wxMax(sizeSpin.y, sizeCombo.y) + VERT_MARGIN;
366
367 wxControl::DoMoveWindow(x, y + yDiff, width, height - yDiff);
368 }
369
370 void wxCalendarCtrl::DoGetPosition(int *x, int *y) const
371 {
372 wxControl::DoGetPosition(x, y);
373
374 // our real top corner is not in this position
375 if ( y )
376 {
377 *y -= m_comboMonth->GetSize().y + VERT_MARGIN;
378 }
379 }
380
381 void wxCalendarCtrl::DoGetSize(int *width, int *height) const
382 {
383 wxControl::DoGetSize(width, height);
384
385 // our real height is bigger
386 if ( height )
387 {
388 *height += m_comboMonth->GetSize().y + VERT_MARGIN;
389 }
390 }
391
392 void wxCalendarCtrl::RecalcGeometry()
393 {
394 if ( m_widthCol != 0 )
395 return;
396
397 wxClientDC dc(this);
398
399 dc.SetFont(m_font);
400
401 // determine the column width (we assume that the weekday names are always
402 // wider (in any language) than the numbers)
403 m_widthCol = 0;
404 wxDateTime::WeekDay wd;
405 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
406 {
407 wxCoord width;
408 dc.GetTextExtent(m_weekdays[wd], &width, &m_heightRow);
409 if ( width > m_widthCol )
410 {
411 m_widthCol = width;
412 }
413 }
414
415 // leave some margins
416 m_widthCol += 2;
417 m_heightRow += 2;
418 }
419
420 // ----------------------------------------------------------------------------
421 // drawing
422 // ----------------------------------------------------------------------------
423
424 void wxCalendarCtrl::OnPaint(wxPaintEvent& event)
425 {
426 wxPaintDC dc(this);
427
428 wxDateTime::WeekDay wd;
429
430 dc.SetFont(m_font);
431
432 RecalcGeometry();
433
434 #if DEBUG_PAINT
435 printf("--- starting to paint, selection: %s, week %u\n",
436 m_date.Format("%a %d-%m-%Y %H:%M:%S").c_str(),
437 GetWeek(m_date));
438 #endif
439
440 // first draw the week days
441 if ( IsExposed(0, 0, 7*m_widthCol, m_heightRow) )
442 {
443 #if DEBUG_PAINT
444 puts("painting the header");
445 #endif
446
447 dc.SetTextForeground(*wxBLUE);
448 dc.SetBrush(wxBrush(*wxLIGHT_GREY, wxSOLID));
449 dc.SetBackgroundMode(wxTRANSPARENT);
450 dc.SetPen(*wxLIGHT_GREY_PEN);
451 dc.DrawRectangle(0, 0, 7*m_widthCol, m_heightRow);
452 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
453 {
454 dc.DrawText(m_weekdays[wd], wd*m_widthCol + 1, 0);
455 }
456 }
457
458 // then the calendar itself
459 dc.SetTextForeground(*wxBLACK);
460 //dc.SetFont(*wxNORMAL_FONT);
461
462 wxCoord y = m_heightRow;
463
464 wxDateTime date = GetStartDate();
465 #if DEBUG_PAINT
466 printf("starting calendar from %s\n",
467 date.Format("%a %d-%m-%Y %H:%M:%S").c_str());
468 #endif
469
470 dc.SetBackgroundMode(wxSOLID);
471 for ( size_t nWeek = 1; nWeek <= 6; nWeek++, y += m_heightRow )
472 {
473 // if the update region doesn't intersect this row, don't paint it
474 if ( !IsExposed(0, y, 7*m_widthCol, m_heightRow - 1) )
475 {
476 date += wxDateSpan::Week();
477
478 continue;
479 }
480
481 #if DEBUG_PAINT
482 printf("painting week %d at y = %d\n", nWeek, y);
483 #endif
484
485 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
486 {
487 if ( IsDateShown(date) )
488 {
489 // don't use wxDate::Format() which prepends 0s
490 wxString day = wxString::Format(_T("%u"), date.GetDay());
491 wxCoord width;
492 dc.GetTextExtent(day, &width, (wxCoord *)NULL);
493
494 bool isSel = m_date == date;
495 if ( isSel )
496 {
497 dc.SetTextForeground(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
498 dc.SetTextBackground(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHT));
499 }
500
501 dc.DrawText(day, wd*m_widthCol + (m_widthCol - width) / 2, y);
502
503 if ( isSel )
504 {
505 dc.SetTextForeground(m_foregroundColour);
506 dc.SetTextBackground(m_backgroundColour);
507 }
508 }
509 //else: just don't draw it
510
511 date += wxDateSpan::Day();
512 }
513 }
514 #if DEBUG_PAINT
515 puts("+++ finished painting");
516 #endif
517 }
518
519 void wxCalendarCtrl::RefreshDate(const wxDateTime& date)
520 {
521 RecalcGeometry();
522
523 wxRect rect;
524
525 // always refresh the whole row at once because our OnPaint() will draw
526 // the whole row anyhow - and this allows the small optimisation in
527 // OnClick() below to work
528 rect.x = 0;
529 rect.y = m_heightRow * GetWeek(date);
530 rect.width = 7*m_widthCol;
531 rect.height = m_heightRow;
532
533 #if DEBUG_PAINT
534 printf("*** refreshing week %d at (%d, %d)-(%d, %d)\n",
535 GetWeek(date),
536 rect.x, rect.y,
537 rect.x + rect.width, rect.y + rect.height);
538 #endif
539
540 Refresh(TRUE, &rect);
541 }
542
543 // ----------------------------------------------------------------------------
544 // mouse handling
545 // ----------------------------------------------------------------------------
546
547 void wxCalendarCtrl::OnClick(wxMouseEvent& event)
548 {
549 RecalcGeometry();
550
551 wxDateTime date;
552 if ( !HitTest(event.GetPosition(), &date) )
553 {
554 event.Skip();
555 }
556 else
557 {
558 ChangeDay(date);
559
560 GenerateEvent(wxEVT_CALENDAR_DAY_CHANGED);
561 }
562 }
563
564 bool wxCalendarCtrl::HitTest(const wxPoint& pos, wxDateTime *date)
565 {
566 RecalcGeometry();
567
568 wxCoord y = pos.y;
569 if ( y < m_heightRow )
570 return FALSE;
571
572 y -= m_heightRow;
573 int week = y / m_heightRow,
574 wday = pos.x / m_widthCol;
575
576 if ( week >= 6 || wday >= 7 )
577 return FALSE;
578
579 wxCHECK_MSG( date, FALSE, _T("bad pointer in wxCalendarCtrl::HitTest") );
580
581 *date = GetStartDate();
582 *date += wxDateSpan::Days(7*week + wday);
583
584 return IsDateShown(*date);
585 }
586
587 // ----------------------------------------------------------------------------
588 // subcontrols events handling
589 // ----------------------------------------------------------------------------
590
591 void wxCalendarCtrl::OnMonthChange(wxCommandEvent& event)
592 {
593 wxDateTime::Tm tm = m_date.GetTm();
594
595 wxDateTime::Month mon = (wxDateTime::Month)event.GetInt();
596 if ( tm.mday > wxDateTime::GetNumberOfDays(mon, tm.year) )
597 {
598 tm.mday = wxDateTime::GetNumberOfDays(mon, tm.year);
599 }
600
601 SetDate(wxDateTime(tm.mday, mon, tm.year));
602
603 GenerateEvent(wxEVT_CALENDAR_MONTH_CHANGED);
604 }
605
606 void wxCalendarCtrl::OnYearChange(wxSpinEvent& event)
607 {
608 wxDateTime::Tm tm = m_date.GetTm();
609
610 int year = event.GetInt();
611 if ( tm.mday > wxDateTime::GetNumberOfDays(tm.mon, year) )
612 {
613 tm.mday = wxDateTime::GetNumberOfDays(tm.mon, year);
614 }
615
616 SetDate(wxDateTime(tm.mday, tm.mon, year));
617
618 GenerateEvent(wxEVT_CALENDAR_YEAR_CHANGED);
619 }
620
621 // ----------------------------------------------------------------------------
622 // keyboard interface
623 // ----------------------------------------------------------------------------
624
625 void wxCalendarCtrl::OnChar(wxKeyEvent& event)
626 {
627 switch ( event.KeyCode() )
628 {
629 case _T('+'):
630 case WXK_ADD:
631 SetDateAndNotify(m_date + wxDateSpan::Year());
632 break;
633
634 case _T('-'):
635 case WXK_SUBTRACT:
636 SetDateAndNotify(m_date - wxDateSpan::Year());
637 break;
638
639 case WXK_PRIOR:
640 SetDateAndNotify(m_date - wxDateSpan::Month());
641 break;
642
643 case WXK_NEXT:
644 SetDateAndNotify(m_date + wxDateSpan::Month());
645 break;
646
647 case WXK_RIGHT:
648 SetDateAndNotify(m_date + wxDateSpan::Day());
649 break;
650
651 case WXK_LEFT:
652 SetDateAndNotify(m_date - wxDateSpan::Day());
653 break;
654
655 case WXK_UP:
656 SetDateAndNotify(m_date - wxDateSpan::Week());
657 break;
658
659 case WXK_DOWN:
660 SetDateAndNotify(m_date + wxDateSpan::Week());
661 break;
662
663 case WXK_HOME:
664 SetDateAndNotify(wxDateTime::Today());
665 break;
666
667 default:
668 event.Skip();
669 }
670 }
671
672 // ----------------------------------------------------------------------------
673 // wxCalendarEvent
674 // ----------------------------------------------------------------------------
675
676 void wxCalendarCtrl::GenerateEvent(wxEventType type)
677 {
678 // we're called for a change in some particular date field but we always
679 // also generate a generic "changed" event
680 wxCalendarEvent event(this, type);
681 wxCalendarEvent event2(this, wxEVT_CALENDAR_SEL_CHANGED);
682
683 (void)GetEventHandler()->ProcessEvent(event);
684 (void)GetEventHandler()->ProcessEvent(event2);
685 }
686
687 wxCalendarEvent::wxCalendarEvent(wxCalendarCtrl *cal, wxEventType type)
688 : wxCommandEvent(type, cal->GetId())
689 {
690 m_date = cal->GetDate();
691 }