2 * ReactOS Calendar Control
3 * Copyright (C) 2006 Thomas Weidenmueller <w3seek@reactos.com>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 static const TCHAR szMonthCalWndClass
[] = TEXT("MonthCalWnd");
23 #define MONTHCAL_HEADERBG COLOR_INACTIVECAPTION
24 #define MONTHCAL_HEADERFG COLOR_INACTIVECAPTIONTEXT
25 #define MONTHCAL_CTRLBG COLOR_WINDOW
26 #define MONTHCAL_CTRLFG COLOR_WINDOWTEXT
27 #define MONTHCAL_SELBG COLOR_ACTIVECAPTION
28 #define MONTHCAL_SELFG COLOR_CAPTIONTEXT
29 #define MONTHCAL_DISABLED_HEADERBG COLOR_INACTIVECAPTION
30 #define MONTHCAL_DISABLED_HEADERFG COLOR_INACTIVECAPTIONTEXT
31 #define MONTHCAL_DISABLED_CTRLBG COLOR_WINDOW
32 #define MONTHCAL_DISABLED_CTRLFG COLOR_WINDOWTEXT
33 #define MONTHCAL_DISABLED_SELBG COLOR_INACTIVECAPTION
34 #define MONTHCAL_DISABLED_SELFG COLOR_INACTIVECAPTIONTEXT
38 typedef struct _MONTHCALWND
60 } MONTHCALWND
, *PMONTHCALWND
;
63 MonthCalNotifyControlParent(IN PMONTHCALWND infoPtr
,
69 if (infoPtr
->hNotify
!= NULL
)
71 LPNMHDR pnmh
= (LPNMHDR
)data
;
73 pnmh
->hwndFrom
= infoPtr
->hSelf
;
74 pnmh
->idFrom
= GetWindowLongPtr(infoPtr
->hSelf
,
78 Ret
= SendMessage(infoPtr
->hNotify
,
88 MonthCalMonthLength(IN WORD Month
,
91 const BYTE MonthDays
[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
94 return MonthDays
[Month
- 1] + ((Year
% 400 == 0) ? 1 : ((Year
% 100 != 0) && (Year
% 4 == 0)) ? 1 : 0);
96 return MonthDays
[Month
- 1];
100 MonthCalWeekInMonth(IN WORD Day
,
103 return (Day
- DayOfWeek
+ 5) / 7;
107 MonthCalDayOfWeek(IN PMONTHCALWND infoPtr
,
112 const BYTE DayOfWeek
[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
116 Ret
= (Year
+ (Year
/ 4) - (Year
/ 100) + (Year
/ 400) + DayOfWeek
[Month
- 1] + Day
+ 6) % 7;
118 return (7 + Ret
- infoPtr
->FirstDayOfWeek
) % 7;
122 MonthCalFirstDayOfWeek(VOID
)
124 TCHAR szBuf
[2] = {0};
127 if (GetLocaleInfo(LOCALE_USER_DEFAULT
,
128 LOCALE_IFIRSTDAYOFWEEK
,
130 sizeof(szBuf
) / sizeof(szBuf
[0])) != 0)
132 Ret
= (WORD
)(szBuf
[0] - TEXT('0'));
139 MonthCalValidDate(IN WORD Day
,
143 if (Month
< 1 || Month
> 12 ||
144 Day
== 0 || Day
> MonthCalMonthLength(Month
,
146 Year
< 1980 || Year
> 2099)
155 MonthCalUpdate(IN PMONTHCALWND infoPtr
)
158 WORD DayOfWeek
, MonthLength
, d
= 0;
160 BOOL RepaintHeader
= FALSE
;
162 NewCellSize
.cx
= infoPtr
->ClientSize
.cx
/ 7;
163 NewCellSize
.cy
= infoPtr
->ClientSize
.cy
/ 7;
165 if (infoPtr
->CellSize
.cx
!= NewCellSize
.cx
||
166 infoPtr
->CellSize
.cy
!= NewCellSize
.cy
);
168 infoPtr
->CellSize
= NewCellSize
;
169 RepaintHeader
= TRUE
;
172 /* update the days layout of the current month */
173 ZeroMemory(infoPtr
->Days
,
174 sizeof(infoPtr
->Days
));
176 DayOfWeek
= MonthCalDayOfWeek(infoPtr
,
181 MonthLength
= MonthCalMonthLength(infoPtr
->Month
,
184 pDay
= &infoPtr
->Days
[0][DayOfWeek
];
185 pDayEnd
= pDay
+ MonthLength
;
186 while (pDay
!= pDayEnd
)
191 /* repaint the control */
194 InvalidateRect(infoPtr
->hSelf
,
203 rcClient
.top
= infoPtr
->CellSize
.cy
;
204 rcClient
.right
= infoPtr
->ClientSize
.cx
;
205 rcClient
.bottom
= infoPtr
->ClientSize
.cy
- rcClient
.top
;
207 InvalidateRect(infoPtr
->hSelf
,
214 MonthCalSetupDayTimer(IN PMONTHCALWND infoPtr
)
216 SYSTEMTIME LocalTime
= {0};
219 /* update the current date */
220 GetLocalTime(&LocalTime
);
222 /* calculate the number of remaining milliseconds until midnight */
223 uElapse
= 1000 - (UINT
)LocalTime
.wMilliseconds
;
224 uElapse
+= (59 - (UINT
)LocalTime
.wSecond
) * 1000;
225 uElapse
+= (59 - (UINT
)LocalTime
.wMinute
) * 60 * 1000;
226 uElapse
+= (23 - (UINT
)LocalTime
.wHour
) * 60 * 60 * 1000;
228 if (uElapse
< USER_TIMER_MINIMUM
|| uElapse
> USER_TIMER_MAXIMUM
)
231 uElapse
+= 100; /* Add a delay of 0.1 seconds */
233 /* setup the new timer */
234 if (SetTimer(infoPtr
->hSelf
,
239 infoPtr
->DayTimerSet
= TRUE
;
244 MonthCalReload(IN PMONTHCALWND infoPtr
)
249 infoPtr
->UIState
= SendMessage(GetAncestor(infoPtr
->hSelf
,
255 /* cache the configuration */
256 infoPtr
->FirstDayOfWeek
= MonthCalFirstDayOfWeek();
258 infoPtr
->hbHeader
= GetSysColorBrush(infoPtr
->Enabled
? MONTHCAL_HEADERBG
: MONTHCAL_DISABLED_HEADERBG
);
259 infoPtr
->hbSelection
= GetSysColorBrush(infoPtr
->Enabled
? MONTHCAL_SELBG
: MONTHCAL_DISABLED_SELBG
);
265 if (GetLocaleInfo(LOCALE_USER_DEFAULT
,
266 LOCALE_SABBREVDAYNAME1
+
267 ((i
+ infoPtr
->FirstDayOfWeek
) % 7),
269 sizeof(szBuf
) / sizeof(szBuf
[0])) != 0)
271 infoPtr
->Week
[i
] = szBuf
[0];
275 /* update the control */
276 MonthCalUpdate(infoPtr
);
280 MonthCalGetDayRect(IN PMONTHCALWND infoPtr
,
284 if (Day
>= 1 && Day
<= MonthCalMonthLength(infoPtr
->Month
,
289 DayOfWeek
= MonthCalDayOfWeek(infoPtr
,
294 rcCell
->left
= DayOfWeek
* infoPtr
->CellSize
.cx
;
295 rcCell
->top
= (MonthCalWeekInMonth(Day
,
296 DayOfWeek
) + 1) * infoPtr
->CellSize
.cy
;
297 rcCell
->right
= rcCell
->left
+ infoPtr
->CellSize
.cx
;
298 rcCell
->bottom
= rcCell
->top
+ infoPtr
->CellSize
.cy
;
307 MonthCalChange(IN PMONTHCALWND infoPtr
)
309 infoPtr
->Changed
= TRUE
;
311 /* kill the day timer */
312 if (infoPtr
->DayTimerSet
)
314 KillTimer(infoPtr
->hSelf
,
316 infoPtr
->DayTimerSet
= FALSE
;
322 MonthCalSetDate(IN PMONTHCALWND infoPtr
,
330 sc
.OldDay
= infoPtr
->Day
;
331 sc
.OldMonth
= infoPtr
->Month
;
332 sc
.OldYear
= infoPtr
->Year
;
337 /* notify the parent */
338 if (!MonthCalNotifyControlParent(infoPtr
,
342 /* check if we actually need to update */
343 if (infoPtr
->Month
!= sc
.NewMonth
||
344 infoPtr
->Year
!= sc
.NewYear
)
346 infoPtr
->Day
= sc
.NewDay
;
347 infoPtr
->Month
= sc
.NewMonth
;
348 infoPtr
->Year
= sc
.NewYear
;
350 MonthCalChange(infoPtr
);
352 /* repaint the entire control */
353 MonthCalUpdate(infoPtr
);
357 else if (infoPtr
->Day
!= sc
.NewDay
)
361 infoPtr
->Day
= sc
.NewDay
;
363 MonthCalChange(infoPtr
);
365 if (MonthCalGetDayRect(infoPtr
,
369 /* repaint the day cells that need to be updated */
370 InvalidateRect(infoPtr
->hSelf
,
373 if (MonthCalGetDayRect(infoPtr
,
377 InvalidateRect(infoPtr
->hSelf
,
391 MonthCalSetLocalTime(IN PMONTHCALWND infoPtr
,
392 OUT SYSTEMTIME
*Time
)
395 SYSTEMTIME LocalTime
= {0};
397 GetLocalTime(&LocalTime
);
399 au
.SystemTime
= LocalTime
;
400 if (!MonthCalNotifyControlParent(infoPtr
,
404 if (MonthCalSetDate(infoPtr
,
409 infoPtr
->Changed
= FALSE
;
413 /* kill the day timer */
414 if (infoPtr
->DayTimerSet
)
416 KillTimer(infoPtr
->hSelf
,
418 infoPtr
->DayTimerSet
= FALSE
;
421 /* setup the new day timer */
422 MonthCalSetupDayTimer(infoPtr
);
431 MonthCalRepaintDay(IN PMONTHCALWND infoPtr
,
436 if (MonthCalGetDayRect(infoPtr
,
440 InvalidateRect(infoPtr
->hSelf
,
447 MonthCalPaint(IN PMONTHCALWND infoPtr
,
453 COLORREF crOldText
, crOldCtrlText
= CLR_INVALID
;
457 #if MONTHCAL_CTRLBG != MONTHCAL_DISABLED_CTRLBG
458 if (!infoPtr
->Enabled
)
462 GetSysColorBrush(MONTHCAL_DISABLED_CTRLBG
));
466 iOldBkMode
= SetBkMode(hDC
,
468 hOldFont
= (HFONT
)SelectObject(hDC
,
471 for (y
= prcUpdate
->top
/ infoPtr
->CellSize
.cy
;
472 y
<= prcUpdate
->bottom
/ infoPtr
->CellSize
.cy
&& y
< 7;
475 rcCell
.top
= y
* infoPtr
->CellSize
.cy
;
476 rcCell
.bottom
= rcCell
.top
+ infoPtr
->CellSize
.cy
;
482 /* paint the header */
483 rcHeader
.left
= prcUpdate
->left
;
484 rcHeader
.top
= rcCell
.top
;
485 rcHeader
.right
= prcUpdate
->right
;
486 rcHeader
.bottom
= rcCell
.bottom
;
492 crOldText
= SetTextColor(hDC
,
493 GetSysColor(infoPtr
->Enabled
? MONTHCAL_HEADERFG
: MONTHCAL_DISABLED_HEADERFG
));
495 for (x
= prcUpdate
->left
/ infoPtr
->CellSize
.cx
;
496 x
<= prcUpdate
->right
/ infoPtr
->CellSize
.cx
&& x
< 7;
499 rcCell
.left
= x
* infoPtr
->CellSize
.cx
;
500 rcCell
.right
= rcCell
.left
+ infoPtr
->CellSize
.cx
;
502 /* write the first letter of each weekday */
507 DT_SINGLELINE
| DT_NOPREFIX
| DT_CENTER
| DT_VCENTER
);
515 if (crOldCtrlText
== CLR_INVALID
)
517 crOldCtrlText
= SetTextColor(hDC
,
518 infoPtr
->Enabled
? MONTHCAL_CTRLFG
: MONTHCAL_DISABLED_CTRLFG
);
521 for (x
= prcUpdate
->left
/ infoPtr
->CellSize
.cx
;
522 x
<= prcUpdate
->right
/ infoPtr
->CellSize
.cx
&& x
< 7;
525 UINT Day
= infoPtr
->Days
[y
- 1][x
];
527 rcCell
.left
= x
* infoPtr
->CellSize
.cx
;
528 rcCell
.right
= rcCell
.left
+ infoPtr
->CellSize
.cx
;
530 /* write the day number */
531 if (Day
!= 0 && Day
< 100)
538 szDayLen
= _stprintf(szDay
,
542 if (GetTextExtentPoint32(hDC
,
547 RECT rcHighlight
= {0};
549 rcText
.left
= rcCell
.left
+ (infoPtr
->CellSize
.cx
/ 2) - (TextSize
.cx
/ 2);
550 rcText
.top
= rcCell
.top
+ (infoPtr
->CellSize
.cy
/ 2) - (TextSize
.cy
/ 2);
551 rcText
.right
= rcText
.left
+ TextSize
.cx
;
552 rcText
.bottom
= rcText
.top
+ TextSize
.cy
;
554 if (Day
== infoPtr
->Day
)
556 rcHighlight
= rcText
;
558 InflateRect(&rcHighlight
,
559 GetSystemMetrics(SM_CXFOCUSBORDER
),
560 GetSystemMetrics(SM_CYFOCUSBORDER
));
564 infoPtr
->hbSelection
))
566 goto FailNoHighlight
;
569 /* highlight the selected day */
570 crOldText
= SetTextColor(hDC
,
571 GetSysColor(infoPtr
->Enabled
? MONTHCAL_SELFG
: MONTHCAL_DISABLED_SELFG
));
576 /* don't change the text color, we're not highlighting it... */
577 crOldText
= CLR_INVALID
;
586 if (Day
== infoPtr
->Day
&& crOldText
!= CLR_INVALID
)
588 if (infoPtr
->HasFocus
&& infoPtr
->Enabled
&& !(infoPtr
->UIState
& UISF_HIDEFOCUS
))
592 crOldBk
= SetBkColor(hDC
,
593 GetSysColor(infoPtr
->Enabled
? MONTHCAL_SELBG
: MONTHCAL_DISABLED_SELBG
));
611 if (crOldCtrlText
!= CLR_INVALID
)
624 MonthCalChangeFont(IN PMONTHCALWND infoPtr
,
628 HFONT hOldFont
= infoPtr
->hFont
;
629 infoPtr
->hFont
= hFont
;
633 InvalidateRect(infoPtr
->hSelf
,
642 MonthCalPtToDay(IN PMONTHCALWND infoPtr
,
648 if (infoPtr
->CellSize
.cx
!= 0 && infoPtr
->CellSize
.cy
!= 0 &&
651 x
/= infoPtr
->CellSize
.cx
;
652 y
/= infoPtr
->CellSize
.cy
;
654 if (x
< 7 && y
!= 0 && y
< 7)
656 Ret
= (WORD
)infoPtr
->Days
[y
- 1][x
];
663 static LRESULT CALLBACK
664 MonthCalWndProc(IN HWND hwnd
,
669 PMONTHCALWND infoPtr
;
672 infoPtr
= (PMONTHCALWND
)GetWindowLongPtr(hwnd
,
675 if (infoPtr
== NULL
&& uMsg
!= WM_CREATE
)
677 goto HandleDefaultMessage
;
682 #if MONTHCAL_CTRLBG != MONTHCAL_DISABLED_CTRLBG
684 Ret
= !infoPtr
->Enabled
;
691 if (infoPtr
->CellSize
.cx
!= 0 && infoPtr
->CellSize
.cy
!= 0)
698 if (!GetUpdateRect(hwnd
,
708 hDC
= BeginPaint(hwnd
,
716 MonthCalPaint(infoPtr
,
729 case WM_LBUTTONDBLCLK
:
734 SelDay
= MonthCalPtToDay(infoPtr
,
735 GET_X_LPARAM(lParam
),
736 GET_Y_LPARAM(lParam
));
737 if (SelDay
!= 0 && SelDay
!= infoPtr
->Day
)
739 MonthCalSetDate(infoPtr
,
751 if (!infoPtr
->HasFocus
)
766 if (infoPtr
->Day
> 7)
768 NewDay
= infoPtr
->Day
- 7;
775 if (infoPtr
->Day
+ 7 <= MonthCalMonthLength(infoPtr
->Month
,
778 NewDay
= infoPtr
->Day
+ 7;
785 if (infoPtr
->Day
> 1)
787 NewDay
= infoPtr
->Day
- 1;
794 if (infoPtr
->Day
< MonthCalMonthLength(infoPtr
->Month
,
797 NewDay
= infoPtr
->Day
+ 1;
803 /* update the selection */
806 MonthCalSetDate(infoPtr
,
812 goto HandleDefaultMessage
;
819 virtKey
= (lParam
!= 0 ? (INT
)((LPMSG
)lParam
)->wParam
: 0);
824 /* change the UI status */
825 SendMessage(GetAncestor(hwnd
,
828 MAKEWPARAM(UIS_INITIALIZE
,
835 Ret
|= DLGC_WANTARROWS
;
841 infoPtr
->HasFocus
= TRUE
;
842 MonthCalRepaintDay(infoPtr
,
849 infoPtr
->HasFocus
= FALSE
;
850 MonthCalRepaintDay(infoPtr
,
855 case WM_UPDATEUISTATE
:
857 DWORD OldUIState
= infoPtr
->UIState
;
858 switch (LOWORD(wParam
))
861 infoPtr
->UIState
|= HIWORD(wParam
);
865 infoPtr
->UIState
&= ~HIWORD(wParam
);
869 if (infoPtr
->UIState
!= OldUIState
)
871 MonthCalRepaintDay(infoPtr
,
879 WORD Day
, Month
, Year
, DaysCount
;
881 Day
= LOWORD(wParam
);
882 Month
= HIWORD(wParam
);
883 Year
= LOWORD(lParam
);
887 if (Month
== (WORD
)-1)
888 Month
= infoPtr
->Month
;
889 if (Year
== (WORD
)-1)
890 Year
= infoPtr
->Year
;
892 DaysCount
= MonthCalMonthLength(Month
,
897 if (MonthCalValidDate(Day
,
901 if (Day
!= infoPtr
->Day
||
902 Month
!= infoPtr
->Month
||
903 Year
!= infoPtr
->Year
)
905 Ret
= MonthCalSetDate(infoPtr
,
916 LPSYSTEMTIME lpSystemTime
= (LPSYSTEMTIME
)wParam
;
918 lpSystemTime
->wYear
= infoPtr
->Year
;
919 lpSystemTime
->wMonth
= infoPtr
->Month
;
920 lpSystemTime
->wDay
= infoPtr
->Day
;
928 MonthCalSetLocalTime(infoPtr
,
936 Ret
= infoPtr
->Changed
;
949 infoPtr
->DayTimerSet
= FALSE
;
951 if (!infoPtr
->Changed
)
953 /* update the system time and setup the new day timer */
954 MonthCalSetLocalTime(infoPtr
,
957 /* update the control */
958 MonthCalUpdate(infoPtr
);
968 Ret
= (LRESULT
)MonthCalChangeFont(infoPtr
,
970 (BOOL
)LOWORD(lParam
));
976 infoPtr
->ClientSize
.cx
= LOWORD(lParam
);
977 infoPtr
->ClientSize
.cy
= HIWORD(lParam
);
978 infoPtr
->CellSize
.cx
= infoPtr
->ClientSize
.cx
/ 7;
979 infoPtr
->CellSize
.cy
= infoPtr
->ClientSize
.cy
/ 7;
981 /* repaint the control */
990 Ret
= (LRESULT
)infoPtr
->hFont
;
996 infoPtr
->Enabled
= ((BOOL
)wParam
!= FALSE
);
997 MonthCalReload(infoPtr
);
1001 case WM_STYLECHANGED
:
1003 if (wParam
== GWL_STYLE
)
1005 BOOL OldEnabled
= infoPtr
->Enabled
;
1006 infoPtr
->Enabled
= !(((LPSTYLESTRUCT
)lParam
)->styleNew
& WS_DISABLED
);
1008 if (OldEnabled
!= infoPtr
->Enabled
)
1010 MonthCalReload(infoPtr
);
1018 infoPtr
= HeapAlloc(GetProcessHeap(),
1020 sizeof(MONTHCALWND
));
1021 if (infoPtr
== NULL
)
1027 SetWindowLongPtr(hwnd
,
1029 (DWORD_PTR
)infoPtr
);
1032 sizeof(MONTHCALWND
));
1034 infoPtr
->hSelf
= hwnd
;
1035 infoPtr
->hNotify
= ((LPCREATESTRUCTW
)lParam
)->hwndParent
;
1036 infoPtr
->Enabled
= !(((LPCREATESTRUCTW
)lParam
)->style
& WS_DISABLED
);
1038 MonthCalSetLocalTime(infoPtr
,
1041 MonthCalReload(infoPtr
);
1047 HeapFree(GetProcessHeap(),
1050 SetWindowLongPtr(hwnd
,
1058 HandleDefaultMessage
:
1059 Ret
= DefWindowProc(hwnd
,
1071 RegisterMonthCalControl(IN HINSTANCE hInstance
)
1075 wc
.style
= CS_DBLCLKS
;
1076 wc
.lpfnWndProc
= MonthCalWndProc
;
1077 wc
.cbWndExtra
= sizeof(PMONTHCALWND
);
1078 wc
.hInstance
= hInstance
;
1079 wc
.hCursor
= LoadCursor(NULL
,
1081 wc
.hbrBackground
= (HBRUSH
)(MONTHCAL_CTRLBG
+ 1);
1082 wc
.lpszClassName
= szMonthCalWndClass
;
1084 return RegisterClass(&wc
) != 0;
1088 UnregisterMonthCalControl(IN HINSTANCE hInstance
)
1090 UnregisterClass(szMonthCalWndClass
,