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