xref: /reactos/dll/cpl/timedate/monthcal.c (revision 1734f297)
1 /*
2  * PROJECT:     ReactOS Timedate Control Panel
3  * LICENSE:     GPL - See COPYING in the top level directory
4  * FILE:        dll/cpl/timedate/monthcal.c
5  * PURPOSE:     Calander implementation
6  * COPYRIGHT:   Copyright 2006 Thomas Weidenmueller <w3seek@reactos.com>
7  *
8  */
9 
10 #include "timedate.h"
11 
12 #include <windowsx.h>
13 
14 static const WCHAR szMonthCalWndClass[] = L"MonthCalWnd";
15 
16 #define MONTHCAL_HEADERBG   COLOR_INACTIVECAPTION
17 #define MONTHCAL_HEADERFG   COLOR_INACTIVECAPTIONTEXT
18 #define MONTHCAL_CTRLBG     COLOR_WINDOW
19 #define MONTHCAL_CTRLFG     COLOR_WINDOWTEXT
20 #define MONTHCAL_SELBG      COLOR_ACTIVECAPTION
21 #define MONTHCAL_SELFG      COLOR_CAPTIONTEXT
22 #define MONTHCAL_DISABLED_HEADERBG  COLOR_INACTIVECAPTION
23 #define MONTHCAL_DISABLED_HEADERFG  COLOR_INACTIVECAPTIONTEXT
24 #define MONTHCAL_DISABLED_CTRLBG    COLOR_WINDOW
25 #define MONTHCAL_DISABLED_CTRLFG    COLOR_WINDOWTEXT
26 #define MONTHCAL_DISABLED_SELBG     COLOR_INACTIVECAPTION
27 #define MONTHCAL_DISABLED_SELFG     COLOR_INACTIVECAPTIONTEXT
28 
29 #define ID_DAYTIMER 1
30 
31 typedef struct _MONTHCALWND
32 {
33     HWND hSelf;
34     HWND hNotify;
35     WORD Day;
36     WORD Month;
37     WORD Year;
38     WORD FirstDayOfWeek;
39     BYTE Days[6][7];
40     WCHAR Week[7];
41     SIZE CellSize;
42     SIZE ClientSize;
43 
44     HFONT hFont;
45     HBRUSH hbHeader;
46     HBRUSH hbSelection;
47 
48     DWORD UIState;
49     UINT Changed : 1;
50     UINT DayTimerSet : 1;
51     UINT Enabled : 1;
52     UINT HasFocus : 1;
53 } MONTHCALWND, *PMONTHCALWND;
54 
55 static LRESULT
56 MonthCalNotifyControlParent(IN PMONTHCALWND infoPtr,
57                             IN UINT code,
58                             IN OUT PVOID data)
59 {
60     LRESULT Ret = 0;
61 
62     if (infoPtr->hNotify != NULL)
63     {
64         LPNMHDR pnmh = (LPNMHDR)data;
65 
66         pnmh->hwndFrom = infoPtr->hSelf;
67         pnmh->idFrom = GetWindowLongPtrW(infoPtr->hSelf,
68                                          GWLP_ID);
69         pnmh->code = code;
70 
71         Ret = SendMessageW(infoPtr->hNotify,
72                            WM_NOTIFY,
73                            (WPARAM)pnmh->idFrom,
74                            (LPARAM)pnmh);
75     }
76 
77     return Ret;
78 }
79 
80 /*
81  * For the year range 1..9999
82  * return 1 if is leap year otherwise 0
83  */
84 static WORD LeapYear(IN WORD Year)
85 {
86 	return
87 #ifdef WITH_1752
88 		(Year <= 1752) ? !(Year % 4) :
89 #endif
90 		!(Year % 4) && ((Year % 100) || !(Year % 400));
91 }
92 
93 static WORD
94 MonthCalMonthLength(IN WORD Month,
95                     IN WORD Year)
96 {
97     const BYTE MonthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
98 
99     if(Month == 2)
100         return MonthDays[Month - 1] + LeapYear(Year);
101     else
102     {
103 #ifdef WITH_1752
104         if ((Year == 1752) && (Month == 9))
105 	   return 19; // Special case: September 1752 has no 3rd-13th
106 	else
107 #endif
108      	   return MonthDays[Month - 1];
109     }
110 }
111 
112 static WORD
113 MonthCalWeekInMonth(IN WORD Day,
114                     IN WORD DayOfWeek)
115 {
116     return (Day - DayOfWeek + 5) / 7;
117 }
118 
119 static WORD
120 MonthCalDayOfWeek(IN PMONTHCALWND infoPtr,
121                   IN WORD Day,
122                   IN WORD Month,
123                   IN WORD Year)
124 {
125     const BYTE DayOfWeek[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
126     WORD Ret;
127 
128     Year -= (Month < 3);
129     Ret = (Year + (Year / 4) - (Year / 100) + (Year / 400) + DayOfWeek[Month - 1] + Day + 6) % 7;
130 
131     return (7 + Ret - infoPtr->FirstDayOfWeek) % 7;
132 }
133 
134 static WORD
135 MonthCalFirstDayOfWeek(VOID)
136 {
137     WCHAR szBuf[2] = {0};
138     WORD Ret = 0;
139 
140     if (GetLocaleInfoW(LOCALE_USER_DEFAULT,
141                        LOCALE_IFIRSTDAYOFWEEK,
142                        szBuf,
143                        sizeof(szBuf) / sizeof(szBuf[0])) != 0)
144     {
145         Ret = (WORD)(szBuf[0] - TEXT('0'));
146     }
147 
148     return Ret;
149 }
150 
151 static BOOL
152 MonthCalValidDate(IN WORD Day,
153                   IN WORD Month,
154                   IN WORD Year)
155 {
156     if (Month < 1 || Month > 12 ||
157         Day == 0 || Day > MonthCalMonthLength(Month,
158                                               Year) ||
159         Year < 1899 || Year > 9999)
160     {
161         return FALSE;
162     }
163 
164     return TRUE;
165 }
166 
167 static VOID
168 MonthCalUpdate(IN PMONTHCALWND infoPtr)
169 {
170     PBYTE pDay, pDayEnd;
171     WORD DayOfWeek, MonthLength, d = 0;
172     SIZE NewCellSize;
173     BOOL RepaintHeader = FALSE;
174 
175     NewCellSize.cx = infoPtr->ClientSize.cx / 7;
176     NewCellSize.cy = infoPtr->ClientSize.cy / 7;
177 
178     if (infoPtr->CellSize.cx != NewCellSize.cx ||
179         infoPtr->CellSize.cy != NewCellSize.cy)
180     {
181         infoPtr->CellSize = NewCellSize;
182         RepaintHeader = TRUE;
183     }
184 
185     /* Update the days layout of the current month */
186     ZeroMemory(infoPtr->Days,
187                sizeof(infoPtr->Days));
188 
189     DayOfWeek = MonthCalDayOfWeek(infoPtr,
190                                   1,
191                                   infoPtr->Month,
192                                   infoPtr->Year);
193 
194     MonthLength = MonthCalMonthLength(infoPtr->Month,
195                                       infoPtr->Year);
196 
197     pDay = &infoPtr->Days[0][DayOfWeek];
198     pDayEnd = pDay + MonthLength;
199     while (pDay != pDayEnd)
200     {
201         *(pDay++) = (BYTE)++d;
202     }
203 
204     /* Repaint the control */
205     if (RepaintHeader)
206     {
207         InvalidateRect(infoPtr->hSelf,
208                        NULL,
209                        TRUE);
210     }
211     else
212     {
213         RECT rcClient;
214 
215         rcClient.left = 0;
216         rcClient.top = infoPtr->CellSize.cy;
217         rcClient.right = infoPtr->ClientSize.cx;
218         rcClient.bottom = infoPtr->ClientSize.cy;
219 
220         InvalidateRect(infoPtr->hSelf,
221                        &rcClient,
222                        TRUE);
223     }
224 }
225 
226 static VOID
227 MonthCalSetupDayTimer(IN PMONTHCALWND infoPtr)
228 {
229     SYSTEMTIME LocalTime = {0};
230     UINT uElapse;
231 
232     /* Update the current date */
233     GetLocalTime(&LocalTime);
234 
235     /* Calculate the number of remaining milliseconds until midnight */
236     uElapse = 1000 - (UINT)LocalTime.wMilliseconds;
237     uElapse += (59 - (UINT)LocalTime.wSecond) * 1000;
238     uElapse += (59 - (UINT)LocalTime.wMinute) * 60 * 1000;
239     uElapse += (23 - (UINT)LocalTime.wHour) * 60 * 60 * 1000;
240 
241     /* Setup the new timer */
242     if (SetTimer(infoPtr->hSelf,
243                  ID_DAYTIMER,
244                  uElapse,
245                  NULL) != 0)
246     {
247         infoPtr->DayTimerSet = TRUE;
248     }
249 }
250 
251 static VOID
252 MonthCalReload(IN PMONTHCALWND infoPtr)
253 {
254     WCHAR szBuf[64];
255     UINT i;
256 
257     infoPtr->UIState = (DWORD)SendMessageW(GetAncestor(infoPtr->hSelf,
258                                                        GA_PARENT),
259                                             WM_QUERYUISTATE,
260                                             0,
261                                             0);
262 
263     /* Cache the configuration */
264     infoPtr->FirstDayOfWeek = MonthCalFirstDayOfWeek();
265 
266     infoPtr->hbHeader = GetSysColorBrush(infoPtr->Enabled ? MONTHCAL_HEADERBG : MONTHCAL_DISABLED_HEADERBG);
267     infoPtr->hbSelection = GetSysColorBrush(infoPtr->Enabled ? MONTHCAL_SELBG : MONTHCAL_DISABLED_SELBG);
268 
269     for (i = 0; i < 7; i++)
270     {
271         if (GetLocaleInfoW(LOCALE_USER_DEFAULT,
272                            LOCALE_SABBREVDAYNAME1 +
273                                ((i + infoPtr->FirstDayOfWeek) % 7),
274                            szBuf,
275                            sizeof(szBuf) / sizeof(szBuf[0])) != 0)
276         {
277             infoPtr->Week[i] = szBuf[0];
278         }
279     }
280 
281     /* Update the control */
282     MonthCalUpdate(infoPtr);
283 }
284 
285 static BOOL
286 MonthCalGetDayRect(IN PMONTHCALWND infoPtr,
287                    IN WORD Day,
288                    OUT RECT *rcCell)
289 {
290     if (Day >= 1 && Day <= MonthCalMonthLength(infoPtr->Month,
291                                                infoPtr->Year))
292     {
293         WORD DayOfWeek;
294 
295         DayOfWeek = MonthCalDayOfWeek(infoPtr,
296                                       Day,
297                                       infoPtr->Month,
298                                       infoPtr->Year);
299 
300         rcCell->left = DayOfWeek * infoPtr->CellSize.cx;
301         rcCell->top = (MonthCalWeekInMonth(Day,
302                                            DayOfWeek) + 1) * infoPtr->CellSize.cy;
303         rcCell->right = rcCell->left + infoPtr->CellSize.cx;
304         rcCell->bottom = rcCell->top + infoPtr->CellSize.cy;
305 
306         return TRUE;
307     }
308 
309     return FALSE;
310 }
311 
312 static VOID
313 MonthCalChange(IN PMONTHCALWND infoPtr)
314 {
315     infoPtr->Changed = TRUE;
316 
317     /* Kill the day timer */
318     if (infoPtr->DayTimerSet)
319     {
320         KillTimer(infoPtr->hSelf,
321                   ID_DAYTIMER);
322         infoPtr->DayTimerSet = FALSE;
323     }
324 }
325 
326 
327 static BOOL
328 MonthCalSetDate(IN PMONTHCALWND infoPtr,
329                 IN WORD Day,
330                 IN WORD Month,
331                 IN WORD Year)
332 {
333     NMMCCSELCHANGE sc;
334     BOOL Ret = FALSE;
335 
336     sc.OldDay = infoPtr->Day;
337     sc.OldMonth = infoPtr->Month;
338     sc.OldYear = infoPtr->Year;
339     sc.NewDay = Day;
340     sc.NewMonth = Month;
341     sc.NewYear = Year;
342 
343     /* Notify the parent */
344     if (!MonthCalNotifyControlParent(infoPtr,
345                                      MCCN_SELCHANGE,
346                                      &sc))
347     {
348         /* Check if we actually need to update */
349         if (infoPtr->Month != sc.NewMonth ||
350             infoPtr->Year != sc.NewYear)
351         {
352             infoPtr->Day = sc.NewDay;
353             infoPtr->Month = sc.NewMonth;
354             infoPtr->Year = sc.NewYear;
355 
356             MonthCalChange(infoPtr);
357 
358             /* Repaint the entire control */
359             MonthCalUpdate(infoPtr);
360 
361             Ret = TRUE;
362         }
363         else if (infoPtr->Day != sc.NewDay)
364         {
365             RECT rcUpdate;
366 
367             infoPtr->Day = sc.NewDay;
368 
369             MonthCalChange(infoPtr);
370 
371             if (MonthCalGetDayRect(infoPtr,
372                                    sc.OldDay,
373                                    &rcUpdate))
374             {
375                 /* Repaint the day cells that need to be updated */
376                 InvalidateRect(infoPtr->hSelf,
377                                &rcUpdate,
378                                TRUE);
379                 if (MonthCalGetDayRect(infoPtr,
380                                        sc.NewDay,
381                                        &rcUpdate))
382                 {
383                     InvalidateRect(infoPtr->hSelf,
384                                    &rcUpdate,
385                                    TRUE);
386                 }
387             }
388 
389             Ret = TRUE;
390         }
391     }
392 
393     return Ret;
394 }
395 
396 static VOID
397 MonthCalSetLocalTime(IN PMONTHCALWND infoPtr,
398                      OUT SYSTEMTIME *Time)
399 {
400     NMMCCAUTOUPDATE au;
401     SYSTEMTIME LocalTime = {0};
402 
403     GetLocalTime(&LocalTime);
404 
405     au.SystemTime = LocalTime;
406     if (!MonthCalNotifyControlParent(infoPtr,
407                                      MCCN_AUTOUPDATE,
408                                      &au))
409     {
410         if (MonthCalSetDate(infoPtr,
411                             LocalTime.wDay,
412                             LocalTime.wMonth,
413                             LocalTime.wYear))
414         {
415             infoPtr->Changed = FALSE;
416         }
417     }
418 
419     /* Kill the day timer */
420     if (infoPtr->DayTimerSet)
421     {
422         KillTimer(infoPtr->hSelf,
423                   ID_DAYTIMER);
424         infoPtr->DayTimerSet = FALSE;
425     }
426 
427     /* Setup the new day timer */
428     MonthCalSetupDayTimer(infoPtr);
429 
430     if (Time != NULL)
431     {
432         *Time = LocalTime;
433     }
434 }
435 
436 static VOID
437 MonthCalRepaintDay(IN PMONTHCALWND infoPtr,
438                    IN WORD Day)
439 {
440     RECT rcCell;
441 
442     if (MonthCalGetDayRect(infoPtr,
443                            Day,
444                            &rcCell))
445     {
446         InvalidateRect(infoPtr->hSelf,
447                        &rcCell,
448                        TRUE);
449     }
450 }
451 
452 static VOID
453 MonthCalPaint(IN PMONTHCALWND infoPtr,
454               IN HDC hDC,
455               IN LPRECT prcUpdate)
456 {
457     LONG x, y;
458     RECT rcCell;
459     COLORREF crOldText, crOldCtrlText = CLR_INVALID;
460     HFONT hOldFont;
461     INT iOldBkMode;
462 
463 #if MONTHCAL_CTRLBG != MONTHCAL_DISABLED_CTRLBG
464     if (!infoPtr->Enabled)
465     {
466         FillRect(hDC,
467                  prcUpdate,
468                  GetSysColorBrush(MONTHCAL_DISABLED_CTRLBG));
469     }
470 #endif
471 
472     iOldBkMode = SetBkMode(hDC,
473                            TRANSPARENT);
474     hOldFont = (HFONT)SelectObject(hDC,
475                                    infoPtr->hFont);
476 
477     for (y = prcUpdate->top / infoPtr->CellSize.cy;
478          y <= prcUpdate->bottom / infoPtr->CellSize.cy && y < 7;
479          y++)
480     {
481         rcCell.top = y * infoPtr->CellSize.cy;
482         rcCell.bottom = rcCell.top + infoPtr->CellSize.cy;
483 
484         if (y == 0)
485         {
486             RECT rcHeader;
487 
488             /* Paint the header */
489             rcHeader.left = prcUpdate->left;
490             rcHeader.top = rcCell.top;
491             rcHeader.right = prcUpdate->right;
492             rcHeader.bottom = rcCell.bottom;
493 
494             FillRect(hDC,
495                      &rcHeader,
496                      infoPtr->hbHeader);
497 
498             crOldText = SetTextColor(hDC,
499                                      GetSysColor(infoPtr->Enabled ? MONTHCAL_HEADERFG : MONTHCAL_DISABLED_HEADERFG));
500 
501             for (x = prcUpdate->left / infoPtr->CellSize.cx;
502                  x <= prcUpdate->right / infoPtr->CellSize.cx && x < 7;
503                  x++)
504             {
505                 rcCell.left = x * infoPtr->CellSize.cx;
506                 rcCell.right = rcCell.left + infoPtr->CellSize.cx;
507 
508                 /* Write the first letter of each weekday */
509                 DrawTextW(hDC,
510                           &infoPtr->Week[x],
511                           1,
512                           &rcCell,
513                           DT_SINGLELINE | DT_NOPREFIX | DT_CENTER | DT_VCENTER);
514             }
515 
516             SetTextColor(hDC,
517                          crOldText);
518         }
519         else
520         {
521             if (crOldCtrlText == CLR_INVALID)
522             {
523                 crOldCtrlText = SetTextColor(hDC,
524                                              GetSysColor(infoPtr->Enabled ? MONTHCAL_CTRLFG : MONTHCAL_DISABLED_CTRLFG));
525             }
526 
527             for (x = prcUpdate->left / infoPtr->CellSize.cx;
528                  x <= prcUpdate->right / infoPtr->CellSize.cx && x < 7;
529                  x++)
530             {
531                 UINT Day = infoPtr->Days[y - 1][x];
532 
533                 rcCell.left = x * infoPtr->CellSize.cx;
534                 rcCell.right = rcCell.left + infoPtr->CellSize.cx;
535 
536                 /* Write the day number */
537                 if (Day != 0 && Day < 100)
538                 {
539                     WCHAR szDay[3];
540                     INT szDayLen;
541                     RECT rcText;
542                     SIZE TextSize;
543 
544                     szDayLen = swprintf(szDay,
545                                          L"%lu",
546                                          Day);
547 
548                     if (GetTextExtentPoint32W(hDC,
549                                               szDay,
550                                               szDayLen,
551                                               &TextSize))
552                     {
553                         RECT rcHighlight = { 0, 0, 0, 0 };
554 
555                         rcText.left = rcCell.left + (infoPtr->CellSize.cx / 2) - (TextSize.cx / 2);
556                         rcText.top = rcCell.top + (infoPtr->CellSize.cy / 2) - (TextSize.cy / 2);
557                         rcText.right = rcText.left + TextSize.cx;
558                         rcText.bottom = rcText.top + TextSize.cy;
559 
560                         if (Day == infoPtr->Day)
561                         {
562                             SIZE TextSel;
563 
564                             TextSel.cx = (infoPtr->CellSize.cx * 2) / 3;
565                             TextSel.cy = (infoPtr->CellSize.cy * 3) / 4;
566 
567                             if (TextSel.cx < rcText.right - rcText.left)
568                                 TextSel.cx = rcText.right - rcText.left;
569                             if (TextSel.cy < rcText.bottom - rcText.top)
570                                 TextSel.cy = rcText.bottom - rcText.top;
571 
572                             rcHighlight.left = rcCell.left + (infoPtr->CellSize.cx / 2) - (TextSel.cx / 2);
573                             rcHighlight.right = rcHighlight.left + TextSel.cx;
574                             rcHighlight.top = rcCell.top + (infoPtr->CellSize.cy / 2) - (TextSel.cy / 2);
575                             rcHighlight.bottom = rcHighlight.top + TextSel.cy;
576 
577                             InflateRect(&rcHighlight,
578                                         GetSystemMetrics(SM_CXFOCUSBORDER),
579                                         GetSystemMetrics(SM_CYFOCUSBORDER));
580 
581                             if (!FillRect(hDC,
582                                           &rcHighlight,
583                                           infoPtr->hbSelection))
584                             {
585                                 goto FailNoHighlight;
586                             }
587 
588                             /* Highlight the selected day */
589                             crOldText = SetTextColor(hDC,
590                                                      GetSysColor(infoPtr->Enabled ? MONTHCAL_SELFG : MONTHCAL_DISABLED_SELFG));
591                         }
592                         else
593                         {
594 FailNoHighlight:
595                             /* Don't change the text color, we're not highlighting it... */
596                             crOldText = CLR_INVALID;
597                         }
598 
599                         TextOutW(hDC,
600                                  rcText.left,
601                                  rcText.top,
602                                  szDay,
603                                  szDayLen);
604 
605                         if (Day == infoPtr->Day && crOldText != CLR_INVALID)
606                         {
607                             if (infoPtr->HasFocus && infoPtr->Enabled && !(infoPtr->UIState & UISF_HIDEFOCUS))
608                             {
609                                 COLORREF crOldBk;
610 
611                                 crOldBk = SetBkColor(hDC,
612                                                      GetSysColor(infoPtr->Enabled ? MONTHCAL_SELBG : MONTHCAL_DISABLED_SELBG));
613 
614                                 DrawFocusRect(hDC,
615                                               &rcHighlight);
616 
617                                 SetBkColor(hDC,
618                                            crOldBk);
619                             }
620 
621                             SetTextColor(hDC,
622                                          crOldText);
623                         }
624                     }
625                 }
626             }
627         }
628     }
629 
630     if (crOldCtrlText != CLR_INVALID)
631     {
632         SetTextColor(hDC,
633                      crOldCtrlText);
634     }
635 
636     SetBkMode(hDC,
637               iOldBkMode);
638     SelectObject(hDC,
639                  (HGDIOBJ)hOldFont);
640 }
641 
642 static HFONT
643 MonthCalChangeFont(IN PMONTHCALWND infoPtr,
644                    IN HFONT hFont,
645                    IN BOOL Redraw)
646 {
647     HFONT hOldFont = infoPtr->hFont;
648     infoPtr->hFont = hFont;
649 
650     if (Redraw)
651     {
652         InvalidateRect(infoPtr->hSelf,
653                        NULL,
654                        TRUE);
655     }
656 
657     return hOldFont;
658 }
659 
660 static WORD
661 MonthCalPtToDay(IN PMONTHCALWND infoPtr,
662                 IN INT x,
663                 IN INT y)
664 {
665     WORD Ret = 0;
666 
667     if (infoPtr->CellSize.cx != 0 && infoPtr->CellSize.cy != 0 &&
668         x >= 0 && y >= 0)
669     {
670         x /= infoPtr->CellSize.cx;
671         y /= infoPtr->CellSize.cy;
672 
673         if (x < 7 && y != 0 && y < 7)
674         {
675             Ret = (WORD)infoPtr->Days[y - 1][x];
676         }
677     }
678 
679     return Ret;
680 }
681 
682 static LRESULT CALLBACK
683 MonthCalWndProc(IN HWND hwnd,
684                 IN UINT uMsg,
685                 IN WPARAM wParam,
686                 IN LPARAM lParam)
687 {
688     PMONTHCALWND infoPtr;
689     LRESULT Ret = 0;
690 
691     infoPtr = (PMONTHCALWND)GetWindowLongPtrW(hwnd,
692                                               0);
693 
694     if (infoPtr == NULL && uMsg != WM_CREATE)
695     {
696         goto HandleDefaultMessage;
697     }
698 
699     switch (uMsg)
700     {
701 #if MONTHCAL_CTRLBG != MONTHCAL_DISABLED_CTRLBG
702         case WM_ERASEBKGND:
703             Ret = !infoPtr->Enabled;
704             break;
705 #endif
706 
707         case WM_PAINT:
708         case WM_PRINTCLIENT:
709         {
710             if (infoPtr->CellSize.cx != 0 && infoPtr->CellSize.cy != 0)
711             {
712                 PAINTSTRUCT ps;
713                 HDC hDC;
714 
715                 if (wParam != 0)
716                 {
717                     if (!GetUpdateRect(hwnd,
718                                        &ps.rcPaint,
719                                        TRUE))
720                     {
721                         break;
722                     }
723                     hDC = (HDC)wParam;
724                 }
725                 else
726                 {
727                     hDC = BeginPaint(hwnd,
728                                      &ps);
729                     if (hDC == NULL)
730                     {
731                         break;
732                     }
733                 }
734 
735                 MonthCalPaint(infoPtr,
736                               hDC,
737                               &ps.rcPaint);
738 
739                 if (wParam == 0)
740                 {
741                     EndPaint(hwnd,
742                              &ps);
743                 }
744             }
745             break;
746         }
747 
748         case WM_LBUTTONDBLCLK:
749         case WM_LBUTTONDOWN:
750         {
751             WORD SelDay;
752 
753             SelDay = MonthCalPtToDay(infoPtr,
754                                      GET_X_LPARAM(lParam),
755                                      GET_Y_LPARAM(lParam));
756             if (SelDay != 0 && SelDay != infoPtr->Day)
757             {
758                 MonthCalSetDate(infoPtr,
759                                 SelDay,
760                                 infoPtr->Month,
761                                 infoPtr->Year);
762             }
763 
764             /* Fall through */
765         }
766 
767         case WM_MBUTTONDOWN:
768         case WM_RBUTTONDOWN:
769         {
770             if (!infoPtr->HasFocus)
771             {
772                 SetFocus(hwnd);
773             }
774             break;
775         }
776 
777         case WM_KEYDOWN:
778         {
779             WORD NewDay = 0;
780 
781             switch (wParam)
782             {
783                 case VK_UP:
784                 {
785                     if (infoPtr->Day > 7)
786                     {
787                         NewDay = infoPtr->Day - 7;
788                     }
789                     break;
790                 }
791 
792                 case VK_DOWN:
793                 {
794                     if (infoPtr->Day + 7 <= MonthCalMonthLength(infoPtr->Month,
795                                                                 infoPtr->Year))
796                     {
797                         NewDay = infoPtr->Day + 7;
798                     }
799                     break;
800                 }
801 
802                 case VK_LEFT:
803                 {
804                     if (infoPtr->Day > 1)
805                     {
806                         NewDay = infoPtr->Day - 1;
807                     }
808                     break;
809                 }
810 
811                 case VK_RIGHT:
812                 {
813                     if (infoPtr->Day < MonthCalMonthLength(infoPtr->Month,
814                                                            infoPtr->Year))
815                     {
816                         NewDay = infoPtr->Day + 1;
817                     }
818                     break;
819                 }
820             }
821 
822             /* Update the selection */
823             if (NewDay != 0)
824             {
825                 MonthCalSetDate(infoPtr,
826                                 NewDay,
827                                 infoPtr->Month,
828                                 infoPtr->Year);
829             }
830 
831             goto HandleDefaultMessage;
832         }
833 
834         case WM_GETDLGCODE:
835         {
836             INT virtKey;
837 
838             virtKey = (lParam != 0 ? (INT)((LPMSG)lParam)->wParam : 0);
839             switch (virtKey)
840             {
841                 case VK_TAB:
842                 {
843                     /* Change the UI status */
844                     SendMessageW(GetAncestor(hwnd,
845                                              GA_PARENT),
846                                  WM_CHANGEUISTATE,
847                                  MAKEWPARAM(UIS_INITIALIZE,
848                                             0),
849                                  0);
850                     break;
851                 }
852             }
853 
854             Ret |= DLGC_WANTARROWS;
855             break;
856         }
857 
858         case WM_SETFOCUS:
859         {
860             infoPtr->HasFocus = TRUE;
861             MonthCalRepaintDay(infoPtr,
862                                infoPtr->Day);
863             break;
864         }
865 
866         case WM_KILLFOCUS:
867         {
868             infoPtr->HasFocus = FALSE;
869             MonthCalRepaintDay(infoPtr,
870                                infoPtr->Day);
871             break;
872         }
873 
874         case WM_UPDATEUISTATE:
875         {
876             DWORD OldUIState;
877 
878             Ret = DefWindowProcW(hwnd,
879                                  uMsg,
880                                  wParam,
881                                  lParam);
882 
883             OldUIState = infoPtr->UIState;
884             switch (LOWORD(wParam))
885             {
886                 case UIS_SET:
887                     infoPtr->UIState |= HIWORD(wParam);
888                     break;
889 
890                 case UIS_CLEAR:
891                     infoPtr->UIState &= ~HIWORD(wParam);
892                     break;
893             }
894 
895             if (infoPtr->UIState != OldUIState)
896             {
897                 MonthCalRepaintDay(infoPtr,
898                                    infoPtr->Day);
899             }
900             break;
901         }
902 
903         case MCCM_SETDATE:
904         {
905             WORD Day, Month, Year, DaysCount;
906 
907             Day = LOWORD(wParam);
908             Month = HIWORD(wParam);
909             Year = LOWORD(lParam);
910 
911             if (Day == (WORD)-1)
912                 Day = infoPtr->Day;
913             if (Month == (WORD)-1)
914                 Month = infoPtr->Month;
915             if (Year == (WORD)-1)
916                 Year = infoPtr->Year;
917 
918             DaysCount = MonthCalMonthLength(Month,
919                                             Year);
920             if (Day > DaysCount)
921                 Day = DaysCount;
922 
923             if (MonthCalValidDate(Day,
924                                   Month,
925                                   Year))
926             {
927                 if (Day != infoPtr->Day ||
928                     Month != infoPtr->Month ||
929                     Year != infoPtr->Year)
930                 {
931                     Ret = MonthCalSetDate(infoPtr,
932                                           Day,
933                                           Month,
934                                           Year);
935                 }
936             }
937             break;
938         }
939 
940         case MCCM_GETDATE:
941         {
942             LPSYSTEMTIME lpSystemTime = (LPSYSTEMTIME)wParam;
943 
944             lpSystemTime->wYear = infoPtr->Year;
945             lpSystemTime->wMonth = infoPtr->Month;
946             lpSystemTime->wDay = infoPtr->Day;
947 
948             Ret = TRUE;
949             break;
950         }
951 
952         case MCCM_RESET:
953         {
954             MonthCalSetLocalTime(infoPtr,
955                                  NULL);
956             Ret = TRUE;
957             break;
958         }
959 
960         case MCCM_CHANGED:
961         {
962             Ret = infoPtr->Changed;
963             break;
964         }
965 
966         case WM_TIMER:
967         {
968             switch (wParam)
969             {
970                 case ID_DAYTIMER:
971                 {
972                     /* Kill the timer */
973                     KillTimer(hwnd,
974                               ID_DAYTIMER);
975                     infoPtr->DayTimerSet = FALSE;
976 
977                     if (!infoPtr->Changed)
978                     {
979                         /* Update the system time and setup the new day timer */
980                         MonthCalSetLocalTime(infoPtr,
981                                              NULL);
982 
983                         /* Update the control */
984                         MonthCalUpdate(infoPtr);
985                     }
986                     break;
987                 }
988             }
989             break;
990         }
991 
992         case WM_SETFONT:
993         {
994             Ret = (LRESULT)MonthCalChangeFont(infoPtr,
995                                               (HFONT)wParam,
996                                               (BOOL)LOWORD(lParam));
997             break;
998         }
999 
1000         case WM_SIZE:
1001         {
1002             infoPtr->ClientSize.cx = LOWORD(lParam);
1003             infoPtr->ClientSize.cy = HIWORD(lParam);
1004             infoPtr->CellSize.cx = infoPtr->ClientSize.cx / 7;
1005             infoPtr->CellSize.cy = infoPtr->ClientSize.cy / 7;
1006 
1007             /* Repaint the control */
1008             InvalidateRect(hwnd,
1009                            NULL,
1010                            TRUE);
1011             break;
1012         }
1013 
1014         case WM_GETFONT:
1015         {
1016             Ret = (LRESULT)infoPtr->hFont;
1017             break;
1018         }
1019 
1020         case WM_ENABLE:
1021         {
1022             infoPtr->Enabled = ((BOOL)wParam != FALSE);
1023             MonthCalReload(infoPtr);
1024             break;
1025         }
1026 
1027         case WM_STYLECHANGED:
1028         {
1029             if (wParam == GWL_STYLE)
1030             {
1031                 unsigned int OldEnabled = infoPtr->Enabled;
1032                 infoPtr->Enabled = !(((LPSTYLESTRUCT)lParam)->styleNew & WS_DISABLED);
1033 
1034                 if (OldEnabled != infoPtr->Enabled)
1035                 {
1036                     MonthCalReload(infoPtr);
1037                 }
1038             }
1039             break;
1040         }
1041 
1042         case WM_CREATE:
1043         {
1044             infoPtr = (MONTHCALWND*) HeapAlloc(GetProcessHeap(),
1045                                 0,
1046                                 sizeof(MONTHCALWND));
1047             if (infoPtr == NULL)
1048             {
1049                 Ret = (LRESULT)-1;
1050                 break;
1051             }
1052 
1053             SetWindowLongPtrW(hwnd,
1054                               0,
1055                               (LONG_PTR)infoPtr);
1056 
1057             ZeroMemory(infoPtr,
1058                        sizeof(MONTHCALWND));
1059 
1060             infoPtr->hSelf = hwnd;
1061             infoPtr->hNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
1062             infoPtr->Enabled = !(((LPCREATESTRUCTW)lParam)->style & WS_DISABLED);
1063 
1064             MonthCalSetLocalTime(infoPtr,
1065                                  NULL);
1066 
1067             MonthCalReload(infoPtr);
1068             break;
1069         }
1070 
1071         case WM_DESTROY:
1072         {
1073             HeapFree(GetProcessHeap(),
1074                      0,
1075                      infoPtr);
1076             SetWindowLongPtrW(hwnd,
1077                               0,
1078                               (DWORD_PTR)NULL);
1079             break;
1080         }
1081 
1082         default:
1083         {
1084 HandleDefaultMessage:
1085             Ret = DefWindowProcW(hwnd,
1086                                  uMsg,
1087                                  wParam,
1088                                  lParam);
1089             break;
1090         }
1091     }
1092 
1093     return Ret;
1094 }
1095 
1096 BOOL
1097 RegisterMonthCalControl(IN HINSTANCE hInstance)
1098 {
1099     WNDCLASSW wc = {0};
1100 
1101     wc.style = CS_DBLCLKS;
1102     wc.lpfnWndProc = MonthCalWndProc;
1103     wc.cbWndExtra = sizeof(PMONTHCALWND);
1104     wc.hInstance = hInstance;
1105     wc.hCursor = LoadCursorW(NULL,
1106                              (LPWSTR)IDC_ARROW);
1107     wc.hbrBackground = (HBRUSH)(MONTHCAL_CTRLBG + 1);
1108     wc.lpszClassName = szMonthCalWndClass;
1109 
1110     return RegisterClassW(&wc) != 0;
1111 }
1112 
1113 VOID
1114 UnregisterMonthCalControl(IN HINSTANCE hInstance)
1115 {
1116     UnregisterClassW(szMonthCalWndClass,
1117                      hInstance);
1118 }
1119