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