1 /*
2 * Date and time picker control
3 *
4 * Copyright 1998, 1999 Eric Kohl
5 * Copyright 1999, 2000 Alex Priem <alexp@sci.kun.nl>
6 * Copyright 2000 Chris Morgan <cmorgan@wpi.edu>
7 * Copyright 2012 Owen Rudge for CodeWeavers
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22 *
23 * TODO:
24 * -- DTS_APPCANPARSE
25 * -- DTS_SHORTDATECENTURYFORMAT
26 * -- DTN_FORMAT
27 * -- DTN_FORMATQUERY
28 * -- DTN_USERSTRING
29 * -- DTN_WMKEYDOWN
30 * -- FORMATCALLBACK
31 */
32
33 #include <math.h>
34 #include <string.h>
35 #include <stdarg.h>
36 #include <stdio.h>
37 #include <limits.h>
38
39 #include "windef.h"
40 #include "winbase.h"
41 #include "wingdi.h"
42 #include "winuser.h"
43 #include "winnls.h"
44 #include "commctrl.h"
45 #include "comctl32.h"
46 #include "wine/debug.h"
47
48 WINE_DEFAULT_DEBUG_CHANNEL(datetime);
49
50 typedef struct
51 {
52 HWND hwndSelf;
53 HWND hMonthCal;
54 HWND hwndNotify;
55 HWND hUpdown;
56 DWORD dwStyle;
57 SYSTEMTIME date;
58 BOOL dateValid;
59 HWND hwndCheckbut;
60 RECT rcClient; /* rect around the edge of the window */
61 RECT rcDraw; /* rect inside of the border */
62 RECT checkbox; /* checkbox allowing the control to be enabled/disabled */
63 RECT calbutton; /* button that toggles the dropdown of the monthcal control */
64 BOOL bCalDepressed; /* TRUE = cal button is depressed */
65 BOOL bDropdownEnabled;
66 int select;
67 WCHAR charsEntered[4];
68 int nCharsEntered;
69 HFONT hFont;
70 int nrFieldsAllocated;
71 int nrFields;
72 int haveFocus;
73 int *fieldspec;
74 RECT *fieldRect;
75 int *buflen;
76 WCHAR textbuf[256];
77 POINT monthcal_pos;
78 int pendingUpdown;
79 } DATETIME_INFO, *LPDATETIME_INFO;
80
81 /* in monthcal.c */
82 extern int MONTHCAL_MonthLength(int month, int year);
83 extern int MONTHCAL_CalculateDayOfWeek(SYSTEMTIME *date, BOOL inplace);
84
85 /* this list of defines is closely related to `allowedformatchars' defined
86 * in datetime.c; the high nibble indicates the `base type' of the format
87 * specifier.
88 * Do not change without first reading DATETIME_UseFormat.
89 *
90 */
91
92 #define DT_END_FORMAT 0
93 #define ONEDIGITDAY 0x01
94 #define TWODIGITDAY 0x02
95 #define THREECHARDAY 0x03
96 #define FULLDAY 0x04
97 #define ONEDIGIT12HOUR 0x11
98 #define TWODIGIT12HOUR 0x12
99 #define ONEDIGIT24HOUR 0x21
100 #define TWODIGIT24HOUR 0x22
101 #define ONEDIGITMINUTE 0x31
102 #define TWODIGITMINUTE 0x32
103 #define ONEDIGITMONTH 0x41
104 #define TWODIGITMONTH 0x42
105 #define THREECHARMONTH 0x43
106 #define FULLMONTH 0x44
107 #define ONEDIGITSECOND 0x51
108 #define TWODIGITSECOND 0x52
109 #define ONELETTERAMPM 0x61
110 #define TWOLETTERAMPM 0x62
111 #define ONEDIGITYEAR 0x71
112 #define TWODIGITYEAR 0x72
113 #define INVALIDFULLYEAR 0x73 /* FIXME - yyy is not valid - we'll treat it as yyyy */
114 #define FULLYEAR 0x74
115 #define FORMATCALLBACK 0x81 /* -> maximum of 0x80 callbacks possible */
116 #define FORMATCALLMASK 0x80
117 #define DT_STRING 0x0100
118
119 #define DTHT_DATEFIELD 0xff /* for hit-testing */
120
121 #define DTHT_NONE 0x1000
122 #define DTHT_CHECKBOX 0x2000 /* these should end at '00' , to make */
123 #define DTHT_MCPOPUP 0x3000 /* & DTHT_DATEFIELD 0 when DATETIME_KeyDown */
124 #define DTHT_GOTFOCUS 0x4000 /* tests for date-fields */
125 #define DTHT_NODATEMASK 0xf000 /* to mask check and drop down from others */
126
127 static BOOL DATETIME_SendSimpleNotify (const DATETIME_INFO *infoPtr, UINT code);
128 static BOOL DATETIME_SendDateTimeChangeNotify (const DATETIME_INFO *infoPtr);
129 static const WCHAR allowedformatchars[] = L"dhHmMstyX";
130 static const int maxrepetition [] = {4,2,2,2,4,2,2,4,-1};
131
132 /* valid date limits */
133 #ifndef __REACTOS__
134 static const SYSTEMTIME max_allowed_date = { .wYear = 9999, .wMonth = 12, .wDayOfWeek = 0, .wDay = 31 };
135 static const SYSTEMTIME min_allowed_date = { .wYear = 1752, .wMonth = 9, .wDayOfWeek = 0, .wDay = 14 };
136 #else
137 static const SYSTEMTIME max_allowed_date = { /*.wYear =*/ 9999, /*.wMonth =*/ 12, /*.wDayOfWeek =*/ 0, /*.wDay =*/ 31 };
138 static const SYSTEMTIME min_allowed_date = { /*.wYear =*/ 1752, /*.wMonth =*/ 9, /*.wDayOfWeek =*/ 0, /*.wDay =*/ 14 };
139 #endif
140
141 static DWORD
DATETIME_GetSystemTime(const DATETIME_INFO * infoPtr,SYSTEMTIME * systime)142 DATETIME_GetSystemTime (const DATETIME_INFO *infoPtr, SYSTEMTIME *systime)
143 {
144 if (!systime) return GDT_NONE;
145
146 if ((infoPtr->dwStyle & DTS_SHOWNONE) &&
147 (SendMessageW (infoPtr->hwndCheckbut, BM_GETCHECK, 0, 0) == BST_UNCHECKED))
148 return GDT_NONE;
149
150 *systime = infoPtr->date;
151
152 return GDT_VALID;
153 }
154
155 /* Checks value is within configured date range
156 *
157 * PARAMETERS
158 *
159 * [I] infoPtr : valid pointer to control data
160 * [I] date : pointer to valid date data to check
161 *
162 * RETURN VALUE
163 *
164 * TRUE - date within configured range
165 * FALSE - date is outside configured range
166 */
DATETIME_IsDateInValidRange(const DATETIME_INFO * infoPtr,const SYSTEMTIME * date)167 static BOOL DATETIME_IsDateInValidRange(const DATETIME_INFO *infoPtr, const SYSTEMTIME *date)
168 {
169 SYSTEMTIME range[2];
170 DWORD limits;
171
172 if ((MONTHCAL_CompareSystemTime(date, &max_allowed_date) == 1) ||
173 (MONTHCAL_CompareSystemTime(date, &min_allowed_date) == -1))
174 return FALSE;
175
176 limits = SendMessageW (infoPtr->hMonthCal, MCM_GETRANGE, 0, (LPARAM)range);
177
178 if (limits & GDTR_MAX)
179 {
180 if (MONTHCAL_CompareSystemTime(date, &range[1]) == 1)
181 return FALSE;
182 }
183
184 if (limits & GDTR_MIN)
185 {
186 if (MONTHCAL_CompareSystemTime(date, &range[0]) == -1)
187 return FALSE;
188 }
189
190 return TRUE;
191 }
192
193 static BOOL
DATETIME_SetSystemTime(DATETIME_INFO * infoPtr,DWORD flag,const SYSTEMTIME * systime)194 DATETIME_SetSystemTime (DATETIME_INFO *infoPtr, DWORD flag, const SYSTEMTIME *systime)
195 {
196 if (!systime) return FALSE;
197
198 TRACE("%04d/%02d/%02d %02d:%02d:%02d\n",
199 systime->wYear, systime->wMonth, systime->wDay,
200 systime->wHour, systime->wMinute, systime->wSecond);
201
202 if (flag == GDT_VALID) {
203 if (systime->wYear == 0 ||
204 systime->wMonth < 1 || systime->wMonth > 12 ||
205 systime->wDay < 1 ||
206 systime->wDay > MONTHCAL_MonthLength(systime->wMonth, systime->wYear) ||
207 systime->wHour > 23 ||
208 systime->wMinute > 59 ||
209 systime->wSecond > 59 ||
210 systime->wMilliseconds > 999
211 )
212 return FALSE;
213
214 /* Windows returns true if the date is valid but outside the limits set */
215 if (!DATETIME_IsDateInValidRange(infoPtr, systime))
216 return TRUE;
217
218 infoPtr->dateValid = TRUE;
219 infoPtr->date = *systime;
220 /* always store a valid day of week */
221 MONTHCAL_CalculateDayOfWeek(&infoPtr->date, TRUE);
222
223 SendMessageW (infoPtr->hMonthCal, MCM_SETCURSEL, 0, (LPARAM)(&infoPtr->date));
224 SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_CHECKED, 0);
225 } else if ((infoPtr->dwStyle & DTS_SHOWNONE) && (flag == GDT_NONE)) {
226 infoPtr->dateValid = FALSE;
227 SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_UNCHECKED, 0);
228 }
229 else
230 return FALSE;
231
232 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
233 return TRUE;
234 }
235
236
237 /***
238 * Split up a formattxt in actions.
239 * See ms documentation for the meaning of the letter codes/'specifiers'.
240 *
241 * Notes:
242 * *'dddddd' is handled as 'dddd' plus 'dd'.
243 * *unrecognized formats are strings (here given the type DT_STRING;
244 * start of the string is encoded in lower bits of DT_STRING.
245 * Therefore, 'string' ends up as '<show seconds>tring'.
246 *
247 */
248 static void
DATETIME_UseFormat(DATETIME_INFO * infoPtr,LPCWSTR formattxt)249 DATETIME_UseFormat (DATETIME_INFO *infoPtr, LPCWSTR formattxt)
250 {
251 unsigned int i;
252 int j, k, len;
253 BOOL inside_literal = FALSE; /* inside '...' */
254 int *nrFields = &infoPtr->nrFields;
255
256 *nrFields = 0;
257 infoPtr->fieldspec[*nrFields] = 0;
258 len = lstrlenW(allowedformatchars);
259 k = 0;
260
261 for (i = 0; formattxt[i]; i++) {
262 TRACE ("\n%d %c:", i, formattxt[i]);
263 if (!inside_literal) {
264 for (j = 0; j < len; j++) {
265 if (allowedformatchars[j]==formattxt[i]) {
266 TRACE ("%c[%d,%x]", allowedformatchars[j], *nrFields, infoPtr->fieldspec[*nrFields]);
267 if ((*nrFields==0) && (infoPtr->fieldspec[*nrFields]==0)) {
268 infoPtr->fieldspec[*nrFields] = (j<<4) + 1;
269 break;
270 }
271 if (infoPtr->fieldspec[*nrFields] >> 4 != j) {
272 (*nrFields)++;
273 infoPtr->fieldspec[*nrFields] = (j<<4) + 1;
274 break;
275 }
276 if ((infoPtr->fieldspec[*nrFields] & 0x0f) == maxrepetition[j]) {
277 (*nrFields)++;
278 infoPtr->fieldspec[*nrFields] = (j<<4) + 1;
279 break;
280 }
281 infoPtr->fieldspec[*nrFields]++;
282 break;
283 } /* if allowedformatchar */
284 } /* for j */
285 }
286 else
287 j = len;
288
289 if (formattxt[i] == '\'')
290 {
291 inside_literal = !inside_literal;
292 continue;
293 }
294
295 /* char is not a specifier: handle char like a string */
296 if (j == len) {
297 if ((*nrFields==0) && (infoPtr->fieldspec[*nrFields]==0)) {
298 infoPtr->fieldspec[*nrFields] = DT_STRING + k;
299 infoPtr->buflen[*nrFields] = 0;
300 } else if ((infoPtr->fieldspec[*nrFields] & DT_STRING) != DT_STRING) {
301 (*nrFields)++;
302 infoPtr->fieldspec[*nrFields] = DT_STRING + k;
303 infoPtr->buflen[*nrFields] = 0;
304 }
305 infoPtr->textbuf[k] = formattxt[i];
306 k++;
307 infoPtr->buflen[*nrFields]++;
308 } /* if j=len */
309
310 if (*nrFields == infoPtr->nrFieldsAllocated) {
311 FIXME ("out of memory; should reallocate. crash ahead.\n");
312 }
313 } /* for i */
314
315 TRACE("\n");
316
317 if (infoPtr->fieldspec[*nrFields] != 0) (*nrFields)++;
318 }
319
320
321 static BOOL
DATETIME_SetFormatW(DATETIME_INFO * infoPtr,LPCWSTR format)322 DATETIME_SetFormatW (DATETIME_INFO *infoPtr, LPCWSTR format)
323 {
324 WCHAR format_buf[80];
325
326 if (!format) {
327 DWORD format_item;
328
329 if ((infoPtr->dwStyle & DTS_SHORTDATECENTURYFORMAT) == DTS_SHORTDATECENTURYFORMAT)
330 format_item = LOCALE_SSHORTDATE;
331 else if ((infoPtr->dwStyle & DTS_LONGDATEFORMAT) == DTS_LONGDATEFORMAT)
332 format_item = LOCALE_SLONGDATE;
333 else if ((infoPtr->dwStyle & DTS_TIMEFORMAT) == DTS_TIMEFORMAT)
334 format_item = LOCALE_STIMEFORMAT;
335 else /* DTS_SHORTDATEFORMAT */
336 format_item = LOCALE_SSHORTDATE;
337 GetLocaleInfoW(LOCALE_USER_DEFAULT, format_item, format_buf, ARRAY_SIZE(format_buf));
338 format = format_buf;
339 }
340
341 DATETIME_UseFormat (infoPtr, format);
342 InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
343
344 return TRUE;
345 }
346
347
348 static BOOL
DATETIME_SetFormatA(DATETIME_INFO * infoPtr,LPCSTR lpszFormat)349 DATETIME_SetFormatA (DATETIME_INFO *infoPtr, LPCSTR lpszFormat)
350 {
351 if (lpszFormat) {
352 BOOL retval;
353 INT len = MultiByteToWideChar(CP_ACP, 0, lpszFormat, -1, NULL, 0);
354 LPWSTR wstr = Alloc(len * sizeof(WCHAR));
355 if (wstr) MultiByteToWideChar(CP_ACP, 0, lpszFormat, -1, wstr, len);
356 retval = DATETIME_SetFormatW (infoPtr, wstr);
357 Free (wstr);
358 return retval;
359 }
360 else
361 return DATETIME_SetFormatW (infoPtr, 0);
362
363 }
364
365
366 static void
DATETIME_ReturnTxt(const DATETIME_INFO * infoPtr,int count,LPWSTR result,int resultSize)367 DATETIME_ReturnTxt (const DATETIME_INFO *infoPtr, int count, LPWSTR result, int resultSize)
368 {
369 SYSTEMTIME date = infoPtr->date;
370 int spec;
371 WCHAR buffer[80];
372
373 *result=0;
374 TRACE ("%d,%d\n", infoPtr->nrFields, count);
375 if (count>infoPtr->nrFields || count < 0) {
376 WARN ("buffer overrun, have %d want %d\n", infoPtr->nrFields, count);
377 return;
378 }
379
380 if (!infoPtr->fieldspec) return;
381
382 spec = infoPtr->fieldspec[count];
383 if (spec & DT_STRING) {
384 int txtlen = infoPtr->buflen[count];
385
386 if (txtlen > resultSize)
387 txtlen = resultSize - 1;
388 memcpy (result, infoPtr->textbuf + (spec &~ DT_STRING), txtlen * sizeof(WCHAR));
389 result[txtlen] = 0;
390 TRACE ("arg%d=%x->[%s]\n", count, infoPtr->fieldspec[count], debugstr_w(result));
391 return;
392 }
393
394
395 switch (spec) {
396 case DT_END_FORMAT:
397 *result = 0;
398 break;
399 case ONEDIGITDAY:
400 wsprintfW (result, L"%d", date.wDay);
401 break;
402 case TWODIGITDAY:
403 wsprintfW (result, L"%.2d", date.wDay);
404 break;
405 case THREECHARDAY:
406 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1+(date.wDayOfWeek+6)%7, result, 4);
407 /*wsprintfW (result,"%.3s",days[date.wDayOfWeek]);*/
408 break;
409 case FULLDAY:
410 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDAYNAME1+(date.wDayOfWeek+6)%7, result, resultSize);
411 break;
412 case ONEDIGIT12HOUR:
413 if (date.wHour == 0) {
414 result[0] = '1';
415 result[1] = '2';
416 result[2] = 0;
417 }
418 else
419 wsprintfW (result, L"%d", date.wHour - (date.wHour > 12 ? 12 : 0));
420 break;
421 case TWODIGIT12HOUR:
422 if (date.wHour == 0) {
423 result[0] = '1';
424 result[1] = '2';
425 result[2] = 0;
426 }
427 else
428 wsprintfW (result, L"%.2d", date.wHour - (date.wHour > 12 ? 12 : 0));
429 break;
430 case ONEDIGIT24HOUR:
431 wsprintfW (result, L"%d", date.wHour);
432 break;
433 case TWODIGIT24HOUR:
434 wsprintfW (result, L"%.2d", date.wHour);
435 break;
436 case ONEDIGITSECOND:
437 wsprintfW (result, L"%d", date.wSecond);
438 break;
439 case TWODIGITSECOND:
440 wsprintfW (result, L"%.2d", date.wSecond);
441 break;
442 case ONEDIGITMINUTE:
443 wsprintfW (result, L"%d", date.wMinute);
444 break;
445 case TWODIGITMINUTE:
446 wsprintfW (result, L"%.2d", date.wMinute);
447 break;
448 case ONEDIGITMONTH:
449 wsprintfW (result, L"%d", date.wMonth);
450 break;
451 case TWODIGITMONTH:
452 wsprintfW (result, L"%.2d", date.wMonth);
453 break;
454 case THREECHARMONTH:
455 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+date.wMonth -1, buffer, ARRAY_SIZE(buffer));
456 wsprintfW (result, L"%s.3s", buffer);
457 break;
458 case FULLMONTH:
459 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1+date.wMonth -1,
460 result, resultSize);
461 break;
462 case ONELETTERAMPM:
463 result[0] = (date.wHour < 12 ? 'A' : 'P');
464 result[1] = 0;
465 break;
466 case TWOLETTERAMPM:
467 result[0] = (date.wHour < 12 ? 'A' : 'P');
468 result[1] = 'M';
469 result[2] = 0;
470 break;
471 case FORMATCALLBACK:
472 FIXME ("Not implemented\n");
473 result[0] = 'x';
474 result[1] = 0;
475 break;
476 case ONEDIGITYEAR:
477 wsprintfW (result, L"%d", date.wYear % 10);
478 break;
479 case TWODIGITYEAR:
480 wsprintfW (result, L"%.2d", date.wYear % 100);
481 break;
482 case INVALIDFULLYEAR:
483 case FULLYEAR:
484 wsprintfW (result, L"%d", date.wYear);
485 break;
486 }
487
488 TRACE ("arg%d=%x->[%s]\n", count, infoPtr->fieldspec[count], debugstr_w(result));
489 }
490
wrap(int val,int delta,int minVal,int maxVal)491 static int wrap(int val, int delta, int minVal, int maxVal)
492 {
493 val += delta;
494 if (delta == INT_MIN || val < minVal) return maxVal;
495 if (delta == INT_MAX || val > maxVal) return minVal;
496 return val;
497 }
498
499 static void
DATETIME_IncreaseField(DATETIME_INFO * infoPtr,int number,int delta)500 DATETIME_IncreaseField (DATETIME_INFO *infoPtr, int number, int delta)
501 {
502 SYSTEMTIME *date = &infoPtr->date;
503 SYSTEMTIME range[2];
504 DWORD limits;
505 BOOL min;
506
507 TRACE ("%d\n", number);
508 if ((number > infoPtr->nrFields) || (number < 0)) return;
509
510 if ((infoPtr->fieldspec[number] & DTHT_DATEFIELD) == 0) return;
511
512 switch (infoPtr->fieldspec[number]) {
513 case ONEDIGITYEAR:
514 case TWODIGITYEAR:
515 case FULLYEAR:
516 if (delta == INT_MIN)
517 date->wYear = 1752;
518 else if (delta == INT_MAX)
519 date->wYear = 9999;
520 else
521 date->wYear = max(min(date->wYear + delta, 9999), 1752);
522
523 if (date->wDay > MONTHCAL_MonthLength(date->wMonth, date->wYear))
524 /* This can happen when moving away from a leap year. */
525 date->wDay = MONTHCAL_MonthLength(date->wMonth, date->wYear);
526 MONTHCAL_CalculateDayOfWeek(date, TRUE);
527 break;
528 case ONEDIGITMONTH:
529 case TWODIGITMONTH:
530 case THREECHARMONTH:
531 case FULLMONTH:
532 date->wMonth = wrap(date->wMonth, delta, 1, 12);
533 MONTHCAL_CalculateDayOfWeek(date, TRUE);
534 delta = 0;
535 /* fall through */
536 case ONEDIGITDAY:
537 case TWODIGITDAY:
538 case THREECHARDAY:
539 case FULLDAY:
540 date->wDay = wrap(date->wDay, delta, 1, MONTHCAL_MonthLength(date->wMonth, date->wYear));
541 MONTHCAL_CalculateDayOfWeek(date, TRUE);
542 break;
543 case ONELETTERAMPM:
544 case TWOLETTERAMPM:
545 delta *= 12;
546 /* fall through */
547 case ONEDIGIT12HOUR:
548 case TWODIGIT12HOUR:
549 case ONEDIGIT24HOUR:
550 case TWODIGIT24HOUR:
551 date->wHour = wrap(date->wHour, delta, 0, 23);
552 break;
553 case ONEDIGITMINUTE:
554 case TWODIGITMINUTE:
555 date->wMinute = wrap(date->wMinute, delta, 0, 59);
556 break;
557 case ONEDIGITSECOND:
558 case TWODIGITSECOND:
559 date->wSecond = wrap(date->wSecond, delta, 0, 59);
560 break;
561 case FORMATCALLBACK:
562 FIXME ("Not implemented\n");
563 break;
564 }
565
566 /* FYI: On 1752/9/14 the calendar changed and England and the
567 * American colonies changed to the Gregorian calendar. This change
568 * involved having September 14th follow September 2nd. So no date
569 * algorithm works before that date.
570 */
571 if (10000 * date->wYear + 100 * date->wMonth + date->wDay < 17520914) {
572 date->wYear = 1752;
573 date->wMonth = 9;
574 date->wDay = 14;
575 date->wSecond = 0;
576 date->wMinute = 0;
577 date->wHour = 0;
578 }
579
580 /* Ensure time is within bounds */
581 limits = SendMessageW (infoPtr->hMonthCal, MCM_GETRANGE, 0, (LPARAM)range);
582 min = delta < 0;
583
584 if (limits & (min ? GDTR_MIN : GDTR_MAX))
585 {
586 int i = (min ? 0 : 1);
587
588 if (MONTHCAL_CompareSystemTime(date, &range[i]) == (min ? -1 : 1))
589 {
590 date->wYear = range[i].wYear;
591 date->wMonth = range[i].wMonth;
592 date->wDayOfWeek = range[i].wDayOfWeek;
593 date->wDay = range[i].wDay;
594 date->wHour = range[i].wHour;
595 date->wMinute = range[i].wMinute;
596 date->wSecond = range[i].wSecond;
597 date->wMilliseconds = range[i].wMilliseconds;
598 }
599 }
600 }
601
DATETIME_GetFieldWidth(const DATETIME_INFO * infoPtr,HDC hdc,int count)602 static int DATETIME_GetFieldWidth (const DATETIME_INFO *infoPtr, HDC hdc, int count)
603 {
604 /* fields are a fixed width, determined by the largest possible string */
605 /* presumably, these widths should be language dependent */
606 int spec;
607 WCHAR buffer[80];
608 LPCWSTR bufptr;
609 SIZE size;
610
611 if (!infoPtr->fieldspec) return 0;
612
613 spec = infoPtr->fieldspec[count];
614 if (spec & DT_STRING) {
615 int txtlen = infoPtr->buflen[count];
616
617 if (txtlen > 79)
618 txtlen = 79;
619 memcpy (buffer, infoPtr->textbuf + (spec &~ DT_STRING), txtlen * sizeof(WCHAR));
620 buffer[txtlen] = 0;
621 bufptr = buffer;
622 }
623 else {
624 switch (spec) {
625 case ONEDIGITDAY:
626 case ONEDIGIT12HOUR:
627 case ONEDIGIT24HOUR:
628 case ONEDIGITSECOND:
629 case ONEDIGITMINUTE:
630 case ONEDIGITMONTH:
631 case ONEDIGITYEAR:
632 /* these seem to use a two byte field */
633 case TWODIGITDAY:
634 case TWODIGIT12HOUR:
635 case TWODIGIT24HOUR:
636 case TWODIGITSECOND:
637 case TWODIGITMINUTE:
638 case TWODIGITMONTH:
639 case TWODIGITYEAR:
640 bufptr = L"22";
641 break;
642 case INVALIDFULLYEAR:
643 case FULLYEAR:
644 bufptr = L"2222";
645 break;
646 case THREECHARMONTH:
647 case FULLMONTH:
648 case THREECHARDAY:
649 case FULLDAY:
650 {
651 const WCHAR *fall;
652 LCTYPE lctype;
653 INT i, max_count;
654 LONG cx;
655
656 /* choose locale data type and fallback string */
657 switch (spec) {
658 case THREECHARDAY:
659 fall = L"Wed";
660 lctype = LOCALE_SABBREVDAYNAME1;
661 max_count = 7;
662 break;
663 case FULLDAY:
664 fall = L"Wednesday";
665 lctype = LOCALE_SDAYNAME1;
666 max_count = 7;
667 break;
668 case THREECHARMONTH:
669 fall = L"Dec";
670 lctype = LOCALE_SABBREVMONTHNAME1;
671 max_count = 12;
672 break;
673 default: /* FULLMONTH */
674 fall = L"September";
675 lctype = LOCALE_SMONTHNAME1;
676 max_count = 12;
677 break;
678 }
679
680 cx = 0;
681 for (i = 0; i < max_count; i++)
682 {
683 if(GetLocaleInfoW(LOCALE_USER_DEFAULT, lctype + i,
684 buffer, ARRAY_SIZE(buffer)))
685 {
686 GetTextExtentPoint32W(hdc, buffer, lstrlenW(buffer), &size);
687 if (size.cx > cx) cx = size.cx;
688 }
689 else /* locale independent fallback on failure */
690 {
691 GetTextExtentPoint32W(hdc, fall, lstrlenW(fall), &size);
692 cx = size.cx;
693 break;
694 }
695 }
696 return cx;
697 }
698 case ONELETTERAMPM:
699 bufptr = L"A";
700 break;
701 case TWOLETTERAMPM:
702 bufptr = L"AM";
703 break;
704 default:
705 bufptr = L"2";
706 break;
707 }
708 }
709 GetTextExtentPoint32W (hdc, bufptr, lstrlenW(bufptr), &size);
710 return size.cx;
711 }
712
713 static void
DATETIME_Refresh(DATETIME_INFO * infoPtr,HDC hdc)714 DATETIME_Refresh (DATETIME_INFO *infoPtr, HDC hdc)
715 {
716 TRACE("\n");
717
718 if (infoPtr->dateValid) {
719 int i, prevright;
720 RECT *field;
721 RECT *rcDraw = &infoPtr->rcDraw;
722 SIZE size;
723 COLORREF oldTextColor;
724 HFONT oldFont = SelectObject (hdc, infoPtr->hFont);
725 INT oldBkMode = SetBkMode (hdc, TRANSPARENT);
726 WCHAR txt[80];
727
728 DATETIME_ReturnTxt (infoPtr, 0, txt, ARRAY_SIZE(txt));
729 GetTextExtentPoint32W (hdc, txt, lstrlenW(txt), &size);
730 rcDraw->bottom = size.cy + 2;
731
732 prevright = infoPtr->checkbox.right = ((infoPtr->dwStyle & DTS_SHOWNONE) ? 18 : 2);
733
734 for (i = 0; i < infoPtr->nrFields; i++) {
735 DATETIME_ReturnTxt (infoPtr, i, txt, ARRAY_SIZE(txt));
736 GetTextExtentPoint32W (hdc, txt, lstrlenW(txt), &size);
737 field = &infoPtr->fieldRect[i];
738 field->left = prevright;
739 field->right = prevright + DATETIME_GetFieldWidth (infoPtr, hdc, i);
740 field->top = rcDraw->top;
741 field->bottom = rcDraw->bottom;
742 prevright = field->right;
743
744 if (infoPtr->dwStyle & WS_DISABLED)
745 oldTextColor = SetTextColor (hdc, comctl32_color.clrGrayText);
746 else if ((infoPtr->haveFocus) && (i == infoPtr->select)) {
747 RECT selection;
748
749 /* fill if focused */
750 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrActiveCaption);
751
752 if (infoPtr->nCharsEntered)
753 {
754 memcpy(txt, infoPtr->charsEntered, infoPtr->nCharsEntered * sizeof(WCHAR));
755 txt[infoPtr->nCharsEntered] = 0;
756 GetTextExtentPoint32W (hdc, txt, lstrlenW(txt), &size);
757 }
758
759 SetRect(&selection, 0, 0, size.cx, size.cy);
760 /* center rectangle */
761 OffsetRect(&selection, (field->right + field->left - size.cx)/2,
762 (field->bottom - size.cy)/2);
763
764 FillRect(hdc, &selection, hbr);
765 DeleteObject (hbr);
766 oldTextColor = SetTextColor (hdc, comctl32_color.clrWindow);
767 }
768 else
769 oldTextColor = SetTextColor (hdc, comctl32_color.clrWindowText);
770
771 /* draw the date text using the colour set above */
772 DrawTextW (hdc, txt, lstrlenW(txt), field, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
773 SetTextColor (hdc, oldTextColor);
774 }
775 SetBkMode (hdc, oldBkMode);
776 SelectObject (hdc, oldFont);
777 }
778
779 if (!(infoPtr->dwStyle & DTS_UPDOWN)) {
780 DrawFrameControl(hdc, &infoPtr->calbutton, DFC_SCROLL,
781 DFCS_SCROLLDOWN | (infoPtr->bCalDepressed ? DFCS_PUSHED : 0) |
782 (infoPtr->dwStyle & WS_DISABLED ? DFCS_INACTIVE : 0) );
783 }
784 }
785
786
787 static INT
DATETIME_HitTest(const DATETIME_INFO * infoPtr,POINT pt)788 DATETIME_HitTest (const DATETIME_INFO *infoPtr, POINT pt)
789 {
790 int i;
791
792 TRACE ("%s\n", wine_dbgstr_point(&pt));
793
794 if (PtInRect (&infoPtr->calbutton, pt)) return DTHT_MCPOPUP;
795 if (PtInRect (&infoPtr->checkbox, pt)) return DTHT_CHECKBOX;
796
797 for (i = 0; i < infoPtr->nrFields; i++) {
798 if (PtInRect (&infoPtr->fieldRect[i], pt)) return i;
799 }
800
801 return DTHT_NONE;
802 }
803
804 /* Returns index of the nearest preceding date field from given,
805 or -1 if none was found */
DATETIME_GetPrevDateField(const DATETIME_INFO * infoPtr,int i)806 static int DATETIME_GetPrevDateField(const DATETIME_INFO *infoPtr, int i)
807 {
808 for(--i; i >= 0; i--)
809 {
810 if (infoPtr->fieldspec[i] & DTHT_DATEFIELD) return i;
811 }
812 return -1;
813 }
814
815 static void
DATETIME_ApplySelectedField(DATETIME_INFO * infoPtr)816 DATETIME_ApplySelectedField (DATETIME_INFO *infoPtr)
817 {
818 int fieldNum = infoPtr->select & DTHT_DATEFIELD;
819 int i, val = 0;
820 BOOL clamp_day = FALSE;
821 SYSTEMTIME date = infoPtr->date;
822 int oldyear;
823
824 if (infoPtr->select == -1 || infoPtr->nCharsEntered == 0)
825 return;
826
827 if ((infoPtr->fieldspec[fieldNum] == ONELETTERAMPM) ||
828 (infoPtr->fieldspec[fieldNum] == TWOLETTERAMPM))
829 val = infoPtr->charsEntered[0];
830 else {
831 for (i=0; i<infoPtr->nCharsEntered; i++)
832 val = val * 10 + infoPtr->charsEntered[i] - '0';
833 }
834
835 infoPtr->nCharsEntered = 0;
836
837 switch (infoPtr->fieldspec[fieldNum]) {
838 case ONEDIGITYEAR:
839 case TWODIGITYEAR:
840 oldyear = date.wYear;
841 date.wYear = date.wYear - (date.wYear%100) + val;
842
843 if (DATETIME_IsDateInValidRange(infoPtr, &date))
844 clamp_day = TRUE;
845 else
846 date.wYear = oldyear;
847
848 break;
849 case INVALIDFULLYEAR:
850 case FULLYEAR:
851 oldyear = date.wYear;
852 date.wYear = val;
853
854 if (DATETIME_IsDateInValidRange(infoPtr, &date))
855 clamp_day = TRUE;
856 else
857 date.wYear = oldyear;
858
859 break;
860 case ONEDIGITMONTH:
861 case TWODIGITMONTH:
862 date.wMonth = val;
863 clamp_day = TRUE;
864 break;
865 case ONEDIGITDAY:
866 case TWODIGITDAY:
867 date.wDay = val;
868 break;
869 case ONEDIGIT12HOUR:
870 case TWODIGIT12HOUR:
871 if (val >= 24)
872 val -= 20;
873
874 if (val >= 13)
875 date.wHour = val;
876 else if (val != 0) {
877 if (date.wHour >= 12) /* preserve current AM/PM state */
878 date.wHour = (val == 12 ? 12 : val + 12);
879 else
880 date.wHour = (val == 12 ? 0 : val);
881 }
882 break;
883 case ONEDIGIT24HOUR:
884 case TWODIGIT24HOUR:
885 date.wHour = val;
886 break;
887 case ONEDIGITMINUTE:
888 case TWODIGITMINUTE:
889 date.wMinute = val;
890 break;
891 case ONEDIGITSECOND:
892 case TWODIGITSECOND:
893 date.wSecond = val;
894 break;
895 case ONELETTERAMPM:
896 case TWOLETTERAMPM:
897 if (val == 'a' || val == 'A') {
898 if (date.wHour >= 12)
899 date.wHour -= 12;
900 } else if (val == 'p' || val == 'P') {
901 if (date.wHour < 12)
902 date.wHour += 12;
903 }
904 break;
905 }
906
907 if (clamp_day && date.wDay > MONTHCAL_MonthLength(date.wMonth, date.wYear))
908 date.wDay = MONTHCAL_MonthLength(date.wMonth, date.wYear);
909
910 if (DATETIME_SetSystemTime(infoPtr, GDT_VALID, &date))
911 DATETIME_SendDateTimeChangeNotify (infoPtr);
912 }
913
914 static void
DATETIME_SetSelectedField(DATETIME_INFO * infoPtr,int select)915 DATETIME_SetSelectedField (DATETIME_INFO *infoPtr, int select)
916 {
917 DATETIME_ApplySelectedField(infoPtr);
918
919 infoPtr->select = select;
920 infoPtr->nCharsEntered = 0;
921 }
922
923 static LRESULT
DATETIME_LButtonDown(DATETIME_INFO * infoPtr,INT x,INT y)924 DATETIME_LButtonDown (DATETIME_INFO *infoPtr, INT x, INT y)
925 {
926 POINT pt;
927 int new;
928
929 pt.x = x;
930 pt.y = y;
931 new = DATETIME_HitTest (infoPtr, pt);
932
933 SetFocus(infoPtr->hwndSelf);
934
935 if (!(new & DTHT_NODATEMASK) || (new == DTHT_NONE))
936 {
937 if (new == DTHT_NONE)
938 new = infoPtr->nrFields - 1;
939 else
940 {
941 /* hitting string part moves selection to next date field to left */
942 if (infoPtr->fieldspec[new] & DT_STRING)
943 {
944 new = DATETIME_GetPrevDateField(infoPtr, new);
945 if (new == -1) return 0;
946 }
947 /* never select full day of week */
948 if (infoPtr->fieldspec[new] == FULLDAY) return 0;
949 }
950 }
951
952 DATETIME_SetSelectedField(infoPtr, new);
953
954 if (infoPtr->select == DTHT_MCPOPUP) {
955 RECT rcMonthCal;
956 POINT pos;
957 SendMessageW(infoPtr->hMonthCal, MCM_GETMINREQRECT, 0, (LPARAM)&rcMonthCal);
958
959 /* FIXME: button actually is only depressed during dropdown of the */
960 /* calendar control and when the mouse is over the button window */
961 infoPtr->bCalDepressed = TRUE;
962
963 /* recalculate the position of the monthcal popup */
964 if(infoPtr->dwStyle & DTS_RIGHTALIGN)
965 pos.x = infoPtr->calbutton.left - (rcMonthCal.right - rcMonthCal.left);
966 else
967 /* FIXME: this should be after the area reserved for the checkbox */
968 pos.x = infoPtr->rcDraw.left;
969
970 pos.y = infoPtr->rcClient.bottom;
971 OffsetRect( &rcMonthCal, pos.x, pos.y );
972 MapWindowPoints( infoPtr->hwndSelf, 0, (POINT *)&rcMonthCal, 2 );
973 SetWindowPos(infoPtr->hMonthCal, 0, rcMonthCal.left, rcMonthCal.top,
974 rcMonthCal.right - rcMonthCal.left, rcMonthCal.bottom - rcMonthCal.top, 0);
975
976 if(IsWindowVisible(infoPtr->hMonthCal)) {
977 ShowWindow(infoPtr->hMonthCal, SW_HIDE);
978 infoPtr->bDropdownEnabled = FALSE;
979 DATETIME_SendSimpleNotify (infoPtr, DTN_CLOSEUP);
980 } else {
981 const SYSTEMTIME *lprgSysTimeArray = &infoPtr->date;
982 TRACE("update calendar %04d/%02d/%02d\n",
983 lprgSysTimeArray->wYear, lprgSysTimeArray->wMonth, lprgSysTimeArray->wDay);
984 SendMessageW(infoPtr->hMonthCal, MCM_SETCURSEL, 0, (LPARAM)(&infoPtr->date));
985
986 if (infoPtr->bDropdownEnabled) {
987 ShowWindow(infoPtr->hMonthCal, SW_SHOW);
988 DATETIME_SendSimpleNotify (infoPtr, DTN_DROPDOWN);
989 }
990 infoPtr->bDropdownEnabled = TRUE;
991 }
992
993 TRACE ("dt:%p mc:%p mc parent:%p, desktop:%p\n",
994 infoPtr->hwndSelf, infoPtr->hMonthCal, infoPtr->hwndNotify, GetDesktopWindow ());
995 }
996
997 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
998
999 return 0;
1000 }
1001
1002
1003 static LRESULT
DATETIME_LButtonUp(DATETIME_INFO * infoPtr)1004 DATETIME_LButtonUp (DATETIME_INFO *infoPtr)
1005 {
1006 if(infoPtr->bCalDepressed) {
1007 infoPtr->bCalDepressed = FALSE;
1008 InvalidateRect(infoPtr->hwndSelf, &(infoPtr->calbutton), TRUE);
1009 }
1010
1011 return 0;
1012 }
1013
1014
1015 static LRESULT
DATETIME_Paint(DATETIME_INFO * infoPtr,HDC hdc)1016 DATETIME_Paint (DATETIME_INFO *infoPtr, HDC hdc)
1017 {
1018 if (!hdc) {
1019 PAINTSTRUCT ps;
1020 hdc = BeginPaint (infoPtr->hwndSelf, &ps);
1021 DATETIME_Refresh (infoPtr, hdc);
1022 EndPaint (infoPtr->hwndSelf, &ps);
1023 } else {
1024 DATETIME_Refresh (infoPtr, hdc);
1025 }
1026
1027 /* Not a click on the dropdown box, enabled it */
1028 infoPtr->bDropdownEnabled = TRUE;
1029
1030 return 0;
1031 }
1032
1033
1034 static LRESULT
DATETIME_Button_Command(DATETIME_INFO * infoPtr,WPARAM wParam,LPARAM lParam)1035 DATETIME_Button_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
1036 {
1037 if( HIWORD(wParam) == BN_CLICKED) {
1038 DWORD state = SendMessageW((HWND)lParam, BM_GETCHECK, 0, 0);
1039 infoPtr->dateValid = (state == BST_CHECKED);
1040 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1041 }
1042 return 0;
1043 }
1044
1045
1046
1047 static LRESULT
DATETIME_Command(DATETIME_INFO * infoPtr,WPARAM wParam,LPARAM lParam)1048 DATETIME_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
1049 {
1050 TRACE("hwndbutton = %p\n", infoPtr->hwndCheckbut);
1051 if(infoPtr->hwndCheckbut == (HWND)lParam)
1052 return DATETIME_Button_Command(infoPtr, wParam, lParam);
1053 return 0;
1054 }
1055
1056
1057 static LRESULT
DATETIME_Enable(DATETIME_INFO * infoPtr,BOOL bEnable)1058 DATETIME_Enable (DATETIME_INFO *infoPtr, BOOL bEnable)
1059 {
1060 TRACE("%p %s\n", infoPtr, bEnable ? "TRUE" : "FALSE");
1061 if (bEnable)
1062 infoPtr->dwStyle &= ~WS_DISABLED;
1063 else
1064 infoPtr->dwStyle |= WS_DISABLED;
1065
1066 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1067
1068 return 0;
1069 }
1070
1071
1072 static LRESULT
DATETIME_EraseBackground(const DATETIME_INFO * infoPtr,HDC hdc)1073 DATETIME_EraseBackground (const DATETIME_INFO *infoPtr, HDC hdc)
1074 {
1075 HBRUSH hBrush, hSolidBrush = NULL;
1076 RECT rc;
1077
1078 if (infoPtr->dwStyle & WS_DISABLED)
1079 hBrush = hSolidBrush = CreateSolidBrush(comctl32_color.clrBtnFace);
1080 else
1081 {
1082 hBrush = (HBRUSH)SendMessageW(infoPtr->hwndNotify, WM_CTLCOLOREDIT,
1083 (WPARAM)hdc, (LPARAM)infoPtr->hwndSelf);
1084 if (!hBrush)
1085 hBrush = hSolidBrush = CreateSolidBrush(comctl32_color.clrWindow);
1086 }
1087
1088 GetClientRect (infoPtr->hwndSelf, &rc);
1089
1090 FillRect (hdc, &rc, hBrush);
1091
1092 if (hSolidBrush)
1093 DeleteObject(hSolidBrush);
1094
1095 return -1;
1096 }
1097
1098
1099 static LRESULT
DATETIME_Notify(DATETIME_INFO * infoPtr,const NMHDR * lpnmh)1100 DATETIME_Notify (DATETIME_INFO *infoPtr, const NMHDR *lpnmh)
1101 {
1102 TRACE ("Got notification %x from %p\n", lpnmh->code, lpnmh->hwndFrom);
1103 TRACE ("info: %p %p %p\n", infoPtr->hwndSelf, infoPtr->hMonthCal, infoPtr->hUpdown);
1104
1105 if (lpnmh->code == MCN_SELECT) {
1106 ShowWindow(infoPtr->hMonthCal, SW_HIDE);
1107 infoPtr->dateValid = TRUE;
1108 SendMessageW (infoPtr->hMonthCal, MCM_GETCURSEL, 0, (LPARAM)&infoPtr->date);
1109 TRACE("got from calendar %04d/%02d/%02d day of week %d\n",
1110 infoPtr->date.wYear, infoPtr->date.wMonth, infoPtr->date.wDay, infoPtr->date.wDayOfWeek);
1111 SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_CHECKED, 0);
1112 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1113 DATETIME_SendDateTimeChangeNotify (infoPtr);
1114 DATETIME_SendSimpleNotify(infoPtr, DTN_CLOSEUP);
1115 }
1116 if ((lpnmh->hwndFrom == infoPtr->hUpdown) && (lpnmh->code == UDN_DELTAPOS)) {
1117 const NM_UPDOWN *lpnmud = (const NM_UPDOWN*)lpnmh;
1118 TRACE("Delta pos %d\n", lpnmud->iDelta);
1119 infoPtr->pendingUpdown = lpnmud->iDelta;
1120 }
1121 return 0;
1122 }
1123
1124
1125 static LRESULT
DATETIME_KeyDown(DATETIME_INFO * infoPtr,DWORD vkCode)1126 DATETIME_KeyDown (DATETIME_INFO *infoPtr, DWORD vkCode)
1127 {
1128 int fieldNum = infoPtr->select & DTHT_DATEFIELD;
1129 int wrap = 0;
1130 int new;
1131
1132 if (!(infoPtr->haveFocus)) return 0;
1133 if ((fieldNum==0) && (infoPtr->select)) return 0;
1134
1135 if (infoPtr->select & FORMATCALLMASK) {
1136 FIXME ("Callbacks not implemented yet\n");
1137 }
1138
1139 switch (vkCode) {
1140 case VK_ADD:
1141 case VK_UP:
1142 infoPtr->nCharsEntered = 0;
1143 DATETIME_IncreaseField (infoPtr, fieldNum, 1);
1144 DATETIME_SendDateTimeChangeNotify (infoPtr);
1145 break;
1146 case VK_SUBTRACT:
1147 case VK_DOWN:
1148 infoPtr->nCharsEntered = 0;
1149 DATETIME_IncreaseField (infoPtr, fieldNum, -1);
1150 DATETIME_SendDateTimeChangeNotify (infoPtr);
1151 break;
1152 case VK_HOME:
1153 infoPtr->nCharsEntered = 0;
1154 DATETIME_IncreaseField (infoPtr, fieldNum, INT_MIN);
1155 DATETIME_SendDateTimeChangeNotify (infoPtr);
1156 break;
1157 case VK_END:
1158 infoPtr->nCharsEntered = 0;
1159 DATETIME_IncreaseField (infoPtr, fieldNum, INT_MAX);
1160 DATETIME_SendDateTimeChangeNotify (infoPtr);
1161 break;
1162 case VK_LEFT:
1163 new = infoPtr->select;
1164 do {
1165 if (new == 0) {
1166 new = new - 1;
1167 wrap++;
1168 } else {
1169 new--;
1170 }
1171 } while ((infoPtr->fieldspec[new] & DT_STRING) && (wrap<2));
1172 if (new != infoPtr->select)
1173 DATETIME_SetSelectedField(infoPtr, new);
1174 break;
1175 case VK_RIGHT:
1176 new = infoPtr->select;
1177 do {
1178 new++;
1179 if (new==infoPtr->nrFields) {
1180 new = 0;
1181 wrap++;
1182 }
1183 } while ((infoPtr->fieldspec[new] & DT_STRING) && (wrap<2));
1184 if (new != infoPtr->select)
1185 DATETIME_SetSelectedField(infoPtr, new);
1186 break;
1187 }
1188
1189 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1190
1191 return 0;
1192 }
1193
1194
1195 static LRESULT
DATETIME_Char(DATETIME_INFO * infoPtr,WPARAM vkCode)1196 DATETIME_Char (DATETIME_INFO *infoPtr, WPARAM vkCode)
1197 {
1198 int fieldNum, fieldSpec;
1199
1200 fieldNum = infoPtr->select & DTHT_DATEFIELD;
1201 fieldSpec = infoPtr->fieldspec[fieldNum];
1202
1203 if (fieldSpec == ONELETTERAMPM || fieldSpec == TWOLETTERAMPM) {
1204 infoPtr->charsEntered[0] = vkCode;
1205 infoPtr->nCharsEntered = 1;
1206
1207 DATETIME_ApplySelectedField(infoPtr);
1208 } else if (vkCode >= '0' && vkCode <= '9') {
1209 int maxChars;
1210
1211 infoPtr->charsEntered[infoPtr->nCharsEntered++] = vkCode;
1212
1213 if (fieldSpec == INVALIDFULLYEAR || fieldSpec == FULLYEAR)
1214 maxChars = 4;
1215 else
1216 maxChars = 2;
1217
1218 if ((fieldSpec == ONEDIGIT12HOUR ||
1219 fieldSpec == TWODIGIT12HOUR ||
1220 fieldSpec == ONEDIGIT24HOUR ||
1221 fieldSpec == TWODIGIT24HOUR) &&
1222 (infoPtr->nCharsEntered == 1))
1223 {
1224 if (vkCode >= '3')
1225 maxChars = 1;
1226 }
1227
1228 if (maxChars == infoPtr->nCharsEntered)
1229 DATETIME_ApplySelectedField(infoPtr);
1230 }
1231
1232 return 0;
1233 }
1234
1235
1236 static LRESULT
DATETIME_VScroll(DATETIME_INFO * infoPtr,WORD wScroll)1237 DATETIME_VScroll (DATETIME_INFO *infoPtr, WORD wScroll)
1238 {
1239 int fieldNum = infoPtr->select & DTHT_DATEFIELD;
1240
1241 if ((SHORT)LOWORD(wScroll) != SB_THUMBPOSITION) return 0;
1242 if (!(infoPtr->haveFocus)) return 0;
1243 if ((fieldNum==0) && (infoPtr->select)) return 0;
1244
1245 if (infoPtr->pendingUpdown >= 0) {
1246 DATETIME_IncreaseField (infoPtr, fieldNum, 1);
1247 DATETIME_SendDateTimeChangeNotify (infoPtr);
1248 }
1249 else {
1250 DATETIME_IncreaseField (infoPtr, fieldNum, -1);
1251 DATETIME_SendDateTimeChangeNotify (infoPtr);
1252 }
1253
1254 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1255
1256 return 0;
1257 }
1258
1259
1260 static LRESULT
DATETIME_KillFocus(DATETIME_INFO * infoPtr,HWND lostFocus)1261 DATETIME_KillFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
1262 {
1263 TRACE("lost focus to %p\n", lostFocus);
1264
1265 if (infoPtr->haveFocus) {
1266 DATETIME_SendSimpleNotify (infoPtr, NM_KILLFOCUS);
1267 infoPtr->haveFocus = 0;
1268 DATETIME_SetSelectedField (infoPtr, -1);
1269 }
1270
1271 InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
1272
1273 return 0;
1274 }
1275
1276
1277 static LRESULT
DATETIME_NCCreate(HWND hwnd,const CREATESTRUCTW * lpcs)1278 DATETIME_NCCreate (HWND hwnd, const CREATESTRUCTW *lpcs)
1279 {
1280 DWORD dwExStyle = GetWindowLongW(hwnd, GWL_EXSTYLE);
1281 /* force control to have client edge */
1282 dwExStyle |= WS_EX_CLIENTEDGE;
1283 SetWindowLongW(hwnd, GWL_EXSTYLE, dwExStyle);
1284
1285 return 1;
1286 }
1287
1288
1289 static LRESULT
DATETIME_SetFocus(DATETIME_INFO * infoPtr,HWND lostFocus)1290 DATETIME_SetFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
1291 {
1292 TRACE("got focus from %p\n", lostFocus);
1293
1294 /* if monthcal is open and it loses focus, close monthcal */
1295 if (infoPtr->hMonthCal && (lostFocus == infoPtr->hMonthCal) &&
1296 IsWindowVisible(infoPtr->hMonthCal))
1297 {
1298 ShowWindow(infoPtr->hMonthCal, SW_HIDE);
1299 DATETIME_SendSimpleNotify(infoPtr, DTN_CLOSEUP);
1300 /* note: this get triggered even if monthcal loses focus to a dropdown
1301 * box click, which occurs without an intermediate WM_PAINT call
1302 */
1303 infoPtr->bDropdownEnabled = FALSE;
1304 return 0;
1305 }
1306
1307 if (infoPtr->haveFocus == 0) {
1308 DATETIME_SendSimpleNotify (infoPtr, NM_SETFOCUS);
1309 infoPtr->haveFocus = DTHT_GOTFOCUS;
1310 }
1311
1312 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1313
1314 return 0;
1315 }
1316
1317
1318 static BOOL
DATETIME_SendDateTimeChangeNotify(const DATETIME_INFO * infoPtr)1319 DATETIME_SendDateTimeChangeNotify (const DATETIME_INFO *infoPtr)
1320 {
1321 NMDATETIMECHANGE dtdtc;
1322
1323 dtdtc.nmhdr.hwndFrom = infoPtr->hwndSelf;
1324 dtdtc.nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1325 dtdtc.nmhdr.code = DTN_DATETIMECHANGE;
1326
1327 dtdtc.dwFlags = infoPtr->dateValid ? GDT_VALID : GDT_NONE;
1328
1329 dtdtc.st = infoPtr->date;
1330 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
1331 dtdtc.nmhdr.idFrom, (LPARAM)&dtdtc);
1332 }
1333
1334
1335 static BOOL
DATETIME_SendSimpleNotify(const DATETIME_INFO * infoPtr,UINT code)1336 DATETIME_SendSimpleNotify (const DATETIME_INFO *infoPtr, UINT code)
1337 {
1338 NMHDR nmhdr;
1339
1340 TRACE("%x\n", code);
1341 nmhdr.hwndFrom = infoPtr->hwndSelf;
1342 nmhdr.idFrom = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1343 nmhdr.code = code;
1344
1345 return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
1346 nmhdr.idFrom, (LPARAM)&nmhdr);
1347 }
1348
1349 static LRESULT
DATETIME_Size(DATETIME_INFO * infoPtr,INT width,INT height)1350 DATETIME_Size (DATETIME_INFO *infoPtr, INT width, INT height)
1351 {
1352 /* set size */
1353 infoPtr->rcClient.bottom = height;
1354 infoPtr->rcClient.right = width;
1355
1356 TRACE("Height=%d, Width=%d\n", infoPtr->rcClient.bottom, infoPtr->rcClient.right);
1357
1358 infoPtr->rcDraw = infoPtr->rcClient;
1359
1360 if (infoPtr->dwStyle & DTS_UPDOWN) {
1361 SetWindowPos(infoPtr->hUpdown, NULL,
1362 infoPtr->rcClient.right-14, 0,
1363 15, infoPtr->rcClient.bottom - infoPtr->rcClient.top,
1364 SWP_NOACTIVATE | SWP_NOZORDER);
1365 }
1366 else {
1367 /* set the size of the button that drops the calendar down */
1368 /* FIXME: account for style that allows button on left side */
1369 infoPtr->calbutton.top = infoPtr->rcDraw.top;
1370 infoPtr->calbutton.bottom= infoPtr->rcDraw.bottom;
1371 infoPtr->calbutton.left = infoPtr->rcDraw.right-15;
1372 infoPtr->calbutton.right = infoPtr->rcDraw.right;
1373 }
1374
1375 /* set enable/disable button size for show none style being enabled */
1376 /* FIXME: these dimensions are completely incorrect */
1377 infoPtr->checkbox.top = infoPtr->rcDraw.top;
1378 infoPtr->checkbox.bottom = infoPtr->rcDraw.bottom;
1379 infoPtr->checkbox.left = infoPtr->rcDraw.left;
1380 infoPtr->checkbox.right = infoPtr->rcDraw.left + 10;
1381
1382 InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1383
1384 return 0;
1385 }
1386
1387 static LRESULT
DATETIME_StyleChanging(DATETIME_INFO * infoPtr,WPARAM wStyleType,STYLESTRUCT * lpss)1388 DATETIME_StyleChanging(DATETIME_INFO *infoPtr, WPARAM wStyleType, STYLESTRUCT *lpss)
1389 {
1390 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
1391 wStyleType, lpss->styleOld, lpss->styleNew);
1392
1393 /* block DTS_SHOWNONE change */
1394 if ((lpss->styleNew ^ lpss->styleOld) & DTS_SHOWNONE)
1395 {
1396 if (lpss->styleOld & DTS_SHOWNONE)
1397 lpss->styleNew |= DTS_SHOWNONE;
1398 else
1399 lpss->styleNew &= ~DTS_SHOWNONE;
1400 }
1401
1402 return 0;
1403 }
1404
1405 static LRESULT
DATETIME_StyleChanged(DATETIME_INFO * infoPtr,WPARAM wStyleType,const STYLESTRUCT * lpss)1406 DATETIME_StyleChanged(DATETIME_INFO *infoPtr, WPARAM wStyleType, const STYLESTRUCT *lpss)
1407 {
1408 TRACE("(styletype=%lx, styleOld=0x%08x, styleNew=0x%08x)\n",
1409 wStyleType, lpss->styleOld, lpss->styleNew);
1410
1411 if (wStyleType != GWL_STYLE) return 0;
1412
1413 infoPtr->dwStyle = lpss->styleNew;
1414
1415 if ( !(lpss->styleOld & DTS_SHOWNONE) && (lpss->styleNew & DTS_SHOWNONE) ) {
1416 infoPtr->hwndCheckbut = CreateWindowExW (0, WC_BUTTONW, 0, WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
1417 2, 2, 13, 13, infoPtr->hwndSelf, 0,
1418 (HINSTANCE)GetWindowLongPtrW (infoPtr->hwndSelf, GWLP_HINSTANCE), 0);
1419 SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, infoPtr->dateValid ? 1 : 0, 0);
1420 }
1421 if ( (lpss->styleOld & DTS_SHOWNONE) && !(lpss->styleNew & DTS_SHOWNONE) ) {
1422 DestroyWindow(infoPtr->hwndCheckbut);
1423 infoPtr->hwndCheckbut = 0;
1424 }
1425 if ( !(lpss->styleOld & DTS_UPDOWN) && (lpss->styleNew & DTS_UPDOWN) ) {
1426 infoPtr->hUpdown = CreateUpDownControl (WS_CHILD | WS_BORDER | WS_VISIBLE, 120, 1, 20, 20,
1427 infoPtr->hwndSelf, 1, 0, 0, UD_MAXVAL, UD_MINVAL, 0);
1428 }
1429 if ( (lpss->styleOld & DTS_UPDOWN) && !(lpss->styleNew & DTS_UPDOWN) ) {
1430 DestroyWindow(infoPtr->hUpdown);
1431 infoPtr->hUpdown = 0;
1432 }
1433
1434 InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1435 return 0;
1436 }
1437
DATETIME_GetIdealSize(DATETIME_INFO * infoPtr,SIZE * size)1438 static BOOL DATETIME_GetIdealSize(DATETIME_INFO *infoPtr, SIZE *size)
1439 {
1440 SIZE field_size;
1441 RECT rect;
1442 WCHAR txt[80];
1443 HDC hdc;
1444 HFONT oldFont;
1445 int i;
1446
1447 size->cx = size->cy = 0;
1448
1449 hdc = GetDC(infoPtr->hwndSelf);
1450 oldFont = SelectObject(hdc, infoPtr->hFont);
1451
1452 /* Get text font height */
1453 DATETIME_ReturnTxt(infoPtr, 0, txt, ARRAY_SIZE(txt));
1454 GetTextExtentPoint32W(hdc, txt, lstrlenW(txt), &field_size);
1455 size->cy = field_size.cy;
1456
1457 /* Get text font width */
1458 for (i = 0; i < infoPtr->nrFields; i++)
1459 {
1460 size->cx += DATETIME_GetFieldWidth(infoPtr, hdc, i);
1461 }
1462
1463 SelectObject(hdc, oldFont);
1464 ReleaseDC(infoPtr->hwndSelf, hdc);
1465
1466 if (infoPtr->dwStyle & DTS_UPDOWN)
1467 {
1468 GetWindowRect(infoPtr->hUpdown, &rect);
1469 size->cx += rect.right - rect.left;
1470 }
1471 else
1472 {
1473 size->cx += infoPtr->calbutton.right - infoPtr->calbutton.left;
1474 }
1475
1476 if (infoPtr->dwStyle & DTS_SHOWNONE)
1477 {
1478 size->cx += infoPtr->checkbox.right - infoPtr->checkbox.left;
1479 }
1480
1481 /* Add space between controls for them not to get too close */
1482 size->cx += 12;
1483 size->cy += 4;
1484
1485 TRACE("cx=%d cy=%d\n", size->cx, size->cy);
1486 return TRUE;
1487 }
1488
1489 static LRESULT
DATETIME_SetFont(DATETIME_INFO * infoPtr,HFONT font,BOOL repaint)1490 DATETIME_SetFont (DATETIME_INFO *infoPtr, HFONT font, BOOL repaint)
1491 {
1492 infoPtr->hFont = font;
1493 if (repaint) InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1494 return 0;
1495 }
1496
1497
1498 static LRESULT
DATETIME_Create(HWND hwnd,const CREATESTRUCTW * lpcs)1499 DATETIME_Create (HWND hwnd, const CREATESTRUCTW *lpcs)
1500 {
1501 DATETIME_INFO *infoPtr = Alloc (sizeof(DATETIME_INFO));
1502 STYLESTRUCT ss = { 0, lpcs->style };
1503
1504 if (!infoPtr) return -1;
1505
1506 infoPtr->hwndSelf = hwnd;
1507 infoPtr->dwStyle = lpcs->style;
1508
1509 infoPtr->nrFieldsAllocated = 32;
1510 infoPtr->fieldspec = Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
1511 infoPtr->fieldRect = Alloc (infoPtr->nrFieldsAllocated * sizeof(RECT));
1512 infoPtr->buflen = Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
1513 infoPtr->hwndNotify = lpcs->hwndParent;
1514 infoPtr->select = -1; /* initially, nothing is selected */
1515 infoPtr->bDropdownEnabled = TRUE;
1516
1517 DATETIME_StyleChanged(infoPtr, GWL_STYLE, &ss);
1518 DATETIME_SetFormatW (infoPtr, 0);
1519
1520 /* create the monthcal control */
1521 infoPtr->hMonthCal = CreateWindowExW (0, MONTHCAL_CLASSW, 0, WS_BORDER | WS_POPUP | WS_CLIPSIBLINGS,
1522 0, 0, 0, 0, infoPtr->hwndSelf, 0, 0, 0);
1523
1524 /* initialize info structure */
1525 GetLocalTime (&infoPtr->date);
1526 infoPtr->dateValid = TRUE;
1527 infoPtr->hFont = GetStockObject(DEFAULT_GUI_FONT);
1528
1529 SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
1530
1531 return 0;
1532 }
1533
1534
1535
1536 static LRESULT
DATETIME_Destroy(DATETIME_INFO * infoPtr)1537 DATETIME_Destroy (DATETIME_INFO *infoPtr)
1538 {
1539 if (infoPtr->hwndCheckbut)
1540 DestroyWindow(infoPtr->hwndCheckbut);
1541 if (infoPtr->hUpdown)
1542 DestroyWindow(infoPtr->hUpdown);
1543 if (infoPtr->hMonthCal)
1544 DestroyWindow(infoPtr->hMonthCal);
1545 SetWindowLongPtrW( infoPtr->hwndSelf, 0, 0 ); /* clear infoPtr */
1546 Free (infoPtr->buflen);
1547 Free (infoPtr->fieldRect);
1548 Free (infoPtr->fieldspec);
1549 Free (infoPtr);
1550 return 0;
1551 }
1552
1553
1554 static INT
DATETIME_GetText(const DATETIME_INFO * infoPtr,INT count,LPWSTR dst)1555 DATETIME_GetText (const DATETIME_INFO *infoPtr, INT count, LPWSTR dst)
1556 {
1557 WCHAR buf[80];
1558 int i;
1559
1560 if (!dst || (count <= 0)) return 0;
1561
1562 dst[0] = 0;
1563 for (i = 0; i < infoPtr->nrFields; i++)
1564 {
1565 DATETIME_ReturnTxt(infoPtr, i, buf, ARRAY_SIZE(buf));
1566 if ((lstrlenW(dst) + lstrlenW(buf)) < count)
1567 lstrcatW(dst, buf);
1568 else break;
1569 }
1570 return lstrlenW(dst);
1571 }
1572
DATETIME_GetTextLength(const DATETIME_INFO * info)1573 static int DATETIME_GetTextLength(const DATETIME_INFO *info)
1574 {
1575 int i, length = 0;
1576 WCHAR buffer[80];
1577
1578 TRACE("%p.\n", info);
1579
1580 for (i = 0; i < info->nrFields; i++)
1581 {
1582 DATETIME_ReturnTxt(info, i, buffer, ARRAY_SIZE(buffer));
1583 length += lstrlenW(buffer);
1584 }
1585 return length;
1586 }
1587
1588 static LRESULT WINAPI
DATETIME_WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)1589 DATETIME_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1590 {
1591 DATETIME_INFO *infoPtr = ((DATETIME_INFO *)GetWindowLongPtrW (hwnd, 0));
1592
1593 TRACE ("%x, %lx, %lx\n", uMsg, wParam, lParam);
1594
1595 if (!infoPtr && (uMsg != WM_CREATE) && (uMsg != WM_NCCREATE))
1596 return DefWindowProcW( hwnd, uMsg, wParam, lParam );
1597
1598 switch (uMsg) {
1599
1600 case DTM_GETSYSTEMTIME:
1601 return DATETIME_GetSystemTime (infoPtr, (SYSTEMTIME *) lParam);
1602
1603 case DTM_SETSYSTEMTIME:
1604 return DATETIME_SetSystemTime (infoPtr, wParam, (SYSTEMTIME *) lParam);
1605
1606 case DTM_GETRANGE:
1607 return SendMessageW (infoPtr->hMonthCal, MCM_GETRANGE, wParam, lParam);
1608
1609 case DTM_SETRANGE:
1610 return SendMessageW (infoPtr->hMonthCal, MCM_SETRANGE, wParam, lParam);
1611
1612 case DTM_SETFORMATA:
1613 return DATETIME_SetFormatA (infoPtr, (LPCSTR)lParam);
1614
1615 case DTM_SETFORMATW:
1616 return DATETIME_SetFormatW (infoPtr, (LPCWSTR)lParam);
1617
1618 case DTM_GETMONTHCAL:
1619 return (LRESULT)infoPtr->hMonthCal;
1620
1621 case DTM_SETMCCOLOR:
1622 return SendMessageW (infoPtr->hMonthCal, MCM_SETCOLOR, wParam, lParam);
1623
1624 case DTM_GETMCCOLOR:
1625 return SendMessageW (infoPtr->hMonthCal, MCM_GETCOLOR, wParam, 0);
1626
1627 case DTM_SETMCFONT:
1628 return SendMessageW (infoPtr->hMonthCal, WM_SETFONT, wParam, lParam);
1629
1630 case DTM_GETMCFONT:
1631 return SendMessageW (infoPtr->hMonthCal, WM_GETFONT, wParam, lParam);
1632
1633 case DTM_GETIDEALSIZE:
1634 return DATETIME_GetIdealSize(infoPtr, (SIZE *)lParam);
1635
1636 case WM_NOTIFY:
1637 return DATETIME_Notify (infoPtr, (LPNMHDR)lParam);
1638
1639 case WM_ENABLE:
1640 return DATETIME_Enable (infoPtr, (BOOL)wParam);
1641
1642 case WM_ERASEBKGND:
1643 return DATETIME_EraseBackground (infoPtr, (HDC)wParam);
1644
1645 case WM_GETDLGCODE:
1646 return DLGC_WANTARROWS | DLGC_WANTCHARS;
1647
1648 case WM_PRINTCLIENT:
1649 case WM_PAINT:
1650 return DATETIME_Paint (infoPtr, (HDC)wParam);
1651
1652 case WM_KEYDOWN:
1653 return DATETIME_KeyDown (infoPtr, wParam);
1654
1655 case WM_CHAR:
1656 return DATETIME_Char (infoPtr, wParam);
1657
1658 case WM_KILLFOCUS:
1659 return DATETIME_KillFocus (infoPtr, (HWND)wParam);
1660
1661 case WM_NCCREATE:
1662 return DATETIME_NCCreate (hwnd, (LPCREATESTRUCTW)lParam);
1663
1664 case WM_SETFOCUS:
1665 return DATETIME_SetFocus (infoPtr, (HWND)wParam);
1666
1667 case WM_SIZE:
1668 return DATETIME_Size (infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1669
1670 case WM_LBUTTONDOWN:
1671 return DATETIME_LButtonDown (infoPtr, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1672
1673 case WM_LBUTTONUP:
1674 return DATETIME_LButtonUp (infoPtr);
1675
1676 case WM_VSCROLL:
1677 return DATETIME_VScroll (infoPtr, (WORD)wParam);
1678
1679 case WM_CREATE:
1680 return DATETIME_Create (hwnd, (LPCREATESTRUCTW)lParam);
1681
1682 case WM_DESTROY:
1683 return DATETIME_Destroy (infoPtr);
1684
1685 case WM_COMMAND:
1686 return DATETIME_Command (infoPtr, wParam, lParam);
1687
1688 case WM_STYLECHANGING:
1689 return DATETIME_StyleChanging(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
1690
1691 case WM_STYLECHANGED:
1692 return DATETIME_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
1693
1694 case WM_SETFONT:
1695 return DATETIME_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);
1696
1697 case WM_GETFONT:
1698 return (LRESULT) infoPtr->hFont;
1699
1700 case WM_GETTEXT:
1701 return (LRESULT) DATETIME_GetText(infoPtr, wParam, (LPWSTR)lParam);
1702
1703 case WM_GETTEXTLENGTH:
1704 return (LRESULT)DATETIME_GetTextLength(infoPtr);
1705
1706 case WM_SETTEXT:
1707 return CB_ERR;
1708
1709 default:
1710 if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
1711 ERR("unknown msg %04x wp=%08lx lp=%08lx\n",
1712 uMsg, wParam, lParam);
1713 return DefWindowProcW (hwnd, uMsg, wParam, lParam);
1714 }
1715 }
1716
1717
1718 void
DATETIME_Register(void)1719 DATETIME_Register (void)
1720 {
1721 WNDCLASSW wndClass;
1722
1723 ZeroMemory (&wndClass, sizeof(WNDCLASSW));
1724 wndClass.style = CS_GLOBALCLASS;
1725 wndClass.lpfnWndProc = DATETIME_WindowProc;
1726 wndClass.cbClsExtra = 0;
1727 wndClass.cbWndExtra = sizeof(DATETIME_INFO *);
1728 wndClass.hCursor = LoadCursorW (0, (LPCWSTR)IDC_ARROW);
1729 wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
1730 wndClass.lpszClassName = DATETIMEPICK_CLASSW;
1731
1732 RegisterClassW (&wndClass);
1733 }
1734
1735
1736 void
DATETIME_Unregister(void)1737 DATETIME_Unregister (void)
1738 {
1739 UnregisterClassW (DATETIMEPICK_CLASSW, NULL);
1740 }
1741