Removed W->A from DEFWND_ImmIsUIMessageW.
[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     HBRUSH hbr;
494     SIZE size;
495     COLORREF oldBk, oldTextColor;
496
497     /* draw control edge */
498     TRACE("\n");
499     hbr = CreateSolidBrush(RGB(255, 255, 255));
500     FillRect(hdc, rcClient, hbr);
501     DrawEdge(hdc, rcClient, EDGE_SUNKEN, BF_RECT);
502     DeleteObject(hbr);
503
504     if (infoPtr->dateValid) {
505         HFONT oldFont = SelectObject (hdc, infoPtr->hFont);
506         WCHAR txt[80];
507
508         DATETIME_ReturnTxt (infoPtr, 0, txt, sizeof(txt)/sizeof(txt[0]));
509         GetTextExtentPoint32W (hdc, txt, strlenW(txt), &size);
510         rcDraw->bottom = size.cy + 2;
511
512         prevright = checkbox->right = ((infoPtr->dwStyle & DTS_SHOWNONE) ? 18 : 2);
513
514         for (i = 0; i < infoPtr->nrFields; i++) {
515             DATETIME_ReturnTxt (infoPtr, i, txt, sizeof(txt)/sizeof(txt[0]));
516             GetTextExtentPoint32W (hdc, txt, strlenW(txt), &size);
517             field = &infoPtr->fieldRect[i];
518             field->left  = prevright;
519             field->right = prevright+size.cx;
520             field->top   = rcDraw->top;
521             field->bottom = rcDraw->bottom;
522             prevright = field->right;
523
524             if ((infoPtr->haveFocus) && (i == infoPtr->select)) {
525                 hbr = CreateSolidBrush (GetSysColor (COLOR_ACTIVECAPTION));
526                 FillRect(hdc, field, hbr);
527                 oldBk = SetBkColor (hdc, GetSysColor(COLOR_ACTIVECAPTION));
528                 oldTextColor = SetTextColor (hdc, GetSysColor(COLOR_WINDOW));
529                 DeleteObject (hbr);
530                 DrawTextW (hdc, txt, strlenW(txt), field, DT_RIGHT | DT_VCENTER | DT_SINGLELINE);
531                 SetBkColor (hdc, oldBk);
532                 SetTextColor (hdc, oldTextColor);
533             } else 
534                 DrawTextW ( hdc, txt, strlenW(txt), field, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
535         }
536         SelectObject (hdc, oldFont);
537     }
538
539     if (!(infoPtr->dwStyle & DTS_UPDOWN)) {
540         DrawFrameControl(hdc, calbutton, DFC_SCROLL,
541                          DFCS_SCROLLDOWN | (infoPtr->bCalDepressed ? DFCS_PUSHED : 0) |
542                          (infoPtr->dwStyle & WS_DISABLED ? DFCS_INACTIVE : 0) );
543     }
544 }
545
546
547 static INT
548 DATETIME_HitTest (DATETIME_INFO *infoPtr, POINT pt)
549 {
550     int i;
551
552     TRACE ("%ld, %ld\n", pt.x, pt.y);
553
554     if (PtInRect (&infoPtr->calbutton, pt)) return DTHT_MCPOPUP;
555     if (PtInRect (&infoPtr->checkbox, pt)) return DTHT_CHECKBOX;
556
557     for (i=0; i < infoPtr->nrFields; i++) {
558         if (PtInRect (&infoPtr->fieldRect[i], pt)) return i;
559     }
560
561     return DTHT_NONE;
562 }
563
564
565 static LRESULT
566 DATETIME_LButtonDown (DATETIME_INFO *infoPtr, WORD wKey, POINTS pts)
567 {
568     POINT pt = { pts.x, pts.y };
569     int old = infoPtr->select;
570     int new = DATETIME_HitTest (infoPtr, pt);
571
572     /* FIXME: might be conditions where we don't want to update infoPtr->select */
573     infoPtr->select = new;
574
575     if (infoPtr->select != old)
576         infoPtr->haveFocus = DTHT_GOTFOCUS;
577
578     if (infoPtr->select == DTHT_MCPOPUP) {
579         /* FIXME: button actually is only depressed during dropdown of the */
580         /* calendar control and when the mouse is over the button window */
581         infoPtr->bCalDepressed = TRUE;
582
583         /* recalculate the position of the monthcal popup */
584         if(infoPtr->dwStyle & DTS_RIGHTALIGN)
585             infoPtr->monthcal_pos.x = infoPtr->rcClient.right - 
586                                      ((infoPtr->calbutton.right - infoPtr->calbutton.left) + 200);
587         else
588             infoPtr->monthcal_pos.x = 8;
589
590         infoPtr->monthcal_pos.y = infoPtr->rcClient.bottom;
591         ClientToScreen (infoPtr->hwndSelf, &(infoPtr->monthcal_pos));
592         /* FIXME My Windoze has cx=about 200, but it probably depends on font size etc */
593         SetWindowPos(infoPtr->hMonthCal, 0, infoPtr->monthcal_pos.x, infoPtr->monthcal_pos.y, 200, 150, 0);
594
595         if(IsWindowVisible(infoPtr->hMonthCal)) {
596             ShowWindow(infoPtr->hMonthCal, SW_HIDE);
597         } else {
598             SYSTEMTIME *lprgSysTimeArray = &infoPtr->date;
599             TRACE("update calendar %04d/%02d/%02d\n", 
600             lprgSysTimeArray->wYear, lprgSysTimeArray->wMonth, lprgSysTimeArray->wDay);
601             SendMessageW(infoPtr->hMonthCal, MCM_SETCURSEL, 0, (LPARAM)(&infoPtr->date));
602             ShowWindow(infoPtr->hMonthCal, SW_SHOW);
603         }
604
605         TRACE ("dt:%p mc:%p mc parent:%p, desktop:%p\n",
606                infoPtr->hwndSelf, infoPtr->hMonthCal, infoPtr->hwndNotify, GetDesktopWindow ());
607         DATETIME_SendSimpleNotify (infoPtr, DTN_DROPDOWN);
608     }
609
610     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
611
612     return 0;
613 }
614
615
616 static LRESULT
617 DATETIME_LButtonUp (DATETIME_INFO *infoPtr, WORD wKey, POINTS pts)
618 {
619     if(infoPtr->bCalDepressed == TRUE) {
620         infoPtr->bCalDepressed = FALSE;
621         InvalidateRect(infoPtr->hwndSelf, &(infoPtr->calbutton), TRUE);
622     }
623
624     return 0;
625 }
626
627
628 static LRESULT
629 DATETIME_Paint (DATETIME_INFO *infoPtr, HDC hdc)
630 {
631     if (!hdc) {
632         PAINTSTRUCT ps;
633         hdc = BeginPaint (infoPtr->hwndSelf, &ps);
634         DATETIME_Refresh (infoPtr, hdc);
635         EndPaint (infoPtr->hwndSelf, &ps);
636     } else {
637         DATETIME_Refresh (infoPtr, hdc);
638     }
639     return 0;
640 }
641
642
643 static LRESULT
644 DATETIME_Button_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
645 {
646     if( HIWORD(wParam) == BN_CLICKED) {
647         DWORD state = SendMessageW((HWND)lParam, BM_GETCHECK, 0, 0);
648         infoPtr->dateValid = (state == BST_CHECKED);
649         InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
650     }
651     return 0;
652 }
653           
654         
655         
656 static LRESULT
657 DATETIME_Command (DATETIME_INFO *infoPtr, WPARAM wParam, LPARAM lParam)
658 {
659     TRACE("hwndbutton = %p\n", infoPtr->hwndCheckbut);
660     if(infoPtr->hwndCheckbut == (HWND)lParam)
661         return DATETIME_Button_Command(infoPtr, wParam, lParam);
662     return 0;
663 }
664
665
666 static LRESULT
667 DATETIME_Notify (DATETIME_INFO *infoPtr, int idCtrl, LPNMHDR lpnmh)
668 {
669     TRACE ("Got notification %x from %p\n", lpnmh->code, lpnmh->hwndFrom);
670     TRACE ("info: %p %p %p\n", infoPtr->hwndSelf, infoPtr->hMonthCal, infoPtr->hUpdown);
671
672     if (lpnmh->code == MCN_SELECT) {
673         ShowWindow(infoPtr->hMonthCal, SW_HIDE);
674         infoPtr->dateValid = TRUE;
675         SendMessageW (infoPtr->hMonthCal, MCM_GETCURSEL, 0, (LPARAM)&infoPtr->date);
676         TRACE("got from calendar %04d/%02d/%02d\n", 
677         infoPtr->date.wYear, infoPtr->date.wMonth, infoPtr->date.wDay);
678         SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, BST_CHECKED, 0);
679         InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
680         DATETIME_SendDateTimeChangeNotify (infoPtr);
681     }
682     return 0;
683 }
684
685
686 static LRESULT
687 DATETIME_KeyDown (DATETIME_INFO *infoPtr, DWORD vkCode, LPARAM flags)
688 {
689     int fieldNum = infoPtr->select & DTHT_DATEFIELD;
690     int wrap = 0;
691
692     if (!(infoPtr->haveFocus)) return 0;
693     if ((fieldNum==0) && (infoPtr->select)) return 0;
694
695     if (infoPtr->select & FORMATCALLMASK) {
696         FIXME ("Callbacks not implemented yet\n");
697     }
698
699     switch (vkCode) {
700         case VK_ADD:
701         case VK_UP:
702             DATETIME_IncreaseField (infoPtr, fieldNum, 1);
703             DATETIME_SendDateTimeChangeNotify (infoPtr);
704             break;
705         case VK_SUBTRACT:
706         case VK_DOWN:
707             DATETIME_IncreaseField (infoPtr, fieldNum, -1);
708             DATETIME_SendDateTimeChangeNotify (infoPtr);
709             break;
710         case VK_HOME:
711             DATETIME_IncreaseField (infoPtr, fieldNum, INT_MIN);
712             DATETIME_SendDateTimeChangeNotify (infoPtr);
713             break;
714         case VK_END:
715             DATETIME_IncreaseField (infoPtr, fieldNum, INT_MAX);
716             DATETIME_SendDateTimeChangeNotify (infoPtr);
717             break;
718         case VK_LEFT:
719             do {
720                 if (infoPtr->select == 0) {
721                     infoPtr->select = infoPtr->nrFields - 1;
722                     wrap++;
723                 } else {
724                     infoPtr->select--;
725                 }
726             } while ((infoPtr->fieldspec[infoPtr->select] & DT_STRING) && (wrap<2));
727             break;
728         case VK_RIGHT:
729             do {
730                 infoPtr->select++;
731                 if (infoPtr->select==infoPtr->nrFields) {
732                     infoPtr->select = 0;
733                     wrap++;
734                 }
735             } while ((infoPtr->fieldspec[infoPtr->select] & DT_STRING) && (wrap<2));
736             break;
737     }
738
739     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
740
741     return 0;
742 }
743
744
745 static LRESULT
746 DATETIME_KillFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
747 {
748     if (infoPtr->haveFocus) {
749         DATETIME_SendSimpleNotify (infoPtr, NM_KILLFOCUS);
750         infoPtr->haveFocus = 0;
751     }
752
753     InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
754
755     return 0;
756 }
757
758
759 static LRESULT
760 DATETIME_SetFocus (DATETIME_INFO *infoPtr, HWND lostFocus)
761 {
762     if (infoPtr->haveFocus == 0) {
763         DATETIME_SendSimpleNotify (infoPtr, NM_SETFOCUS);
764         infoPtr->haveFocus = DTHT_GOTFOCUS;
765     }
766
767     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
768
769     return 0;
770 }
771
772
773 static BOOL
774 DATETIME_SendDateTimeChangeNotify (DATETIME_INFO *infoPtr)
775 {
776     NMDATETIMECHANGE dtdtc;
777
778     dtdtc.nmhdr.hwndFrom = infoPtr->hwndSelf;
779     dtdtc.nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
780     dtdtc.nmhdr.code     = DTN_DATETIMECHANGE;
781
782     dtdtc.dwFlags = (infoPtr->dwStyle & DTS_SHOWNONE) ? GDT_NONE : GDT_VALID;
783
784     MONTHCAL_CopyTime (&infoPtr->date, &dtdtc.st);
785     return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
786                                 (WPARAM)dtdtc.nmhdr.idFrom, (LPARAM)&dtdtc);
787 }
788
789
790 static BOOL
791 DATETIME_SendSimpleNotify (DATETIME_INFO *infoPtr, UINT code)
792 {
793     NMHDR nmhdr;
794
795     TRACE("%x\n", code);
796     nmhdr.hwndFrom = infoPtr->hwndSelf;
797     nmhdr.idFrom   = GetWindowLongPtrW(infoPtr->hwndSelf, GWLP_ID);
798     nmhdr.code     = code;
799
800     return (BOOL) SendMessageW (infoPtr->hwndNotify, WM_NOTIFY,
801                                 (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);
802 }
803
804 static LRESULT
805 DATETIME_Size (DATETIME_INFO *infoPtr, WORD flags, INT width, INT height)
806 {
807     /* set size */
808     infoPtr->rcClient.bottom = height;
809     infoPtr->rcClient.right = width;
810
811     TRACE("Height=%ld, Width=%ld\n", infoPtr->rcClient.bottom, infoPtr->rcClient.right);
812
813     /* use DrawEdge to adjust the size of rcEdge to get rcDraw */
814     memcpy((&infoPtr->rcDraw), (&infoPtr->rcClient), sizeof(infoPtr->rcDraw));
815
816     DrawEdge(NULL, &(infoPtr->rcDraw), EDGE_SUNKEN, BF_RECT | BF_ADJUST);
817
818     /* set the size of the button that drops the calendar down */
819     /* FIXME: account for style that allows button on left side */
820     infoPtr->calbutton.top   = infoPtr->rcDraw.top;
821     infoPtr->calbutton.bottom= infoPtr->rcDraw.bottom;
822     infoPtr->calbutton.left  = infoPtr->rcDraw.right-15;
823     infoPtr->calbutton.right = infoPtr->rcDraw.right;
824
825     /* set enable/disable button size for show none style being enabled */
826     /* FIXME: these dimensions are completely incorrect */
827     infoPtr->checkbox.top = infoPtr->rcDraw.top;
828     infoPtr->checkbox.bottom = infoPtr->rcDraw.bottom;
829     infoPtr->checkbox.left = infoPtr->rcDraw.left;
830     infoPtr->checkbox.right = infoPtr->rcDraw.left + 10;
831
832     /* update the position of the monthcal control */
833     if(infoPtr->dwStyle & DTS_RIGHTALIGN)
834         infoPtr->monthcal_pos.x = infoPtr->rcClient.right - ((infoPtr->calbutton.right -
835                                   infoPtr->calbutton.left) + 145);
836     else
837         infoPtr->monthcal_pos.x = 8;
838
839     infoPtr->monthcal_pos.y = infoPtr->rcClient.bottom;
840     ClientToScreen (infoPtr->hwndSelf, &(infoPtr->monthcal_pos));
841     SetWindowPos(infoPtr->hMonthCal, 0, infoPtr->monthcal_pos.x, infoPtr->monthcal_pos.y, 145, 150, 0);
842
843     InvalidateRect(infoPtr->hwndSelf, NULL, FALSE);
844
845     return 0;
846 }
847
848
849 static LRESULT 
850 DATETIME_StyleChanged(DATETIME_INFO *infoPtr, WPARAM wStyleType, LPSTYLESTRUCT lpss)
851 {
852     static const WCHAR buttonW[] = { 'b', 'u', 't', 't', 'o', 'n', 0 };
853
854     TRACE("(styletype=%x, styleOld=0x%08lx, styleNew=0x%08lx)\n",
855           wStyleType, lpss->styleOld, lpss->styleNew);
856
857     if (wStyleType != GWL_STYLE) return 0;
858   
859     infoPtr->dwStyle = lpss->styleNew;
860
861     if ( !(lpss->styleOld & DTS_SHOWNONE) && (lpss->styleNew & DTS_SHOWNONE) ) {
862         infoPtr->hwndCheckbut = CreateWindowExW (0, buttonW, 0, WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
863                                                  2, 2, 13, 13, infoPtr->hwndSelf, 0, 
864                                                 (HINSTANCE)GetWindowLongPtrW (infoPtr->hwndSelf, GWLP_HINSTANCE), 0);
865         SendMessageW (infoPtr->hwndCheckbut, BM_SETCHECK, 1, 0);
866     }
867     if ( (lpss->styleOld & DTS_SHOWNONE) && !(lpss->styleNew & DTS_SHOWNONE) ) {
868         DestroyWindow(infoPtr->hwndCheckbut);
869         infoPtr->hwndCheckbut = 0;
870     }
871     if ( !(lpss->styleOld & DTS_UPDOWN) && (lpss->styleNew & DTS_UPDOWN) ) {
872         infoPtr->hUpdown = CreateUpDownControl (WS_CHILD | WS_BORDER | WS_VISIBLE, 120, 1, 20, 20, 
873                                                 infoPtr->hwndSelf, 1, 0, 0, UD_MAXVAL, UD_MINVAL, 0);
874     }
875     if ( (lpss->styleOld & DTS_UPDOWN) && !(lpss->styleNew & DTS_UPDOWN) ) {
876         DestroyWindow(infoPtr->hUpdown);
877         infoPtr->hUpdown = 0;
878     }
879
880     InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
881     return 0;
882 }
883
884
885 static LRESULT
886 DATETIME_Create (HWND hwnd, LPCREATESTRUCTW lpcs)
887 {
888     static const WCHAR SysMonthCal32W[] = { 'S', 'y', 's', 'M', 'o', 'n', 't', 'h', 'C', 'a', 'l', '3', '2', 0 };
889     DATETIME_INFO *infoPtr = (DATETIME_INFO *)Alloc (sizeof(DATETIME_INFO));
890     STYLESTRUCT ss = { 0, lpcs->style };
891
892     if (!infoPtr) return -1;
893
894     infoPtr->hwndSelf = hwnd;
895     infoPtr->dwStyle = lpcs->style;
896
897     infoPtr->nrFieldsAllocated = 32;
898     infoPtr->fieldspec = (int *) Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
899     infoPtr->fieldRect = (RECT *) Alloc (infoPtr->nrFieldsAllocated * sizeof(RECT));
900     infoPtr->buflen = (int *) Alloc (infoPtr->nrFieldsAllocated * sizeof(int));
901     infoPtr->hwndNotify = lpcs->hwndParent;
902
903     DATETIME_StyleChanged(infoPtr, GWL_STYLE, &ss);
904     DATETIME_SetFormatW (infoPtr, 0);
905
906     /* create the monthcal control */
907     infoPtr->hMonthCal = CreateWindowExW (0, SysMonthCal32W, 0, WS_BORDER | WS_POPUP | WS_CLIPSIBLINGS, 
908                                           0, 0, 0, 0, infoPtr->hwndSelf, 0, 0, 0);
909
910     /* initialize info structure */
911     GetSystemTime (&infoPtr->date);
912     infoPtr->dateValid = TRUE;
913     infoPtr->hFont = GetStockObject(DEFAULT_GUI_FONT);
914
915     SetWindowLongPtrW (hwnd, 0, (DWORD_PTR)infoPtr);
916
917     return 0;
918 }
919
920
921
922 static LRESULT
923 DATETIME_Destroy (DATETIME_INFO *infoPtr)
924 {
925     if (infoPtr->hwndCheckbut)
926         DestroyWindow(infoPtr->hwndCheckbut);
927     if (infoPtr->hUpdown)
928         DestroyWindow(infoPtr->hUpdown);
929     if (infoPtr->hMonthCal) 
930         DestroyWindow(infoPtr->hMonthCal);
931     SetWindowLongPtrW( infoPtr->hwndSelf, 0, 0 ); /* clear infoPtr */
932     Free (infoPtr);
933     return 0;
934 }
935
936
937 static LRESULT WINAPI
938 DATETIME_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
939 {
940     DATETIME_INFO *infoPtr = ((DATETIME_INFO *)GetWindowLongPtrW (hwnd, 0));
941     LRESULT ret;
942
943     TRACE ("%x, %x, %lx\n", uMsg, wParam, lParam);
944
945     if (!infoPtr && (uMsg != WM_CREATE))
946         return DefWindowProcW( hwnd, uMsg, wParam, lParam );
947
948     switch (uMsg) {
949
950     case DTM_GETSYSTEMTIME:
951         return DATETIME_GetSystemTime (infoPtr, (SYSTEMTIME *) lParam);
952
953     case DTM_SETSYSTEMTIME:
954         return DATETIME_SetSystemTime (infoPtr, wParam, (SYSTEMTIME *) lParam);
955
956     case DTM_GETRANGE:
957         ret = SendMessageW (infoPtr->hMonthCal, MCM_GETRANGE, wParam, lParam);
958         return ret ? ret : 1; /* bug emulation */
959
960     case DTM_SETRANGE:
961         return SendMessageW (infoPtr->hMonthCal, MCM_SETRANGE, wParam, lParam);
962
963     case DTM_SETFORMATA:
964         return DATETIME_SetFormatA (infoPtr, (LPCSTR)lParam);
965
966     case DTM_SETFORMATW:
967         return DATETIME_SetFormatW (infoPtr, (LPCWSTR)lParam);
968
969     case DTM_GETMONTHCAL:
970         return (LRESULT)infoPtr->hMonthCal;
971
972     case DTM_SETMCCOLOR:
973         return SendMessageW (infoPtr->hMonthCal, MCM_SETCOLOR, wParam, lParam);
974
975     case DTM_GETMCCOLOR:
976         return SendMessageW (infoPtr->hMonthCal, MCM_GETCOLOR, wParam, 0);
977
978     case DTM_SETMCFONT:
979         return SendMessageW (infoPtr->hMonthCal, WM_SETFONT, wParam, lParam);
980
981     case DTM_GETMCFONT:
982         return SendMessageW (infoPtr->hMonthCal, WM_GETFONT, wParam, lParam);
983
984     case WM_NOTIFY:
985         return DATETIME_Notify (infoPtr, (int)wParam, (LPNMHDR)lParam);
986
987     case WM_GETDLGCODE:
988         return DLGC_WANTARROWS | DLGC_WANTCHARS;
989
990     case WM_PAINT:
991         return DATETIME_Paint (infoPtr, (HDC)wParam);
992
993     case WM_KEYDOWN:
994         return DATETIME_KeyDown (infoPtr, wParam, lParam);
995
996     case WM_KILLFOCUS:
997         return DATETIME_KillFocus (infoPtr, (HWND)wParam);
998
999     case WM_SETFOCUS:
1000         return DATETIME_SetFocus (infoPtr, (HWND)wParam);
1001
1002     case WM_SIZE:
1003         return DATETIME_Size (infoPtr, wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam));
1004
1005     case WM_LBUTTONDOWN:
1006         return DATETIME_LButtonDown (infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
1007
1008     case WM_LBUTTONUP:
1009         return DATETIME_LButtonUp (infoPtr, (WORD)wParam, MAKEPOINTS(lParam));
1010
1011     case WM_CREATE:
1012         return DATETIME_Create (hwnd, (LPCREATESTRUCTW)lParam);
1013
1014     case WM_DESTROY:
1015         return DATETIME_Destroy (infoPtr);
1016
1017     case WM_COMMAND:
1018         return DATETIME_Command (infoPtr, wParam, lParam);
1019
1020     case WM_STYLECHANGED:
1021         return DATETIME_StyleChanged(infoPtr, wParam, (LPSTYLESTRUCT)lParam);
1022
1023     default:
1024         if ((uMsg >= WM_USER) && (uMsg < WM_APP))
1025                 ERR("unknown msg %04x wp=%08x lp=%08lx\n",
1026                      uMsg, wParam, lParam);
1027         return DefWindowProcW (hwnd, uMsg, wParam, lParam);
1028     }
1029     return 0;
1030 }
1031
1032
1033 void
1034 DATETIME_Register (void)
1035 {
1036     WNDCLASSW wndClass;
1037
1038     ZeroMemory (&wndClass, sizeof(WNDCLASSW));
1039     wndClass.style         = CS_GLOBALCLASS;
1040     wndClass.lpfnWndProc   = DATETIME_WindowProc;
1041     wndClass.cbClsExtra    = 0;
1042     wndClass.cbWndExtra    = sizeof(DATETIME_INFO *);
1043     wndClass.hCursor       = LoadCursorW (0, (LPCWSTR)IDC_ARROW);
1044     wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
1045     wndClass.lpszClassName = DATETIMEPICK_CLASSW;
1046
1047     RegisterClassW (&wndClass);
1048 }
1049
1050
1051 void
1052 DATETIME_Unregister (void)
1053 {
1054     UnregisterClassW (DATETIMEPICK_CLASSW, NULL);
1055 }