wininet: Rename HTTP_Base64 to HTTP_EncodeBase64 to more accurately reflect what...
[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_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];
530     LPCWSTR bufptr;
531     SIZE size;
532
533     TRACE ("%d,%d\n", infoPtr->nrFields, count);
534     if (count>infoPtr->nrFields || count < 0) {
535         WARN ("buffer overrun, have %d want %d\n", infoPtr->nrFields, count);
536         return;
537     }
538
539     if (!infoPtr->fieldspec) return;
540
541     spec = infoPtr->fieldspec[count];
542     if (spec & DT_STRING) {
543         int txtlen = infoPtr->buflen[count];
544
545         if (txtlen > 79)
546             txtlen = 79;
547         memcpy (buffer, infoPtr->textbuf + (spec &~ DT_STRING), txtlen * sizeof(WCHAR));
548         buffer[txtlen] = 0;
549         bufptr = buffer;
550     }
551     else {
552         switch (spec) {
553             case ONEDIGITDAY:
554             case ONEDIGIT12HOUR:
555             case ONEDIGIT24HOUR:
556             case ONEDIGITSECOND:
557             case ONEDIGITMINUTE:
558             case ONEDIGITMONTH:
559             case ONEDIGITYEAR:
560                 /* these seem to use a two byte field */
561             case TWODIGITDAY:
562             case TWODIGIT12HOUR:
563             case TWODIGIT24HOUR:
564             case TWODIGITSECOND:
565             case TWODIGITMINUTE:
566             case TWODIGITMONTH:
567             case TWODIGITYEAR:
568                 bufptr = fld_d2W;
569                 break;
570             case INVALIDFULLYEAR:
571             case FULLYEAR:
572                 bufptr = fld_d4W;
573                 break;
574             case THREECHARDAY:
575                 bufptr = fld_day3;
576                 break;
577             case FULLDAY:
578                 bufptr = fld_day;
579                 break;
580             case THREECHARMONTH:
581                 bufptr = fld_mon3;
582                 break;
583             case FULLMONTH:
584                 bufptr = fld_mon;
585                 break;
586             case ONELETTERAMPM:
587                 bufptr = fld_am1;
588                 break;
589             case TWOLETTERAMPM:
590                 bufptr = fld_am2;
591                 break;
592             default:
593                 bufptr = fld_d1W;
594                 break;
595         }
596     }
597     GetTextExtentPoint32W (hdc, bufptr, strlenW(bufptr), &size);
598     *fieldWidthPtr = size.cx;
599 }
600
601 static void 
602 DATETIME_Refresh (DATETIME_INFO *infoPtr, HDC hdc)
603 {
604     int i,prevright;
605     RECT *field;
606     RECT *rcDraw = &infoPtr->rcDraw;
607     RECT *calbutton = &infoPtr->calbutton;
608     RECT *checkbox = &infoPtr->checkbox;
609     SIZE size;
610     COLORREF oldTextColor;
611     SHORT fieldWidth = 0;
612
613     /* draw control edge */
614     TRACE("\n");
615
616     if (infoPtr->dateValid) {
617         HFONT oldFont = SelectObject (hdc, infoPtr->hFont);
618         INT oldBkMode = SetBkMode (hdc, TRANSPARENT);
619         WCHAR txt[80];
620
621         DATETIME_ReturnTxt (infoPtr, 0, txt, sizeof(txt)/sizeof(txt[0]));
622         GetTextExtentPoint32W (hdc, txt, strlenW(txt), &size);
623         rcDraw->bottom = size.cy + 2;
624
625         prevright = checkbox->right = ((infoPtr->dwStyle & DTS_SHOWNONE) ? 18 : 2);
626
627         for (i = 0; i < infoPtr->nrFields; i++) {
628             DATETIME_ReturnTxt (infoPtr, i, txt, sizeof(txt)/sizeof(txt[0]));
629             GetTextExtentPoint32W (hdc, txt, strlenW(txt), &size);
630             DATETIME_ReturnFieldWidth (infoPtr, hdc, i, &fieldWidth);
631             field = &infoPtr->fieldRect[i];
632             field->left  = prevright;
633             field->right = prevright + fieldWidth;
634             field->top   = rcDraw->top;
635             field->bottom = rcDraw->bottom;
636             prevright = field->right;
637
638             if (infoPtr->dwStyle & WS_DISABLED)
639                 oldTextColor = SetTextColor (hdc, comctl32_color.clrGrayText);
640             else if ((infoPtr->haveFocus) && (i == infoPtr->select)) {
641                 /* fill if focussed */
642                 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrActiveCaption);
643                 FillRect(hdc, field, hbr);
644                 DeleteObject (hbr);
645                 oldTextColor = SetTextColor (hdc, comctl32_color.clrWindow);
646             }
647             else
648                 oldTextColor = SetTextColor (hdc, comctl32_color.clrWindowText);
649
650             /* draw the date text using the colour set above */
651             DrawTextW (hdc, txt, strlenW(txt), field, DT_RIGHT | DT_VCENTER | DT_SINGLELINE);
652             SetTextColor (hdc, oldTextColor);
653         }
654         SetBkMode (hdc, oldBkMode);
655         SelectObject (hdc, oldFont);
656     }
657
658     if (!(infoPtr->dwStyle & DTS_UPDOWN)) {
659         DrawFrameControl(hdc, calbutton, DFC_SCROLL,
660                          DFCS_SCROLLDOWN | (infoPtr->bCalDepressed ? DFCS_PUSHED : 0) |
661                          (infoPtr->dwStyle & WS_DISABLED ? DFCS_INACTIVE : 0) );
662     }
663 }
664
665
666 static INT
667 DATETIME_HitTest (DATETIME_INFO *infoPtr, POINT pt)
668 {
669     int i;
670
671     TRACE ("%d, %d\n", pt.x, pt.y);
672
673     if (PtInRect (&infoPtr->calbutton, pt)) return DTHT_MCPOPUP;
674     if (PtInRect (&infoPtr->checkbox, pt)) return DTHT_CHECKBOX;
675
676     for (i=0; i < infoPtr->nrFields; i++) {
677         if (PtInRect (&infoPtr->fieldRect[i], pt)) return i;
678     }
679
680     return DTHT_NONE;
681 }
682
683
684 static LRESULT
685 DATETIME_LButtonDown (DATETIME_INFO *infoPtr, WORD wKey, INT x, INT y)
686 {
687     POINT pt;
688     int old, new;
689
690     pt.x = x;
691     pt.y = y;
692     old = infoPtr->select;
693     new = DATETIME_HitTest (infoPtr, pt);
694
695     /* FIXME: might be conditions where we don't want to update infoPtr->select */
696     infoPtr->select = new;
697
698     SetFocus(infoPtr->hwndSelf);
699
700     if (infoPtr->select == DTHT_MCPOPUP) {
701         RECT rcMonthCal;
702         SendMessageW(infoPtr->hMonthCal, MCM_GETMINREQRECT, 0, (LPARAM)&rcMonthCal);
703
704         /* FIXME: button actually is only depressed during dropdown of the */
705         /* calendar control and when the mouse is over the button window */
706         infoPtr->bCalDepressed = TRUE;
707
708         /* recalculate the position of the monthcal popup */
709         if(infoPtr->dwStyle & DTS_RIGHTALIGN)
710             infoPtr->monthcal_pos.x = infoPtr->calbutton.left - 
711                 (rcMonthCal.right - rcMonthCal.left);
712         else
713             /* FIXME: this should be after the area reserved for the checkbox */
714             infoPtr->monthcal_pos.x = infoPtr->rcDraw.left;
715
716         infoPtr->monthcal_pos.y = infoPtr->rcClient.bottom;
717         ClientToScreen (infoPtr->hwndSelf, &(infoPtr->monthcal_pos));
718         SetWindowPos(infoPtr->hMonthCal, 0, infoPtr->monthcal_pos.x,
719             infoPtr->monthcal_pos.y, rcMonthCal.right - rcMonthCal.left,
720             rcMonthCal.bottom - rcMonthCal.top, 0);
721
722         if(IsWindowVisible(infoPtr->hMonthCal)) {
723             ShowWindow(infoPtr->hMonthCal, SW_HIDE);
724         } else {
725             SYSTEMTIME *lprgSysTimeArray = &infoPtr->date;
726             TRACE("update calendar %04d/%02d/%02d\n", 
727             lprgSysTimeArray->wYear, lprgSysTimeArray->wMonth, lprgSysTimeArray->wDay);
728             SendMessageW(infoPtr->hMonthCal, MCM_SETCURSEL, 0, (LPARAM)(&infoPtr->date));
729             ShowWindow(infoPtr->hMonthCal, SW_SHOW);
730         }
731
732         TRACE ("dt:%p mc:%p mc parent:%p, desktop:%p\n",
733                infoPtr->hwndSelf, infoPtr->hMonthCal, infoPtr->hwndNotify, GetDesktopWindow ());
734         DATETIME_SendSimpleNotify (infoPtr, DTN_DROPDOWN);
735     }
736
737     InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
738
739     return 0;
740 }
741
742
743 static LRESULT
744 DATETIME_LButtonUp (DATETIME_INFO *infoPtr, WORD wKey)
745 {
746     if(infoPtr->bCalDepressed) {
747         infoPtr->bCalDepressed = FALSE;
748         InvalidateRect(infoPtr->hwndSelf, &(infoPtr->calbutton), TRUE);
749     }
750
751     return 0;
752 }
753
754
755 static LRESULT
756 DATETIME_Paint (DATETIME_INFO *infoPtr, HDC hdc)
757 {
758     if (!hdc) {
759         PAINTSTRUCT ps;
760         hdc = BeginPaint (infoPtr->hwndSelf, &ps);
761         DATETIME_Refresh (infoPtr, hdc);
762         EndPaint (infoPtr->hwndSelf, &ps);
763     } else {
764         DATETIME_Refresh (infoPtr, hdc);
765     }
766     return 0;
767 }
768
769
770 static LRESULT
771 DATETIME_Button_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
772 {
773     if( HIWORD(wParam) == BN_CLICKED) {
774         DWORD state = SendMessageW((HWND)lParam, BM_GETCHECK, 0, 0);
775         infoPtr->dateValid = (state == BST_CHECKED);
776         InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
777     }
778     return 0;
779 }
780           
781         
782         
783 static LRESULT
784 DATETIME_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
785 {
786     TRACE("hwndbutton = %p\n", infoPtr->hwndCheckbut);
787     if(infoPtr->hwndCheckbut == (HWND)lParam)
788         return DATETIME_Button_Command(infoPtr, wParam, lParam);
789     return 0;
790 }
791
792
793 static LRESULT
794 DATETIME_Enable (DATETIME_INFO *infoPtr, BOOL bEnable)
795 {
796     TRACE("%p %s\n", infoPtr, bEnable ? "TRUE" : "FALSE");
797     if (bEnable)
798         infoPtr->dwStyle &= ~WS_DISABLED;
799     else
800         infoPtr->dwStyle |= WS_DISABLED;
801     return 0;
802 }
803
804
805 static LRESULT
806 DATETIME_EraseBackground (DATETIME_INFO *infoPtr, HDC hdc)
807 {
808     HBRUSH hBrush, hSolidBrush = NULL;
809     RECT   rc;
810
811     if (infoPtr->dwStyle & WS_DISABLED)
812         hBrush = hSolidBrush = CreateSolidBrush(comctl32_color.clrBtnFace);
813     else
814     {
815         hBrush = (HBRUSH)SendMessageW(infoPtr->hwndNotify, WM_CTLCOLOREDIT,
816                                       (WPARAM)hdc, (LPARAM)infoPtr->hwndSelf);
817         if (!hBrush)
818             hBrush = hSolidBrush = CreateSolidBrush(comctl32_color.clrWindow);
819     }
820
821     GetClientRect (infoPtr->hwndSelf, &rc);
822
823     FillRect (hdc, &rc, hBrush);
824
825     if (hSolidBrush)
826         DeleteObject(hSolidBrush);
827
828     return -1;
829 }
830
831
832 static LRESULT
833 DATETIME_Notify (DATETIME_INFO *infoPtr, int idCtrl, LPNMHDR lpnmh)
834 {
835     TRACE ("Got notification %x from %p\n", lpnmh->code, lpnmh->hwndFrom);
836     TRACE ("info: %p %p %p\n", infoPtr->hwndSelf, infoPtr->hMonthCal, infoPtr->hUpdown);
837
838     if (lpnmh->code == MCN_SELECT) {
839         ShowWindow(infoPtr->hMonthCal, SW_HIDE);
840         infoPtr->dateValid = TRUE;
841         SendMessageW (infoPtr->hMonthCal, MCM_GETCURSEL, 0, (LPARAM)&infoPtr->date);
842         TRACE("got from calendar %04d/%02d/%02d day of week %d\n", 
843         infoPtr->date.wYear, infoPtr->date.wMonth, infoPtr->date.wDay, infoPtr->date.wDayOfWeek);
844         SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_CHECKED, 0);
845         InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
846         DATETIME_SendDateTimeChangeNotify (infoPtr);
847     }
848     if ((lpnmh->hwndFrom == infoPtr->hUpdown) && (lpnmh->code == UDN_DELTAPOS)) {
849         LPNMUPDOWN lpnmud = (LPNMUPDOWN)lpnmh;
850         TRACE("Delta pos %d\n", lpnmud->iDelta);
851         infoPtr->pendingUpdown = lpnmud->iDelta;
852     }
853     return 0;
854 }
855
856
857 static LRESULT
858 DATETIME_KeyDown (DATETIME_INFO *infoPtr, DWORD vkCode, LPARAM flags)
859 {
860     int fieldNum = infoPtr->select & DTHT_DATEFIELD;
861     int wrap = 0;
862
863     if (!(infoPtr->haveFocus)) return 0;
864     if ((fieldNum==0) && (infoPtr->select)) return 0;
865
866     if (infoPtr->select & FORMATCALLMASK) {
867         FIXME ("Callbacks not implemented yet\n");
868     }
869
870     if (vkCode >= '0' && vkCode <= '9') {
871         /* this is a somewhat simplified version of what Windows does */
872         SYSTEMTIME *date = &infoPtr->date;
873         switch (infoPtr->fieldspec[fieldNum]) {
874             case ONEDIGITYEAR:
875             case TWODIGITYEAR:
876                 date->wYear = date->wYear - (date->wYear%100) +
877                         (date->wYear%10)*10 + (vkCode-'0');
878                 date->wDayOfWeek = DATETIME_CalculateDayOfWeek(
879                         date->wDay,date->wMonth,date->wYear);
880                 DATETIME_SendDateTimeChangeNotify (infoPtr);
881                 break;
882             case INVALIDFULLYEAR:
883             case FULLYEAR:
884                 date->wYear = (date->wYear%1000)*10 + (vkCode-'0');
885                 date->wDayOfWeek = DATETIME_CalculateDayOfWeek(
886                         date->wDay,date->wMonth,date->wYear);
887                 DATETIME_SendDateTimeChangeNotify (infoPtr);
888                 break;
889             case ONEDIGITMONTH:
890             case TWODIGITMONTH:
891                 if ((date->wMonth%10) > 1 || (vkCode-'0') > 2)
892                     date->wMonth = vkCode-'0';
893                 else
894                     date->wMonth = (date->wMonth%10)*10+vkCode-'0';
895                 date->wDayOfWeek = DATETIME_CalculateDayOfWeek(
896                         date->wDay,date->wMonth,date->wYear);
897                 DATETIME_SendDateTimeChangeNotify (infoPtr);
898                 break;
899             case ONEDIGITDAY:
900             case TWODIGITDAY:
901                 /* probably better checking here would help */
902                 if ((date->wDay%10) >= 3 && (vkCode-'0') > 1)
903                     date->wDay = vkCode-'0';
904                 else
905                     date->wDay = (date->wDay%10)*10+vkCode-'0';
906                 date->wDayOfWeek = DATETIME_CalculateDayOfWeek(
907                         date->wDay,date->wMonth,date->wYear);
908                 DATETIME_SendDateTimeChangeNotify (infoPtr);
909                 break;
910             case ONEDIGIT12HOUR:
911             case TWODIGIT12HOUR:
912                 if ((date->wHour%10) > 1 || (vkCode-'0') > 2)
913                     date->wHour = vkCode-'0';
914                 else
915                     date->wHour = (date->wHour%10)*10+vkCode-'0';
916                 DATETIME_SendDateTimeChangeNotify (infoPtr);
917                 break;
918             case ONEDIGIT24HOUR:
919             case TWODIGIT24HOUR:
920                 if ((date->wHour%10) > 2)
921                     date->wHour = vkCode-'0';
922                 else if ((date->wHour%10) == 2 && (vkCode-'0') > 3)
923                     date->wHour = vkCode-'0';
924                 else
925                     date->wHour = (date->wHour%10)*10+vkCode-'0';
926                 DATETIME_SendDateTimeChangeNotify (infoPtr);
927                 break;
928             case ONEDIGITMINUTE:
929             case TWODIGITMINUTE:
930                 if ((date->wMinute%10) > 5)
931                     date->wMinute = vkCode-'0';
932                 else
933                     date->wMinute = (date->wMinute%10)*10+vkCode-'0';
934                 DATETIME_SendDateTimeChangeNotify (infoPtr);
935                 break;
936             case ONEDIGITSECOND:
937             case TWODIGITSECOND:
938                 if ((date->wSecond%10) > 5)
939                     date->wSecond = vkCode-'0';
940                 else
941                     date->wSecond = (date->wSecond%10)*10+vkCode-'0';
942                 DATETIME_SendDateTimeChangeNotify (infoPtr);
943                 break;
944         }
945     }
946     
947     switch (vkCode) {
948         case VK_ADD:
949         case VK_UP:
950             DATETIME_IncreaseField (infoPtr, fieldNum, 1);
951             DATETIME_SendDateTimeChangeNotify (infoPtr);
952             break;
953         case VK_SUBTRACT:
954         case VK_DOWN:
955             DATETIME_IncreaseField (infoPtr, fieldNum, -1);
956             DATETIME_SendDateTimeChangeNotify (infoPtr);
957             break;
958         case VK_HOME:
959             DATETIME_IncreaseField (infoPtr, fieldNum, INT_MIN);
960             DATETIME_SendDateTimeChangeNotify (infoPtr);
961             break;
962         case VK_END:
963             DATETIME_IncreaseField (infoPtr, fieldNum, INT_MAX);
964             DATETIME_SendDateTimeChangeNotify (infoPtr);
965             break;
966         case VK_LEFT:
967             do {
968                 if (infoPtr->select == 0) {
969                     infoPtr->select = infoPtr->nrFields - 1;
970                     wrap++;
971                 } else {
972                     infoPtr->select--;
973                 }
974             } while ((infoPtr->fieldspec[infoPtr->select] & DT_STRING) && (wrap<2));
975             break;
976         case VK_RIGHT:
977             do {
978                 infoPtr->select++;
979                 if (infoPtr->select==infoPtr->nrFields) {
980                     infoPtr->select = 0;
981                     wrap++;
982                 }
983             } while ((infoPtr->fieldspec[infoPtr->select] & DT_STRING) && (wrap<2));
984             break;
985     }
986
987     InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
988
989     return 0;
990 }
991
992
993 static LRESULT
994 DATETIME_VScroll (DATETIME_INFO *infoPtr, WORD wScroll)
995 {
996     int fieldNum = infoPtr->select & DTHT_DATEFIELD;
997
998     if ((SHORT)LOWORD(wScroll) != SB_THUMBPOSITION) return 0;
999     if (!(infoPtr->haveFocus)) return 0;
1000     if ((fieldNum==0) && (infoPtr->select)) return 0;
1001
1002     if (infoPtr->pendingUpdown >= 0) {
1003         DATETIME_IncreaseField (infoPtr, fieldNum, 1);
1004         DATETIME_SendDateTimeChangeNotify (infoPtr);
1005     }
1006     else {
1007         DATETIME_IncreaseField (infoPtr, fieldNum, -1);
1008         DATETIME_SendDateTimeChangeNotify (infoPtr);
1009     }
1010
1011     InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1012
1013     return 0;
1014 }
1015
1016
1017 static LRESULT
1018 DATETIME_KillFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
1019 {
1020     TRACE("lost focus to %p\n", lostFocus);
1021
1022     if (infoPtr->haveFocus) {
1023         DATETIME_SendSimpleNotify (infoPtr, NM_KILLFOCUS);
1024         infoPtr->haveFocus = 0;
1025     }
1026
1027     InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
1028
1029     return 0;
1030 }
1031
1032
1033 static LRESULT
1034 DATETIME_NCCreate (HWND hwnd, LPCREATESTRUCTW lpcs)
1035 {
1036     DWORD dwExStyle = GetWindowLongW(hwnd, GWL_EXSTYLE);
1037     /* force control to have client edge */
1038     dwExStyle |= WS_EX_CLIENTEDGE;
1039     SetWindowLongW(hwnd, GWL_EXSTYLE, dwExStyle);
1040
1041     return DefWindowProcW(hwnd, WM_NCCREATE, 0, (LPARAM)lpcs);
1042 }
1043
1044
1045 static LRESULT
1046 DATETIME_SetFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
1047 {
1048     TRACE("got focus from %p\n", lostFocus);
1049
1050     if (infoPtr->haveFocus == 0) {
1051         DATETIME_SendSimpleNotify (infoPtr, NM_SETFOCUS);
1052         infoPtr->haveFocus = DTHT_GOTFOCUS;
1053     }
1054
1055     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1056
1057     return 0;
1058 }
1059
1060
1061 static BOOL
1062 DATETIME_SendDateTimeChangeNotify (DATETIME_INFO *infoPtr)
1063 {
1064     NMDATETIMECHANGE dtdtc;
1065
1066     dtdtc.nmhdr.hwndFrom = infoPtr->hwndSelf;
1067     dtdtc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1068     dtdtc.nmhdr.code     = DTN_DATETIMECHANGE;
1069
1070     dtdtc.dwFlags = (infoPtr->dwStyle & DTS_SHOWNONE) ? GDT_NONE : GDT_VALID;
1071
1072     MONTHCAL_CopyTime (&infoPtr->date, &dtdtc.st);
1073     return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
1074                                 (WPARAM)dtdtc.nmhdr.idFrom, (LPARAM)&dtdtc);
1075 }
1076
1077
1078 static BOOL
1079 DATETIME_SendSimpleNotify (DATETIME_INFO *infoPtr, UINT code)
1080 {
1081     NMHDR nmhdr;
1082
1083     TRACE("%x\n", code);
1084     nmhdr.hwndFrom = infoPtr->hwndSelf;
1085     nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
1086     nmhdr.code     = code;
1087
1088     return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
1089                                 (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);
1090 }
1091
1092 static LRESULT
1093 DATETIME_Size (DATETIME_INFO *infoPtr, WORD flags, INT width, INT height)
1094 {
1095     /* set size */
1096     infoPtr->rcClient.bottom = height;
1097     infoPtr->rcClient.right = width;
1098
1099     TRACE("Height=%d, Width=%d\n", infoPtr->rcClient.bottom, infoPtr->rcClient.right);
1100
1101     infoPtr->rcDraw = infoPtr->rcClient;
1102     
1103     if (infoPtr->dwStyle & DTS_UPDOWN) {
1104         SetWindowPos(infoPtr->hUpdown, NULL,
1105             infoPtr->rcClient.right-14, 0,
1106             15, infoPtr->rcClient.bottom - infoPtr->rcClient.top,
1107             SWP_NOACTIVATE | SWP_NOZORDER);
1108     }
1109     else {
1110         /* set the size of the button that drops the calendar down */
1111         /* FIXME: account for style that allows button on left side */
1112         infoPtr->calbutton.top   = infoPtr->rcDraw.top;
1113         infoPtr->calbutton.bottom= infoPtr->rcDraw.bottom;
1114         infoPtr->calbutton.left  = infoPtr->rcDraw.right-15;
1115         infoPtr->calbutton.right = infoPtr->rcDraw.right;
1116     }
1117
1118     /* set enable/disable button size for show none style being enabled */
1119     /* FIXME: these dimensions are completely incorrect */
1120     infoPtr->checkbox.top = infoPtr->rcDraw.top;
1121     infoPtr->checkbox.bottom = infoPtr->rcDraw.bottom;
1122     infoPtr->checkbox.left = infoPtr->rcDraw.left;
1123     infoPtr->checkbox.right = infoPtr->rcDraw.left + 10;
1124
1125     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
1126
1127     return 0;
1128 }
1129
1130
1131 static LRESULT 
1132 DATETIME_StyleChanged(DATETIME_INFO *infoPtr, WPARAM wStyleType, LPSTYLESTRUCT lpss)
1133 {
1134     static const WCHAR buttonW[] = { 'b', 'u', 't', 't', 'o', 'n', 0 };
1135
1136     TRACE("(styletype=%x, styleOld=0x%08x, styleNew=0x%08x)\n",
1137           wStyleType, lpss->styleOld, lpss->styleNew);
1138
1139     if (wStyleType != GWL_STYLE) return 0;
1140   
1141     infoPtr->dwStyle = lpss->styleNew;
1142
1143     if ( !(lpss->styleOld & DTS_SHOWNONE) && (lpss->styleNew & DTS_SHOWNONE) ) {
1144         infoPtr->hwndCheckbut = CreateWindowExW (0, buttonW, 0, WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
1145                                                  2, 2, 13, 13, infoPtr->hwndSelf, 0, 
1146                                                 (HINSTANCE)GetWindowLongPtrW (infoPtr->hwndSelf, GWLP_HINSTANCE), 0);
1147         SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, 1, 0);
1148     }
1149     if ( (lpss->styleOld & DTS_SHOWNONE) && !(lpss->styleNew & DTS_SHOWNONE) ) {
1150         DestroyWindow(infoPtr->hwndCheckbut);
1151         infoPtr->hwndCheckbut = 0;
1152     }
1153     if ( !(lpss->styleOld & DTS_UPDOWN) && (lpss->styleNew & DTS_UPDOWN) ) {
1154         infoPtr->hUpdown = CreateUpDownControl (WS_CHILD | WS_BORDER | WS_VISIBLE, 120, 1, 20, 20, 
1155                                                 infoPtr->hwndSelf, 1, 0, 0, UD_MAXVAL, UD_MINVAL, 0);
1156     }
1157     if ( (lpss->styleOld & DTS_UPDOWN) && !(lpss->styleNew & DTS_UPDOWN) ) {
1158         DestroyWindow(infoPtr->hUpdown);
1159         infoPtr->hUpdown = 0;
1160     }
1161
1162     InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1163     return 0;
1164 }
1165
1166
1167 static LRESULT
1168 DATETIME_SetFont (DATETIME_INFO *infoPtr, HFONT font, BOOL repaint)
1169 {
1170     infoPtr->hFont = font;
1171     if (repaint) InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
1172     return 0;
1173 }
1174
1175
1176 static LRESULT
1177 DATETIME_Create (HWND hwnd, LPCREATESTRUCTW lpcs)
1178 {
1179     static const WCHAR SysMonthCal32W[] = { 'S', 'y', 's', 'M', 'o', 'n', 't', 'h', 'C', 'a', 'l', '3', '2', 0 };
1180     DATETIME_INFO *infoPtr = (DATETIME_INFO *)Alloc (sizeof(DATETIME_INFO));
1181     STYLESTRUCT ss = { 0, lpcs->style };
1182
1183     if (!infoPtr) return -1;
1184
1185     infoPtr->hwndSelf = hwnd;
1186     infoPtr->dwStyle = lpcs->style;
1187
1188     infoPtr->nrFieldsAllocated = 32;
1189     infoPtr->fieldspec = (int *) Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
1190     infoPtr->fieldRect = (RECT *) Alloc (infoPtr->nrFieldsAllocated * sizeof(RECT));
1191     infoPtr->buflen = (int *) Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
1192     infoPtr->hwndNotify = lpcs->hwndParent;
1193     infoPtr->select = -1; /* initially, nothing is selected */
1194
1195     DATETIME_StyleChanged(infoPtr, GWL_STYLE, &ss);
1196     DATETIME_SetFormatW (infoPtr, 0);
1197
1198     /* create the monthcal control */
1199     infoPtr->hMonthCal = CreateWindowExW (0, SysMonthCal32W, 0, WS_BORDER | WS_POPUP | WS_CLIPSIBLINGS, 
1200                                           0, 0, 0, 0, infoPtr->hwndSelf, 0, 0, 0);
1201
1202     /* initialize info structure */
1203     GetLocalTime (&infoPtr->date);
1204     infoPtr->dateValid = TRUE;
1205     infoPtr->hFont = GetStockObject(DEFAULT_GUI_FONT);
1206
1207     SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
1208
1209     return 0;
1210 }
1211
1212
1213
1214 static LRESULT
1215 DATETIME_Destroy (DATETIME_INFO *infoPtr)
1216 {
1217     if (infoPtr->hwndCheckbut)
1218         DestroyWindow(infoPtr->hwndCheckbut);
1219     if (infoPtr->hUpdown)
1220         DestroyWindow(infoPtr->hUpdown);
1221     if (infoPtr->hMonthCal) 
1222         DestroyWindow(infoPtr->hMonthCal);
1223     SetWindowLongPtrW( infoPtr->hwndSelf, 0, 0 ); /* clear infoPtr */
1224     Free (infoPtr);
1225     return 0;
1226 }
1227
1228
1229 static LRESULT WINAPI
1230 DATETIME_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1231 {
1232     DATETIME_INFO *infoPtr = ((DATETIME_INFO *)GetWindowLongPtrW (hwnd, 0));
1233     LRESULT ret;
1234
1235     TRACE ("%x, %x, %lx\n", uMsg, wParam, lParam);
1236
1237     if (!infoPtr && (uMsg != WM_CREATE) && (uMsg != WM_NCCREATE))
1238         return DefWindowProcW( hwnd, uMsg, wParam, lParam );
1239
1240     switch (uMsg) {
1241
1242     case DTM_GETSYSTEMTIME:
1243         return DATETIME_GetSystemTime (infoPtr, (SYSTEMTIME *) lParam);
1244
1245     case DTM_SETSYSTEMTIME:
1246         return DATETIME_SetSystemTime (infoPtr, wParam, (SYSTEMTIME *) lParam);
1247
1248     case DTM_GETRANGE:
1249         ret = SendMessageW (infoPtr->hMonthCal, MCM_GETRANGE, wParam, lParam);
1250         return ret ? ret : 1; /* bug emulation */
1251
1252     case DTM_SETRANGE:
1253         return SendMessageW (infoPtr->hMonthCal, MCM_SETRANGE, wParam, lParam);
1254
1255     case DTM_SETFORMATA:
1256         return DATETIME_SetFormatA (infoPtr, (LPCSTR)lParam);
1257
1258     case DTM_SETFORMATW:
1259         return DATETIME_SetFormatW (infoPtr, (LPCWSTR)lParam);
1260
1261     case DTM_GETMONTHCAL:
1262         return (LRESULT)infoPtr->hMonthCal;
1263
1264     case DTM_SETMCCOLOR:
1265         return SendMessageW (infoPtr->hMonthCal, MCM_SETCOLOR, wParam, lParam);
1266
1267     case DTM_GETMCCOLOR:
1268         return SendMessageW (infoPtr->hMonthCal, MCM_GETCOLOR, wParam, 0);
1269
1270     case DTM_SETMCFONT:
1271         return SendMessageW (infoPtr->hMonthCal, WM_SETFONT, wParam, lParam);
1272
1273     case DTM_GETMCFONT:
1274         return SendMessageW (infoPtr->hMonthCal, WM_GETFONT, wParam, lParam);
1275
1276     case WM_NOTIFY:
1277         return DATETIME_Notify (infoPtr, (int)wParam, (LPNMHDR)lParam);
1278
1279     case WM_ENABLE:
1280         return DATETIME_Enable (infoPtr, (BOOL)wParam);
1281
1282     case WM_ERASEBKGND:
1283         return DATETIME_EraseBackground (infoPtr, (HDC)wParam);
1284
1285     case WM_GETDLGCODE:
1286         return DLGC_WANTARROWS | DLGC_WANTCHARS;
1287
1288     case WM_PRINTCLIENT:
1289     case WM_PAINT:
1290         return DATETIME_Paint (infoPtr, (HDC)wParam);
1291
1292     case WM_KEYDOWN:
1293         return DATETIME_KeyDown (infoPtr, wParam, lParam);
1294
1295     case WM_KILLFOCUS:
1296         return DATETIME_KillFocus (infoPtr, (HWND)wParam);
1297
1298     case WM_NCCREATE:
1299         return DATETIME_NCCreate (hwnd, (LPCREATESTRUCTW)lParam);
1300
1301     case WM_SETFOCUS:
1302         return DATETIME_SetFocus (infoPtr, (HWND)wParam);
1303
1304     case WM_SIZE:
1305         return DATETIME_Size (infoPtr, wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1306
1307     case WM_LBUTTONDOWN:
1308         return DATETIME_LButtonDown (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1309
1310     case WM_LBUTTONUP:
1311         return DATETIME_LButtonUp (infoPtr, (WORD)wParam);
1312
1313     case WM_VSCROLL:
1314         return DATETIME_VScroll (infoPtr, (WORD)wParam);
1315
1316     case WM_CREATE:
1317         return DATETIME_Create (hwnd, (LPCREATESTRUCTW)lParam);
1318
1319     case WM_DESTROY:
1320         return DATETIME_Destroy (infoPtr);
1321
1322     case WM_COMMAND:
1323         return DATETIME_Command (infoPtr, wParam, lParam);
1324
1325     case WM_STYLECHANGED:
1326         return DATETIME_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
1327
1328     case WM_SETFONT:
1329         return DATETIME_SetFont(infoPtr, (HFONT)wParam, (BOOL)lParam);
1330
1331     case WM_GETFONT:
1332         return (LRESULT) infoPtr->hFont;
1333
1334     default:
1335         if ((uMsg >= WM_USER) && (uMsg < WM_APP))
1336                 ERR("unknown msg %04x wp=%08x lp=%08lx\n",
1337                      uMsg, wParam, lParam);
1338         return DefWindowProcW (hwnd, uMsg, wParam, lParam);
1339     }
1340 }
1341
1342
1343 void
1344 DATETIME_Register (void)
1345 {
1346     WNDCLASSW wndClass;
1347
1348     ZeroMemory (&wndClass, sizeof(WNDCLASSW));
1349     wndClass.style         = CS_GLOBALCLASS;
1350     wndClass.lpfnWndProc   = DATETIME_WindowProc;
1351     wndClass.cbClsExtra    = 0;
1352     wndClass.cbWndExtra    = sizeof(DATETIME_INFO *);
1353     wndClass.hCursor       = LoadCursorW (0, (LPCWSTR)IDC_ARROW);
1354     wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
1355     wndClass.lpszClassName = DATETIMEPICK_CLASSW;
1356
1357     RegisterClassW (&wndClass);
1358 }
1359
1360
1361 void
1362 DATETIME_Unregister (void)
1363 {
1364     UnregisterClassW (DATETIMEPICK_CLASSW, NULL);
1365 }