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