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