Merge r53510 from 2.8 branch
[wxWidgets.git] / src / generic / datectlg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/datectlg.cpp
3 // Purpose: generic wxDatePickerCtrlGeneric implementation
4 // Author: Andreas Pflug
5 // Modified by:
6 // Created: 2005-01-19
7 // RCS-ID: $Id$
8 // Copyright: (c) 2005 Andreas Pflug <pgadmin@pse-consulting.de>
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #if wxUSE_DATEPICKCTRL
27
28 #include "wx/datectrl.h"
29
30 // use this version if we're explicitly requested to do it or if it's the only
31 // one we have
32 #if !defined(wxHAS_NATIVE_DATEPICKCTRL) || \
33 (defined(wxUSE_DATEPICKCTRL_GENERIC) && wxUSE_DATEPICKCTRL_GENERIC)
34
35 #ifndef WX_PRECOMP
36 #include "wx/dialog.h"
37 #include "wx/dcmemory.h"
38 #include "wx/panel.h"
39 #include "wx/textctrl.h"
40 #include "wx/valtext.h"
41 #endif
42
43 #ifdef wxHAS_NATIVE_DATEPICKCTRL
44 // this header is not included from wx/datectrl.h if we have a native
45 // version, but we do need it here
46 #include "wx/generic/datectrl.h"
47 #endif
48
49 #include "wx/dateevt.h"
50
51 #include "wx/calctrl.h"
52 #include "wx/generic/calctrlg.h"
53 #include "wx/combo.h"
54
55 // ----------------------------------------------------------------------------
56 // constants
57 // ----------------------------------------------------------------------------
58
59 #if defined(__WXMSW__)
60 #define CALBORDER 0
61 #else
62 #define CALBORDER 4
63 #endif
64
65 // ----------------------------------------------------------------------------
66 // global variables
67 // ----------------------------------------------------------------------------
68
69
70 // ----------------------------------------------------------------------------
71 // local classes
72 // ----------------------------------------------------------------------------
73
74 class wxCalendarComboPopup : public wxGenericCalendarCtrl,
75 public wxComboPopup
76 {
77 public:
78
79 wxCalendarComboPopup() : wxGenericCalendarCtrl(),
80 wxComboPopup()
81 {
82 }
83
84 virtual void Init()
85 {
86 }
87
88 // NB: Don't create lazily since it didn't work that way before
89 // wxComboCtrl was used, and changing behaviour would almost
90 // certainly introduce new bugs.
91 virtual bool Create(wxWindow* parent)
92 {
93 if ( !wxGenericCalendarCtrl::Create(parent, wxID_ANY, wxDefaultDateTime,
94 wxPoint(0, 0), wxDefaultSize,
95 wxCAL_SHOW_HOLIDAYS | wxBORDER_SUNKEN) )
96 return false;
97
98 wxWindow *yearControl = wxGenericCalendarCtrl::GetYearControl();
99
100 wxClientDC dc(yearControl);
101 dc.SetFont(yearControl->GetFont());
102 wxCoord width, dummy;
103 dc.GetTextExtent(wxT("2000"), &width, &dummy);
104 width += ConvertDialogToPixels(wxSize(20, 0)).x;
105
106 wxSize calSize = wxGenericCalendarCtrl::GetBestSize();
107 wxSize yearSize = yearControl->GetSize();
108 yearSize.x = width;
109
110 wxPoint yearPosition = yearControl->GetPosition();
111
112 SetFormat("%x");
113
114 width = yearPosition.x + yearSize.x+2+CALBORDER/2;
115 if (width < calSize.x-4)
116 width = calSize.x-4;
117
118 int calPos = (width-calSize.x)/2;
119 if (calPos == -1)
120 {
121 calPos = 0;
122 width += 2;
123 }
124 wxGenericCalendarCtrl::SetSize(calPos, 0, calSize.x, calSize.y);
125 yearControl->SetSize(width-yearSize.x-CALBORDER/2, yearPosition.y,
126 yearSize.x, yearSize.y);
127 wxGenericCalendarCtrl::GetMonthControl()->Move(0, 0);
128
129 m_useSize.x = width+CALBORDER/2;
130 m_useSize.y = calSize.y-2+CALBORDER;
131
132 wxWindow* tx = m_combo->GetTextCtrl();
133 if ( !tx )
134 tx = m_combo;
135
136 tx->Connect(wxEVT_KILL_FOCUS,
137 wxFocusEventHandler(wxCalendarComboPopup::OnKillTextFocus),
138 NULL, this);
139
140 return true;
141 }
142
143 virtual wxSize GetAdjustedSize(int WXUNUSED(minWidth),
144 int WXUNUSED(prefHeight),
145 int WXUNUSED(maxHeight))
146 {
147 return m_useSize;
148 }
149
150 virtual wxWindow *GetControl() { return this; }
151
152 void SetDateValue(const wxDateTime& date)
153 {
154 if ( date.IsValid() )
155 {
156 m_combo->SetText(date.Format(m_format));
157 SetDate(date);
158 }
159 else // invalid date
160 {
161 wxASSERT_MSG( HasDPFlag(wxDP_ALLOWNONE),
162 _T("this control must have a valid date") );
163
164 m_combo->SetText(wxEmptyString);
165 }
166 }
167
168 bool ParseDateTime(const wxString& s, wxDateTime* pDt)
169 {
170 wxASSERT(pDt);
171
172 if ( !s.empty() )
173 {
174 pDt->ParseFormat(s.c_str(), m_format);
175 if ( !pDt->IsValid() )
176 return false;
177 }
178
179 return true;
180 }
181
182 void SendDateEvent(const wxDateTime& dt)
183 {
184 // Sends both wxCalendarEvent and wxDateEvent
185 wxWindow* datePicker = m_combo->GetParent();
186
187 wxCalendarEvent cev(datePicker, dt, wxEVT_CALENDAR_SEL_CHANGED);
188 datePicker->GetEventHandler()->ProcessEvent(cev);
189
190 wxDateEvent event(datePicker, dt, wxEVT_DATE_CHANGED);
191 datePicker->GetEventHandler()->ProcessEvent(event);
192 }
193
194 private:
195
196 void OnCalKey(wxKeyEvent & ev)
197 {
198 if (ev.GetKeyCode() == WXK_ESCAPE && !ev.HasModifiers())
199 Dismiss();
200 else
201 ev.Skip();
202 }
203
204 void OnSelChange(wxCalendarEvent &ev)
205 {
206 m_combo->SetText(GetDate().Format(m_format));
207
208 if ( ev.GetEventType() == wxEVT_CALENDAR_DOUBLECLICKED )
209 {
210 Dismiss();
211 }
212
213 SendDateEvent(GetDate());
214 }
215
216 void OnKillTextFocus(wxFocusEvent &ev)
217 {
218 ev.Skip();
219
220 const wxDateTime& dtOld = GetDate();
221
222 wxDateTime dt;
223 wxString value = m_combo->GetValue();
224 if ( !ParseDateTime(value, &dt) )
225 {
226 if ( !HasDPFlag(wxDP_ALLOWNONE) )
227 dt = dtOld;
228 }
229
230 m_combo->SetText(GetStringValueFor(dt));
231
232 if ( !dt.IsValid() && HasDPFlag(wxDP_ALLOWNONE) )
233 return;
234
235 // notify that we had to change the date after validation
236 if ( (dt.IsValid() && (!dtOld.IsValid() || dt != dtOld)) ||
237 (!dt.IsValid() && dtOld.IsValid()) )
238 {
239 SetDate(dt);
240 SendDateEvent(dt);
241 }
242 }
243
244 bool HasDPFlag(int flag)
245 {
246 return m_combo->GetParent()->HasFlag(flag);
247 }
248
249 bool SetFormat(const wxString& fmt)
250 {
251 m_format.clear();
252
253 wxDateTime dt;
254 dt.ParseFormat(wxT("2003-10-13"), wxT("%Y-%m-%d"));
255 wxString str(dt.Format(fmt));
256
257 const wxChar *p = str.c_str();
258 while ( *p )
259 {
260 int n=wxAtoi(p);
261 if (n == dt.GetDay())
262 {
263 m_format.Append(wxT("%d"));
264 p += 2;
265 }
266 else if (n == (int)dt.GetMonth()+1)
267 {
268 m_format.Append(wxT("%m"));
269 p += 2;
270 }
271 else if (n == dt.GetYear())
272 {
273 m_format.Append(wxT("%Y"));
274 p += 4;
275 }
276 else if (n == (dt.GetYear() % 100))
277 {
278 if ( HasDPFlag(wxDP_SHOWCENTURY) )
279 m_format.Append(wxT("%Y"));
280 else
281 m_format.Append(wxT("%y"));
282 p += 2;
283 }
284 else
285 m_format.Append(*p++);
286 }
287
288 if ( m_combo )
289 {
290 wxArrayString allowedChars;
291 for ( wxChar c = _T('0'); c <= _T('9'); c++ )
292 allowedChars.Add(wxString(c, 1));
293
294 const wxChar *p2 = m_format.c_str();
295 while ( *p2 )
296 {
297 if ( *p2 == '%')
298 p2 += 2;
299 else
300 allowedChars.Add(wxString(*p2++, 1));
301 }
302
303 #if wxUSE_VALIDATORS
304 wxTextValidator tv(wxFILTER_INCLUDE_CHAR_LIST);
305 tv.SetIncludes(allowedChars);
306 m_combo->SetValidator(tv);
307 #endif
308
309 if ( GetDate().IsValid() )
310 m_combo->SetText(GetDate().Format(m_format));
311 }
312
313 return true;
314 }
315
316 virtual void SetStringValue(const wxString& s)
317 {
318 wxDateTime dt;
319 if ( !s.empty() && ParseDateTime(s, &dt) )
320 SetDate(dt);
321 //else: keep the old value
322 }
323
324 virtual wxString GetStringValue() const
325 {
326 return GetStringValueFor(GetDate());
327 }
328
329 private:
330 // returns either the given date representation using the current format or
331 // an empty string if it's invalid
332 wxString GetStringValueFor(const wxDateTime& dt) const
333 {
334 wxString val;
335 if ( dt.IsValid() )
336 val = dt.Format(m_format);
337
338 return val;
339 }
340
341 wxSize m_useSize;
342 wxString m_format;
343
344 DECLARE_EVENT_TABLE()
345 };
346
347
348 BEGIN_EVENT_TABLE(wxCalendarComboPopup, wxGenericCalendarCtrl)
349 EVT_KEY_DOWN(wxCalendarComboPopup::OnCalKey)
350 EVT_CALENDAR_SEL_CHANGED(wxID_ANY, wxCalendarComboPopup::OnSelChange)
351 EVT_CALENDAR_PAGE_CHANGED(wxID_ANY, wxCalendarComboPopup::OnSelChange)
352 EVT_CALENDAR(wxID_ANY, wxCalendarComboPopup::OnSelChange)
353 END_EVENT_TABLE()
354
355
356 // ============================================================================
357 // wxDatePickerCtrlGeneric implementation
358 // ============================================================================
359
360 BEGIN_EVENT_TABLE(wxDatePickerCtrlGeneric, wxDatePickerCtrlBase)
361 EVT_TEXT(wxID_ANY, wxDatePickerCtrlGeneric::OnText)
362 EVT_SIZE(wxDatePickerCtrlGeneric::OnSize)
363 EVT_SET_FOCUS(wxDatePickerCtrlGeneric::OnFocus)
364 END_EVENT_TABLE()
365
366 #ifndef wxHAS_NATIVE_DATEPICKCTRL
367 IMPLEMENT_DYNAMIC_CLASS(wxDatePickerCtrl, wxControl)
368 #endif
369
370 // ----------------------------------------------------------------------------
371 // creation
372 // ----------------------------------------------------------------------------
373
374 bool wxDatePickerCtrlGeneric::Create(wxWindow *parent,
375 wxWindowID id,
376 const wxDateTime& date,
377 const wxPoint& pos,
378 const wxSize& size,
379 long style,
380 const wxValidator& validator,
381 const wxString& name)
382 {
383 wxASSERT_MSG( !(style & wxDP_SPIN),
384 _T("wxDP_SPIN style not supported, use wxDP_DEFAULT") );
385
386 if ( !wxControl::Create(parent, id, pos, size,
387 style | wxCLIP_CHILDREN | wxWANTS_CHARS | wxBORDER_NONE,
388 validator, name) )
389 {
390 return false;
391 }
392
393 InheritAttributes();
394
395 m_combo = new wxComboCtrl(this, -1, wxEmptyString,
396 wxDefaultPosition, wxDefaultSize);
397
398 m_combo->SetCtrlMainWnd(this);
399
400 m_popup = new wxCalendarComboPopup();
401
402 #if defined(__WXMSW__)
403 // without this keyboard navigation in month control doesn't work
404 m_combo->UseAltPopupWindow();
405 #endif
406 m_combo->SetPopupControl(m_popup);
407
408 m_popup->SetDateValue(date.IsValid() ? date : wxDateTime::Today());
409
410 SetInitialSize(size);
411
412 return true;
413 }
414
415
416 void wxDatePickerCtrlGeneric::Init()
417 {
418 m_combo = NULL;
419 m_popup = NULL;
420 }
421
422 wxDatePickerCtrlGeneric::~wxDatePickerCtrlGeneric()
423 {
424 }
425
426 bool wxDatePickerCtrlGeneric::Destroy()
427 {
428 if ( m_combo )
429 m_combo->Destroy();
430
431 m_combo = NULL;
432 m_popup = NULL;
433
434 return wxControl::Destroy();
435 }
436
437 // ----------------------------------------------------------------------------
438 // overridden base class methods
439 // ----------------------------------------------------------------------------
440
441 wxSize wxDatePickerCtrlGeneric::DoGetBestSize() const
442 {
443 return m_combo->GetBestSize();
444 }
445
446 // ----------------------------------------------------------------------------
447 // wxDatePickerCtrlGeneric API
448 // ----------------------------------------------------------------------------
449
450 bool
451 wxDatePickerCtrlGeneric::SetDateRange(const wxDateTime& lowerdate,
452 const wxDateTime& upperdate)
453 {
454 return m_popup->SetDateRange(lowerdate, upperdate);
455 }
456
457
458 wxDateTime wxDatePickerCtrlGeneric::GetValue() const
459 {
460 return m_popup->GetDate();
461 }
462
463
464 void wxDatePickerCtrlGeneric::SetValue(const wxDateTime& date)
465 {
466 m_popup->SetDateValue(date);
467 }
468
469
470 bool wxDatePickerCtrlGeneric::GetRange(wxDateTime *dt1, wxDateTime *dt2) const
471 {
472 if (dt1)
473 *dt1 = m_popup->GetLowerDateLimit();
474 if (dt2)
475 *dt2 = m_popup->GetUpperDateLimit();
476 return true;
477 }
478
479
480 void
481 wxDatePickerCtrlGeneric::SetRange(const wxDateTime &dt1, const wxDateTime &dt2)
482 {
483 m_popup->SetDateRange(dt1, dt2);
484 }
485
486 wxGenericCalendarCtrl *wxDatePickerCtrlGeneric::GetCalendar() const
487 {
488 return m_popup;
489 }
490
491 // ----------------------------------------------------------------------------
492 // event handlers
493 // ----------------------------------------------------------------------------
494
495
496 void wxDatePickerCtrlGeneric::OnSize(wxSizeEvent& event)
497 {
498 if ( m_combo )
499 m_combo->SetSize(GetClientSize());
500
501 event.Skip();
502 }
503
504
505 void wxDatePickerCtrlGeneric::OnText(wxCommandEvent &ev)
506 {
507 ev.SetEventObject(this);
508 ev.SetId(GetId());
509 GetParent()->GetEventHandler()->ProcessEvent(ev);
510
511 // We'll create an additional event if the date is valid.
512 // If the date isn't valid, the user's probably in the middle of typing
513 wxDateTime dt;
514 if ( !m_popup->ParseDateTime(m_combo->GetValue(), &dt) )
515 return;
516
517 m_popup->SendDateEvent(dt);
518 }
519
520
521 void wxDatePickerCtrlGeneric::OnFocus(wxFocusEvent& WXUNUSED(event))
522 {
523 m_combo->SetFocus();
524 }
525
526
527 #endif // wxUSE_DATEPICKCTRL_GENERIC
528
529 #endif // wxUSE_DATEPICKCTRL
530