Fix for always failing assert in GetValue. Save the same value that
[wxWidgets.git] / src / msw / datectrl.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/datectrl.cpp
3 // Purpose: wxDatePickerCtrl implementation
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 2005-01-09
7 // RCS-ID: $Id$
8 // Copyright: (c) 2005 Vadim Zeitlin <vadim@wxwindows.org>
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/msw/wrapwin.h"
30 #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
31 #include "wx/app.h"
32 #include "wx/intl.h"
33 #include "wx/dcclient.h"
34 #include "wx/msw/private.h"
35 #endif
36
37 #include "wx/datectrl.h"
38 #include "wx/dynlib.h"
39
40 #define _WX_DEFINE_DATE_EVENTS_
41 #include "wx/dateevt.h"
42
43 // apparently some versions of mingw define these macros erroneously
44 #ifndef DateTime_GetSystemtime
45 #define DateTime_GetSystemtime DateTime_GetSystemTime
46 #endif
47
48 #ifndef DateTime_SetSystemtime
49 #define DateTime_SetSystemtime DateTime_SetSystemTime
50 #endif
51
52 IMPLEMENT_DYNAMIC_CLASS(wxDatePickerCtrl, wxControl)
53
54 // ============================================================================
55 // implementation
56 // ============================================================================
57
58 // ----------------------------------------------------------------------------
59 // helpers for wxDateTime <-> SYSTEMTIME conversion
60 // ----------------------------------------------------------------------------
61
62 static inline void wxFromSystemTime(wxDateTime *dt, const SYSTEMTIME& st)
63 {
64 dt->Set(st.wDay,
65 wx_static_cast(wxDateTime::Month, wxDateTime::Jan + st.wMonth - 1),
66 st.wYear,
67 0, 0, 0);
68 }
69
70 static inline void wxToSystemTime(SYSTEMTIME *st, const wxDateTime& dt)
71 {
72 const wxDateTime::Tm tm(dt.GetTm());
73
74 st->wYear = (WXWORD)tm.year;
75 st->wMonth = (WXWORD)(tm.mon - wxDateTime::Jan + 1);
76 st->wDay = tm.mday;
77
78 st->wDayOfWeek =
79 st->wHour =
80 st->wMinute =
81 st->wSecond =
82 st->wMilliseconds = 0;
83 }
84
85 // ----------------------------------------------------------------------------
86 // wxDatePickerCtrl creation
87 // ----------------------------------------------------------------------------
88
89 bool
90 wxDatePickerCtrl::Create(wxWindow *parent,
91 wxWindowID id,
92 const wxDateTime& dt,
93 const wxPoint& pos,
94 const wxSize& size,
95 long style,
96 const wxValidator& validator,
97 const wxString& name)
98 {
99 // although we already call InitCommonControls() in app.cpp which is
100 // supposed to initialize all common controls, in comctl32.dll 4.72 (and
101 // presumably earlier versions 4.70 and 4.71, date time picker not being
102 // supported in < 4.70 anyhow) it does not do it and we have to initialize
103 // it explicitly
104 static bool s_initDone = false; // MT-ok: used from GUI thread only
105 if ( !s_initDone )
106 {
107 #ifndef __WXWINCE__
108 if ( wxApp::GetComCtl32Version() < 470 )
109 {
110 wxLogError(_("This system doesn't support date picker control, please upgrade your version of comctl32.dll"));
111
112 return false;
113 }
114 #endif
115
116 #if wxUSE_DYNLIB_CLASS
117 INITCOMMONCONTROLSEX icex;
118 icex.dwSize = sizeof(icex);
119 icex.dwICC = ICC_DATE_CLASSES;
120
121 wxDynamicLibrary dllComCtl32(
122 #ifdef __WXWINCE__
123 _T("commctrl.dll")
124 #else
125 _T("comctl32.dll")
126 #endif
127 , wxDL_VERBATIM);
128
129 if ( dllComCtl32.IsLoaded() )
130 {
131 typedef BOOL (WINAPI *ICCEx_t)(INITCOMMONCONTROLSEX *);
132 wxDYNLIB_FUNCTION( ICCEx_t, InitCommonControlsEx, dllComCtl32 );
133
134 if ( pfnInitCommonControlsEx )
135 {
136 (*pfnInitCommonControlsEx)(&icex);
137 }
138
139 s_initDone = true;
140 }
141 #endif
142 }
143
144
145 // use wxDP_SPIN if wxDP_DEFAULT (0) was given as style
146 if ( !(style & wxDP_DROPDOWN) )
147 style |= wxDP_SPIN;
148
149 // initialize the base class
150 if ( !CreateControl(parent, id, pos, size, style, validator, name) )
151 return false;
152
153 // create the native control
154 if ( !MSWCreateControl(DATETIMEPICK_CLASS, wxEmptyString, pos, size) )
155 return false;
156
157 if ( dt.IsValid() || (style & wxDP_ALLOWNONE) )
158 SetValue(dt);
159
160 return true;
161 }
162
163 WXDWORD wxDatePickerCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const
164 {
165 WXDWORD styleMSW = wxDatePickerCtrlBase::MSWGetStyle(style, exstyle);
166
167 // although MSDN doesn't mention it, DTS_UPDOWN doesn't work with
168 // comctl32.dll 4.72
169 if ( wxApp::GetComCtl32Version() > 472 && (style & wxDP_SPIN) )
170 styleMSW |= DTS_UPDOWN;
171 //else: drop down by default
172
173 #ifdef DTS_SHORTDATECENTURYFORMAT
174 if ( style & wxDP_SHOWCENTURY )
175 styleMSW |= DTS_SHORTDATECENTURYFORMAT;
176 else
177 #endif // DTS_SHORTDATECENTURYFORMAT
178 styleMSW |= DTS_SHORTDATEFORMAT;
179
180 if ( style & wxDP_ALLOWNONE )
181 styleMSW |= DTS_SHOWNONE;
182
183 return styleMSW;
184 }
185
186 // TODO: handle WM_WININICHANGE
187
188 // ----------------------------------------------------------------------------
189 // wxDatePickerCtrl geometry
190 // ----------------------------------------------------------------------------
191
192 wxSize wxDatePickerCtrl::DoGetBestSize() const
193 {
194 wxClientDC dc(wx_const_cast(wxDatePickerCtrl *, this));
195 dc.SetFont(GetFont());
196
197 // we can't use FormatDate() here as the CRT doesn't always use the same
198 // format as the date picker control
199 wxString s;
200 for ( int len = 100; ; len *= 2 )
201 {
202 if ( ::GetDateFormat
203 (
204 LOCALE_USER_DEFAULT, // the control should use the same
205 DATE_SHORTDATE, // the format used by the control
206 NULL, // use current date (we don't care)
207 NULL, // no custom format
208 wxStringBuffer(s, len), // output buffer
209 len // and its length
210 ) )
211 {
212 // success
213 break;
214 }
215
216 const DWORD rc = ::GetLastError();
217 if ( rc != ERROR_INSUFFICIENT_BUFFER )
218 {
219 wxLogApiError(_T("GetDateFormat"), rc);
220
221 // fall back on wxDateTime, what else to do?
222 s = wxDateTime::Today().FormatDate();
223 break;
224 }
225 }
226
227 // the control adds a lot of extra space around separators
228 s.Replace(_T(","), _T(" , "));
229
230 int x, y;
231 dc.GetTextExtent(s, &x, &y);
232
233 wxSize best(x + 40 /* margin + arrows */, EDIT_HEIGHT_FROM_CHAR_HEIGHT(y));
234 CacheBestSize(best);
235 return best;
236 }
237
238 // ----------------------------------------------------------------------------
239 // wxDatePickerCtrl operations
240 // ----------------------------------------------------------------------------
241
242 void wxDatePickerCtrl::SetValue(const wxDateTime& dt)
243 {
244 wxCHECK_RET( dt.IsValid() || HasFlag(wxDP_ALLOWNONE),
245 _T("this control requires a valid date") );
246
247 SYSTEMTIME st;
248 if ( dt.IsValid() )
249 wxToSystemTime(&st, dt);
250 if ( !DateTime_SetSystemtime(GetHwnd(),
251 dt.IsValid() ? GDT_VALID : GDT_NONE,
252 &st) )
253 {
254 wxLogDebug(_T("DateTime_SetSystemtime() failed"));
255 }
256
257 wxFromSystemTime(&m_date, st);
258 }
259
260 wxDateTime wxDatePickerCtrl::GetValue() const
261 {
262 #ifdef __WXDEBUG__
263 wxDateTime dt;
264 SYSTEMTIME st;
265 if ( DateTime_GetSystemtime(GetHwnd(), &st) == GDT_VALID )
266 {
267 wxFromSystemTime(&dt, st);
268 }
269
270 wxASSERT_MSG( m_date.IsValid() == dt.IsValid() &&
271 (!dt.IsValid() || dt == m_date),
272 _T("bug in wxDatePickerCtrl: m_date not in sync") );
273 #endif // __WXDEBUG__
274
275 return m_date;
276 }
277
278 void wxDatePickerCtrl::SetRange(const wxDateTime& dt1, const wxDateTime& dt2)
279 {
280 SYSTEMTIME st[2];
281
282 DWORD flags = 0;
283 if ( dt1.IsValid() )
284 {
285 wxToSystemTime(&st[0], dt1);
286 flags |= GDTR_MIN;
287 }
288
289 if ( dt2.IsValid() )
290 {
291 wxToSystemTime(&st[1], dt2);
292 flags |= GDTR_MAX;
293 }
294
295 if ( !DateTime_SetRange(GetHwnd(), flags, st) )
296 {
297 wxLogDebug(_T("DateTime_SetRange() failed"));
298 }
299 }
300
301 bool wxDatePickerCtrl::GetRange(wxDateTime *dt1, wxDateTime *dt2) const
302 {
303 SYSTEMTIME st[2];
304
305 DWORD flags = DateTime_GetRange(GetHwnd(), st);
306 if ( dt1 )
307 {
308 if ( flags & GDTR_MIN )
309 wxFromSystemTime(dt1, st[0]);
310 else
311 *dt1 = wxDefaultDateTime;
312 }
313
314 if ( dt2 )
315 {
316 if ( flags & GDTR_MAX )
317 wxFromSystemTime(dt2, st[1]);
318 else
319 *dt2 = wxDefaultDateTime;
320 }
321
322 return flags != 0;
323 }
324
325 // ----------------------------------------------------------------------------
326 // wxDatePickerCtrl events
327 // ----------------------------------------------------------------------------
328
329 bool
330 wxDatePickerCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
331 {
332 NMHDR* hdr = (NMHDR *)lParam;
333 switch ( hdr->code )
334 {
335 case DTN_DATETIMECHANGE:
336 {
337 NMDATETIMECHANGE *dtch = (NMDATETIMECHANGE *)hdr;
338 wxDateTime dt;
339 if ( dtch->dwFlags == GDT_VALID )
340 wxFromSystemTime(&dt, dtch->st);
341
342 // filter out duplicate DTN_DATETIMECHANGE events which the native
343 // control sends us when using wxDP_DROPDOWN style
344 if ( !m_date.IsValid() || dt != m_date )
345 {
346 m_date = dt;
347 wxDateEvent event(this, dt, wxEVT_DATE_CHANGED);
348 if ( GetEventHandler()->ProcessEvent(event) )
349 {
350 *result = 0;
351 return true;
352 }
353 }
354 }
355 }
356
357 return wxDatePickerCtrlBase::MSWOnNotify(idCtrl, lParam, result);
358 }
359
360 #endif // wxUSE_DATEPICKCTRL