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