- Implement WM_ENABLE handler to update cached control style when the
[wine] / dlls / comctl32 / datetime.c
1 /*
2  * Date and time picker control
3  *
4  * Copyright 1998, 1999 Eric Kohl
5  * Copyright 1999, 2000 Alex Priem <alexp@sci.kun.nl>
6  * Copyright 2000 Chris Morgan <cmorgan@wpi.edu>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  * NOTE
23  * 
24  * This code was audited for completeness against the documented features
25  * of Comctl32.dll version 6.0 on Oct. 20, 2004, by Dimitrie O. Paun.
26  * 
27  * Unless otherwise noted, we believe this code to be complete, as per
28  * the specification mentioned above.
29  * If you discover missing features, or bugs, please note them below.
30  * 
31  * TODO:
32  *    -- DTS_APPCANPARSE
33  *    -- DTS_SHORTDATECENTURYFORMAT
34  *    -- DTN_CLOSEUP
35  *    -- DTN_FORMAT
36  *    -- DTN_FORMATQUERY
37  *    -- DTN_USERSTRING
38  *    -- DTN_WMKEYDOWN
39  *    -- FORMATCALLBACK
40  */
41
42 #include <math.h>
43 #include <string.h>
44 #include <stdarg.h>
45 #include <stdio.h>
46 #include <limits.h>
47
48 #include "windef.h"
49 #include "winbase.h"
50 #include "wingdi.h"
51 #include "winuser.h"
52 #include "winnls.h"
53 #include "commctrl.h"
54 #include "comctl32.h"
55 #include "wine/debug.h"
56 #include "wine/unicode.h"
57
58 WINE_DEFAULT_DEBUG_CHANNEL(datetime);
59
60 typedef struct
61 {
62     HWND hwndSelf;
63     HWND hMonthCal;
64     HWND hwndNotify;
65     HWND hUpdown;
66     DWORD dwStyle;
67     SYSTEMTIME date;
68     BOOL dateValid;
69     HWND hwndCheckbut;
70     RECT rcClient; /* rect around the edge of the window */
71     RECT rcDraw; /* rect inside of the border */
72     RECT checkbox;  /* checkbox allowing the control to be enabled/disabled */
73     RECT calbutton; /* button that toggles the dropdown of the monthcal control */
74     BOOL bCalDepressed; /* TRUE = cal button is depressed */
75     int  select;
76     HFONT hFont;
77     int nrFieldsAllocated;
78     int nrFields;
79     int haveFocus;
80     int *fieldspec;
81     RECT *fieldRect;
82     int  *buflen;
83     WCHAR textbuf[256];
84     POINT monthcal_pos;
85 } DATETIME_INFO, *LPDATETIME_INFO;
86
87 /* in monthcal.c */
88 extern int MONTHCAL_MonthLength(int month, int year);
89
90 /* this list of defines is closely related to `allowedformatchars' defined
91  * in datetime.c; the high nibble indicates the `base type' of the format
92  * specifier.
93  * Do not change without first reading DATETIME_UseFormat.
94  *
95  */
96
97 #define DT_END_FORMAT      0
98 #define ONEDIGITDAY     0x01
99 #define TWODIGITDAY     0x02
100 #define THREECHARDAY    0x03
101 #define FULLDAY         0x04
102 #define ONEDIGIT12HOUR  0x11
103 #define TWODIGIT12HOUR  0x12
104 #define ONEDIGIT24HOUR  0x21
105 #define TWODIGIT24HOUR  0x22
106 #define ONEDIGITMINUTE  0x31
107 #define TWODIGITMINUTE  0x32
108 #define ONEDIGITMONTH   0x41
109 #define TWODIGITMONTH   0x42
110 #define THREECHARMONTH  0x43
111 #define FULLMONTH       0x44
112 #define ONEDIGITSECOND  0x51
113 #define TWODIGITSECOND  0x52
114 #define ONELETTERAMPM   0x61
115 #define TWOLETTERAMPM   0x62
116 #define ONEDIGITYEAR    0x71
117 #define TWODIGITYEAR    0x72
118 #define INVALIDFULLYEAR 0x73      /* FIXME - yyy is not valid - we'll treat it as yyyy */
119 #define FULLYEAR        0x74
120 #define FORMATCALLBACK  0x81      /* -> maximum of 0x80 callbacks possible */
121 #define FORMATCALLMASK  0x80
122 #define DT_STRING       0x0100
123
124 #define DTHT_DATEFIELD  0xff      /* for hit-testing */
125
126 #define DTHT_NONE     0
127 #define DTHT_CHECKBOX 0x200     /* these should end at '00' , to make */
128 #define DTHT_MCPOPUP  0x300     /* & DTHT_DATEFIELD 0 when DATETIME_KeyDown */
129 #define DTHT_GOTFOCUS 0x400     /* tests for date-fields */
130
131 static BOOL DATETIME_SendSimpleNotify (DATETIME_INFO *infoPtr, UINT code);
132 static BOOL DATETIME_SendDateTimeChangeNotify (DATETIME_INFO *infoPtr);
133 extern void MONTHCAL_CopyTime(const SYSTEMTIME *from, SYSTEMTIME *to);
134 static const WCHAR allowedformatchars[] = {'d', 'h', 'H', 'm', 'M', 's', 't', 'y', 'X', '\'', 0};
135 static const int maxrepetition [] = {4,2,2,2,4,2,2,4,-1,-1};
136
137
138 static DWORD
139 DATETIME_GetSystemTime (DATETIME_INFO *infoPtr, SYSTEMTIME *lprgSysTimeArray)
140 {
141     if (!lprgSysTimeArray) return GDT_NONE;
142
143     if ((infoPtr->dwStyle & DTS_SHOWNONE) &&
144         (SendMessageW (infoPtr->hwndCheckbut, BM_GETCHECK, 0, 0) == BST_UNCHECKED))
145         return GDT_NONE;
146
147     MONTHCAL_CopyTime (&infoPtr->date, lprgSysTimeArray);
148
149     return GDT_VALID;
150 }
151
152
153 static BOOL
154 DATETIME_SetSystemTime (DATETIME_INFO *infoPtr, DWORD flag, SYSTEMTIME *lprgSysTimeArray)
155 {
156     if (!lprgSysTimeArray) return 0;
157
158     TRACE("%04d/%02d/%02d %02d:%02d:%02d\n",
159           lprgSysTimeArray->wYear, lprgSysTimeArray->wMonth, lprgSysTimeArray->wDay,
160           lprgSysTimeArray->wHour, lprgSysTimeArray->wMinute, lprgSysTimeArray->wSecond);
161
162     if (flag == GDT_VALID) {
163         infoPtr->dateValid = TRUE;
164         MONTHCAL_CopyTime (lprgSysTimeArray, &infoPtr->date);
165         SendMessageW (infoPtr->hMonthCal, MCM_SETCURSEL, 0, (LPARAM)(&infoPtr->date));
166         SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_CHECKED, 0);
167     } else if (flag == GDT_NONE) {
168         infoPtr->dateValid = FALSE;
169         SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_UNCHECKED, 0);
170     }
171
172     InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
173     return TRUE;
174 }
175
176
177 /***
178  * Split up a formattxt in actions.
179  * See ms documentation for the meaning of the letter codes/'specifiers'.
180  *
181  * Notes:
182  * *'dddddd' is handled as 'dddd' plus 'dd'.
183  * *unrecognized formats are strings (here given the type DT_STRING;
184  * start of the string is encoded in lower bits of DT_STRING.
185  * Therefore, 'string' ends finally up as '<show seconds>tring'.
186  *
187  */
188 static void
189 DATETIME_UseFormat (DATETIME_INFO *infoPtr, LPCWSTR formattxt)
190 {
191     unsigned int i;
192     int j, k, len;
193     int *nrFields = &infoPtr->nrFields;
194
195     *nrFields = 0;
196     infoPtr->fieldspec[*nrFields] = 0;
197     len = strlenW(allowedformatchars);
198     k = 0;
199
200     for (i = 0; formattxt[i]; i++)  {
201         TRACE ("\n%d %c:", i, formattxt[i]);
202         for (j = 0; j < len; j++) {
203             if (allowedformatchars[j]==formattxt[i]) {
204                 TRACE ("%c[%d,%x]", allowedformatchars[j], *nrFields, infoPtr->fieldspec[*nrFields]);
205                 if ((*nrFields==0) && (infoPtr->fieldspec[*nrFields]==0)) {
206                     infoPtr->fieldspec[*nrFields] = (j<<4) + 1;
207                     break;
208                 }
209                 if (infoPtr->fieldspec[*nrFields] >> 4 != j) {
210                     (*nrFields)++;
211                     infoPtr->fieldspec[*nrFields] = (j<<4) + 1;
212                     break;
213                 }
214                 if ((infoPtr->fieldspec[*nrFields] & 0x0f) == maxrepetition[j]) {
215                     (*nrFields)++;
216                     infoPtr->fieldspec[*nrFields] = (j<<4) + 1;
217                     break;
218                 }
219                 infoPtr->fieldspec[*nrFields]++;
220                 break;
221             }   /* if allowedformatchar */
222         } /* for j */
223
224         /* char is not a specifier: handle char like a string */
225         if (j == len) {
226             if ((*nrFields==0) && (infoPtr->fieldspec[*nrFields]==0)) {
227                 infoPtr->fieldspec[*nrFields] = DT_STRING + k;
228                 infoPtr->buflen[*nrFields] = 0;
229             } else if ((infoPtr->fieldspec[*nrFields] & DT_STRING) != DT_STRING)  {
230                 (*nrFields)++;
231                 infoPtr->fieldspec[*nrFields] = DT_STRING + k;
232                 infoPtr->buflen[*nrFields] = 0;
233             }
234             infoPtr->textbuf[k] = formattxt[i];
235             k++;
236             infoPtr->buflen[*nrFields]++;
237         }   /* if j=len */
238
239         if (*nrFields == infoPtr->nrFieldsAllocated) {
240             FIXME ("out of memory; should reallocate. crash ahead.\n");
241         }
242     } /* for i */
243
244     TRACE("\n");
245
246     if (infoPtr->fieldspec[*nrFields] != 0) (*nrFields)++;
247 }
248
249
250 static BOOL
251 DATETIME_SetFormatW (DATETIME_INFO *infoPtr, LPCWSTR lpszFormat)
252 {
253     if (!lpszFormat) {
254         WCHAR format_buf[80];
255         DWORD format_item;
256
257         if (infoPtr->dwStyle & DTS_LONGDATEFORMAT)
258             format_item = LOCALE_SLONGDATE;
259         else if (infoPtr->dwStyle & DTS_TIMEFORMAT)
260             format_item = LOCALE_STIMEFORMAT;
261         else /* DTS_SHORTDATEFORMAT */
262             format_item = LOCALE_SSHORTDATE;
263         GetLocaleInfoW( GetSystemDefaultLCID(), format_item, format_buf, sizeof(format_buf)/sizeof(format_buf[0]));
264         lpszFormat = format_buf;
265     }
266
267     DATETIME_UseFormat (infoPtr, lpszFormat);
268
269     return infoPtr->nrFields;
270 }
271
272
273 static BOOL
274 DATETIME_SetFormatA (DATETIME_INFO *infoPtr, LPCSTR lpszFormat)
275 {
276     if (lpszFormat) {
277         BOOL retval;
278         INT len = MultiByteToWideChar(CP_ACP, 0, lpszFormat, -1, NULL, 0);
279         LPWSTR wstr = Alloc(len * sizeof(WCHAR));
280         if (wstr) MultiByteToWideChar(CP_ACP, 0, lpszFormat, -1, wstr, len);
281         retval = DATETIME_SetFormatW (infoPtr, wstr);
282         Free (wstr);
283         return retval;
284     }
285     else
286         return DATETIME_SetFormatW (infoPtr, 0);
287
288 }
289
290
291 static void
292 DATETIME_ReturnTxt (DATETIME_INFO *infoPtr, int count, LPWSTR result, int resultSize)
293 {
294     static const WCHAR fmt_dW[] = { '%', 'd', 0 };
295     static const WCHAR fmt__2dW[] = { '%', '.', '2', 'd', 0 };
296     static const WCHAR fmt__3sW[] = { '%', '.', '3', 's', 0 };
297     SYSTEMTIME date = infoPtr->date;
298     int spec;
299     WCHAR buffer[80];
300
301     *result=0;
302     TRACE ("%d,%d\n", infoPtr->nrFields, count);
303     if (count>infoPtr->nrFields || count < 0) {
304         WARN ("buffer overrun, have %d want %d\n", infoPtr->nrFields, count);
305         return;
306     }
307
308     if (!infoPtr->fieldspec) return;
309
310     spec = infoPtr->fieldspec[count];
311     if (spec & DT_STRING) {
312         int txtlen = infoPtr->buflen[count];
313
314         if (txtlen > resultSize)
315             txtlen = resultSize - 1;
316         memcpy (result, infoPtr->textbuf + (spec &~ DT_STRING), txtlen * sizeof(WCHAR));
317         result[txtlen] = 0;
318         TRACE ("arg%d=%x->[%s]\n", count, infoPtr->fieldspec[count], debugstr_w(result));
319         return;
320     }
321
322
323     switch (spec) {
324         case DT_END_FORMAT:
325             *result = 0;
326             break;
327         case ONEDIGITDAY:
328             wsprintfW (result, fmt_dW, date.wDay);
329             break;
330         case TWODIGITDAY:
331             wsprintfW (result, fmt__2dW, date.wDay);
332             break;
333         case THREECHARDAY:
334             GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1+(date.wDayOfWeek+6)%7, result, 4);
335             /*wsprintfW (result,"%.3s",days[date.wDayOfWeek]);*/
336             break;
337         case FULLDAY:
338             GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDAYNAME1+(date.wDayOfWeek+6)%7, result, resultSize);
339             break;
340         case ONEDIGIT12HOUR:
341             wsprintfW (result, fmt_dW, date.wHour - (date.wHour > 12 ? 12 : 0));
342             break;
343         case TWODIGIT12HOUR:
344             wsprintfW (result, fmt__2dW, date.wHour - (date.wHour > 12 ? 12 : 0));
345             break;
346         case ONEDIGIT24HOUR:
347             wsprintfW (result, fmt_dW, date.wHour);
348             break;
349         case TWODIGIT24HOUR:
350             wsprintfW (result, fmt__2dW, date.wHour);
351             break;
352         case ONEDIGITSECOND:
353             wsprintfW (result, fmt_dW, date.wSecond);
354             break;
355         case TWODIGITSECOND:
356             wsprintfW (result, fmt__2dW, date.wSecond);
357             break;
358         case ONEDIGITMINUTE:
359             wsprintfW (result, fmt_dW, date.wMinute);
360             break;
361         case TWODIGITMINUTE:
362             wsprintfW (result, fmt__2dW, date.wMinute);
363             break;
364         case ONEDIGITMONTH:
365             wsprintfW (result, fmt_dW, date.wMonth);
366             break;
367         case TWODIGITMONTH:
368             wsprintfW (result, fmt__2dW, date.wMonth);
369             break;
370         case THREECHARMONTH:
371             GetLocaleInfoW(GetSystemDefaultLCID(), LOCALE_SMONTHNAME1+date.wMonth -1, 
372                            buffer, sizeof(buffer)/sizeof(buffer[0]));
373             wsprintfW (result, fmt__3sW, buffer);
374             break;
375         case FULLMONTH:
376             GetLocaleInfoW(GetSystemDefaultLCID(),LOCALE_SMONTHNAME1+date.wMonth -1,
377                            result, resultSize);
378             break;
379         case ONELETTERAMPM:
380             result[0] = (date.wHour < 12 ? 'A' : 'P');
381             result[1] = 0;
382             break;
383         case TWOLETTERAMPM:
384             result[0] = (date.wHour < 12 ? 'A' : 'P');
385             result[1] = 'M';
386             result[2] = 0;
387             break;
388         case FORMATCALLBACK:
389             FIXME ("Not implemented\n");
390             result[0] = 'x';
391             result[1] = 0;
392             break;
393         case ONEDIGITYEAR:
394             wsprintfW (result, fmt_dW, date.wYear-10* (int) floor(date.wYear/10));
395             break;
396         case TWODIGITYEAR:
397             wsprintfW (result, fmt__2dW, date.wYear-100* (int) floor(date.wYear/100));
398             break;
399         case INVALIDFULLYEAR:
400         case FULLYEAR:
401             wsprintfW (result, fmt_dW, date.wYear);
402             break;
403     }
404
405     TRACE ("arg%d=%x->[%s]\n", count, infoPtr->fieldspec[count], debugstr_w(result));
406 }
407
408 static int wrap(int val, int delta, int minVal, int maxVal)
409 {
410     val += delta;
411     if (delta == INT_MIN || val < minVal) return maxVal;
412     if (delta == INT_MAX || val > maxVal) return minVal;
413     return val;
414 }
415
416 static void
417 DATETIME_IncreaseField (DATETIME_INFO *infoPtr, int number, int delta)
418 {
419     SYSTEMTIME *date = &infoPtr->date;
420
421     TRACE ("%d\n", number);
422     if ((number > infoPtr->nrFields) || (number < 0)) return;
423
424     if ((infoPtr->fieldspec[number] & DTHT_DATEFIELD) == 0) return;
425
426     switch (infoPtr->fieldspec[number]) {
427         case ONEDIGITYEAR:
428         case TWODIGITYEAR:
429         case FULLYEAR:
430             date->wYear = wrap(date->wYear, delta, 1752, 9999);
431             break;
432         case ONEDIGITMONTH:
433         case TWODIGITMONTH:
434         case THREECHARMONTH:
435         case FULLMONTH:
436             date->wMonth = wrap(date->wMonth, delta, 1, 12);
437             delta = 0;
438             /* fall through */
439         case ONEDIGITDAY:
440         case TWODIGITDAY:
441         case THREECHARDAY:
442         case FULLDAY:
443             date->wDay = wrap(date->wDay, delta, 1, MONTHCAL_MonthLength(date->wMonth, date->wYear));
444             break;
445         case ONELETTERAMPM:
446         case TWOLETTERAMPM:
447             delta *= 12;
448             /* fall through */
449         case ONEDIGIT12HOUR:
450         case TWODIGIT12HOUR:
451         case ONEDIGIT24HOUR:
452         case TWODIGIT24HOUR:
453             date->wHour = wrap(date->wHour, delta, 0, 23);
454             break;
455         case ONEDIGITMINUTE:
456         case TWODIGITMINUTE:
457             date->wMinute = wrap(date->wMinute, delta, 0, 59);
458             break;
459         case ONEDIGITSECOND:
460         case TWODIGITSECOND:
461             date->wSecond = wrap(date->wSecond, delta, 0, 59);
462             break;
463         case FORMATCALLBACK:
464             FIXME ("Not implemented\n");
465             break;
466     }
467
468     /* FYI: On 1752/9/14 the calendar changed and England and the
469      * American colonies changed to the Gregorian calendar. This change
470      * involved having September 14th follow September 2nd. So no date
471      * algorithm works before that date.
472      */
473     if (10000 * date->wYear + 100 * date->wMonth + date->wDay < 17520914) {
474         date->wYear = 1752;
475         date->wMonth = 9;
476         date->wDay = 14;
477         date->wSecond = 0;
478         date->wMinute = 0;
479         date->wHour = 0;
480     }
481 }
482
483
484 static void 
485 DATETIME_Refresh (DATETIME_INFO *infoPtr, HDC hdc)
486 {
487     int i,prevright;
488     RECT *field;
489     RECT *rcDraw = &infoPtr->rcDraw;
490     RECT *rcClient = &infoPtr->rcClient;
491     RECT *calbutton = &infoPtr->calbutton;
492     RECT *checkbox = &infoPtr->checkbox;
493     SIZE size;
494     COLORREF oldTextColor;
495
496     /* draw control edge */
497     TRACE("\n");
498     DrawEdge(hdc, rcClient, EDGE_SUNKEN, BF_RECT);
499
500     if (infoPtr->dateValid) {
501         HFONT oldFont = SelectObject (hdc, infoPtr->hFont);
502         INT oldBkMode = SetBkMode (hdc, TRANSPARENT);
503         WCHAR txt[80];
504
505         DATETIME_ReturnTxt (infoPtr, 0, txt, sizeof(txt)/sizeof(txt[0]));
506         GetTextExtentPoint32W (hdc, txt, strlenW(txt), &size);
507         rcDraw->bottom = size.cy + 2;
508
509         prevright = checkbox->right = ((infoPtr->dwStyle & DTS_SHOWNONE) ? 18 : 2);
510
511         for (i = 0; i < infoPtr->nrFields; i++) {
512             DATETIME_ReturnTxt (infoPtr, i, txt, sizeof(txt)/sizeof(txt[0]));
513             GetTextExtentPoint32W (hdc, txt, strlenW(txt), &size);
514             field = &infoPtr->fieldRect[i];
515             field->left  = prevright;
516             field->right = prevright+size.cx;
517             field->top   = rcDraw->top;
518             field->bottom = rcDraw->bottom;
519             prevright = field->right;
520
521             if (infoPtr->dwStyle & WS_DISABLED)
522                 oldTextColor = SetTextColor (hdc, comctl32_color.clrGrayText);
523             else if ((infoPtr->haveFocus) && (i == infoPtr->select)) {
524                 /* fill if focussed */
525                 HBRUSH hbr = CreateSolidBrush (comctl32_color.clrActiveCaption);
526                 FillRect(hdc, field, hbr);
527                 DeleteObject (hbr);
528                 oldTextColor = SetTextColor (hdc, comctl32_color.clrWindow);
529             }
530             else
531                 oldTextColor = SetTextColor (hdc, comctl32_color.clrWindowText);
532
533             /* draw the date text using the colour set above */
534             DrawTextW (hdc, txt, strlenW(txt), field, DT_RIGHT | DT_VCENTER | DT_SINGLELINE);
535             SetTextColor (hdc, oldTextColor);
536         }
537         SetBkMode (hdc, oldBkMode);
538         SelectObject (hdc, oldFont);
539     }
540
541     if (!(infoPtr->dwStyle & DTS_UPDOWN)) {
542         DrawFrameControl(hdc, calbutton, DFC_SCROLL,
543                          DFCS_SCROLLDOWN | (infoPtr->bCalDepressed ? DFCS_PUSHED : 0) |
544                          (infoPtr->dwStyle & WS_DISABLED ? DFCS_INACTIVE : 0) );
545     }
546 }
547
548
549 static INT
550 DATETIME_HitTest (DATETIME_INFO *infoPtr, POINT pt)
551 {
552     int i;
553
554     TRACE ("%ld, %ld\n", pt.x, pt.y);
555
556     if (PtInRect (&infoPtr->calbutton, pt)) return DTHT_MCPOPUP;
557     if (PtInRect (&infoPtr->checkbox, pt)) return DTHT_CHECKBOX;
558
559     for (i=0; i < infoPtr->nrFields; i++) {
560         if (PtInRect (&infoPtr->fieldRect[i], pt)) return i;
561     }
562
563     return DTHT_NONE;
564 }
565
566
567 static LRESULT
568 DATETIME_LButtonDown (DATETIME_INFO *infoPtr, WORD wKey, INT x, INT y)
569 {
570     POINT pt;
571     int old, new;
572
573     pt.x = x;
574     pt.y = y;
575     old = infoPtr->select;
576     new = DATETIME_HitTest (infoPtr, pt);
577
578     /* FIXME: might be conditions where we don't want to update infoPtr->select */
579     infoPtr->select = new;
580
581     if (infoPtr->select != old)
582         infoPtr->haveFocus = DTHT_GOTFOCUS;
583
584     if (infoPtr->select == DTHT_MCPOPUP) {
585         /* FIXME: button actually is only depressed during dropdown of the */
586         /* calendar control and when the mouse is over the button window */
587         infoPtr->bCalDepressed = TRUE;
588
589         /* recalculate the position of the monthcal popup */
590         if(infoPtr->dwStyle & DTS_RIGHTALIGN)
591             infoPtr->monthcal_pos.x = infoPtr->rcClient.right - 
592                                      ((infoPtr->calbutton.right - infoPtr->calbutton.left) + 200);
593         else
594             infoPtr->monthcal_pos.x = 8;
595
596         infoPtr->monthcal_pos.y = infoPtr->rcClient.bottom;
597         ClientToScreen (infoPtr->hwndSelf, &(infoPtr->monthcal_pos));
598         /* FIXME My Windoze has cx=about 200, but it probably depends on font size etc */
599         SetWindowPos(infoPtr->hMonthCal, 0, infoPtr->monthcal_pos.x, infoPtr->monthcal_pos.y, 200, 150, 0);
600
601         if(IsWindowVisible(infoPtr->hMonthCal)) {
602             ShowWindow(infoPtr->hMonthCal, SW_HIDE);
603         } else {
604             SYSTEMTIME *lprgSysTimeArray = &infoPtr->date;
605             TRACE("update calendar %04d/%02d/%02d\n", 
606             lprgSysTimeArray->wYear, lprgSysTimeArray->wMonth, lprgSysTimeArray->wDay);
607             SendMessageW(infoPtr->hMonthCal, MCM_SETCURSEL, 0, (LPARAM)(&infoPtr->date));
608             ShowWindow(infoPtr->hMonthCal, SW_SHOW);
609         }
610
611         TRACE ("dt:%p mc:%p mc parent:%p, desktop:%p\n",
612                infoPtr->hwndSelf, infoPtr->hMonthCal, infoPtr->hwndNotify, GetDesktopWindow ());
613         DATETIME_SendSimpleNotify (infoPtr, DTN_DROPDOWN);
614     }
615
616     InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
617
618     return 0;
619 }
620
621
622 static LRESULT
623 DATETIME_LButtonUp (DATETIME_INFO *infoPtr, WORD wKey)
624 {
625     if(infoPtr->bCalDepressed) {
626         infoPtr->bCalDepressed = FALSE;
627         InvalidateRect(infoPtr->hwndSelf, &(infoPtr->calbutton), TRUE);
628     }
629
630     return 0;
631 }
632
633
634 static LRESULT
635 DATETIME_Paint (DATETIME_INFO *infoPtr, HDC hdc)
636 {
637     if (!hdc) {
638         PAINTSTRUCT ps;
639         hdc = BeginPaint (infoPtr->hwndSelf, &ps);
640         DATETIME_Refresh (infoPtr, hdc);
641         EndPaint (infoPtr->hwndSelf, &ps);
642     } else {
643         DATETIME_Refresh (infoPtr, hdc);
644     }
645     return 0;
646 }
647
648
649 static LRESULT
650 DATETIME_Button_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
651 {
652     if( HIWORD(wParam) == BN_CLICKED) {
653         DWORD state = SendMessageW((HWND)lParam, BM_GETCHECK, 0, 0);
654         infoPtr->dateValid = (state == BST_CHECKED);
655         InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
656     }
657     return 0;
658 }
659           
660         
661         
662 static LRESULT
663 DATETIME_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
664 {
665     TRACE("hwndbutton = %p\n", infoPtr->hwndCheckbut);
666     if(infoPtr->hwndCheckbut == (HWND)lParam)
667         return DATETIME_Button_Command(infoPtr, wParam, lParam);
668     return 0;
669 }
670
671
672 static LRESULT
673 DATETIME_Enable (DATETIME_INFO *infoPtr, BOOL bEnable)
674 {
675     TRACE("%p %s\n", infoPtr, bEnable ? "TRUE" : "FALSE");
676     if (bEnable)
677         infoPtr->dwStyle &= ~WS_DISABLED;
678     else
679         infoPtr->dwStyle |= WS_DISABLED;
680     return 0;
681 }
682
683
684 static LRESULT
685 DATETIME_EraseBackground (DATETIME_INFO *infoPtr, HDC hdc)
686 {
687     HBRUSH hBrush, hSolidBrush = NULL;
688     RECT   rc;
689
690     if (infoPtr->dwStyle & WS_DISABLED)
691         hBrush = hSolidBrush = CreateSolidBrush(comctl32_color.clrBtnFace);
692     else
693     {
694         hBrush = (HBRUSH)SendMessageW(infoPtr->hwndNotify, WM_CTLCOLOREDIT,
695                                       (WPARAM)hdc, (LPARAM)infoPtr->hwndSelf);
696         if (!hBrush)
697             hBrush = hSolidBrush = CreateSolidBrush(comctl32_color.clrWindow);
698     }
699
700     GetClientRect (infoPtr->hwndSelf, &rc);
701
702     FillRect (hdc, &rc, hBrush);
703
704     if (hSolidBrush)
705         DeleteObject(hSolidBrush);
706
707     return -1;
708 }
709
710
711 static LRESULT
712 DATETIME_Notify (DATETIME_INFO *infoPtr, int idCtrl, LPNMHDR lpnmh)
713 {
714     TRACE ("Got notification %x from %p\n", lpnmh->code, lpnmh->hwndFrom);
715     TRACE ("info: %p %p %p\n", infoPtr->hwndSelf, infoPtr->hMonthCal, infoPtr->hUpdown);
716
717     if (lpnmh->code == MCN_SELECT) {
718         ShowWindow(infoPtr->hMonthCal, SW_HIDE);
719         infoPtr->dateValid = TRUE;
720         SendMessageW (infoPtr->hMonthCal, MCM_GETCURSEL, 0, (LPARAM)&infoPtr->date);
721         TRACE("got from calendar %04d/%02d/%02d\n", 
722         infoPtr->date.wYear, infoPtr->date.wMonth, infoPtr->date.wDay);
723         SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_CHECKED, 0);
724         InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
725         DATETIME_SendDateTimeChangeNotify (infoPtr);
726     }
727     return 0;
728 }
729
730
731 static LRESULT
732 DATETIME_KeyDown (DATETIME_INFO *infoPtr, DWORD vkCode, LPARAM flags)
733 {
734     int fieldNum = infoPtr->select & DTHT_DATEFIELD;
735     int wrap = 0;
736
737     if (!(infoPtr->haveFocus)) return 0;
738     if ((fieldNum==0) && (infoPtr->select)) return 0;
739
740     if (infoPtr->select & FORMATCALLMASK) {
741         FIXME ("Callbacks not implemented yet\n");
742     }
743
744     switch (vkCode) {
745         case VK_ADD:
746         case VK_UP:
747             DATETIME_IncreaseField (infoPtr, fieldNum, 1);
748             DATETIME_SendDateTimeChangeNotify (infoPtr);
749             break;
750         case VK_SUBTRACT:
751         case VK_DOWN:
752             DATETIME_IncreaseField (infoPtr, fieldNum, -1);
753             DATETIME_SendDateTimeChangeNotify (infoPtr);
754             break;
755         case VK_HOME:
756             DATETIME_IncreaseField (infoPtr, fieldNum, INT_MIN);
757             DATETIME_SendDateTimeChangeNotify (infoPtr);
758             break;
759         case VK_END:
760             DATETIME_IncreaseField (infoPtr, fieldNum, INT_MAX);
761             DATETIME_SendDateTimeChangeNotify (infoPtr);
762             break;
763         case VK_LEFT:
764             do {
765                 if (infoPtr->select == 0) {
766                     infoPtr->select = infoPtr->nrFields - 1;
767                     wrap++;
768                 } else {
769                     infoPtr->select--;
770                 }
771             } while ((infoPtr->fieldspec[infoPtr->select] & DT_STRING) && (wrap<2));
772             break;
773         case VK_RIGHT:
774             do {
775                 infoPtr->select++;
776                 if (infoPtr->select==infoPtr->nrFields) {
777                     infoPtr->select = 0;
778                     wrap++;
779                 }
780             } while ((infoPtr->fieldspec[infoPtr->select] & DT_STRING) && (wrap<2));
781             break;
782     }
783
784     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
785
786     return 0;
787 }
788
789
790 static LRESULT
791 DATETIME_KillFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
792 {
793     if (infoPtr->haveFocus) {
794         DATETIME_SendSimpleNotify (infoPtr, NM_KILLFOCUS);
795         infoPtr->haveFocus = 0;
796     }
797
798     InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
799
800     return 0;
801 }
802
803
804 static LRESULT
805 DATETIME_SetFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
806 {
807     if (infoPtr->haveFocus == 0) {
808         DATETIME_SendSimpleNotify (infoPtr, NM_SETFOCUS);
809         infoPtr->haveFocus = DTHT_GOTFOCUS;
810     }
811
812     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
813
814     return 0;
815 }
816
817
818 static BOOL
819 DATETIME_SendDateTimeChangeNotify (DATETIME_INFO *infoPtr)
820 {
821     NMDATETIMECHANGE dtdtc;
822
823     dtdtc.nmhdr.hwndFrom = infoPtr->hwndSelf;
824     dtdtc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
825     dtdtc.nmhdr.code     = DTN_DATETIMECHANGE;
826
827     dtdtc.dwFlags = (infoPtr->dwStyle & DTS_SHOWNONE) ? GDT_NONE : GDT_VALID;
828
829     MONTHCAL_CopyTime (&infoPtr->date, &dtdtc.st);
830     return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
831                                 (WPARAM)dtdtc.nmhdr.idFrom, (LPARAM)&dtdtc);
832 }
833
834
835 static BOOL
836 DATETIME_SendSimpleNotify (DATETIME_INFO *infoPtr, UINT code)
837 {
838     NMHDR nmhdr;
839
840     TRACE("%x\n", code);
841     nmhdr.hwndFrom = infoPtr->hwndSelf;
842     nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
843     nmhdr.code     = code;
844
845     return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
846                                 (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);
847 }
848
849 static LRESULT
850 DATETIME_Size (DATETIME_INFO *infoPtr, WORD flags, INT width, INT height)
851 {
852     /* set size */
853     infoPtr->rcClient.bottom = height;
854     infoPtr->rcClient.right = width;
855
856     TRACE("Height=%ld, Width=%ld\n", infoPtr->rcClient.bottom, infoPtr->rcClient.right);
857
858     memcpy((&infoPtr->rcDraw), (&infoPtr->rcClient), sizeof(infoPtr->rcDraw));
859
860     /* subract the size of the edge drawn by DrawEdge */
861     InflateRect(&infoPtr->rcDraw, -GetSystemMetrics(SM_CXEDGE),
862                 -GetSystemMetrics(SM_CYEDGE));
863
864     /* set the size of the button that drops the calendar down */
865     /* FIXME: account for style that allows button on left side */
866     infoPtr->calbutton.top   = infoPtr->rcDraw.top;
867     infoPtr->calbutton.bottom= infoPtr->rcDraw.bottom;
868     infoPtr->calbutton.left  = infoPtr->rcDraw.right-15;
869     infoPtr->calbutton.right = infoPtr->rcDraw.right;
870
871     /* set enable/disable button size for show none style being enabled */
872     /* FIXME: these dimensions are completely incorrect */
873     infoPtr->checkbox.top = infoPtr->rcDraw.top;
874     infoPtr->checkbox.bottom = infoPtr->rcDraw.bottom;
875     infoPtr->checkbox.left = infoPtr->rcDraw.left;
876     infoPtr->checkbox.right = infoPtr->rcDraw.left + 10;
877
878     /* update the position of the monthcal control */
879     if(infoPtr->dwStyle & DTS_RIGHTALIGN)
880         infoPtr->monthcal_pos.x = infoPtr->rcClient.right - ((infoPtr->calbutton.right -
881                                   infoPtr->calbutton.left) + 145);
882     else
883         infoPtr->monthcal_pos.x = 8;
884
885     infoPtr->monthcal_pos.y = infoPtr->rcClient.bottom;
886     ClientToScreen (infoPtr->hwndSelf, &(infoPtr->monthcal_pos));
887     SetWindowPos(infoPtr->hMonthCal, 0, infoPtr->monthcal_pos.x, infoPtr->monthcal_pos.y, 145, 150, 0);
888
889     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
890
891     return 0;
892 }
893
894
895 static LRESULT 
896 DATETIME_StyleChanged(DATETIME_INFO *infoPtr, WPARAM wStyleType, LPSTYLESTRUCT lpss)
897 {
898     static const WCHAR buttonW[] = { 'b', 'u', 't', 't', 'o', 'n', 0 };
899
900     TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
901           wStyleType, lpss->styleOld, lpss->styleNew);
902
903     if (wStyleType != GWL_STYLE) return 0;
904   
905     infoPtr->dwStyle = lpss->styleNew;
906
907     if ( !(lpss->styleOld & DTS_SHOWNONE) && (lpss->styleNew & DTS_SHOWNONE) ) {
908         infoPtr->hwndCheckbut = CreateWindowExW (0, buttonW, 0, WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
909                                                  2, 2, 13, 13, infoPtr->hwndSelf, 0, 
910                                                 (HINSTANCE)GetWindowLongPtrW (infoPtr->hwndSelf, GWLP_HINSTANCE), 0);
911         SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, 1, 0);
912     }
913     if ( (lpss->styleOld & DTS_SHOWNONE) && !(lpss->styleNew & DTS_SHOWNONE) ) {
914         DestroyWindow(infoPtr->hwndCheckbut);
915         infoPtr->hwndCheckbut = 0;
916     }
917     if ( !(lpss->styleOld & DTS_UPDOWN) && (lpss->styleNew & DTS_UPDOWN) ) {
918         infoPtr->hUpdown = CreateUpDownControl (WS_CHILD | WS_BORDER | WS_VISIBLE, 120, 1, 20, 20, 
919                                                 infoPtr->hwndSelf, 1, 0, 0, UD_MAXVAL, UD_MINVAL, 0);
920     }
921     if ( (lpss->styleOld & DTS_UPDOWN) && !(lpss->styleNew & DTS_UPDOWN) ) {
922         DestroyWindow(infoPtr->hUpdown);
923         infoPtr->hUpdown = 0;
924     }
925
926     InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
927     return 0;
928 }
929
930
931 static LRESULT
932 DATETIME_Create (HWND hwnd, LPCREATESTRUCTW lpcs)
933 {
934     static const WCHAR SysMonthCal32W[] = { 'S', 'y', 's', 'M', 'o', 'n', 't', 'h', 'C', 'a', 'l', '3', '2', 0 };
935     DATETIME_INFO *infoPtr = (DATETIME_INFO *)Alloc (sizeof(DATETIME_INFO));
936     STYLESTRUCT ss = { 0, lpcs->style };
937
938     if (!infoPtr) return -1;
939
940     infoPtr->hwndSelf = hwnd;
941     infoPtr->dwStyle = lpcs->style;
942
943     infoPtr->nrFieldsAllocated = 32;
944     infoPtr->fieldspec = (int *) Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
945     infoPtr->fieldRect = (RECT *) Alloc (infoPtr->nrFieldsAllocated * sizeof(RECT));
946     infoPtr->buflen = (int *) Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
947     infoPtr->hwndNotify = lpcs->hwndParent;
948     infoPtr->select = -1; /* initially, nothing is selected */
949
950     DATETIME_StyleChanged(infoPtr, GWL_STYLE, &ss);
951     DATETIME_SetFormatW (infoPtr, 0);
952
953     /* create the monthcal control */
954     infoPtr->hMonthCal = CreateWindowExW (0, SysMonthCal32W, 0, WS_BORDER | WS_POPUP | WS_CLIPSIBLINGS, 
955                                           0, 0, 0, 0, infoPtr->hwndSelf, 0, 0, 0);
956
957     /* initialize info structure */
958     GetSystemTime (&infoPtr->date);
959     infoPtr->dateValid = TRUE;
960     infoPtr->hFont = GetStockObject(DEFAULT_GUI_FONT);
961
962     SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
963
964     return 0;
965 }
966
967
968
969 static LRESULT
970 DATETIME_Destroy (DATETIME_INFO *infoPtr)
971 {
972     if (infoPtr->hwndCheckbut)
973         DestroyWindow(infoPtr->hwndCheckbut);
974     if (infoPtr->hUpdown)
975         DestroyWindow(infoPtr->hUpdown);
976     if (infoPtr->hMonthCal) 
977         DestroyWindow(infoPtr->hMonthCal);
978     SetWindowLongPtrW( infoPtr->hwndSelf, 0, 0 ); /* clear infoPtr */
979     Free (infoPtr);
980     return 0;
981 }
982
983
984 static LRESULT WINAPI
985 DATETIME_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
986 {
987     DATETIME_INFO *infoPtr = ((DATETIME_INFO *)GetWindowLongPtrW (hwnd, 0));
988     LRESULT ret;
989
990     TRACE ("%x, %x, %lx\n", uMsg, wParam, lParam);
991
992     if (!infoPtr && (uMsg != WM_CREATE))
993         return DefWindowProcW( hwnd, uMsg, wParam, lParam );
994
995     switch (uMsg) {
996
997     case DTM_GETSYSTEMTIME:
998         return DATETIME_GetSystemTime (infoPtr, (SYSTEMTIME *) lParam);
999
1000     case DTM_SETSYSTEMTIME:
1001         return DATETIME_SetSystemTime (infoPtr, wParam, (SYSTEMTIME *) lParam);
1002
1003     case DTM_GETRANGE:
1004         ret = SendMessageW (infoPtr->hMonthCal, MCM_GETRANGE, wParam, lParam);
1005         return ret ? ret : 1; /* bug emulation */
1006
1007     case DTM_SETRANGE:
1008         return SendMessageW (infoPtr->hMonthCal, MCM_SETRANGE, wParam, lParam);
1009
1010     case DTM_SETFORMATA:
1011         return DATETIME_SetFormatA (infoPtr, (LPCSTR)lParam);
1012
1013     case DTM_SETFORMATW:
1014         return DATETIME_SetFormatW (infoPtr, (LPCWSTR)lParam);
1015
1016     case DTM_GETMONTHCAL:
1017         return (LRESULT)infoPtr->hMonthCal;
1018
1019     case DTM_SETMCCOLOR:
1020         return SendMessageW (infoPtr->hMonthCal, MCM_SETCOLOR, wParam, lParam);
1021
1022     case DTM_GETMCCOLOR:
1023         return SendMessageW (infoPtr->hMonthCal, MCM_GETCOLOR, wParam, 0);
1024
1025     case DTM_SETMCFONT:
1026         return SendMessageW (infoPtr->hMonthCal, WM_SETFONT, wParam, lParam);
1027
1028     case DTM_GETMCFONT:
1029         return SendMessageW (infoPtr->hMonthCal, WM_GETFONT, wParam, lParam);
1030
1031     case WM_NOTIFY:
1032         return DATETIME_Notify (infoPtr, (int)wParam, (LPNMHDR)lParam);
1033
1034     case WM_ENABLE:
1035         return DATETIME_Enable (infoPtr, (BOOL)wParam);
1036
1037     case WM_ERASEBKGND:
1038         return DATETIME_EraseBackground (infoPtr, (HDC)wParam);
1039
1040     case WM_GETDLGCODE:
1041         return DLGC_WANTARROWS | DLGC_WANTCHARS;
1042
1043     case WM_PAINT:
1044         return DATETIME_Paint (infoPtr, (HDC)wParam);
1045
1046     case WM_KEYDOWN:
1047         return DATETIME_KeyDown (infoPtr, wParam, lParam);
1048
1049     case WM_KILLFOCUS:
1050         return DATETIME_KillFocus (infoPtr, (HWND)wParam);
1051
1052     case WM_SETFOCUS:
1053         return DATETIME_SetFocus (infoPtr, (HWND)wParam);
1054
1055     case WM_SIZE:
1056         return DATETIME_Size (infoPtr, wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1057
1058     case WM_LBUTTONDOWN:
1059         return DATETIME_LButtonDown (infoPtr, (WORD)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1060
1061     case WM_LBUTTONUP:
1062         return DATETIME_LButtonUp (infoPtr, (WORD)wParam);
1063
1064     case WM_CREATE:
1065         return DATETIME_Create (hwnd, (LPCREATESTRUCTW)lParam);
1066
1067     case WM_DESTROY:
1068         return DATETIME_Destroy (infoPtr);
1069
1070     case WM_COMMAND:
1071         return DATETIME_Command (infoPtr, wParam, lParam);
1072
1073     case WM_STYLECHANGED:
1074         return DATETIME_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
1075
1076     default:
1077         if ((uMsg >= WM_USER) && (uMsg < WM_APP))
1078                 ERR("unknown msg %04x wp=%08x lp=%08lx\n",
1079                      uMsg, wParam, lParam);
1080         return DefWindowProcW (hwnd, uMsg, wParam, lParam);
1081     }
1082     return 0;
1083 }
1084
1085
1086 void
1087 DATETIME_Register (void)
1088 {
1089     WNDCLASSW wndClass;
1090
1091     ZeroMemory (&wndClass, sizeof(WNDCLASSW));
1092     wndClass.style         = CS_GLOBALCLASS;
1093     wndClass.lpfnWndProc   = DATETIME_WindowProc;
1094     wndClass.cbClsExtra    = 0;
1095     wndClass.cbWndExtra    = sizeof(DATETIME_INFO *);
1096     wndClass.hCursor       = LoadCursorW (0, (LPCWSTR)IDC_ARROW);
1097     wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
1098     wndClass.lpszClassName = DATETIMEPICK_CLASSW;
1099
1100     RegisterClassW (&wndClass);
1101 }
1102
1103
1104 void
1105 DATETIME_Unregister (void)
1106 {
1107     UnregisterClassW (DATETIMEPICK_CLASSW, NULL);
1108 }