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