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>
14 static const WCHAR szMonthCalWndClass
[] = L
"MonthCalWnd";
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
31 typedef struct _MONTHCALWND
53 } MONTHCALWND
, *PMONTHCALWND
;
56 MonthCalNotifyControlParent(IN PMONTHCALWND infoPtr
,
62 if (infoPtr
->hNotify
!= NULL
)
64 LPNMHDR pnmh
= (LPNMHDR
)data
;
66 pnmh
->hwndFrom
= infoPtr
->hSelf
;
67 pnmh
->idFrom
= GetWindowLongPtrW(infoPtr
->hSelf
,
71 Ret
= SendMessageW(infoPtr
->hNotify
,
81 * For the year range 1..9999
82 * return 1 if is leap year otherwise 0
84 static WORD
LeapYear(IN WORD Year
)
88 (Year
<= 1752) ? !(Year
% 4) :
90 !(Year
% 4) && ((Year
% 100) || !(Year
% 400));
94 MonthCalMonthLength(IN WORD Month
,
97 const BYTE MonthDays
[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
100 return MonthDays
[Month
- 1] + LeapYear(Year
);
104 if ((Year
== 1752) && (Month
== 9))
105 return 19; // Special case: September 1752 has no 3rd-13th
108 return MonthDays
[Month
- 1];
113 MonthCalWeekInMonth(IN WORD Day
,
116 return (Day
- DayOfWeek
+ 5) / 7;
120 MonthCalDayOfWeek(IN PMONTHCALWND infoPtr
,
125 const BYTE DayOfWeek
[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
129 Ret
= (Year
+ (Year
/ 4) - (Year
/ 100) + (Year
/ 400) + DayOfWeek
[Month
- 1] + Day
+ 6) % 7;
131 return (7 + Ret
- infoPtr
->FirstDayOfWeek
) % 7;
135 MonthCalFirstDayOfWeek(VOID
)
137 WCHAR szBuf
[2] = {0};
140 if (GetLocaleInfoW(LOCALE_USER_DEFAULT
,
141 LOCALE_IFIRSTDAYOFWEEK
,
143 sizeof(szBuf
) / sizeof(szBuf
[0])) != 0)
145 Ret
= (WORD
)(szBuf
[0] - TEXT('0'));
152 MonthCalValidDate(IN WORD Day
,
156 if (Month
< 1 || Month
> 12 ||
157 Day
== 0 || Day
> MonthCalMonthLength(Month
,
159 Year
< 1899 || Year
> 9999)
168 MonthCalUpdate(IN PMONTHCALWND infoPtr
)
171 WORD DayOfWeek
, MonthLength
, d
= 0;
173 BOOL RepaintHeader
= FALSE
;
175 NewCellSize
.cx
= infoPtr
->ClientSize
.cx
/ 7;
176 NewCellSize
.cy
= infoPtr
->ClientSize
.cy
/ 7;
178 if (infoPtr
->CellSize
.cx
!= NewCellSize
.cx
||
179 infoPtr
->CellSize
.cy
!= NewCellSize
.cy
)
181 infoPtr
->CellSize
= NewCellSize
;
182 RepaintHeader
= TRUE
;
185 /* Update the days layout of the current month */
186 ZeroMemory(infoPtr
->Days
,
187 sizeof(infoPtr
->Days
));
189 DayOfWeek
= MonthCalDayOfWeek(infoPtr
,
194 MonthLength
= MonthCalMonthLength(infoPtr
->Month
,
197 pDay
= &infoPtr
->Days
[0][DayOfWeek
];
198 pDayEnd
= pDay
+ MonthLength
;
199 while (pDay
!= pDayEnd
)
201 *(pDay
++) = (BYTE
)++d
;
204 /* Repaint the control */
207 InvalidateRect(infoPtr
->hSelf
,
216 rcClient
.top
= infoPtr
->CellSize
.cy
;
217 rcClient
.right
= infoPtr
->ClientSize
.cx
;
218 rcClient
.bottom
= infoPtr
->ClientSize
.cy
;
220 InvalidateRect(infoPtr
->hSelf
,
227 MonthCalSetupDayTimer(IN PMONTHCALWND infoPtr
)
229 SYSTEMTIME LocalTime
= {0};
232 /* Update the current date */
233 GetLocalTime(&LocalTime
);
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;
241 if (uElapse
< USER_TIMER_MINIMUM
|| uElapse
> USER_TIMER_MAXIMUM
)
244 uElapse
+= 100; /* Add a delay of 0.1 seconds */
246 /* Setup the new timer */
247 if (SetTimer(infoPtr
->hSelf
,
252 infoPtr
->DayTimerSet
= TRUE
;
257 MonthCalReload(IN PMONTHCALWND infoPtr
)
262 infoPtr
->UIState
= (DWORD
)SendMessageW(GetAncestor(infoPtr
->hSelf
,
268 /* Cache the configuration */
269 infoPtr
->FirstDayOfWeek
= MonthCalFirstDayOfWeek();
271 infoPtr
->hbHeader
= GetSysColorBrush(infoPtr
->Enabled
? MONTHCAL_HEADERBG
: MONTHCAL_DISABLED_HEADERBG
);
272 infoPtr
->hbSelection
= GetSysColorBrush(infoPtr
->Enabled
? MONTHCAL_SELBG
: MONTHCAL_DISABLED_SELBG
);
274 for (i
= 0; i
< 7; i
++)
276 if (GetLocaleInfoW(LOCALE_USER_DEFAULT
,
277 LOCALE_SABBREVDAYNAME1
+
278 ((i
+ infoPtr
->FirstDayOfWeek
) % 7),
280 sizeof(szBuf
) / sizeof(szBuf
[0])) != 0)
282 infoPtr
->Week
[i
] = szBuf
[0];
286 /* Update the control */
287 MonthCalUpdate(infoPtr
);
291 MonthCalGetDayRect(IN PMONTHCALWND infoPtr
,
295 if (Day
>= 1 && Day
<= MonthCalMonthLength(infoPtr
->Month
,
300 DayOfWeek
= MonthCalDayOfWeek(infoPtr
,
305 rcCell
->left
= DayOfWeek
* infoPtr
->CellSize
.cx
;
306 rcCell
->top
= (MonthCalWeekInMonth(Day
,
307 DayOfWeek
) + 1) * infoPtr
->CellSize
.cy
;
308 rcCell
->right
= rcCell
->left
+ infoPtr
->CellSize
.cx
;
309 rcCell
->bottom
= rcCell
->top
+ infoPtr
->CellSize
.cy
;
318 MonthCalChange(IN PMONTHCALWND infoPtr
)
320 infoPtr
->Changed
= TRUE
;
322 /* Kill the day timer */
323 if (infoPtr
->DayTimerSet
)
325 KillTimer(infoPtr
->hSelf
,
327 infoPtr
->DayTimerSet
= FALSE
;
333 MonthCalSetDate(IN PMONTHCALWND infoPtr
,
341 sc
.OldDay
= infoPtr
->Day
;
342 sc
.OldMonth
= infoPtr
->Month
;
343 sc
.OldYear
= infoPtr
->Year
;
348 /* Notify the parent */
349 if (!MonthCalNotifyControlParent(infoPtr
,
353 /* Check if we actually need to update */
354 if (infoPtr
->Month
!= sc
.NewMonth
||
355 infoPtr
->Year
!= sc
.NewYear
)
357 infoPtr
->Day
= sc
.NewDay
;
358 infoPtr
->Month
= sc
.NewMonth
;
359 infoPtr
->Year
= sc
.NewYear
;
361 MonthCalChange(infoPtr
);
363 /* Repaint the entire control */
364 MonthCalUpdate(infoPtr
);
368 else if (infoPtr
->Day
!= sc
.NewDay
)
372 infoPtr
->Day
= sc
.NewDay
;
374 MonthCalChange(infoPtr
);
376 if (MonthCalGetDayRect(infoPtr
,
380 /* Repaint the day cells that need to be updated */
381 InvalidateRect(infoPtr
->hSelf
,
384 if (MonthCalGetDayRect(infoPtr
,
388 InvalidateRect(infoPtr
->hSelf
,
402 MonthCalSetLocalTime(IN PMONTHCALWND infoPtr
,
403 OUT SYSTEMTIME
*Time
)
406 SYSTEMTIME LocalTime
= {0};
408 GetLocalTime(&LocalTime
);
410 au
.SystemTime
= LocalTime
;
411 if (!MonthCalNotifyControlParent(infoPtr
,
415 if (MonthCalSetDate(infoPtr
,
420 infoPtr
->Changed
= FALSE
;
424 /* Kill the day timer */
425 if (infoPtr
->DayTimerSet
)
427 KillTimer(infoPtr
->hSelf
,
429 infoPtr
->DayTimerSet
= FALSE
;
432 /* Setup the new day timer */
433 MonthCalSetupDayTimer(infoPtr
);
442 MonthCalRepaintDay(IN PMONTHCALWND infoPtr
,
447 if (MonthCalGetDayRect(infoPtr
,
451 InvalidateRect(infoPtr
->hSelf
,
458 MonthCalPaint(IN PMONTHCALWND infoPtr
,
464 COLORREF crOldText
, crOldCtrlText
= CLR_INVALID
;
468 #if MONTHCAL_CTRLBG != MONTHCAL_DISABLED_CTRLBG
469 if (!infoPtr
->Enabled
)
473 GetSysColorBrush(MONTHCAL_DISABLED_CTRLBG
));
477 iOldBkMode
= SetBkMode(hDC
,
479 hOldFont
= (HFONT
)SelectObject(hDC
,
482 for (y
= prcUpdate
->top
/ infoPtr
->CellSize
.cy
;
483 y
<= prcUpdate
->bottom
/ infoPtr
->CellSize
.cy
&& y
< 7;
486 rcCell
.top
= y
* infoPtr
->CellSize
.cy
;
487 rcCell
.bottom
= rcCell
.top
+ infoPtr
->CellSize
.cy
;
493 /* Paint the header */
494 rcHeader
.left
= prcUpdate
->left
;
495 rcHeader
.top
= rcCell
.top
;
496 rcHeader
.right
= prcUpdate
->right
;
497 rcHeader
.bottom
= rcCell
.bottom
;
503 crOldText
= SetTextColor(hDC
,
504 GetSysColor(infoPtr
->Enabled
? MONTHCAL_HEADERFG
: MONTHCAL_DISABLED_HEADERFG
));
506 for (x
= prcUpdate
->left
/ infoPtr
->CellSize
.cx
;
507 x
<= prcUpdate
->right
/ infoPtr
->CellSize
.cx
&& x
< 7;
510 rcCell
.left
= x
* infoPtr
->CellSize
.cx
;
511 rcCell
.right
= rcCell
.left
+ infoPtr
->CellSize
.cx
;
513 /* Write the first letter of each weekday */
518 DT_SINGLELINE
| DT_NOPREFIX
| DT_CENTER
| DT_VCENTER
);
526 if (crOldCtrlText
== CLR_INVALID
)
528 crOldCtrlText
= SetTextColor(hDC
,
529 GetSysColor(infoPtr
->Enabled
? MONTHCAL_CTRLFG
: MONTHCAL_DISABLED_CTRLFG
));
532 for (x
= prcUpdate
->left
/ infoPtr
->CellSize
.cx
;
533 x
<= prcUpdate
->right
/ infoPtr
->CellSize
.cx
&& x
< 7;
536 UINT Day
= infoPtr
->Days
[y
- 1][x
];
538 rcCell
.left
= x
* infoPtr
->CellSize
.cx
;
539 rcCell
.right
= rcCell
.left
+ infoPtr
->CellSize
.cx
;
541 /* Write the day number */
542 if (Day
!= 0 && Day
< 100)
549 szDayLen
= swprintf(szDay
,
553 if (GetTextExtentPoint32W(hDC
,
558 RECT rcHighlight
= { 0, 0, 0, 0 };
560 rcText
.left
= rcCell
.left
+ (infoPtr
->CellSize
.cx
/ 2) - (TextSize
.cx
/ 2);
561 rcText
.top
= rcCell
.top
+ (infoPtr
->CellSize
.cy
/ 2) - (TextSize
.cy
/ 2);
562 rcText
.right
= rcText
.left
+ TextSize
.cx
;
563 rcText
.bottom
= rcText
.top
+ TextSize
.cy
;
565 if (Day
== infoPtr
->Day
)
569 TextSel
.cx
= (infoPtr
->CellSize
.cx
* 2) / 3;
570 TextSel
.cy
= (infoPtr
->CellSize
.cy
* 3) / 4;
572 if (TextSel
.cx
< rcText
.right
- rcText
.left
)
573 TextSel
.cx
= rcText
.right
- rcText
.left
;
574 if (TextSel
.cy
< rcText
.bottom
- rcText
.top
)
575 TextSel
.cy
= rcText
.bottom
- rcText
.top
;
577 rcHighlight
.left
= rcCell
.left
+ (infoPtr
->CellSize
.cx
/ 2) - (TextSel
.cx
/ 2);
578 rcHighlight
.right
= rcHighlight
.left
+ TextSel
.cx
;
579 rcHighlight
.top
= rcCell
.top
+ (infoPtr
->CellSize
.cy
/ 2) - (TextSel
.cy
/ 2);
580 rcHighlight
.bottom
= rcHighlight
.top
+ TextSel
.cy
;
582 InflateRect(&rcHighlight
,
583 GetSystemMetrics(SM_CXFOCUSBORDER
),
584 GetSystemMetrics(SM_CYFOCUSBORDER
));
588 infoPtr
->hbSelection
))
590 goto FailNoHighlight
;
593 /* Highlight the selected day */
594 crOldText
= SetTextColor(hDC
,
595 GetSysColor(infoPtr
->Enabled
? MONTHCAL_SELFG
: MONTHCAL_DISABLED_SELFG
));
600 /* Don't change the text color, we're not highlighting it... */
601 crOldText
= CLR_INVALID
;
610 if (Day
== infoPtr
->Day
&& crOldText
!= CLR_INVALID
)
612 if (infoPtr
->HasFocus
&& infoPtr
->Enabled
&& !(infoPtr
->UIState
& UISF_HIDEFOCUS
))
616 crOldBk
= SetBkColor(hDC
,
617 GetSysColor(infoPtr
->Enabled
? MONTHCAL_SELBG
: MONTHCAL_DISABLED_SELBG
));
635 if (crOldCtrlText
!= CLR_INVALID
)
648 MonthCalChangeFont(IN PMONTHCALWND infoPtr
,
652 HFONT hOldFont
= infoPtr
->hFont
;
653 infoPtr
->hFont
= hFont
;
657 InvalidateRect(infoPtr
->hSelf
,
666 MonthCalPtToDay(IN PMONTHCALWND infoPtr
,
672 if (infoPtr
->CellSize
.cx
!= 0 && infoPtr
->CellSize
.cy
!= 0 &&
675 x
/= infoPtr
->CellSize
.cx
;
676 y
/= infoPtr
->CellSize
.cy
;
678 if (x
< 7 && y
!= 0 && y
< 7)
680 Ret
= (WORD
)infoPtr
->Days
[y
- 1][x
];
687 static LRESULT CALLBACK
688 MonthCalWndProc(IN HWND hwnd
,
693 PMONTHCALWND infoPtr
;
696 infoPtr
= (PMONTHCALWND
)GetWindowLongPtrW(hwnd
,
699 if (infoPtr
== NULL
&& uMsg
!= WM_CREATE
)
701 goto HandleDefaultMessage
;
706 #if MONTHCAL_CTRLBG != MONTHCAL_DISABLED_CTRLBG
708 Ret
= !infoPtr
->Enabled
;
715 if (infoPtr
->CellSize
.cx
!= 0 && infoPtr
->CellSize
.cy
!= 0)
722 if (!GetUpdateRect(hwnd
,
732 hDC
= BeginPaint(hwnd
,
740 MonthCalPaint(infoPtr
,
753 case WM_LBUTTONDBLCLK
:
758 SelDay
= MonthCalPtToDay(infoPtr
,
759 GET_X_LPARAM(lParam
),
760 GET_Y_LPARAM(lParam
));
761 if (SelDay
!= 0 && SelDay
!= infoPtr
->Day
)
763 MonthCalSetDate(infoPtr
,
775 if (!infoPtr
->HasFocus
)
790 if (infoPtr
->Day
> 7)
792 NewDay
= infoPtr
->Day
- 7;
799 if (infoPtr
->Day
+ 7 <= MonthCalMonthLength(infoPtr
->Month
,
802 NewDay
= infoPtr
->Day
+ 7;
809 if (infoPtr
->Day
> 1)
811 NewDay
= infoPtr
->Day
- 1;
818 if (infoPtr
->Day
< MonthCalMonthLength(infoPtr
->Month
,
821 NewDay
= infoPtr
->Day
+ 1;
827 /* Update the selection */
830 MonthCalSetDate(infoPtr
,
836 goto HandleDefaultMessage
;
843 virtKey
= (lParam
!= 0 ? (INT
)((LPMSG
)lParam
)->wParam
: 0);
848 /* Change the UI status */
849 SendMessageW(GetAncestor(hwnd
,
852 MAKEWPARAM(UIS_INITIALIZE
,
859 Ret
|= DLGC_WANTARROWS
;
865 infoPtr
->HasFocus
= TRUE
;
866 MonthCalRepaintDay(infoPtr
,
873 infoPtr
->HasFocus
= FALSE
;
874 MonthCalRepaintDay(infoPtr
,
879 case WM_UPDATEUISTATE
:
883 Ret
= DefWindowProcW(hwnd
,
888 OldUIState
= infoPtr
->UIState
;
889 switch (LOWORD(wParam
))
892 infoPtr
->UIState
|= HIWORD(wParam
);
896 infoPtr
->UIState
&= ~HIWORD(wParam
);
900 if (infoPtr
->UIState
!= OldUIState
)
902 MonthCalRepaintDay(infoPtr
,
910 WORD Day
, Month
, Year
, DaysCount
;
912 Day
= LOWORD(wParam
);
913 Month
= HIWORD(wParam
);
914 Year
= LOWORD(lParam
);
918 if (Month
== (WORD
)-1)
919 Month
= infoPtr
->Month
;
920 if (Year
== (WORD
)-1)
921 Year
= infoPtr
->Year
;
923 DaysCount
= MonthCalMonthLength(Month
,
928 if (MonthCalValidDate(Day
,
932 if (Day
!= infoPtr
->Day
||
933 Month
!= infoPtr
->Month
||
934 Year
!= infoPtr
->Year
)
936 Ret
= MonthCalSetDate(infoPtr
,
947 LPSYSTEMTIME lpSystemTime
= (LPSYSTEMTIME
)wParam
;
949 lpSystemTime
->wYear
= infoPtr
->Year
;
950 lpSystemTime
->wMonth
= infoPtr
->Month
;
951 lpSystemTime
->wDay
= infoPtr
->Day
;
959 MonthCalSetLocalTime(infoPtr
,
967 Ret
= infoPtr
->Changed
;
980 infoPtr
->DayTimerSet
= FALSE
;
982 if (!infoPtr
->Changed
)
984 /* Update the system time and setup the new day timer */
985 MonthCalSetLocalTime(infoPtr
,
988 /* Update the control */
989 MonthCalUpdate(infoPtr
);
999 Ret
= (LRESULT
)MonthCalChangeFont(infoPtr
,
1001 (BOOL
)LOWORD(lParam
));
1007 infoPtr
->ClientSize
.cx
= LOWORD(lParam
);
1008 infoPtr
->ClientSize
.cy
= HIWORD(lParam
);
1009 infoPtr
->CellSize
.cx
= infoPtr
->ClientSize
.cx
/ 7;
1010 infoPtr
->CellSize
.cy
= infoPtr
->ClientSize
.cy
/ 7;
1012 /* Repaint the control */
1013 InvalidateRect(hwnd
,
1021 Ret
= (LRESULT
)infoPtr
->hFont
;
1027 infoPtr
->Enabled
= ((BOOL
)wParam
!= FALSE
);
1028 MonthCalReload(infoPtr
);
1032 case WM_STYLECHANGED
:
1034 if (wParam
== GWL_STYLE
)
1036 unsigned int OldEnabled
= infoPtr
->Enabled
;
1037 infoPtr
->Enabled
= !(((LPSTYLESTRUCT
)lParam
)->styleNew
& WS_DISABLED
);
1039 if (OldEnabled
!= infoPtr
->Enabled
)
1041 MonthCalReload(infoPtr
);
1049 infoPtr
= (MONTHCALWND
*) HeapAlloc(GetProcessHeap(),
1051 sizeof(MONTHCALWND
));
1052 if (infoPtr
== NULL
)
1058 SetWindowLongPtrW(hwnd
,
1063 sizeof(MONTHCALWND
));
1065 infoPtr
->hSelf
= hwnd
;
1066 infoPtr
->hNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
1067 infoPtr
->Enabled
= !(((LPCREATESTRUCTW
)lParam
)->style
& WS_DISABLED
);
1069 MonthCalSetLocalTime(infoPtr
,
1072 MonthCalReload(infoPtr
);
1078 HeapFree(GetProcessHeap(),
1081 SetWindowLongPtrW(hwnd
,
1089 HandleDefaultMessage
:
1090 Ret
= DefWindowProcW(hwnd
,
1102 RegisterMonthCalControl(IN HINSTANCE hInstance
)
1106 wc
.style
= CS_DBLCLKS
;
1107 wc
.lpfnWndProc
= MonthCalWndProc
;
1108 wc
.cbWndExtra
= sizeof(PMONTHCALWND
);
1109 wc
.hInstance
= hInstance
;
1110 wc
.hCursor
= LoadCursorW(NULL
,
1112 wc
.hbrBackground
= (HBRUSH
)(MONTHCAL_CTRLBG
+ 1);
1113 wc
.lpszClassName
= szMonthCalWndClass
;
1115 return RegisterClassW(&wc
) != 0;
1119 UnregisterMonthCalControl(IN HINSTANCE hInstance
)
1121 UnregisterClassW(szMonthCalWndClass
,