/*++ Copyright (c) 1994-1998, Microsoft Corporation All rights reserved. Module Name: calendar.c Abstract: This module implements the calendar control for the Date/Time applet. Revision History: --*/ // // Include Files. // #include "timedate.h" #include "rc.h" // // Constant Declarations. // #define DEF_FIRST_WEEKDAY (6) #define cBorderX 5 #define cBorderY 3 #define cBorderSelect 1 #define IS_FE_LANGUAGE(p) (((p) == LANG_CHINESE) || \ ((p) == LANG_JAPANESE) || \ ((p) == LANG_KOREAN)) // // Typedef Declarations. // // // Struture for global data. // typedef struct _CALINFO { HWND hwnd; // the hwnd HFONT hfontCal; // the font to use BOOL fFocus; // do we have the focus int cxBlank; // size of a blank int cxChar; // the width of digits int cyChar; // the height of digits } CALINFO, *PCALINFO; //////////////////////////////////////////////////////////////////////////// // // GetFirstDayOfAnyWeek // // For this function ONLY: // 0 = Monday // 6 = Sunday // //////////////////////////////////////////////////////////////////////////// int GetFirstDayOfAnyWeek() { static int iDay = -1; if (iDay < 0) { TCHAR ch[2] = { 0 }; *ch = TEXT('0') + DEF_FIRST_WEEKDAY; GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, ch, 2); iDay = ( ((*ch >= TEXT('0')) && (*ch <= TEXT('6'))) ? ((int)*ch - TEXT('0')) : DEF_FIRST_WEEKDAY ); } return (iDay); } //////////////////////////////////////////////////////////////////////////// // // GetLocalWeekday // //////////////////////////////////////////////////////////////////////////// int GetLocalWeekday() { // // Convert local first day to 0==sunday and subtract from today. // return ((wDateTime[WEEKDAY] + 7 - ((GetFirstDayOfAnyWeek() + 1) % 7)) % 7); } void DetermineDayOfWeek() { FILETIME FileTime; SYSTEMTIME SystemTime; SystemTime.wHour = wDateTime[HOUR]; SystemTime.wMinute = wDateTime[MINUTE]; SystemTime.wSecond = wDateTime[SECOND]; SystemTime.wMilliseconds = 0; SystemTime.wMonth = wDateTime[MONTH]; SystemTime.wDay = wDateTime[DAY]; SystemTime.wYear = wDateTime[YEAR]; SystemTimeToFileTime(&SystemTime, &FileTime); FileTimeToSystemTime(&FileTime, &SystemTime); wDateTime[WEEKDAY] = SystemTime.wDayOfWeek; } //////////////////////////////////////////////////////////////////////////// // // GetFirstDayOfTheMonth // //////////////////////////////////////////////////////////////////////////// int GetFirstDayOfTheMonth() { DetermineDayOfWeek(); return ((GetLocalWeekday() + 8 - (wDateTime[DAY] % 7)) % 7); } //////////////////////////////////////////////////////////////////////////// // // GetDaysOfTheMonth // //////////////////////////////////////////////////////////////////////////// int GetDaysOfTheMonth( int iMonth) { int cDays; int nYear; // // Calculate the number of days in the current month - // add one if this is a leap year and the month is February. // if (iMonth <= 7) { cDays = 30 + (iMonth % 2); } else { cDays = 31 - (iMonth % 2); } if (iMonth == 2) { cDays = 28; nYear = wDateTime[YEAR]; if ((nYear % 4 == 0) && ((nYear % 100 != 0) || (nYear % 400 == 0))) { cDays++; } } return (cDays); } //////////////////////////////////////////////////////////////////////////// // // AdjustDeltaDay // // Adjust the day part of the current date // //////////////////////////////////////////////////////////////////////////// void AdjustDeltaDay( HWND hwnd, int iDay) { GetTime(); if (wDateTime[DAY] != iDay) { wPrevDateTime[DAY] = wDateTime[DAY] = (WORD)iDay; fDateDirty = TRUE; // // Let our parent know that we changed. // FORWARD_WM_COMMAND( GetParent(hwnd), GetWindowLong(hwnd, GWL_ID), hwnd, CBN_SELCHANGE, SendMessage ); } } int GetCalendarName(LPTSTR pszName, int cch) { TCHAR szDateString[100]; SYSTEMTIME SystemTime; int cchResult = 0; SystemTime.wHour = wDateTime[HOUR]; SystemTime.wMinute = wDateTime[MINUTE]; SystemTime.wSecond = wDateTime[SECOND]; SystemTime.wMilliseconds = 0; SystemTime.wMonth = wDateTime[MONTH]; SystemTime.wDay = wDateTime[DAY]; SystemTime.wYear = wDateTime[YEAR]; if (0 != GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &SystemTime, NULL, szDateString, ARRAYSIZE(szDateString))) { if (pszName) lstrcpyn( pszName, szDateString, cch); cchResult = lstrlen(szDateString); } return cchResult; } //////////////////////////////////////////////////////////////////////////// // // ChangeCurrentDate // // If we pass in iNewCol < 0, we simply want to invalidate todays date. // This is used when we gain and lose focus. // //////////////////////////////////////////////////////////////////////////// void ChangeCurrentDate( PCALINFO pci, int iNewCol, int iNewRow) { int iFirstDay, iRow, iColumn; RECT rc, rcT; GetClientRect(pci->hwnd, &rc); iFirstDay = GetFirstDayOfTheMonth(); iColumn = (wDateTime[DAY] - 1 + iFirstDay) % 7; iRow = 1 + ((wDateTime[DAY] - 1 + iFirstDay) / 7); rcT.left = (((rc.right - rc.left) * iColumn) / 7) + cBorderX - cBorderSelect; rcT.right = rcT.left + (pci->cxChar * 2) + (2 * cBorderSelect); rcT.top = ((rc.bottom - rc.top) * iRow ) / 7 + cBorderY - cBorderSelect; rcT.bottom = rcT.top + pci->cyChar + (2 * cBorderSelect); InvalidateRect(pci->hwnd, &rcT, FALSE); if (iNewCol >= 0) { AdjustDeltaDay(pci->hwnd, ((iNewRow - 1) * 7) + iNewCol + 1 - iFirstDay); } } //////////////////////////////////////////////////////////////////////////// // // CalendarPaint // //////////////////////////////////////////////////////////////////////////// #ifdef UNICODE #define NUM_DBCS_CHARS 1 // 1 Unicode char #else #define NUM_DBCS_CHARS 2 // 1 lead byte, 1 trail byte #endif BOOL CalendarPaint( PCALINFO pci, HWND hwnd) { RECT rc, rcT; PAINTSTRUCT ps; HDC hdc; int iWeek, iWeekDay, iDay, iMaxDays; int iFirstDayOfWeek, iFirstDayOfMonth; TCHAR pszDate[3]; TCHAR szShortDay[25]; DWORD dwbkColor; COLORREF o_TextColor; LCID Locale; LANGID LangID = GetUserDefaultLangID(); BOOL IsFELang = IS_FE_LANGUAGE(PRIMARYLANGID(LangID)); iFirstDayOfMonth = GetFirstDayOfTheMonth(); iMaxDays = GetDaysOfTheMonth(wDateTime[MONTH]); iDay = 1; pszDate[0] = TEXT(' '); pszDate[1] = TEXT('0'); // // Paint the background of the dates page. // hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rc); FillRect(hdc, &rc, GetSysColorBrush(COLOR_WINDOW)); // // The day specifier. // rcT.left = rc.left; rcT.right = rc.right; rcT.top = rc.top; rcT.bottom = rc.top + ((rc.bottom - rc.top) / 7); FillRect(hdc, &rcT, GetSysColorBrush(COLOR_INACTIVECAPTION)); // // Fill the page. // SetBkColor(hdc, GetSysColor(COLOR_WINDOW)); SelectFont(hdc, pci->hfontCal); SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT)); // // See if we need to calculate the size of characters. // if (pci->cxChar == 0) { DrawText(hdc, TEXT("0"), 1, &rcT, DT_CALCRECT); pci->cxChar = rcT.right - rcT.left; pci->cyChar = rcT.bottom - rcT.top; DrawText(hdc, TEXT(" "), 1, &rcT, DT_CALCRECT); pci->cxBlank = rcT.right - rcT.left; } for (iWeek = 1; (iWeek < 7); iWeek++) { for (iWeekDay = iFirstDayOfMonth; (iWeekDay < 7) && (iDay <= iMaxDays); iWeekDay++) { rcT.left = ((((rc.right - rc.left) * iWeekDay) / 7)) + cBorderX; rcT.top = (((rc.bottom - rc.top) * iWeek) / 7) + cBorderY; rcT.right = rcT.left + 20; rcT.bottom = rcT.top + 20; if (pszDate[1] == TEXT('9')) { pszDate[1] = TEXT('0'); if (pszDate[0] == TEXT(' ')) { pszDate[0] = TEXT('1'); } else { pszDate[0] = pszDate[0] + 1; } } else { pszDate[1] = pszDate[1] + 1; } if (wDateTime[DAY] == iDay) { dwbkColor = GetBkColor(hdc); SetBkColor(hdc, GetSysColor(COLOR_ACTIVECAPTION)); o_TextColor = GetTextColor(hdc); SetTextColor(hdc, GetSysColor(COLOR_CAPTIONTEXT)); } ExtTextOut( hdc, rcT.left, rcT.top, 0, &rcT, (LPTSTR)pszDate, 2, NULL ); // // If we drew it inverted - put it back. // if (wDateTime[DAY] == iDay) { // // If we have the focus we also need to draw the focus // rectangle for this item. // if (pci->fFocus) { rcT.bottom = rcT.top + pci->cyChar; if (iDay <= 9) { rcT.right = rcT.left + pci->cxChar + pci->cxBlank; } else { rcT.right = rcT.left + 2 * pci->cxChar; } DrawFocusRect(hdc, &rcT); } SetBkColor(hdc, dwbkColor); SetTextColor(hdc, o_TextColor); } iFirstDayOfMonth = 0; iDay++; } } // // Set the FONT color for the SMTWTFS line. // dwbkColor = SetBkColor(hdc, GetSysColor(COLOR_INACTIVECAPTION)); SetTextColor(hdc, GetSysColor(COLOR_INACTIVECAPTIONTEXT)); iFirstDayOfWeek = GetFirstDayOfAnyWeek(); if (!IsFELang) { // // Not a FE locale. // // If it's Arabic or Syriac, then we want to use the US locale to get the // first letter of the abbreviated day name to display in the calendar. // Locale = ((PRIMARYLANGID(LangID) == LANG_ARABIC) || (PRIMARYLANGID(LangID) == LANG_SYRIAC)) ? MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), SORT_DEFAULT) : LOCALE_USER_DEFAULT; for (iWeekDay = 0; (iWeekDay < 7); iWeekDay++) { GetLocaleInfo( Locale, LOCALE_SABBREVDAYNAME1 + (iWeekDay + iFirstDayOfWeek) % 7, szShortDay, sizeof(szShortDay) / sizeof(TCHAR) ); if (*szShortDay) { *szShortDay = (TCHAR)CharUpper((LPTSTR)(DWORD_PTR)*szShortDay); } TextOut( hdc, (((rc.right - rc.left) * iWeekDay) / 7) + cBorderX, cBorderY, szShortDay, 1 ); } } else { // // FE Locale. // for (iWeekDay = 0; (iWeekDay < 7); iWeekDay++) { GetLocaleInfo( LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1 + (iWeekDay + iFirstDayOfWeek) % 7, szShortDay, sizeof(szShortDay) / sizeof(TCHAR) ); #ifndef UNICODE if (*szShortDay && !IsDBCSLeadByte(*szShortDay)) #else if (*szShortDay) #endif { *szShortDay = (TCHAR)CharUpper((LPTSTR)(DWORD_PTR)*szShortDay); } if ((PRIMARYLANGID(LangID) == LANG_CHINESE) && (lstrlen(szShortDay) == 3 * NUM_DBCS_CHARS)) { TextOut( hdc, (((rc.right - rc.left) * iWeekDay) / 7) + cBorderX, cBorderY, (LangID == MAKELANGID( LANG_CHINESE, SUBLANG_CHINESE_HONGKONG )) ? szShortDay : szShortDay + (2 * NUM_DBCS_CHARS), 1 * NUM_DBCS_CHARS ); } else { TextOut( hdc, (((rc.right - rc.left) * iWeekDay) / 7) + cBorderX, cBorderY, szShortDay, lstrlen(szShortDay) ); } } } SetBkColor(hdc, dwbkColor); EndPaint(hwnd, &ps); return (TRUE); } //////////////////////////////////////////////////////////////////////////// // // IsValidClick // //////////////////////////////////////////////////////////////////////////// BOOL IsValidClick( HWND hwnd, int x, int y) { int iT; if (y == 0) { return (FALSE); } iT = GetFirstDayOfTheMonth(); if ((y == 1) && (x < iT)) { return (FALSE); } iT += GetDaysOfTheMonth(wDateTime[MONTH]) - 1; if (y > ((iT / 7) + 1)) { return (FALSE); } if ((y == ((iT / 7) + 1)) && (x > (iT % 7))) { return (FALSE); } return (TRUE); } //////////////////////////////////////////////////////////////////////////// // // HandleDateChange // //////////////////////////////////////////////////////////////////////////// BOOL HandleDateChange( PCALINFO pci, int x, int y) { RECT rc, rcT; int ix, iy; GetClientRect(pci->hwnd, &rc); ix = (x * 7) / (rc.right - rc.left); iy = (y * 7) / (rc.bottom - rc.top); if (IsValidClick(pci->hwnd, ix, iy)) { rcT.left = (((rc.right - rc.left) * ix)/ 7) + cBorderX - cBorderSelect; rcT.right = rcT.left + (2 * pci->cxChar) + (2 * cBorderSelect); rcT.top = ((rc.bottom - rc.top) * iy) / 7 + cBorderY - cBorderSelect; rcT.bottom = rcT.top + pci->cyChar + (2 * cBorderSelect); InvalidateRect(pci->hwnd, &rcT, FALSE); ChangeCurrentDate( pci, (x * 7) / (rc.right - rc.left), (y * 7) / (rc.bottom - rc.top) ); NotifyWinEvent(EVENT_OBJECT_NAMECHANGE , pci->hwnd, OBJID_WINDOW, CHILDID_SELF); return (TRUE); } else { return (FALSE); } } //////////////////////////////////////////////////////////////////////////// // // HandleKeyDown // //////////////////////////////////////////////////////////////////////////// void HandleKeyDown( PCALINFO pci, int vk, LPARAM lParam) { RECT rcT; // // First thing, lets try to figure out what the current x and y is. // int ix = GetLocalWeekday(); int iy = (wDateTime[DAY] + GetFirstDayOfTheMonth() - 1) / 7; switch (vk) { case ( VK_LEFT ) : { ix--; if (ix < 0) { ix = 6; iy--; } break; } case ( VK_RIGHT ) : { ix++; if (ix == 7) { ix = 0; iy++; } break; } case ( VK_UP ) : { iy--; break; } case ( VK_DOWN ) : { iy++; break; } default : { // // Ignore the character. // return; } } // // The y's are offset for the days of the week. // iy++; if (!IsValidClick(pci->hwnd, ix, iy)) { return; } GetClientRect(pci->hwnd, &rcT); rcT.left = ((rcT.right * ix) / 7) + cBorderX - cBorderSelect; rcT.right = rcT.left + (2 * pci->cxChar) + (2 * cBorderSelect); rcT.top = (rcT.bottom * iy) / 7 + cBorderY - cBorderSelect; rcT.bottom = rcT.top + pci->cyChar + (2 * cBorderSelect); InvalidateRect(pci->hwnd, &rcT, FALSE); // // First try, simply call to change the date. // ChangeCurrentDate(pci, ix, iy); NotifyWinEvent(EVENT_OBJECT_NAMECHANGE , pci->hwnd, OBJID_WINDOW, CHILDID_SELF); } //////////////////////////////////////////////////////////////////////////// // // CalWndProc // //////////////////////////////////////////////////////////////////////////// LRESULT CALLBACK CalWndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { PCALINFO pci; pci = (PCALINFO)GetWindowLongPtr(hwnd, 0); switch (message) { case ( WM_CREATE ) : { pci = (PCALINFO)LocalAlloc(LPTR, sizeof(CALINFO)); if (pci == 0) { return (-1); } pci->hwnd = hwnd; SetWindowLongPtr(hwnd, 0, (LONG_PTR)pci); GetDate(); break; } case ( WM_NCDESTROY ) : { if (pci) { LocalFree((HLOCAL)pci); } break; } case ( WM_SETFONT ) : { if (wParam) { pci->hfontCal = (HFONT)wParam; pci->cxChar = 0; } break; } case ( WM_GETTEXT ) : { return GetCalendarName((LPTSTR)lParam, (int)wParam); } case ( WM_GETTEXTLENGTH ) : { return GetCalendarName(NULL, 0); } case ( WM_PAINT ) : { CalendarPaint(pci, hwnd); break; } case ( WM_LBUTTONDOWN ) : { SetFocus(hwnd); HandleDateChange(pci, LOWORD(lParam), HIWORD(lParam)); break; } case ( WM_SETFOCUS ) : { pci->fFocus = TRUE; ChangeCurrentDate(pci, -1, -1); break; } case ( WM_KILLFOCUS ) : { pci->fFocus = FALSE; ChangeCurrentDate(pci, -1, -1); break; } case ( WM_KEYDOWN ) : { HandleKeyDown(pci, (int)wParam, lParam); break; } case ( WM_GETDLGCODE ) : { return (DLGC_WANTARROWS); break; } default : { return ( DefWindowProc(hwnd, message, wParam, lParam) ); } } return (0); } //////////////////////////////////////////////////////////////////////////// // // CalendarInit // //////////////////////////////////////////////////////////////////////////// TCHAR const c_szCalClass[] = CALENDAR_CLASS; BOOL CalendarInit( HANDLE hInstance) { WNDCLASS wc; if (!GetClassInfo(hInstance, c_szCalClass, &wc)) { wc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof(PCALINFO); wc.hCursor = NULL; wc.hbrBackground = NULL; wc.hIcon = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = c_szCalClass; wc.hInstance = hInstance; wc.lpfnWndProc = CalWndProc; return (RegisterClass(&wc)); } return (TRUE); }