Work around wxDateTime::ParseFormat() problem in generic wxDateTimePickerCtrl.
[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 #ifndef WX_PRECOMP
29 #include "wx/dialog.h"
30 #include "wx/dcmemory.h"
31 #include "wx/intl.h"
32 #include "wx/panel.h"
33 #include "wx/textctrl.h"
34 #include "wx/valtext.h"
35 #endif
36
37 #include "wx/calctrl.h"
38 #include "wx/combo.h"
39
40 #include "wx/datectrl.h"
41 #include "wx/generic/datectrl.h"
42
43 // ----------------------------------------------------------------------------
44 // constants
45 // ----------------------------------------------------------------------------
46
47
48 // ----------------------------------------------------------------------------
49 // global variables
50 // ----------------------------------------------------------------------------
51
52
53 // ----------------------------------------------------------------------------
54 // local classes
55 // ----------------------------------------------------------------------------
56
57 class wxCalendarComboPopup : public wxCalendarCtrl,
58 public wxComboPopup
59 {
60 public:
61
62 wxCalendarComboPopup() : wxCalendarCtrl(),
63 wxComboPopup()
64 {
65 }
66
67 virtual void Init()
68 {
69 }
70
71 // NB: Don't create lazily since it didn't work that way before
72 // wxComboCtrl was used, and changing behaviour would almost
73 // certainly introduce new bugs.
74 virtual bool Create(wxWindow* parent)
75 {
76 if ( !wxCalendarCtrl::Create(parent, wxID_ANY, wxDefaultDateTime,
77 wxPoint(0, 0), wxDefaultSize,
78 wxCAL_SEQUENTIAL_MONTH_SELECTION
79 | wxCAL_SHOW_HOLIDAYS | wxBORDER_SUNKEN) )
80 return false;
81
82 SetFormat(GetLocaleDateFormat());
83
84 m_useSize = wxCalendarCtrl::GetBestSize();
85
86 wxWindow* tx = m_combo->GetTextCtrl();
87 if ( !tx )
88 tx = m_combo;
89
90 tx->Connect(wxEVT_KILL_FOCUS,
91 wxFocusEventHandler(wxCalendarComboPopup::OnKillTextFocus),
92 NULL, this);
93
94 return true;
95 }
96
97 virtual wxSize GetAdjustedSize(int WXUNUSED(minWidth),
98 int WXUNUSED(prefHeight),
99 int WXUNUSED(maxHeight))
100 {
101 return m_useSize;
102 }
103
104 virtual wxWindow *GetControl() { return this; }
105
106 void SetDateValue(const wxDateTime& date)
107 {
108 if ( date.IsValid() )
109 {
110 m_combo->SetText(date.Format(m_format));
111 SetDate(date);
112 }
113 else // invalid date
114 {
115 wxASSERT_MSG( HasDPFlag(wxDP_ALLOWNONE),
116 wxT("this control must have a valid date") );
117
118 m_combo->SetText(wxEmptyString);
119 }
120 }
121
122 bool IsTextEmpty() const
123 {
124 return m_combo->GetTextCtrl()->IsEmpty();
125 }
126
127 bool ParseDateTime(const wxString& s, wxDateTime* pDt)
128 {
129 wxASSERT(pDt);
130
131 if ( !s.empty() )
132 {
133 pDt->ParseFormat(s, m_format);
134 if ( !pDt->IsValid() )
135 return false;
136 }
137
138 return true;
139 }
140
141 void SendDateEvent(const wxDateTime& dt)
142 {
143 // Sends both wxCalendarEvent and wxDateEvent
144 wxWindow* datePicker = m_combo->GetParent();
145
146 wxCalendarEvent cev(datePicker, dt, wxEVT_CALENDAR_SEL_CHANGED);
147 datePicker->GetEventHandler()->ProcessEvent(cev);
148
149 wxDateEvent event(datePicker, dt, wxEVT_DATE_CHANGED);
150 datePicker->GetEventHandler()->ProcessEvent(event);
151 }
152
153 private:
154
155 void OnCalKey(wxKeyEvent & ev)
156 {
157 if (ev.GetKeyCode() == WXK_ESCAPE && !ev.HasModifiers())
158 Dismiss();
159 else
160 ev.Skip();
161 }
162
163 void OnSelChange(wxCalendarEvent &ev)
164 {
165 m_combo->SetText(GetDate().Format(m_format));
166
167 if ( ev.GetEventType() == wxEVT_CALENDAR_DOUBLECLICKED )
168 {
169 Dismiss();
170 }
171
172 SendDateEvent(GetDate());
173 }
174
175 void OnKillTextFocus(wxFocusEvent &ev)
176 {
177 ev.Skip();
178
179 const wxDateTime& dtOld = GetDate();
180
181 wxDateTime dt;
182 wxString value = m_combo->GetValue();
183 if ( !ParseDateTime(value, &dt) )
184 {
185 if ( !HasDPFlag(wxDP_ALLOWNONE) )
186 dt = dtOld;
187 }
188
189 m_combo->SetText(GetStringValueFor(dt));
190
191 if ( !dt.IsValid() && HasDPFlag(wxDP_ALLOWNONE) )
192 return;
193
194 // notify that we had to change the date after validation
195 if ( (dt.IsValid() && (!dtOld.IsValid() || dt != dtOld)) ||
196 (!dt.IsValid() && dtOld.IsValid()) )
197 {
198 SetDate(dt);
199 SendDateEvent(dt);
200 }
201 }
202
203 bool HasDPFlag(int flag) const
204 {
205 return m_combo->GetParent()->HasFlag(flag);
206 }
207
208 // Return the format to be used for the dates shown by the control. This
209 // functions honours wxDP_SHOWCENTURY flag.
210 wxString GetLocaleDateFormat() const
211 {
212 #if wxUSE_INTL
213 wxString fmt = wxLocale::GetInfo(wxLOCALE_SHORT_DATE_FMT);
214 if ( HasDPFlag(wxDP_SHOWCENTURY) )
215 fmt.Replace("%y", "%Y");
216
217 return fmt;
218 #else // !wxUSE_INTL
219 return wxT("x");
220 #endif // wxUSE_INTL/!wxUSE_INTL
221 }
222
223 bool SetFormat(const wxString& fmt)
224 {
225 m_format = fmt;
226
227 if ( m_combo )
228 {
229 wxArrayString allowedChars;
230 for ( wxChar c = wxT('0'); c <= wxT('9'); c++ )
231 allowedChars.Add(wxString(c, 1));
232
233 const wxChar *p2 = m_format.c_str();
234 while ( *p2 )
235 {
236 if ( *p2 == '%')
237 p2 += 2;
238 else
239 allowedChars.Add(wxString(*p2++, 1));
240 }
241
242 #if wxUSE_VALIDATORS
243 wxTextValidator tv(wxFILTER_INCLUDE_CHAR_LIST);
244 tv.SetIncludes(allowedChars);
245 m_combo->SetValidator(tv);
246 #endif
247
248 if ( GetDate().IsValid() )
249 m_combo->SetText(GetDate().Format(m_format));
250 }
251
252 return true;
253 }
254
255 virtual void SetStringValue(const wxString& s)
256 {
257 wxDateTime dt;
258 if ( !s.empty() && ParseDateTime(s, &dt) )
259 SetDate(dt);
260 //else: keep the old value
261 }
262
263 virtual wxString GetStringValue() const
264 {
265 return GetStringValueFor(GetDate());
266 }
267
268 private:
269 // returns either the given date representation using the current format or
270 // an empty string if it's invalid
271 wxString GetStringValueFor(const wxDateTime& dt) const
272 {
273 wxString val;
274 if ( dt.IsValid() )
275 val = dt.Format(m_format);
276
277 return val;
278 }
279
280 wxSize m_useSize;
281 wxString m_format;
282
283 DECLARE_EVENT_TABLE()
284 };
285
286
287 BEGIN_EVENT_TABLE(wxCalendarComboPopup, wxCalendarCtrl)
288 EVT_KEY_DOWN(wxCalendarComboPopup::OnCalKey)
289 EVT_CALENDAR_SEL_CHANGED(wxID_ANY, wxCalendarComboPopup::OnSelChange)
290 EVT_CALENDAR_PAGE_CHANGED(wxID_ANY, wxCalendarComboPopup::OnSelChange)
291 EVT_CALENDAR(wxID_ANY, wxCalendarComboPopup::OnSelChange)
292 END_EVENT_TABLE()
293
294
295 // ============================================================================
296 // wxDatePickerCtrlGeneric implementation
297 // ============================================================================
298
299 BEGIN_EVENT_TABLE(wxDatePickerCtrlGeneric, wxDatePickerCtrlBase)
300 EVT_TEXT(wxID_ANY, wxDatePickerCtrlGeneric::OnText)
301 EVT_SIZE(wxDatePickerCtrlGeneric::OnSize)
302 EVT_SET_FOCUS(wxDatePickerCtrlGeneric::OnFocus)
303 END_EVENT_TABLE()
304
305 #ifndef wxHAS_NATIVE_DATEPICKCTRL
306 IMPLEMENT_DYNAMIC_CLASS(wxDatePickerCtrl, wxControl)
307 #endif
308
309 // ----------------------------------------------------------------------------
310 // creation
311 // ----------------------------------------------------------------------------
312
313 bool wxDatePickerCtrlGeneric::Create(wxWindow *parent,
314 wxWindowID id,
315 const wxDateTime& date,
316 const wxPoint& pos,
317 const wxSize& size,
318 long style,
319 const wxValidator& validator,
320 const wxString& name)
321 {
322 wxASSERT_MSG( !(style & wxDP_SPIN),
323 wxT("wxDP_SPIN style not supported, use wxDP_DEFAULT") );
324
325 if ( !wxControl::Create(parent, id, pos, size,
326 style | wxCLIP_CHILDREN | wxWANTS_CHARS | wxBORDER_NONE,
327 validator, name) )
328 {
329 return false;
330 }
331
332 InheritAttributes();
333
334 m_combo = new wxComboCtrl(this, -1, wxEmptyString,
335 wxDefaultPosition, wxDefaultSize);
336
337 m_combo->SetCtrlMainWnd(this);
338
339 m_popup = new wxCalendarComboPopup();
340
341 #if defined(__WXMSW__)
342 // without this keyboard navigation in month control doesn't work
343 m_combo->UseAltPopupWindow();
344 #endif
345 m_combo->SetPopupControl(m_popup);
346
347 m_popup->SetDateValue(date.IsValid() ? date : wxDateTime::Today());
348
349 SetInitialSize(size);
350
351 return true;
352 }
353
354
355 void wxDatePickerCtrlGeneric::Init()
356 {
357 m_combo = NULL;
358 m_popup = NULL;
359 }
360
361 wxDatePickerCtrlGeneric::~wxDatePickerCtrlGeneric()
362 {
363 }
364
365 bool wxDatePickerCtrlGeneric::Destroy()
366 {
367 if ( m_combo )
368 m_combo->Destroy();
369
370 m_combo = NULL;
371 m_popup = NULL;
372
373 return wxControl::Destroy();
374 }
375
376 // ----------------------------------------------------------------------------
377 // overridden base class methods
378 // ----------------------------------------------------------------------------
379
380 wxSize wxDatePickerCtrlGeneric::DoGetBestSize() const
381 {
382 return m_combo->GetBestSize();
383 }
384
385 wxWindowList wxDatePickerCtrlGeneric::GetCompositeWindowParts() const
386 {
387 wxWindowList parts;
388 parts.push_back(m_combo);
389 parts.push_back(m_popup);
390 return parts;
391 }
392
393 // ----------------------------------------------------------------------------
394 // wxDatePickerCtrlGeneric API
395 // ----------------------------------------------------------------------------
396
397 bool
398 wxDatePickerCtrlGeneric::SetDateRange(const wxDateTime& lowerdate,
399 const wxDateTime& upperdate)
400 {
401 return m_popup->SetDateRange(lowerdate, upperdate);
402 }
403
404
405 wxDateTime wxDatePickerCtrlGeneric::GetValue() const
406 {
407 if ( HasFlag(wxDP_ALLOWNONE) && m_popup->IsTextEmpty() )
408 return wxInvalidDateTime;
409 return m_popup->GetDate();
410 }
411
412
413 void wxDatePickerCtrlGeneric::SetValue(const wxDateTime& date)
414 {
415 m_popup->SetDateValue(date);
416 }
417
418
419 bool wxDatePickerCtrlGeneric::GetRange(wxDateTime *dt1, wxDateTime *dt2) const
420 {
421 return m_popup->GetDateRange(dt1, dt2);
422 }
423
424
425 void
426 wxDatePickerCtrlGeneric::SetRange(const wxDateTime &dt1, const wxDateTime &dt2)
427 {
428 m_popup->SetDateRange(dt1, dt2);
429 }
430
431 wxCalendarCtrl *wxDatePickerCtrlGeneric::GetCalendar() const
432 {
433 return m_popup;
434 }
435
436 // ----------------------------------------------------------------------------
437 // event handlers
438 // ----------------------------------------------------------------------------
439
440
441 void wxDatePickerCtrlGeneric::OnSize(wxSizeEvent& event)
442 {
443 if ( m_combo )
444 m_combo->SetSize(GetClientSize());
445
446 event.Skip();
447 }
448
449
450 void wxDatePickerCtrlGeneric::OnText(wxCommandEvent &ev)
451 {
452 ev.SetEventObject(this);
453 ev.SetId(GetId());
454 GetParent()->GetEventHandler()->ProcessEvent(ev);
455
456 // We'll create an additional event if the date is valid.
457 // If the date isn't valid, the user's probably in the middle of typing
458 wxDateTime dt;
459 if ( !m_popup || !m_popup->ParseDateTime(m_combo->GetValue(), &dt) )
460 return;
461
462 m_popup->SendDateEvent(dt);
463 }
464
465
466 void wxDatePickerCtrlGeneric::OnFocus(wxFocusEvent& WXUNUSED(event))
467 {
468 m_combo->SetFocus();
469 }
470
471
472 #endif // wxUSE_DATEPICKCTRL
473