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>
12 static const WCHAR szMonthCalWndClass
[] = L
"MonthCalWnd";
14 #define MONTHCAL_HEADERBG COLOR_INACTIVECAPTION
15 #define MONTHCAL_HEADERFG COLOR_INACTIVECAPTIONTEXT
16 #define MONTHCAL_CTRLBG COLOR_WINDOW
17 #define MONTHCAL_CTRLFG COLOR_WINDOWTEXT
18 #define MONTHCAL_SELBG COLOR_ACTIVECAPTION
19 #define MONTHCAL_SELFG COLOR_CAPTIONTEXT
20 #define MONTHCAL_DISABLED_HEADERBG COLOR_INACTIVECAPTION
21 #define MONTHCAL_DISABLED_HEADERFG COLOR_INACTIVECAPTIONTEXT
22 #define MONTHCAL_DISABLED_CTRLBG COLOR_WINDOW
23 #define MONTHCAL_DISABLED_CTRLFG COLOR_WINDOWTEXT
24 #define MONTHCAL_DISABLED_SELBG COLOR_INACTIVECAPTION
25 #define MONTHCAL_DISABLED_SELFG COLOR_INACTIVECAPTIONTEXT
29 typedef struct _MONTHCALWND
51 } MONTHCALWND
, *PMONTHCALWND
;
54 MonthCalNotifyControlParent(IN PMONTHCALWND infoPtr
,
60 if (infoPtr
->hNotify
!= NULL
)
62 LPNMHDR pnmh
= (LPNMHDR
)data
;
64 pnmh
->hwndFrom
= infoPtr
->hSelf
;
65 pnmh
->idFrom
= GetWindowLongPtrW(infoPtr
->hSelf
,
69 Ret
= SendMessageW(infoPtr
->hNotify
,
79 * For the year range 1..9999
80 * return 1 if is leap year otherwise 0
82 static WORD
LeapYear(IN WORD Year
)
86 (Year
<= 1752) ? !(Year
% 4) :
88 !(Year
% 4) && ((Year
% 100) || !(Year
% 400));
92 MonthCalMonthLength(IN WORD Month
,
95 const BYTE MonthDays
[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
98 return MonthDays
[Month
- 1] + LeapYear(Year
);
102 if ((Year
== 1752) && (Month
== 9))
103 return 19; // Special case: September 1752 has no 3rd-13th
106 return MonthDays
[Month
- 1];
111 MonthCalWeekInMonth(IN WORD Day
,
114 return (Day
- DayOfWeek
+ 5) / 7;
118 MonthCalDayOfWeek(IN PMONTHCALWND infoPtr
,
123 const BYTE DayOfWeek
[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
127 Ret
= (Year
+ (Year
/ 4) - (Year
/ 100) + (Year
/ 400) + DayOfWeek
[Month
- 1] + Day
+ 6) % 7;
129 return (7 + Ret
- infoPtr
->FirstDayOfWeek
) % 7;
133 MonthCalFirstDayOfWeek(VOID
)
135 WCHAR szBuf
[2] = {0};
138 if (GetLocaleInfoW(LOCALE_USER_DEFAULT
,
139 LOCALE_IFIRSTDAYOFWEEK
,
141 sizeof(szBuf
) / sizeof(szBuf
[0])) != 0)
143 Ret
= (WORD
)(szBuf
[0] - TEXT('0'));
150 MonthCalValidDate(IN WORD Day
,
154 if (Month
< 1 || Month
> 12 ||
155 Day
== 0 || Day
> MonthCalMonthLength(Month
,
157 Year
< 1899 || Year
> 9999)
166 MonthCalUpdate(IN PMONTHCALWND infoPtr
)
169 WORD DayOfWeek
, MonthLength
, d
= 0;
171 BOOL RepaintHeader
= FALSE
;
173 NewCellSize
.cx
= infoPtr
->ClientSize
.cx
/ 7;
174 NewCellSize
.cy
= infoPtr
->ClientSize
.cy
/ 7;
176 if (infoPtr
->CellSize
.cx
!= NewCellSize
.cx
||
177 infoPtr
->CellSize
.cy
!= NewCellSize
.cy
)
179 infoPtr
->CellSize
= NewCellSize
;
180 RepaintHeader
= TRUE
;
183 /* Update the days layout of the current month */
184 ZeroMemory(infoPtr
->Days
,
185 sizeof(infoPtr
->Days
));
187 DayOfWeek
= MonthCalDayOfWeek(infoPtr
,
192 MonthLength
= MonthCalMonthLength(infoPtr
->Month
,
195 pDay
= &infoPtr
->Days
[0][DayOfWeek
];
196 pDayEnd
= pDay
+ MonthLength
;
197 while (pDay
!= pDayEnd
)
199 *(pDay
++) = (BYTE
)++d
;
202 /* Repaint the control */
205 InvalidateRect(infoPtr
->hSelf
,
214 rcClient
.top
= infoPtr
->CellSize
.cy
;
215 rcClient
.right
= infoPtr
->ClientSize
.cx
;
216 rcClient
.bottom
= infoPtr
->ClientSize
.cy
;
218 InvalidateRect(infoPtr
->hSelf
,
225 MonthCalSetupDayTimer(IN PMONTHCALWND infoPtr
)
227 SYSTEMTIME LocalTime
= {0};
230 /* Update the current date */
231 GetLocalTime(&LocalTime
);
233 /* Calculate the number of remaining milliseconds until midnight */
234 uElapse
= 1000 - (UINT
)LocalTime
.wMilliseconds
;
235 uElapse
+= (59 - (UINT
)LocalTime
.wSecond
) * 1000;
236 uElapse
+= (59 - (UINT
)LocalTime
.wMinute
) * 60 * 1000;
237 uElapse
+= (23 - (UINT
)LocalTime
.wHour
) * 60 * 60 * 1000;
239 if (uElapse
< USER_TIMER_MINIMUM
|| uElapse
> USER_TIMER_MAXIMUM
)
242 uElapse
+= 100; /* Add a delay of 0.1 seconds */
244 /* Setup the new timer */
245 if (SetTimer(infoPtr
->hSelf
,
250 infoPtr
->DayTimerSet
= TRUE
;
255 MonthCalReload(IN PMONTHCALWND infoPtr
)
260 infoPtr
->UIState
= (DWORD
)SendMessageW(GetAncestor(infoPtr
->hSelf
,
266 /* Cache the configuration */
267 infoPtr
->FirstDayOfWeek
= MonthCalFirstDayOfWeek();
269 infoPtr
->hbHeader
= GetSysColorBrush(infoPtr
->Enabled
? MONTHCAL_HEADERBG
: MONTHCAL_DISABLED_HEADERBG
);
270 infoPtr
->hbSelection
= GetSysColorBrush(infoPtr
->Enabled
? MONTHCAL_SELBG
: MONTHCAL_DISABLED_SELBG
);
272 for (i
= 0; i
< 7; i
++)
274 if (GetLocaleInfoW(LOCALE_USER_DEFAULT
,
275 LOCALE_SABBREVDAYNAME1
+
276 ((i
+ infoPtr
->FirstDayOfWeek
) % 7),
278 sizeof(szBuf
) / sizeof(szBuf
[0])) != 0)
280 infoPtr
->Week
[i
] = szBuf
[0];
284 /* Update the control */
285 MonthCalUpdate(infoPtr
);
289 MonthCalGetDayRect(IN PMONTHCALWND infoPtr
,
293 if (Day
>= 1 && Day
<= MonthCalMonthLength(infoPtr
->Month
,
298 DayOfWeek
= MonthCalDayOfWeek(infoPtr
,
303 rcCell
->left
= DayOfWeek
* infoPtr
->CellSize
.cx
;
304 rcCell
->top
= (MonthCalWeekInMonth(Day
,
305 DayOfWeek
) + 1) * infoPtr
->CellSize
.cy
;
306 rcCell
->right
= rcCell
->left
+ infoPtr
->CellSize
.cx
;
307 rcCell
->bottom
= rcCell
->top
+ infoPtr
->CellSize
.cy
;
316 MonthCalChange(IN PMONTHCALWND infoPtr
)
318 infoPtr
->Changed
= TRUE
;
320 /* Kill the day timer */
321 if (infoPtr
->DayTimerSet
)
323 KillTimer(infoPtr
->hSelf
,
325 infoPtr
->DayTimerSet
= FALSE
;
331 MonthCalSetDate(IN PMONTHCALWND infoPtr
,
339 sc
.OldDay
= infoPtr
->Day
;
340 sc
.OldMonth
= infoPtr
->Month
;
341 sc
.OldYear
= infoPtr
->Year
;
346 /* Notify the parent */
347 if (!MonthCalNotifyControlParent(infoPtr
,
351 /* Check if we actually need to update */
352 if (infoPtr
->Month
!= sc
.NewMonth
||
353 infoPtr
->Year
!= sc
.NewYear
)
355 infoPtr
->Day
= sc
.NewDay
;
356 infoPtr
->Month
= sc
.NewMonth
;
357 infoPtr
->Year
= sc
.NewYear
;
359 MonthCalChange(infoPtr
);
361 /* Repaint the entire control */
362 MonthCalUpdate(infoPtr
);
366 else if (infoPtr
->Day
!= sc
.NewDay
)
370 infoPtr
->Day
= sc
.NewDay
;
372 MonthCalChange(infoPtr
);
374 if (MonthCalGetDayRect(infoPtr
,
378 /* Repaint the day cells that need to be updated */
379 InvalidateRect(infoPtr
->hSelf
,
382 if (MonthCalGetDayRect(infoPtr
,
386 InvalidateRect(infoPtr
->hSelf
,
400 MonthCalSetLocalTime(IN PMONTHCALWND infoPtr
,
401 OUT SYSTEMTIME
*Time
)
404 SYSTEMTIME LocalTime
= {0};
406 GetLocalTime(&LocalTime
);
408 au
.SystemTime
= LocalTime
;
409 if (!MonthCalNotifyControlParent(infoPtr
,
413 if (MonthCalSetDate(infoPtr
,
418 infoPtr
->Changed
= FALSE
;
422 /* Kill the day timer */
423 if (infoPtr
->DayTimerSet
)
425 KillTimer(infoPtr
->hSelf
,
427 infoPtr
->DayTimerSet
= FALSE
;
430 /* Setup the new day timer */
431 MonthCalSetupDayTimer(infoPtr
);
440 MonthCalRepaintDay(IN PMONTHCALWND infoPtr
,
445 if (MonthCalGetDayRect(infoPtr
,
449 InvalidateRect(infoPtr
->hSelf
,
456 MonthCalPaint(IN PMONTHCALWND infoPtr
,
462 COLORREF crOldText
, crOldCtrlText
= CLR_INVALID
;
466 #if MONTHCAL_CTRLBG != MONTHCAL_DISABLED_CTRLBG
467 if (!infoPtr
->Enabled
)
471 GetSysColorBrush(MONTHCAL_DISABLED_CTRLBG
));
475 iOldBkMode
= SetBkMode(hDC
,
477 hOldFont
= (HFONT
)SelectObject(hDC
,
480 for (y
= prcUpdate
->top
/ infoPtr
->CellSize
.cy
;
481 y
<= prcUpdate
->bottom
/ infoPtr
->CellSize
.cy
&& y
< 7;
484 rcCell
.top
= y
* infoPtr
->CellSize
.cy
;
485 rcCell
.bottom
= rcCell
.top
+ infoPtr
->CellSize
.cy
;
491 /* Paint the header */
492 rcHeader
.left
= prcUpdate
->left
;
493 rcHeader
.top
= rcCell
.top
;
494 rcHeader
.right
= prcUpdate
->right
;
495 rcHeader
.bottom
= rcCell
.bottom
;
501 crOldText
= SetTextColor(hDC
,
502 GetSysColor(infoPtr
->Enabled
? MONTHCAL_HEADERFG
: MONTHCAL_DISABLED_HEADERFG
));
504 for (x
= prcUpdate
->left
/ infoPtr
->CellSize
.cx
;
505 x
<= prcUpdate
->right
/ infoPtr
->CellSize
.cx
&& x
< 7;
508 rcCell
.left
= x
* infoPtr
->CellSize
.cx
;
509 rcCell
.right
= rcCell
.left
+ infoPtr
->CellSize
.cx
;
511 /* Write the first letter of each weekday */
516 DT_SINGLELINE
| DT_NOPREFIX
| DT_CENTER
| DT_VCENTER
);
524 if (crOldCtrlText
== CLR_INVALID
)
526 crOldCtrlText
= SetTextColor(hDC
,
527 GetSysColor(infoPtr
->Enabled
? MONTHCAL_CTRLFG
: MONTHCAL_DISABLED_CTRLFG
));
530 for (x
= prcUpdate
->left
/ infoPtr
->CellSize
.cx
;
531 x
<= prcUpdate
->right
/ infoPtr
->CellSize
.cx
&& x
< 7;
534 UINT Day
= infoPtr
->Days
[y
- 1][x
];
536 rcCell
.left
= x
* infoPtr
->CellSize
.cx
;
537 rcCell
.right
= rcCell
.left
+ infoPtr
->CellSize
.cx
;
539 /* Write the day number */
540 if (Day
!= 0 && Day
< 100)
547 szDayLen
= swprintf(szDay
,
551 if (GetTextExtentPoint32W(hDC
,
556 RECT rcHighlight
= { 0, 0, 0, 0 };
558 rcText
.left
= rcCell
.left
+ (infoPtr
->CellSize
.cx
/ 2) - (TextSize
.cx
/ 2);
559 rcText
.top
= rcCell
.top
+ (infoPtr
->CellSize
.cy
/ 2) - (TextSize
.cy
/ 2);
560 rcText
.right
= rcText
.left
+ TextSize
.cx
;
561 rcText
.bottom
= rcText
.top
+ TextSize
.cy
;
563 if (Day
== infoPtr
->Day
)
567 TextSel
.cx
= (infoPtr
->CellSize
.cx
* 2) / 3;
568 TextSel
.cy
= (infoPtr
->CellSize
.cy
* 3) / 4;
570 if (TextSel
.cx
< rcText
.right
- rcText
.left
)
571 TextSel
.cx
= rcText
.right
- rcText
.left
;
572 if (TextSel
.cy
< rcText
.bottom
- rcText
.top
)
573 TextSel
.cy
= rcText
.bottom
- rcText
.top
;
575 rcHighlight
.left
= rcCell
.left
+ (infoPtr
->CellSize
.cx
/ 2) - (TextSel
.cx
/ 2);
576 rcHighlight
.right
= rcHighlight
.left
+ TextSel
.cx
;
577 rcHighlight
.top
= rcCell
.top
+ (infoPtr
->CellSize
.cy
/ 2) - (TextSel
.cy
/ 2);
578 rcHighlight
.bottom
= rcHighlight
.top
+ TextSel
.cy
;
580 InflateRect(&rcHighlight
,
581 GetSystemMetrics(SM_CXFOCUSBORDER
),
582 GetSystemMetrics(SM_CYFOCUSBORDER
));
586 infoPtr
->hbSelection
))
588 goto FailNoHighlight
;
591 /* Highlight the selected day */
592 crOldText
= SetTextColor(hDC
,
593 GetSysColor(infoPtr
->Enabled
? MONTHCAL_SELFG
: MONTHCAL_DISABLED_SELFG
));
598 /* Don't change the text color, we're not highlighting it... */
599 crOldText
= CLR_INVALID
;
608 if (Day
== infoPtr
->Day
&& crOldText
!= CLR_INVALID
)
610 if (infoPtr
->HasFocus
&& infoPtr
->Enabled
&& !(infoPtr
->UIState
& UISF_HIDEFOCUS
))
614 crOldBk
= SetBkColor(hDC
,
615 GetSysColor(infoPtr
->Enabled
? MONTHCAL_SELBG
: MONTHCAL_DISABLED_SELBG
));
633 if (crOldCtrlText
!= CLR_INVALID
)
646 MonthCalChangeFont(IN PMONTHCALWND infoPtr
,
650 HFONT hOldFont
= infoPtr
->hFont
;
651 infoPtr
->hFont
= hFont
;
655 InvalidateRect(infoPtr
->hSelf
,
664 MonthCalPtToDay(IN PMONTHCALWND infoPtr
,
670 if (infoPtr
->CellSize
.cx
!= 0 && infoPtr
->CellSize
.cy
!= 0 &&
673 x
/= infoPtr
->CellSize
.cx
;
674 y
/= infoPtr
->CellSize
.cy
;
676 if (x
< 7 && y
!= 0 && y
< 7)
678 Ret
= (WORD
)infoPtr
->Days
[y
- 1][x
];
685 static LRESULT CALLBACK
686 MonthCalWndProc(IN HWND hwnd
,
691 PMONTHCALWND infoPtr
;
694 infoPtr
= (PMONTHCALWND
)GetWindowLongPtrW(hwnd
,
697 if (infoPtr
== NULL
&& uMsg
!= WM_CREATE
)
699 goto HandleDefaultMessage
;
704 #if MONTHCAL_CTRLBG != MONTHCAL_DISABLED_CTRLBG
706 Ret
= !infoPtr
->Enabled
;
713 if (infoPtr
->CellSize
.cx
!= 0 && infoPtr
->CellSize
.cy
!= 0)
720 if (!GetUpdateRect(hwnd
,
730 hDC
= BeginPaint(hwnd
,
738 MonthCalPaint(infoPtr
,
751 case WM_LBUTTONDBLCLK
:
756 SelDay
= MonthCalPtToDay(infoPtr
,
757 GET_X_LPARAM(lParam
),
758 GET_Y_LPARAM(lParam
));
759 if (SelDay
!= 0 && SelDay
!= infoPtr
->Day
)
761 MonthCalSetDate(infoPtr
,
773 if (!infoPtr
->HasFocus
)
788 if (infoPtr
->Day
> 7)
790 NewDay
= infoPtr
->Day
- 7;
797 if (infoPtr
->Day
+ 7 <= MonthCalMonthLength(infoPtr
->Month
,
800 NewDay
= infoPtr
->Day
+ 7;
807 if (infoPtr
->Day
> 1)
809 NewDay
= infoPtr
->Day
- 1;
816 if (infoPtr
->Day
< MonthCalMonthLength(infoPtr
->Month
,
819 NewDay
= infoPtr
->Day
+ 1;
825 /* Update the selection */
828 MonthCalSetDate(infoPtr
,
834 goto HandleDefaultMessage
;
841 virtKey
= (lParam
!= 0 ? (INT
)((LPMSG
)lParam
)->wParam
: 0);
846 /* Change the UI status */
847 SendMessageW(GetAncestor(hwnd
,
850 MAKEWPARAM(UIS_INITIALIZE
,
857 Ret
|= DLGC_WANTARROWS
;
863 infoPtr
->HasFocus
= TRUE
;
864 MonthCalRepaintDay(infoPtr
,
871 infoPtr
->HasFocus
= FALSE
;
872 MonthCalRepaintDay(infoPtr
,
877 case WM_UPDATEUISTATE
:
881 Ret
= DefWindowProcW(hwnd
,
886 OldUIState
= infoPtr
->UIState
;
887 switch (LOWORD(wParam
))
890 infoPtr
->UIState
|= HIWORD(wParam
);
894 infoPtr
->UIState
&= ~HIWORD(wParam
);
898 if (infoPtr
->UIState
!= OldUIState
)
900 MonthCalRepaintDay(infoPtr
,
908 WORD Day
, Month
, Year
, DaysCount
;
910 Day
= LOWORD(wParam
);
911 Month
= HIWORD(wParam
);
912 Year
= LOWORD(lParam
);
916 if (Month
== (WORD
)-1)
917 Month
= infoPtr
->Month
;
918 if (Year
== (WORD
)-1)
919 Year
= infoPtr
->Year
;
921 DaysCount
= MonthCalMonthLength(Month
,
926 if (MonthCalValidDate(Day
,
930 if (Day
!= infoPtr
->Day
||
931 Month
!= infoPtr
->Month
||
932 Year
!= infoPtr
->Year
)
934 Ret
= MonthCalSetDate(infoPtr
,
945 LPSYSTEMTIME lpSystemTime
= (LPSYSTEMTIME
)wParam
;
947 lpSystemTime
->wYear
= infoPtr
->Year
;
948 lpSystemTime
->wMonth
= infoPtr
->Month
;
949 lpSystemTime
->wDay
= infoPtr
->Day
;
957 MonthCalSetLocalTime(infoPtr
,
965 Ret
= infoPtr
->Changed
;
978 infoPtr
->DayTimerSet
= FALSE
;
980 if (!infoPtr
->Changed
)
982 /* Update the system time and setup the new day timer */
983 MonthCalSetLocalTime(infoPtr
,
986 /* Update the control */
987 MonthCalUpdate(infoPtr
);
997 Ret
= (LRESULT
)MonthCalChangeFont(infoPtr
,
999 (BOOL
)LOWORD(lParam
));
1005 infoPtr
->ClientSize
.cx
= LOWORD(lParam
);
1006 infoPtr
->ClientSize
.cy
= HIWORD(lParam
);
1007 infoPtr
->CellSize
.cx
= infoPtr
->ClientSize
.cx
/ 7;
1008 infoPtr
->CellSize
.cy
= infoPtr
->ClientSize
.cy
/ 7;
1010 /* Repaint the control */
1011 InvalidateRect(hwnd
,
1019 Ret
= (LRESULT
)infoPtr
->hFont
;
1025 infoPtr
->Enabled
= ((BOOL
)wParam
!= FALSE
);
1026 MonthCalReload(infoPtr
);
1030 case WM_STYLECHANGED
:
1032 if (wParam
== GWL_STYLE
)
1034 unsigned int OldEnabled
= infoPtr
->Enabled
;
1035 infoPtr
->Enabled
= !(((LPSTYLESTRUCT
)lParam
)->styleNew
& WS_DISABLED
);
1037 if (OldEnabled
!= infoPtr
->Enabled
)
1039 MonthCalReload(infoPtr
);
1047 infoPtr
= (MONTHCALWND
*) HeapAlloc(GetProcessHeap(),
1049 sizeof(MONTHCALWND
));
1050 if (infoPtr
== NULL
)
1056 SetWindowLongPtrW(hwnd
,
1061 sizeof(MONTHCALWND
));
1063 infoPtr
->hSelf
= hwnd
;
1064 infoPtr
->hNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
1065 infoPtr
->Enabled
= !(((LPCREATESTRUCTW
)lParam
)->style
& WS_DISABLED
);
1067 MonthCalSetLocalTime(infoPtr
,
1070 MonthCalReload(infoPtr
);
1076 HeapFree(GetProcessHeap(),
1079 SetWindowLongPtrW(hwnd
,
1087 HandleDefaultMessage
:
1088 Ret
= DefWindowProcW(hwnd
,
1100 RegisterMonthCalControl(IN HINSTANCE hInstance
)
1104 wc
.style
= CS_DBLCLKS
;
1105 wc
.lpfnWndProc
= MonthCalWndProc
;
1106 wc
.cbWndExtra
= sizeof(PMONTHCALWND
);
1107 wc
.hInstance
= hInstance
;
1108 wc
.hCursor
= LoadCursorW(NULL
,
1110 wc
.hbrBackground
= (HBRUSH
)(MONTHCAL_CTRLBG
+ 1);
1111 wc
.lpszClassName
= szMonthCalWndClass
;
1113 return RegisterClassW(&wc
) != 0;
1117 UnregisterMonthCalControl(IN HINSTANCE hInstance
)
1119 UnregisterClassW(szMonthCalWndClass
,