]> git.saurik.com Git - wxWidgets.git/blob - src/generic/calctrl.cpp
1. many, many, many warnings fixed (from HP-UX build log; 50% are still left)
[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 * WXUNUSED(parent),
151 wxWindowID WXUNUSED(id),
152 const wxDateTime& date,
153 const wxPoint& WXUNUSED(pos),
154 const wxSize& size,
155 long style,
156 const wxString& WXUNUSED(name))
157 {
158 SetWindowStyle(style | (wxRAISED_BORDER | 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
300 // rewind back
301 date.SetToPrevWeekDay(GetWindowStyle() & wxCAL_MONDAY_FIRST
302 ? wxDateTime::Mon : wxDateTime::Sun);
303
304 // be sure to do it or it might gain 1 hour if DST changed in between
305 date.ResetTime();
306
307 return date;
308 }
309
310 bool wxCalendarCtrl::IsDateShown(const wxDateTime& date) const
311 {
312 return date.GetMonth() == m_date.GetMonth();
313 }
314
315 size_t wxCalendarCtrl::GetWeek(const wxDateTime& date) const
316 {
317 return date.GetWeekOfMonth(GetWindowStyle() & wxCAL_MONDAY_FIRST
318 ? wxDateTime::Monday_First
319 : wxDateTime::Sunday_First);
320 }
321
322 // ----------------------------------------------------------------------------
323 // size management
324 // ----------------------------------------------------------------------------
325
326 // this is a composite control and it must arrange its parts each time its
327 // size or position changes: the combobox and spinctrl are along the top of
328 // the available area and the calendar takes up therest of the space
329
330 // the constants used for the layout
331 #define VERT_MARGIN 5 // distance between combo and calendar
332 #define HORZ_MARGIN 15 // spin
333
334 wxSize wxCalendarCtrl::DoGetBestSize() const
335 {
336 // calc the size of the calendar
337 ((wxCalendarCtrl *)this)->RecalcGeometry(); // const_cast
338
339 wxCoord width = 7*m_widthCol,
340 height = 7*m_heightRow;
341
342 wxSize sizeCombo = m_comboMonth->GetBestSize(),
343 sizeSpin = m_spinYear->GetBestSize();
344
345 height += VERT_MARGIN + wxMax(sizeCombo.y, sizeSpin.y);
346
347 return wxSize(width, height);
348 }
349
350 void wxCalendarCtrl::DoSetSize(int x, int y,
351 int width, int height,
352 int sizeFlags)
353 {
354 wxControl::DoSetSize(x, y, width, height, sizeFlags);
355 }
356
357 void wxCalendarCtrl::DoMoveWindow(int x, int y, int width, int height)
358 {
359 wxSize sizeCombo = m_comboMonth->GetSize();
360 m_comboMonth->Move(x, y);
361
362 int xDiff = sizeCombo.x + HORZ_MARGIN;
363 m_spinYear->SetSize(x + xDiff, y, width - xDiff, sizeCombo.y);
364
365 wxSize sizeSpin = m_spinYear->GetSize();
366 int yDiff = wxMax(sizeSpin.y, sizeCombo.y) + VERT_MARGIN;
367
368 wxControl::DoMoveWindow(x, y + yDiff, width, height - yDiff);
369 }
370
371 void wxCalendarCtrl::DoGetPosition(int *x, int *y) const
372 {
373 wxControl::DoGetPosition(x, y);
374
375 // our real top corner is not in this position
376 if ( y )
377 {
378 *y -= m_comboMonth->GetSize().y + VERT_MARGIN;
379 }
380 }
381
382 void wxCalendarCtrl::DoGetSize(int *width, int *height) const
383 {
384 wxControl::DoGetSize(width, height);
385
386 // our real height is bigger
387 if ( height )
388 {
389 *height += m_comboMonth->GetSize().y + VERT_MARGIN;
390 }
391 }
392
393 void wxCalendarCtrl::RecalcGeometry()
394 {
395 if ( m_widthCol != 0 )
396 return;
397
398 wxClientDC dc(this);
399
400 dc.SetFont(m_font);
401
402 // determine the column width (we assume that the weekday names are always
403 // wider (in any language) than the numbers)
404 m_widthCol = 0;
405 wxDateTime::WeekDay wd;
406 for ( wd = wxDateTime::Sun; wd < wxDateTime::Inv_WeekDay; wxNextWDay(wd) )
407 {
408 wxCoord width;
409 dc.GetTextExtent(m_weekdays[wd], &width, &m_heightRow);
410 if ( width > m_widthCol )
411 {
412 m_widthCol = width;
413 }
414 }
415
416 // leave some margins
417 m_widthCol += 2;
418 m_heightRow += 2;
419 }
420
421 // ----------------------------------------------------------------------------
422 // drawing
423 // ----------------------------------------------------------------------------
424
425 void wxCalendarCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
426 {
427 wxPaintDC dc(this);
428
429 dc.SetFont(m_font);
430
431 RecalcGeometry();
432
433 #if DEBUG_PAINT
434 printf("--- starting to paint, selection: %s, week %u\n",
435 m_date.Format("%a %d-%m-%Y %H:%M:%S").c_str(),
436 GetWeek(m_date));
437 #endif
438
439 // first draw the week days
440 if ( IsExposed(0, 0, 7*m_widthCol, m_heightRow) )
441 {
442 #if DEBUG_PAINT
443 puts("painting the header");
444 #endif
445
446 dc.SetTextForeground(*wxBLUE);
447 dc.SetBrush(wxBrush(*wxLIGHT_GREY, wxSOLID));
448 dc.SetBackgroundMode(wxTRANSPARENT);
449 dc.SetPen(*wxLIGHT_GREY_PEN);
450 dc.DrawRectangle(0, 0, 7*m_widthCol, m_heightRow);
451
452 bool startOnMonday = (GetWindowStyle() & wxCAL_MONDAY_FIRST) != 0;
453 for ( size_t wd = 0; wd < 7; wd++ )
454 {
455 size_t n;
456 if ( startOnMonday )
457 n = wd == 6 ? 0 : wd + 1;
458 else
459 n = wd;
460
461 dc.DrawText(m_weekdays[n], wd*m_widthCol + 1, 0);
462 }
463 }
464
465 // then the calendar itself
466 dc.SetTextForeground(*wxBLACK);
467 //dc.SetFont(*wxNORMAL_FONT);
468
469 wxCoord y = m_heightRow;
470
471 wxDateTime date = GetStartDate();
472 #if DEBUG_PAINT
473 printf("starting calendar from %s\n",
474 date.Format("%a %d-%m-%Y %H:%M:%S").c_str());
475 #endif
476
477 dc.SetBackgroundMode(wxSOLID);
478 for ( size_t nWeek = 1; nWeek <= 6; nWeek++, y += m_heightRow )
479 {
480 // if the update region doesn't intersect this row, don't paint it
481 if ( !IsExposed(0, y, 7*m_widthCol, m_heightRow - 1) )
482 {
483 date += wxDateSpan::Week();
484
485 continue;
486 }
487
488 #if DEBUG_PAINT
489 printf("painting week %d at y = %d\n", nWeek, y);
490 #endif
491
492 for ( size_t wd = 0; wd < 7; wd++ )
493 {
494 if ( IsDateShown(date) )
495 {
496 // don't use wxDate::Format() which prepends 0s
497 wxString day = wxString::Format(_T("%u"), date.GetDay());
498 wxCoord width;
499 dc.GetTextExtent(day, &width, (wxCoord *)NULL);
500
501 bool isSel = m_date == date;
502 if ( isSel )
503 {
504 dc.SetTextForeground(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
505 dc.SetTextBackground(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_HIGHLIGHT));
506 }
507
508 dc.DrawText(day, wd*m_widthCol + (m_widthCol - width) / 2, y);
509
510 if ( isSel )
511 {
512 dc.SetTextForeground(m_foregroundColour);
513 dc.SetTextBackground(m_backgroundColour);
514 }
515 }
516 //else: just don't draw it
517
518 date += wxDateSpan::Day();
519 }
520 }
521 #if DEBUG_PAINT
522 puts("+++ finished painting");
523 #endif
524 }
525
526 void wxCalendarCtrl::RefreshDate(const wxDateTime& date)
527 {
528 RecalcGeometry();
529
530 wxRect rect;
531
532 // always refresh the whole row at once because our OnPaint() will draw
533 // the whole row anyhow - and this allows the small optimisation in
534 // OnClick() below to work
535 rect.x = 0;
536 rect.y = m_heightRow * GetWeek(date);
537 rect.width = 7*m_widthCol;
538 rect.height = m_heightRow;
539
540 #if DEBUG_PAINT
541 printf("*** refreshing week %d at (%d, %d)-(%d, %d)\n",
542 GetWeek(date),
543 rect.x, rect.y,
544 rect.x + rect.width, rect.y + rect.height);
545 #endif
546
547 Refresh(TRUE, &rect);
548 }
549
550 // ----------------------------------------------------------------------------
551 // mouse handling
552 // ----------------------------------------------------------------------------
553
554 void wxCalendarCtrl::OnClick(wxMouseEvent& event)
555 {
556 RecalcGeometry();
557
558 wxDateTime date;
559 if ( !HitTest(event.GetPosition(), &date) )
560 {
561 event.Skip();
562 }
563 else
564 {
565 ChangeDay(date);
566
567 GenerateEvent(wxEVT_CALENDAR_DAY_CHANGED);
568 }
569 }
570
571 bool wxCalendarCtrl::HitTest(const wxPoint& pos, wxDateTime *date)
572 {
573 RecalcGeometry();
574
575 wxCoord y = pos.y;
576 if ( y < m_heightRow )
577 return FALSE;
578
579 y -= m_heightRow;
580 int week = y / m_heightRow,
581 wday = pos.x / m_widthCol;
582
583 if ( week >= 6 || wday >= 7 )
584 return FALSE;
585
586 wxCHECK_MSG( date, FALSE, _T("bad pointer in wxCalendarCtrl::HitTest") );
587
588 *date = GetStartDate();
589 *date += wxDateSpan::Days(7*week + wday);
590
591 return IsDateShown(*date);
592 }
593
594 // ----------------------------------------------------------------------------
595 // subcontrols events handling
596 // ----------------------------------------------------------------------------
597
598 void wxCalendarCtrl::OnMonthChange(wxCommandEvent& event)
599 {
600 wxDateTime::Tm tm = m_date.GetTm();
601
602 wxDateTime::Month mon = (wxDateTime::Month)event.GetInt();
603 if ( tm.mday > wxDateTime::GetNumberOfDays(mon, tm.year) )
604 {
605 tm.mday = wxDateTime::GetNumberOfDays(mon, tm.year);
606 }
607
608 SetDate(wxDateTime(tm.mday, mon, tm.year));
609
610 GenerateEvent(wxEVT_CALENDAR_MONTH_CHANGED);
611 }
612
613 void wxCalendarCtrl::OnYearChange(wxSpinEvent& event)
614 {
615 wxDateTime::Tm tm = m_date.GetTm();
616
617 int year = (int)event.GetInt();
618 if ( tm.mday > wxDateTime::GetNumberOfDays(tm.mon, year) )
619 {
620 tm.mday = wxDateTime::GetNumberOfDays(tm.mon, year);
621 }
622
623 SetDate(wxDateTime(tm.mday, tm.mon, year));
624
625 GenerateEvent(wxEVT_CALENDAR_YEAR_CHANGED);
626 }
627
628 // ----------------------------------------------------------------------------
629 // keyboard interface
630 // ----------------------------------------------------------------------------
631
632 void wxCalendarCtrl::OnChar(wxKeyEvent& event)
633 {
634 switch ( event.KeyCode() )
635 {
636 case _T('+'):
637 case WXK_ADD:
638 SetDateAndNotify(m_date + wxDateSpan::Year());
639 break;
640
641 case _T('-'):
642 case WXK_SUBTRACT:
643 SetDateAndNotify(m_date - wxDateSpan::Year());
644 break;
645
646 case WXK_PRIOR:
647 SetDateAndNotify(m_date - wxDateSpan::Month());
648 break;
649
650 case WXK_NEXT:
651 SetDateAndNotify(m_date + wxDateSpan::Month());
652 break;
653
654 case WXK_RIGHT:
655 if ( event.ControlDown() )
656 SetDateAndNotify(wxDateTime(m_date).SetToNextWeekDay(
657 GetWindowStyle() & wxCAL_MONDAY_FIRST
658 ? wxDateTime::Sun : wxDateTime::Sat));
659 else
660 SetDateAndNotify(m_date + wxDateSpan::Day());
661 break;
662
663 case WXK_LEFT:
664 if ( event.ControlDown() )
665 SetDateAndNotify(wxDateTime(m_date).SetToPrevWeekDay(
666 GetWindowStyle() & wxCAL_MONDAY_FIRST
667 ? wxDateTime::Mon : wxDateTime::Sun));
668 else
669 SetDateAndNotify(m_date - wxDateSpan::Day());
670 break;
671
672 case WXK_UP:
673 SetDateAndNotify(m_date - wxDateSpan::Week());
674 break;
675
676 case WXK_DOWN:
677 SetDateAndNotify(m_date + wxDateSpan::Week());
678 break;
679
680 case WXK_HOME:
681 if ( event.ControlDown() )
682 SetDateAndNotify(wxDateTime::Today());
683 else
684 SetDateAndNotify(wxDateTime(1, m_date.GetMonth(), m_date.GetYear()));
685 break;
686
687 case WXK_END:
688 SetDateAndNotify(wxDateTime(m_date).SetToLastMonthDay());
689 break;
690
691 default:
692 event.Skip();
693 }
694 }
695
696 // ----------------------------------------------------------------------------
697 // wxCalendarEvent
698 // ----------------------------------------------------------------------------
699
700 void wxCalendarCtrl::GenerateEvent(wxEventType type)
701 {
702 // we're called for a change in some particular date field but we always
703 // also generate a generic "changed" event
704 wxCalendarEvent event(this, type);
705 wxCalendarEvent event2(this, wxEVT_CALENDAR_SEL_CHANGED);
706
707 (void)GetEventHandler()->ProcessEvent(event);
708 (void)GetEventHandler()->ProcessEvent(event2);
709 }
710
711 wxCalendarEvent::wxCalendarEvent(wxCalendarCtrl *cal, wxEventType type)
712 : wxCommandEvent(type, cal->GetId())
713 {
714 m_date = cal->GetDate();
715 }