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