xref: /reactos/dll/win32/comctl32/datetime.c (revision 53221834)
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
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  */
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
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
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
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
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
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 
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
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 
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
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
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 */
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 
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
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
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
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
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 
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
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
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
1737 DATETIME_Unregister (void)
1738 {
1739     UnregisterClassW (DATETIMEPICK_CLASSW, NULL);
1740 }
1741