[BROWSEUI]
[reactos.git] / dll / cpl / timedate / monthcal.c
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 if (uElapse < USER_TIMER_MINIMUM || uElapse > USER_TIMER_MAXIMUM)
242 uElapse = 1000;
243 else
244 uElapse += 100; /* Add a delay of 0.1 seconds */
245
246 /* Setup the new timer */
247 if (SetTimer(infoPtr->hSelf,
248 ID_DAYTIMER,
249 uElapse,
250 NULL) != 0)
251 {
252 infoPtr->DayTimerSet = TRUE;
253 }
254 }
255
256 static VOID
257 MonthCalReload(IN PMONTHCALWND infoPtr)
258 {
259 WCHAR szBuf[64];
260 UINT i;
261
262 infoPtr->UIState = (DWORD)SendMessageW(GetAncestor(infoPtr->hSelf,
263 GA_PARENT),
264 WM_QUERYUISTATE,
265 0,
266 0);
267
268 /* Cache the configuration */
269 infoPtr->FirstDayOfWeek = MonthCalFirstDayOfWeek();
270
271 infoPtr->hbHeader = GetSysColorBrush(infoPtr->Enabled ? MONTHCAL_HEADERBG : MONTHCAL_DISABLED_HEADERBG);
272 infoPtr->hbSelection = GetSysColorBrush(infoPtr->Enabled ? MONTHCAL_SELBG : MONTHCAL_DISABLED_SELBG);
273
274 for (i = 0; i < 7; i++)
275 {
276 if (GetLocaleInfoW(LOCALE_USER_DEFAULT,
277 LOCALE_SABBREVDAYNAME1 +
278 ((i + infoPtr->FirstDayOfWeek) % 7),
279 szBuf,
280 sizeof(szBuf) / sizeof(szBuf[0])) != 0)
281 {
282 infoPtr->Week[i] = szBuf[0];
283 }
284 }
285
286 /* Update the control */
287 MonthCalUpdate(infoPtr);
288 }
289
290 static BOOL
291 MonthCalGetDayRect(IN PMONTHCALWND infoPtr,
292 IN WORD Day,
293 OUT RECT *rcCell)
294 {
295 if (Day >= 1 && Day <= MonthCalMonthLength(infoPtr->Month,
296 infoPtr->Year))
297 {
298 WORD DayOfWeek;
299
300 DayOfWeek = MonthCalDayOfWeek(infoPtr,
301 Day,
302 infoPtr->Month,
303 infoPtr->Year);
304
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;
310
311 return TRUE;
312 }
313
314 return FALSE;
315 }
316
317 static VOID
318 MonthCalChange(IN PMONTHCALWND infoPtr)
319 {
320 infoPtr->Changed = TRUE;
321
322 /* Kill the day timer */
323 if (infoPtr->DayTimerSet)
324 {
325 KillTimer(infoPtr->hSelf,
326 ID_DAYTIMER);
327 infoPtr->DayTimerSet = FALSE;
328 }
329 }
330
331
332 static BOOL
333 MonthCalSetDate(IN PMONTHCALWND infoPtr,
334 IN WORD Day,
335 IN WORD Month,
336 IN WORD Year)
337 {
338 NMMCCSELCHANGE sc;
339 BOOL Ret = FALSE;
340
341 sc.OldDay = infoPtr->Day;
342 sc.OldMonth = infoPtr->Month;
343 sc.OldYear = infoPtr->Year;
344 sc.NewDay = Day;
345 sc.NewMonth = Month;
346 sc.NewYear = Year;
347
348 /* Notify the parent */
349 if (!MonthCalNotifyControlParent(infoPtr,
350 MCCN_SELCHANGE,
351 &sc))
352 {
353 /* Check if we actually need to update */
354 if (infoPtr->Month != sc.NewMonth ||
355 infoPtr->Year != sc.NewYear)
356 {
357 infoPtr->Day = sc.NewDay;
358 infoPtr->Month = sc.NewMonth;
359 infoPtr->Year = sc.NewYear;
360
361 MonthCalChange(infoPtr);
362
363 /* Repaint the entire control */
364 MonthCalUpdate(infoPtr);
365
366 Ret = TRUE;
367 }
368 else if (infoPtr->Day != sc.NewDay)
369 {
370 RECT rcUpdate;
371
372 infoPtr->Day = sc.NewDay;
373
374 MonthCalChange(infoPtr);
375
376 if (MonthCalGetDayRect(infoPtr,
377 sc.OldDay,
378 &rcUpdate))
379 {
380 /* Repaint the day cells that need to be updated */
381 InvalidateRect(infoPtr->hSelf,
382 &rcUpdate,
383 TRUE);
384 if (MonthCalGetDayRect(infoPtr,
385 sc.NewDay,
386 &rcUpdate))
387 {
388 InvalidateRect(infoPtr->hSelf,
389 &rcUpdate,
390 TRUE);
391 }
392 }
393
394 Ret = TRUE;
395 }
396 }
397
398 return Ret;
399 }
400
401 static VOID
402 MonthCalSetLocalTime(IN PMONTHCALWND infoPtr,
403 OUT SYSTEMTIME *Time)
404 {
405 NMMCCAUTOUPDATE au;
406 SYSTEMTIME LocalTime = {0};
407
408 GetLocalTime(&LocalTime);
409
410 au.SystemTime = LocalTime;
411 if (!MonthCalNotifyControlParent(infoPtr,
412 MCCN_AUTOUPDATE,
413 &au))
414 {
415 if (MonthCalSetDate(infoPtr,
416 LocalTime.wDay,
417 LocalTime.wMonth,
418 LocalTime.wYear))
419 {
420 infoPtr->Changed = FALSE;
421 }
422 }
423
424 /* Kill the day timer */
425 if (infoPtr->DayTimerSet)
426 {
427 KillTimer(infoPtr->hSelf,
428 ID_DAYTIMER);
429 infoPtr->DayTimerSet = FALSE;
430 }
431
432 /* Setup the new day timer */
433 MonthCalSetupDayTimer(infoPtr);
434
435 if (Time != NULL)
436 {
437 *Time = LocalTime;
438 }
439 }
440
441 static VOID
442 MonthCalRepaintDay(IN PMONTHCALWND infoPtr,
443 IN WORD Day)
444 {
445 RECT rcCell;
446
447 if (MonthCalGetDayRect(infoPtr,
448 Day,
449 &rcCell))
450 {
451 InvalidateRect(infoPtr->hSelf,
452 &rcCell,
453 TRUE);
454 }
455 }
456
457 static VOID
458 MonthCalPaint(IN PMONTHCALWND infoPtr,
459 IN HDC hDC,
460 IN LPRECT prcUpdate)
461 {
462 LONG x, y;
463 RECT rcCell;
464 COLORREF crOldText, crOldCtrlText = CLR_INVALID;
465 HFONT hOldFont;
466 INT iOldBkMode;
467
468 #if MONTHCAL_CTRLBG != MONTHCAL_DISABLED_CTRLBG
469 if (!infoPtr->Enabled)
470 {
471 FillRect(hDC,
472 prcUpdate,
473 GetSysColorBrush(MONTHCAL_DISABLED_CTRLBG));
474 }
475 #endif
476
477 iOldBkMode = SetBkMode(hDC,
478 TRANSPARENT);
479 hOldFont = (HFONT)SelectObject(hDC,
480 infoPtr->hFont);
481
482 for (y = prcUpdate->top / infoPtr->CellSize.cy;
483 y <= prcUpdate->bottom / infoPtr->CellSize.cy && y < 7;
484 y++)
485 {
486 rcCell.top = y * infoPtr->CellSize.cy;
487 rcCell.bottom = rcCell.top + infoPtr->CellSize.cy;
488
489 if (y == 0)
490 {
491 RECT rcHeader;
492
493 /* Paint the header */
494 rcHeader.left = prcUpdate->left;
495 rcHeader.top = rcCell.top;
496 rcHeader.right = prcUpdate->right;
497 rcHeader.bottom = rcCell.bottom;
498
499 FillRect(hDC,
500 &rcHeader,
501 infoPtr->hbHeader);
502
503 crOldText = SetTextColor(hDC,
504 GetSysColor(infoPtr->Enabled ? MONTHCAL_HEADERFG : MONTHCAL_DISABLED_HEADERFG));
505
506 for (x = prcUpdate->left / infoPtr->CellSize.cx;
507 x <= prcUpdate->right / infoPtr->CellSize.cx && x < 7;
508 x++)
509 {
510 rcCell.left = x * infoPtr->CellSize.cx;
511 rcCell.right = rcCell.left + infoPtr->CellSize.cx;
512
513 /* Write the first letter of each weekday */
514 DrawTextW(hDC,
515 &infoPtr->Week[x],
516 1,
517 &rcCell,
518 DT_SINGLELINE | DT_NOPREFIX | DT_CENTER | DT_VCENTER);
519 }
520
521 SetTextColor(hDC,
522 crOldText);
523 }
524 else
525 {
526 if (crOldCtrlText == CLR_INVALID)
527 {
528 crOldCtrlText = SetTextColor(hDC,
529 GetSysColor(infoPtr->Enabled ? MONTHCAL_CTRLFG : MONTHCAL_DISABLED_CTRLFG));
530 }
531
532 for (x = prcUpdate->left / infoPtr->CellSize.cx;
533 x <= prcUpdate->right / infoPtr->CellSize.cx && x < 7;
534 x++)
535 {
536 UINT Day = infoPtr->Days[y - 1][x];
537
538 rcCell.left = x * infoPtr->CellSize.cx;
539 rcCell.right = rcCell.left + infoPtr->CellSize.cx;
540
541 /* Write the day number */
542 if (Day != 0 && Day < 100)
543 {
544 WCHAR szDay[3];
545 INT szDayLen;
546 RECT rcText;
547 SIZE TextSize;
548
549 szDayLen = swprintf(szDay,
550 L"%lu",
551 Day);
552
553 if (GetTextExtentPoint32W(hDC,
554 szDay,
555 szDayLen,
556 &TextSize))
557 {
558 RECT rcHighlight = { 0, 0, 0, 0 };
559
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;
564
565 if (Day == infoPtr->Day)
566 {
567 SIZE TextSel;
568
569 TextSel.cx = (infoPtr->CellSize.cx * 2) / 3;
570 TextSel.cy = (infoPtr->CellSize.cy * 3) / 4;
571
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;
576
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;
581
582 InflateRect(&rcHighlight,
583 GetSystemMetrics(SM_CXFOCUSBORDER),
584 GetSystemMetrics(SM_CYFOCUSBORDER));
585
586 if (!FillRect(hDC,
587 &rcHighlight,
588 infoPtr->hbSelection))
589 {
590 goto FailNoHighlight;
591 }
592
593 /* Highlight the selected day */
594 crOldText = SetTextColor(hDC,
595 GetSysColor(infoPtr->Enabled ? MONTHCAL_SELFG : MONTHCAL_DISABLED_SELFG));
596 }
597 else
598 {
599 FailNoHighlight:
600 /* Don't change the text color, we're not highlighting it... */
601 crOldText = CLR_INVALID;
602 }
603
604 TextOutW(hDC,
605 rcText.left,
606 rcText.top,
607 szDay,
608 szDayLen);
609
610 if (Day == infoPtr->Day && crOldText != CLR_INVALID)
611 {
612 if (infoPtr->HasFocus && infoPtr->Enabled && !(infoPtr->UIState & UISF_HIDEFOCUS))
613 {
614 COLORREF crOldBk;
615
616 crOldBk = SetBkColor(hDC,
617 GetSysColor(infoPtr->Enabled ? MONTHCAL_SELBG : MONTHCAL_DISABLED_SELBG));
618
619 DrawFocusRect(hDC,
620 &rcHighlight);
621
622 SetBkColor(hDC,
623 crOldBk);
624 }
625
626 SetTextColor(hDC,
627 crOldText);
628 }
629 }
630 }
631 }
632 }
633 }
634
635 if (crOldCtrlText != CLR_INVALID)
636 {
637 SetTextColor(hDC,
638 crOldCtrlText);
639 }
640
641 SetBkMode(hDC,
642 iOldBkMode);
643 SelectObject(hDC,
644 (HGDIOBJ)hOldFont);
645 }
646
647 static HFONT
648 MonthCalChangeFont(IN PMONTHCALWND infoPtr,
649 IN HFONT hFont,
650 IN BOOL Redraw)
651 {
652 HFONT hOldFont = infoPtr->hFont;
653 infoPtr->hFont = hFont;
654
655 if (Redraw)
656 {
657 InvalidateRect(infoPtr->hSelf,
658 NULL,
659 TRUE);
660 }
661
662 return hOldFont;
663 }
664
665 static WORD
666 MonthCalPtToDay(IN PMONTHCALWND infoPtr,
667 IN INT x,
668 IN INT y)
669 {
670 WORD Ret = 0;
671
672 if (infoPtr->CellSize.cx != 0 && infoPtr->CellSize.cy != 0 &&
673 x >= 0 && y >= 0)
674 {
675 x /= infoPtr->CellSize.cx;
676 y /= infoPtr->CellSize.cy;
677
678 if (x < 7 && y != 0 && y < 7)
679 {
680 Ret = (WORD)infoPtr->Days[y - 1][x];
681 }
682 }
683
684 return Ret;
685 }
686
687 static LRESULT CALLBACK
688 MonthCalWndProc(IN HWND hwnd,
689 IN UINT uMsg,
690 IN WPARAM wParam,
691 IN LPARAM lParam)
692 {
693 PMONTHCALWND infoPtr;
694 LRESULT Ret = 0;
695
696 infoPtr = (PMONTHCALWND)GetWindowLongPtrW(hwnd,
697 0);
698
699 if (infoPtr == NULL && uMsg != WM_CREATE)
700 {
701 goto HandleDefaultMessage;
702 }
703
704 switch (uMsg)
705 {
706 #if MONTHCAL_CTRLBG != MONTHCAL_DISABLED_CTRLBG
707 case WM_ERASEBKGND:
708 Ret = !infoPtr->Enabled;
709 break;
710 #endif
711
712 case WM_PAINT:
713 case WM_PRINTCLIENT:
714 {
715 if (infoPtr->CellSize.cx != 0 && infoPtr->CellSize.cy != 0)
716 {
717 PAINTSTRUCT ps;
718 HDC hDC;
719
720 if (wParam != 0)
721 {
722 if (!GetUpdateRect(hwnd,
723 &ps.rcPaint,
724 TRUE))
725 {
726 break;
727 }
728 hDC = (HDC)wParam;
729 }
730 else
731 {
732 hDC = BeginPaint(hwnd,
733 &ps);
734 if (hDC == NULL)
735 {
736 break;
737 }
738 }
739
740 MonthCalPaint(infoPtr,
741 hDC,
742 &ps.rcPaint);
743
744 if (wParam == 0)
745 {
746 EndPaint(hwnd,
747 &ps);
748 }
749 }
750 break;
751 }
752
753 case WM_LBUTTONDBLCLK:
754 case WM_LBUTTONDOWN:
755 {
756 WORD SelDay;
757
758 SelDay = MonthCalPtToDay(infoPtr,
759 GET_X_LPARAM(lParam),
760 GET_Y_LPARAM(lParam));
761 if (SelDay != 0 && SelDay != infoPtr->Day)
762 {
763 MonthCalSetDate(infoPtr,
764 SelDay,
765 infoPtr->Month,
766 infoPtr->Year);
767 }
768
769 /* Fall through */
770 }
771
772 case WM_MBUTTONDOWN:
773 case WM_RBUTTONDOWN:
774 {
775 if (!infoPtr->HasFocus)
776 {
777 SetFocus(hwnd);
778 }
779 break;
780 }
781
782 case WM_KEYDOWN:
783 {
784 WORD NewDay = 0;
785
786 switch (wParam)
787 {
788 case VK_UP:
789 {
790 if (infoPtr->Day > 7)
791 {
792 NewDay = infoPtr->Day - 7;
793 }
794 break;
795 }
796
797 case VK_DOWN:
798 {
799 if (infoPtr->Day + 7 <= MonthCalMonthLength(infoPtr->Month,
800 infoPtr->Year))
801 {
802 NewDay = infoPtr->Day + 7;
803 }
804 break;
805 }
806
807 case VK_LEFT:
808 {
809 if (infoPtr->Day > 1)
810 {
811 NewDay = infoPtr->Day - 1;
812 }
813 break;
814 }
815
816 case VK_RIGHT:
817 {
818 if (infoPtr->Day < MonthCalMonthLength(infoPtr->Month,
819 infoPtr->Year))
820 {
821 NewDay = infoPtr->Day + 1;
822 }
823 break;
824 }
825 }
826
827 /* Update the selection */
828 if (NewDay != 0)
829 {
830 MonthCalSetDate(infoPtr,
831 NewDay,
832 infoPtr->Month,
833 infoPtr->Year);
834 }
835
836 goto HandleDefaultMessage;
837 }
838
839 case WM_GETDLGCODE:
840 {
841 INT virtKey;
842
843 virtKey = (lParam != 0 ? (INT)((LPMSG)lParam)->wParam : 0);
844 switch (virtKey)
845 {
846 case VK_TAB:
847 {
848 /* Change the UI status */
849 SendMessageW(GetAncestor(hwnd,
850 GA_PARENT),
851 WM_CHANGEUISTATE,
852 MAKEWPARAM(UIS_INITIALIZE,
853 0),
854 0);
855 break;
856 }
857 }
858
859 Ret |= DLGC_WANTARROWS;
860 break;
861 }
862
863 case WM_SETFOCUS:
864 {
865 infoPtr->HasFocus = TRUE;
866 MonthCalRepaintDay(infoPtr,
867 infoPtr->Day);
868 break;
869 }
870
871 case WM_KILLFOCUS:
872 {
873 infoPtr->HasFocus = FALSE;
874 MonthCalRepaintDay(infoPtr,
875 infoPtr->Day);
876 break;
877 }
878
879 case WM_UPDATEUISTATE:
880 {
881 DWORD OldUIState;
882
883 Ret = DefWindowProcW(hwnd,
884 uMsg,
885 wParam,
886 lParam);
887
888 OldUIState = infoPtr->UIState;
889 switch (LOWORD(wParam))
890 {
891 case UIS_SET:
892 infoPtr->UIState |= HIWORD(wParam);
893 break;
894
895 case UIS_CLEAR:
896 infoPtr->UIState &= ~HIWORD(wParam);
897 break;
898 }
899
900 if (infoPtr->UIState != OldUIState)
901 {
902 MonthCalRepaintDay(infoPtr,
903 infoPtr->Day);
904 }
905 break;
906 }
907
908 case MCCM_SETDATE:
909 {
910 WORD Day, Month, Year, DaysCount;
911
912 Day = LOWORD(wParam);
913 Month = HIWORD(wParam);
914 Year = LOWORD(lParam);
915
916 if (Day == (WORD)-1)
917 Day = infoPtr->Day;
918 if (Month == (WORD)-1)
919 Month = infoPtr->Month;
920 if (Year == (WORD)-1)
921 Year = infoPtr->Year;
922
923 DaysCount = MonthCalMonthLength(Month,
924 Year);
925 if (Day > DaysCount)
926 Day = DaysCount;
927
928 if (MonthCalValidDate(Day,
929 Month,
930 Year))
931 {
932 if (Day != infoPtr->Day ||
933 Month != infoPtr->Month ||
934 Year != infoPtr->Year)
935 {
936 Ret = MonthCalSetDate(infoPtr,
937 Day,
938 Month,
939 Year);
940 }
941 }
942 break;
943 }
944
945 case MCCM_GETDATE:
946 {
947 LPSYSTEMTIME lpSystemTime = (LPSYSTEMTIME)wParam;
948
949 lpSystemTime->wYear = infoPtr->Year;
950 lpSystemTime->wMonth = infoPtr->Month;
951 lpSystemTime->wDay = infoPtr->Day;
952
953 Ret = TRUE;
954 break;
955 }
956
957 case MCCM_RESET:
958 {
959 MonthCalSetLocalTime(infoPtr,
960 NULL);
961 Ret = TRUE;
962 break;
963 }
964
965 case MCCM_CHANGED:
966 {
967 Ret = infoPtr->Changed;
968 break;
969 }
970
971 case WM_TIMER:
972 {
973 switch (wParam)
974 {
975 case ID_DAYTIMER:
976 {
977 /* Kill the timer */
978 KillTimer(hwnd,
979 ID_DAYTIMER);
980 infoPtr->DayTimerSet = FALSE;
981
982 if (!infoPtr->Changed)
983 {
984 /* Update the system time and setup the new day timer */
985 MonthCalSetLocalTime(infoPtr,
986 NULL);
987
988 /* Update the control */
989 MonthCalUpdate(infoPtr);
990 }
991 break;
992 }
993 }
994 break;
995 }
996
997 case WM_SETFONT:
998 {
999 Ret = (LRESULT)MonthCalChangeFont(infoPtr,
1000 (HFONT)wParam,
1001 (BOOL)LOWORD(lParam));
1002 break;
1003 }
1004
1005 case WM_SIZE:
1006 {
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;
1011
1012 /* Repaint the control */
1013 InvalidateRect(hwnd,
1014 NULL,
1015 TRUE);
1016 break;
1017 }
1018
1019 case WM_GETFONT:
1020 {
1021 Ret = (LRESULT)infoPtr->hFont;
1022 break;
1023 }
1024
1025 case WM_ENABLE:
1026 {
1027 infoPtr->Enabled = ((BOOL)wParam != FALSE);
1028 MonthCalReload(infoPtr);
1029 break;
1030 }
1031
1032 case WM_STYLECHANGED:
1033 {
1034 if (wParam == GWL_STYLE)
1035 {
1036 unsigned int OldEnabled = infoPtr->Enabled;
1037 infoPtr->Enabled = !(((LPSTYLESTRUCT)lParam)->styleNew & WS_DISABLED);
1038
1039 if (OldEnabled != infoPtr->Enabled)
1040 {
1041 MonthCalReload(infoPtr);
1042 }
1043 }
1044 break;
1045 }
1046
1047 case WM_CREATE:
1048 {
1049 infoPtr = (MONTHCALWND*) HeapAlloc(GetProcessHeap(),
1050 0,
1051 sizeof(MONTHCALWND));
1052 if (infoPtr == NULL)
1053 {
1054 Ret = (LRESULT)-1;
1055 break;
1056 }
1057
1058 SetWindowLongPtrW(hwnd,
1059 0,
1060 (LONG_PTR)infoPtr);
1061
1062 ZeroMemory(infoPtr,
1063 sizeof(MONTHCALWND));
1064
1065 infoPtr->hSelf = hwnd;
1066 infoPtr->hNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
1067 infoPtr->Enabled = !(((LPCREATESTRUCTW)lParam)->style & WS_DISABLED);
1068
1069 MonthCalSetLocalTime(infoPtr,
1070 NULL);
1071
1072 MonthCalReload(infoPtr);
1073 break;
1074 }
1075
1076 case WM_DESTROY:
1077 {
1078 HeapFree(GetProcessHeap(),
1079 0,
1080 infoPtr);
1081 SetWindowLongPtrW(hwnd,
1082 0,
1083 (DWORD_PTR)NULL);
1084 break;
1085 }
1086
1087 default:
1088 {
1089 HandleDefaultMessage:
1090 Ret = DefWindowProcW(hwnd,
1091 uMsg,
1092 wParam,
1093 lParam);
1094 break;
1095 }
1096 }
1097
1098 return Ret;
1099 }
1100
1101 BOOL
1102 RegisterMonthCalControl(IN HINSTANCE hInstance)
1103 {
1104 WNDCLASSW wc = {0};
1105
1106 wc.style = CS_DBLCLKS;
1107 wc.lpfnWndProc = MonthCalWndProc;
1108 wc.cbWndExtra = sizeof(PMONTHCALWND);
1109 wc.hInstance = hInstance;
1110 wc.hCursor = LoadCursorW(NULL,
1111 (LPWSTR)IDC_ARROW);
1112 wc.hbrBackground = (HBRUSH)(MONTHCAL_CTRLBG + 1);
1113 wc.lpszClassName = szMonthCalWndClass;
1114
1115 return RegisterClassW(&wc) != 0;
1116 }
1117
1118 VOID
1119 UnregisterMonthCalControl(IN HINSTANCE hInstance)
1120 {
1121 UnregisterClassW(szMonthCalWndClass,
1122 hInstance);
1123 }