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