fix for crash under wxX11: we get text event there when m_popup is still NULL
[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("%x");
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)
196 {
197 return m_combo->GetParent()->HasFlag(flag);
198 }
199
200 bool SetFormat(const wxString& fmt)
201 {
202 m_format.clear();
203
204 wxDateTime dt;
205 dt.ParseFormat(wxT("2003-10-13"), wxT("%Y-%m-%d"));
206 wxString str(dt.Format(fmt));
207
208 const wxChar *p = str.c_str();
209 while ( *p )
210 {
211 int n=wxAtoi(p);
212 if (n == dt.GetDay())
213 {
214 m_format.Append(wxT("%d"));
215 p += 2;
216 }
217 else if (n == (int)dt.GetMonth()+1)
218 {
219 m_format.Append(wxT("%m"));
220 p += 2;
221 }
222 else if (n == dt.GetYear())
223 {
224 m_format.Append(wxT("%Y"));
225 p += 4;
226 }
227 else if (n == (dt.GetYear() % 100))
228 {
229 if ( HasDPFlag(wxDP_SHOWCENTURY) )
230 m_format.Append(wxT("%Y"));
231 else
232 m_format.Append(wxT("%y"));
233 p += 2;
234 }
235 else
236 m_format.Append(*p++);
237 }
238
239 if ( m_combo )
240 {
241 wxArrayString allowedChars;
242 for ( wxChar c = _T('0'); c <= _T('9'); c++ )
243 allowedChars.Add(wxString(c, 1));
244
245 const wxChar *p2 = m_format.c_str();
246 while ( *p2 )
247 {
248 if ( *p2 == '%')
249 p2 += 2;
250 else
251 allowedChars.Add(wxString(*p2++, 1));
252 }
253
254 #if wxUSE_VALIDATORS
255 wxTextValidator tv(wxFILTER_INCLUDE_CHAR_LIST);
256 tv.SetIncludes(allowedChars);
257 m_combo->SetValidator(tv);
258 #endif
259
260 if ( GetDate().IsValid() )
261 m_combo->SetText(GetDate().Format(m_format));
262 }
263
264 return true;
265 }
266
267 virtual void SetStringValue(const wxString& s)
268 {
269 wxDateTime dt;
270 if ( !s.empty() && ParseDateTime(s, &dt) )
271 SetDate(dt);
272 //else: keep the old value
273 }
274
275 virtual wxString GetStringValue() const
276 {
277 return GetStringValueFor(GetDate());
278 }
279
280 private:
281 // returns either the given date representation using the current format or
282 // an empty string if it's invalid
283 wxString GetStringValueFor(const wxDateTime& dt) const
284 {
285 wxString val;
286 if ( dt.IsValid() )
287 val = dt.Format(m_format);
288
289 return val;
290 }
291
292 wxSize m_useSize;
293 wxString m_format;
294
295 DECLARE_EVENT_TABLE()
296 };
297
298
299 BEGIN_EVENT_TABLE(wxCalendarComboPopup, wxCalendarCtrl)
300 EVT_KEY_DOWN(wxCalendarComboPopup::OnCalKey)
301 EVT_CALENDAR_SEL_CHANGED(wxID_ANY, wxCalendarComboPopup::OnSelChange)
302 EVT_CALENDAR_PAGE_CHANGED(wxID_ANY, wxCalendarComboPopup::OnSelChange)
303 EVT_CALENDAR(wxID_ANY, wxCalendarComboPopup::OnSelChange)
304 END_EVENT_TABLE()
305
306
307 // ============================================================================
308 // wxDatePickerCtrlGeneric implementation
309 // ============================================================================
310
311 BEGIN_EVENT_TABLE(wxDatePickerCtrlGeneric, wxDatePickerCtrlBase)
312 EVT_TEXT(wxID_ANY, wxDatePickerCtrlGeneric::OnText)
313 EVT_SIZE(wxDatePickerCtrlGeneric::OnSize)
314 EVT_SET_FOCUS(wxDatePickerCtrlGeneric::OnFocus)
315 END_EVENT_TABLE()
316
317 #ifndef wxHAS_NATIVE_DATEPICKCTRL
318 IMPLEMENT_DYNAMIC_CLASS(wxDatePickerCtrl, wxControl)
319 #endif
320
321 // ----------------------------------------------------------------------------
322 // creation
323 // ----------------------------------------------------------------------------
324
325 bool wxDatePickerCtrlGeneric::Create(wxWindow *parent,
326 wxWindowID id,
327 const wxDateTime& date,
328 const wxPoint& pos,
329 const wxSize& size,
330 long style,
331 const wxValidator& validator,
332 const wxString& name)
333 {
334 wxASSERT_MSG( !(style & wxDP_SPIN),
335 _T("wxDP_SPIN style not supported, use wxDP_DEFAULT") );
336
337 if ( !wxControl::Create(parent, id, pos, size,
338 style | wxCLIP_CHILDREN | wxWANTS_CHARS | wxBORDER_NONE,
339 validator, name) )
340 {
341 return false;
342 }
343
344 InheritAttributes();
345
346 m_combo = new wxComboCtrl(this, -1, wxEmptyString,
347 wxDefaultPosition, wxDefaultSize);
348
349 m_combo->SetCtrlMainWnd(this);
350
351 m_popup = new wxCalendarComboPopup();
352
353 #if defined(__WXMSW__)
354 // without this keyboard navigation in month control doesn't work
355 m_combo->UseAltPopupWindow();
356 #endif
357 m_combo->SetPopupControl(m_popup);
358
359 m_popup->SetDateValue(date.IsValid() ? date : wxDateTime::Today());
360
361 SetInitialSize(size);
362
363 return true;
364 }
365
366
367 void wxDatePickerCtrlGeneric::Init()
368 {
369 m_combo = NULL;
370 m_popup = NULL;
371 }
372
373 wxDatePickerCtrlGeneric::~wxDatePickerCtrlGeneric()
374 {
375 }
376
377 bool wxDatePickerCtrlGeneric::Destroy()
378 {
379 if ( m_combo )
380 m_combo->Destroy();
381
382 m_combo = NULL;
383 m_popup = NULL;
384
385 return wxControl::Destroy();
386 }
387
388 // ----------------------------------------------------------------------------
389 // overridden base class methods
390 // ----------------------------------------------------------------------------
391
392 wxSize wxDatePickerCtrlGeneric::DoGetBestSize() const
393 {
394 return m_combo->GetBestSize();
395 }
396
397 // ----------------------------------------------------------------------------
398 // wxDatePickerCtrlGeneric API
399 // ----------------------------------------------------------------------------
400
401 bool
402 wxDatePickerCtrlGeneric::SetDateRange(const wxDateTime& lowerdate,
403 const wxDateTime& upperdate)
404 {
405 return m_popup->SetDateRange(lowerdate, upperdate);
406 }
407
408
409 wxDateTime wxDatePickerCtrlGeneric::GetValue() const
410 {
411 return m_popup->GetDate();
412 }
413
414
415 void wxDatePickerCtrlGeneric::SetValue(const wxDateTime& date)
416 {
417 m_popup->SetDateValue(date);
418 }
419
420
421 bool wxDatePickerCtrlGeneric::GetRange(wxDateTime *dt1, wxDateTime *dt2) const
422 {
423 return m_popup->GetDateRange(dt1, dt2);
424 }
425
426
427 void
428 wxDatePickerCtrlGeneric::SetRange(const wxDateTime &dt1, const wxDateTime &dt2)
429 {
430 m_popup->SetDateRange(dt1, dt2);
431 }
432
433 wxCalendarCtrl *wxDatePickerCtrlGeneric::GetCalendar() const
434 {
435 return m_popup;
436 }
437
438 // ----------------------------------------------------------------------------
439 // event handlers
440 // ----------------------------------------------------------------------------
441
442
443 void wxDatePickerCtrlGeneric::OnSize(wxSizeEvent& event)
444 {
445 if ( m_combo )
446 m_combo->SetSize(GetClientSize());
447
448 event.Skip();
449 }
450
451
452 void wxDatePickerCtrlGeneric::OnText(wxCommandEvent &ev)
453 {
454 ev.SetEventObject(this);
455 ev.SetId(GetId());
456 GetParent()->GetEventHandler()->ProcessEvent(ev);
457
458 // We'll create an additional event if the date is valid.
459 // If the date isn't valid, the user's probably in the middle of typing
460 wxDateTime dt;
461 if ( !m_popup || !m_popup->ParseDateTime(m_combo->GetValue(), &dt) )
462 return;
463
464 m_popup->SendDateEvent(dt);
465 }
466
467
468 void wxDatePickerCtrlGeneric::OnFocus(wxFocusEvent& WXUNUSED(event))
469 {
470 m_combo->SetFocus();
471 }
472
473
474 #endif // wxUSE_DATEPICKCTRL
475