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