|
|
#include "ctlspriv.h"
#include "scdttime.h"
#include "monthcal.h"
#include "prshti.h" // for StrDup_AtoW
// TODO
//
// #6329: When Min/Max range is set, then dates before the min
// or after the max are painted in the normal date color. They
// should be painted with MCSC_TRAILINGTEXT color. (Or we should
// add a new color to cover this case.) Feature requested by Jobi George
//
// 9577: We want a DAYSTATE like structure for the background
// color of dates. For highlighting. Perhaps a COLORSTATE per
// registered background color.
//
// private message
#define MCMP_WINDOWPOSCHANGED (MCM_FIRST - 1) // MCM_FIRST is way over WM_USER
#define DTMP_WINDOWPOSCHANGED (DTM_FIRST - 1) // DTM_FIRST is way over WM_USER
// MONTHCAL
LRESULT CALLBACK MonthCalWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); LRESULT MCNcCreateHandler(HWND hwnd); LRESULT MCCreateHandler(MONTHCAL *pmc, HWND hwnd, LPCREATESTRUCT lpcs); LRESULT MCOnStyleChanging(MONTHCAL *pmc, UINT gwl, LPSTYLESTRUCT pinfo); LRESULT MCOnStyleChanged(MONTHCAL *pmc, UINT gwl, LPSTYLESTRUCT pinfo); void MCCalcSizes(MONTHCAL *pmc); void MCHandleSetFont(MONTHCAL *pmc, HFONT hfont, BOOL fRedraw); void MCPaint(MONTHCAL *pmc, HDC hdc); void MCPaintMonth(MONTHCAL *pmc, HDC hdc, RECT *prc, int iMonth, int iYear, int iIndex, BOOL fDrawPrev, BOOL fDrawNext, HBRUSH hbrSelect); void MCNcDestroyHandler(HWND hwnd, MONTHCAL *pmc, WPARAM wParam, LPARAM lParam); void MCRecomputeSizing(MONTHCAL *pmc, RECT *prect); LRESULT MCSizeHandler(MONTHCAL *pmc, RECT *prc); void MCUpdateMonthNamePos(MONTHCAL *pmc); void MCUpdateStartEndDates(MONTHCAL *pmc, SYSTEMTIME *pstStart); void MCGetRcForDay(MONTHCAL *pmc, int iMonth, int iDay, RECT *prc); void MCGetRcForMonth(MONTHCAL *pmc, int iMonth, RECT *prc); void MCUpdateToday(MONTHCAL *pmc); void MCUpdateRcDayCur(MONTHCAL *pmc, SYSTEMTIME *pst); void MCUpdateDayState(MONTHCAL *pmc); int MCGetOffsetForYrMo(MONTHCAL *pmc, int iYear, int iMonth); int MCIsSelectedDayMoYr(MONTHCAL *pmc, int iDay, int iMonth, int iYear); BOOL MCIsBoldOffsetDay(MONTHCAL *pmc, int nDay, int iIndex); BOOL FGetOffsetForPt(MONTHCAL *pmc, POINT pt, int *piOffset); BOOL FGetRowColForRelPt(MONTHCAL *pmc, POINT ptRel, int *piRow, int *piCol); BOOL FGetDateForPt(MONTHCAL *pmc, POINT pt, SYSTEMTIME *pst, int* piDay, int* piCol, int* piRow, LPRECT prcMonth); LRESULT MCContextMenu(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam); LRESULT MCLButtonDown(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam); LRESULT MCLButtonUp(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam); LRESULT MCMouseMove(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam); LRESULT MCHandleTimer(MONTHCAL *pmc, WPARAM wParam); LRESULT MCHandleKeydown(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam); LRESULT MCHandleChar(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam); int MCIncrStartMonth(MONTHCAL *pmc, int nDelta, BOOL fDelayDayChange); void MCGetTitleRcsForOffset(MONTHCAL* pmc, int iOffset, LPRECT prcMonth, LPRECT prcYear); BOOL MCSetDate(MONTHCAL *pmc, SYSTEMTIME *pst); void MCNotifySelChange(MONTHCAL *pmc, UINT uMsg); void MCInvalidateDates(MONTHCAL *pmc, SYSTEMTIME *pst1, SYSTEMTIME *pst2); void MCInvalidateMonthDays(MONTHCAL *pmc); void MCSetToday(MONTHCAL* pmc, SYSTEMTIME* pst); void MCGetTodayBtnRect(MONTHCAL *pmc, RECT *prc); void GetYrMoForOffset(MONTHCAL *pmc, int iOffset, int *piYear, int *piMonth); BOOL FScrollIntoView(MONTHCAL *pmc); void MCFreeCalendarInfo(PCALENDARTYPE pct); void MCGetCalendarInfo(PCALENDARTYPE pct); BOOL MCIsDateStringRTL(TCHAR tch);
// DATEPICK
LRESULT CALLBACK DatePickWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); LRESULT DPNcCreateHandler(HWND hwnd); LRESULT DPCreateHandler(DATEPICK *pdp, HWND hwnd, LPCREATESTRUCT lpcs); LRESULT DPOnStyleChanging(DATEPICK *pdp, UINT gwl, LPSTYLESTRUCT pinfo); LRESULT DPOnStyleChanged(DATEPICK *pdp, UINT gwl, LPSTYLESTRUCT pinfo); void DPHandleLocaleChange(DATEPICK *pdp); void DPDestroyHandler(HWND hwnd, DATEPICK *pdp, WPARAM wParam, LPARAM lParam); void DPHandleSetFont(DATEPICK *pdp, HFONT hfont, BOOL fRedraw); void DPPaint(DATEPICK *pdp, HDC hdc); void DPLBD_MonthCal(DATEPICK *pdp, BOOL fLButtonDown); LRESULT DPLButtonDown(DATEPICK *pdp, WPARAM wParam, LPARAM lParam); LRESULT DPLButtonUp(DATEPICK *pdp, WPARAM wParam, LPARAM lParam); void DPRecomputeSizing(DATEPICK *pdp, RECT *prect); LRESULT DPHandleKeydown(DATEPICK *pdp, WPARAM wParam, LPARAM lParam); LRESULT DPHandleChar(DATEPICK *pdp, WPARAM wParam, LPARAM lParam); void DPNotifyDateChange(DATEPICK *pdp); BOOL DPSetDate(DATEPICK *pdp, SYSTEMTIME *pst, BOOL fMungeDate); void DPDrawDropdownButton(DATEPICK *pdp, HDC hdc, BOOL fPressed); void SECGetSystemtime(LPSUBEDITCONTROL psec, LPSYSTEMTIME pst);
static TCHAR const g_rgchMCName[] = MONTHCAL_CLASS; static TCHAR const g_rgchDTPName[] = DATETIMEPICK_CLASS;
// MONTHCAL globals
#define g_szTextExtentDef TEXT("0000")
#define g_szNumFmt TEXT("%d")
//
// Epoch = the beginning of the universe (the earliest date we support)
// Armageddon = the end of the universe (the latest date we support)
//
// Epoch is 14-sep-1752 because that's when the Gregorian calendar
// kicked in. The day before 14-sep-1752 was 2-sep-1752 (in British
// and US history; other countries switched at other times).
//
// Armageddon is 31-dec-9999 because we assume four digits for years
// is enough. (Oh no, the Y10K problem...)
//
const SYSTEMTIME c_stEpoch = { 1752, 9, 0, 14, 0, 0, 0, 0 }; const SYSTEMTIME c_stArmageddon = { 9999, 12, 0, 31, 23, 59, 59, 999 };
BOOL InitDateClasses(HINSTANCE hinst) { WNDCLASS wc; BOOL fRegistered = FALSE; wc.style = CS_GLOBALCLASS; wc.lpfnWndProc = MonthCalWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof(LPVOID); wc.hInstance = hinst; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = NULL; wc.lpszClassName = g_rgchMCName;
if (RegisterClass(&wc) || (GetLastError() == ERROR_CLASS_ALREADY_EXISTS)) { wc.lpfnWndProc = DatePickWndProc; wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = g_rgchDTPName;
fRegistered = (RegisterClass(&wc) || (GetLastError() == ERROR_CLASS_ALREADY_EXISTS));
}
return fRegistered; }
////////////////////////////////////////////////////////////////////////////
//
// MonthCal stuff
//
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////
//
// MCInsert/RemoveMarkers
//
// QuickSummary: Convert the string "MMMM yyyy" into "\1MMMM\3 \2yyyy\4".
//
// In order to lay out the month/year info in the header, we have to be
// able to extract the month and year out of the formatted string so we
// know what their rectangles are. We do this by wrapping the month and
// year inserts with markers so we can extract them after formatting.
//
// Since \1 through \4 are control characters, they won't conflict with
// displayable characters in the actual format string. And just to play it
// safe, if we actually see a format character, we erase it from the string.
//
// MCInsertMarkers inserts the markers into the output string so we can
// extract the substrings later. Quotation marks are funky since you can
// write a format of "'The' mm'''th month of' yyyy". Note that a simple
// even-odd test works for detecting whether we are inside or outside
// quotation marks, even in the nested quotation mark case.
//
void MCInsertMarkers(LPTSTR pszOut, LPCTSTR pszIn) { BOOL fInQuote = FALSE; UINT flSeen = 0; UINT flThis;
for (;;) { TCHAR ch = *pszIn; switch (ch) {
// At end of string, terminate the output buffer and go home
case TEXT('\0'): *pszOut = TEXT('\0'); return;
case TEXT('m'): case TEXT('M'): flThis = IMM_MONTHSTART; goto CheckMarker;
case TEXT('y'): flThis = IMM_YEARSTART; goto CheckMarker;
CheckMarker: // If inside a quotation mark or we've already done this guy,
// then just treat it as a regular character.
if (fInQuote || (flSeen & flThis)) goto CopyChar;
flSeen |= flThis;
*pszOut++ = (TCHAR)flThis; // Don't need to use CharNext because we know *pszIn is "m" "M" or "y"
for ( ; *pszIn == ch; pszIn++) { *pszOut++ = ch; } *pszOut++ = (TCHAR)(flThis + DMM_STARTEND);
// Restart the loop so we re-parse the character at *pszIn
continue;
// Toggle the quotation mark gizmo if we see one, and then just
// copy it.
case '\'': fInQuote ^= TRUE; goto CopyChar;
//
// Don't let these sneak into the output format or it
// will confuse us.
//
case IMM_MONTHSTART: case IMM_YEARSTART: case IMM_MONTHEND: case IMM_YEAREND: break;
default: CopyChar: *pszOut++ = ch; break;
}
pszIn++; // We handled the DBCS case already
}
// NOTREACHED
}
//
// MCRemoveMarkers hunts down the marker characters and strips them out,
// recording their locations in the optional MONTHMETRICS (as character
// indices).
//
void MCRemoveMarkers(LPTSTR pszBuf, PMONTHMETRICS pmm) { int iWrite, iRead;
//
// If by some horrid error we can't find our markers, just pretend
// they were at the start of the string.
//
if (pmm) { pmm->rgi[IMM_MONTHSTART] = 0; pmm->rgi[IMM_YEARSTART ] = 0; pmm->rgi[IMM_MONTHEND ] = 0; pmm->rgi[IMM_YEAREND ] = 0; }
iWrite = iRead = 0; for (;;) { TCHAR ch = pszBuf[iRead]; switch (ch) { // At end of string, terminate the output buffer and go home
case TEXT('\0'): pszBuf[iWrite] = TEXT('\0'); return;
// If we find a marker, eat it and remember its location
case IMM_MONTHSTART: case IMM_YEARSTART: case IMM_MONTHEND: case IMM_YEAREND: if (pmm) pmm->rgi[ch] = iWrite; break;
// Otherwise, just copy it to the output
default: pszBuf[iWrite++] = ch; break;
} iRead++; } // NOTREACHED
}
////////////////////////////////////
//
// Like LocalizedLoadString, except that we get the string from
// LOCAL_USER_DEFAULT instead of GetUserDefaultUILanguage().
//
// LOCALE_USER_DEFAULT is the same as GetUserDefaultLCID(), and
// LANGIDFROMLCID(GetUserDefaultLCID()) is the same as GetUserDefaultLangID().
//
// So we pass GetUserDefaultLangID() as the language.
//
int MCLoadString(UINT uID, LPWSTR lpBuffer, int nBufferMax) { return CCLoadStringEx(uID, lpBuffer, nBufferMax, GetUserDefaultLangID()); }
////////////////////////////////////
//
// Get the localized calendar info
//
BOOL UpdateLocaleInfo(MONTHCAL* pmc, LPLOCALEINFO pli) { int i; TCHAR szBuf[64]; int cch; LPTSTR pc = szBuf;
//
// Get information about the calendar (e.g., is it supported?)
//
MCGetCalendarInfo(&pmc->ct);
//
// Check if the calendar title is an RTL string
//
GetDateFormat(pmc->ct.lcid, 0, NULL, TEXT("MMMM"), szBuf, ARRAYSIZE(szBuf)); pmc->fHeaderRTL = (WORD) MCIsDateStringRTL(szBuf[0]);
//
// get the short date format and sniff it to see if it displays the year
// or month first
//
MCLoadString(IDS_MONTHFMT, pli->szMonthFmt, ARRAYSIZE(pli->szMonthFmt));
//
// Try to get the MONTHYEAR format from NLS. If not supported by NLS,
// then use the hard-coded value in our resources. Note that we
// subtract 4 from the buffer size because we may insert up to four
// marker characters.
//
COMPILETIME_ASSERT(ARRAYSIZE(szBuf) >= ARRAYSIZE(pli->szMonthYearFmt)); szBuf[0] = TEXT('\0');
GetLocaleInfo(pmc->ct.lcid, LOCALE_SYEARMONTH, szBuf, ARRAYSIZE(pli->szMonthYearFmt) - CCH_MARKERS); if (!szBuf[0]) { MCLoadString(IDS_MONTHYEARFMT, szBuf, ARRAYSIZE(pli->szMonthYearFmt) - CCH_MARKERS); }
MCInsertMarkers(pli->szMonthYearFmt, szBuf);
//
// REARCHITECT this code needs to change to use CAL_ values when we
// want to support multiple calendars.
//
//
// Get the month names
//
for (i = 0; i < 12; i++) { cch = GetLocaleInfo(pmc->ct.lcid, LOCALE_SMONTHNAME1 + i, pli->rgszMonth[i], CCHMAXMONTH); if (cch == 0) // the calendar is pretty useless without month names...
return(FALSE); }
//
// Get the days of the week
//
for (i = 0; i < 7; i++) { cch = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1 + i, pli->rgszDay[i], CCHMAXABBREVDAY); if (cch == 0) // the calendar is pretty useless without day names...
return(FALSE); }
//
// If we haven't already set what the first day of the week is, get the
// localized setting.
//
if (!pmc->fFirstDowSet) { cch = GetLocaleInfo(pmc->ct.lcid, LOCALE_IFIRSTDAYOFWEEK, szBuf, ARRAYSIZE(szBuf)); if (cch > 0) pli->dowStartWeek = szBuf[0] - TEXT('0'); }
//
// Get the first week of the year
//
cch = GetLocaleInfo(pmc->ct.lcid, LOCALE_IFIRSTWEEKOFYEAR, szBuf, ARRAYSIZE(szBuf)); if (cch > 0) pli->firstWeek = szBuf[0] - TEXT('0'); // Set up pointers
for (i = 0; i < 12; i++) pli->rgpszMonth[i] = pli->rgszMonth[i];
for (i = 0; i < 7; i++) pli->rgpszDay[i] = pli->rgszDay[i];
// Get static strings
MCLoadString(IDS_TODAY, pli->szToday, ARRAYSIZE(pli->szToday)); MCLoadString(IDS_GOTOTODAY, pli->szGoToToday, ARRAYSIZE(pli->szGoToToday));
// if we've been initialized
if (pmc->hinstance) { SYSTEMTIME st; CopyDate(pmc->stMonthFirst, st); MCUpdateStartEndDates(pmc, &st); } return(TRUE); }
void MCReloadMenus(MONTHCAL *pmc) { int i;
if (pmc->hmenuCtxt) DestroyMenu(pmc->hmenuCtxt); if (pmc->hmenuMonth) DestroyMenu(pmc->hmenuMonth);
pmc->hmenuCtxt = CreatePopupMenu(); if (pmc->hmenuCtxt) AppendMenu(pmc->hmenuCtxt, MF_STRING, 1, pmc->li.szGoToToday);
pmc->hmenuMonth = CreatePopupMenu(); if (pmc->hmenuMonth) { for (i = 0; i < 12; i++) AppendMenu(pmc->hmenuMonth, MF_STRING, i + 1, pmc->li.rgszMonth[i]); } }
BOOL MCHandleEraseBkgnd(MONTHCAL* pmc, HDC hdc) { RECT rc;
GetClipBox(hdc, &rc); FillRectClr(hdc, &rc, pmc->clr[MCSC_BACKGROUND]); return TRUE; }
LRESULT MCHandleHitTest(MONTHCAL* pmc, PMCHITTESTINFO phti) { int iMonth; RECT rc; if (!phti || phti->cbSize != sizeof(MCHITTESTINFO)) return -1;
phti->uHit = MCHT_NOWHERE;
MCGetTodayBtnRect(pmc, &rc); if (PtInRect(&rc, phti->pt) && MonthCal_ShowToday(pmc)) { phti->uHit = MCHT_TODAYLINK; } else if (pmc->fSpinPrev = (WORD) PtInRect(&pmc->rcPrev, phti->pt)) { phti->uHit = MCHT_TITLEBTNPREV; } else if (PtInRect(&pmc->rcNext, phti->pt)) { phti->uHit = MCHT_TITLEBTNNEXT; } else if (FGetOffsetForPt(pmc, phti->pt, &iMonth)) { RECT rcMonth; // bounding rect for month containg phti->pt
POINT ptRel; // relative point in a month
int month; int year;
MCGetRcForMonth(pmc, iMonth, &rcMonth); ptRel.x = phti->pt.x - rcMonth.left; ptRel.y = phti->pt.y - rcMonth.top;
GetYrMoForOffset(pmc, iMonth, &year, &month); phti->st.wMonth = (WORD) month; phti->st.wYear = (WORD) year; //
// if calendar is showing week numbers and the point lies in the
// the week numbers, get the date for day immediately to the right
// of the week number containing the point
//
if (MonthCal_ShowWeekNumbers(pmc) && PtInRect(&pmc->rcWeekNum, ptRel)) { phti->uHit |= MCHT_CALENDARWEEKNUM; phti->pt.x += pmc->rcDayNum.left; FGetDateForPt(pmc, phti->pt, &phti->st, NULL, NULL, NULL, NULL); }
//
// if the point lies in the days of the week header, then return
// the day of the week containing the point
//
else if (PtInRect(&pmc->rcDow, ptRel)) { int iRow; int iCol; phti->uHit |= MCHT_CALENDARDAY; ptRel.y = pmc->rcDayNum.top; FGetRowColForRelPt(pmc, ptRel, &iRow, &iCol); phti->st.wDayOfWeek = (WORD) iCol; }
//
// if the point lies in the actually calendar part, then return the
// date containg the point
//
else if (PtInRect(&pmc->rcDayNum, ptRel)) { int iDay; // we're in the calendar part!
phti->uHit |= MCHT_CALENDAR;
if (FGetDateForPt(pmc, phti->pt, &phti->st, &iDay, NULL, NULL, NULL)) { phti->uHit |= MCHT_CALENDARDATE; // if it was beyond the bounds of the days we're showing
// and also FGetDateForPt returns TRUE, then we're on the boundary
// of the displayed months
if (iDay <= 0) { phti->uHit |= MCHT_PREV; } else if (iDay > pmc->rgcDay[iMonth + 1]) { phti->uHit |= MCHT_NEXT; } } } else { RECT rcMonthTitle; RECT rcYearTitle;
// otherwise we're in the title
phti->uHit |= MCHT_TITLE; MCGetTitleRcsForOffset(pmc, iMonth, &rcMonthTitle, &rcYearTitle);
if (PtInRect(&rcMonthTitle, phti->pt)) { phti->uHit |= MCHT_TITLEMONTH; } else if (PtInRect(&rcYearTitle, phti->pt)) { phti->uHit |= MCHT_TITLEYEAR; } } }
DebugMsg(TF_MONTHCAL, TEXT("mc: Hittest returns : %d %d %d %d)"), (int)phti->st.wDay, (int)phti->st.wMonth, (int)phti->st.wYear, (int)phti->st.wDayOfWeek ); return phti->uHit; }
void MonthCal_OnPaint(MONTHCAL *pmc, HDC hdc) { if (hdc) { MCPaint(pmc, hdc); } else { PAINTSTRUCT ps; hdc = BeginPaint(pmc->ci.hwnd, &ps); MCPaint(pmc, hdc); EndPaint(pmc->ci.hwnd, &ps); } }
BOOL MCGetDateFormatWithTempYear(PCALENDARTYPE pct, SYSTEMTIME *pst, LPCTSTR pszFormat, UINT uYear, LPTSTR pszBuf, UINT cchBuf) { BOOL fRc; WORD wYear = pst->wYear; pst->wYear = (WORD)uYear; fRc = GetDateFormat(pct->lcid, 0, pst, pszFormat, pszBuf, cchBuf); if (!fRc) { // AIGH! I hate Feburary 29. In case we are Feb 29 1996 and the
// user changes to a non-leap year, force the day to something valid
// in February 1997 (or whatever year the user finally picked).
//
// We can't blindly smash the day to 1 because the era might change
// in the middle of the month.
WORD wDay = pst->wDay;
ASSERT(pst->wDay == 29); pst->wDay = 28; fRc = GetDateFormat(pct->lcid, 0, pst, pszFormat, pszBuf, cchBuf); pst->wDay = wDay; } pst->wYear = wYear; return fRc; }
void MCUpdateEditYear(MONTHCAL *pmc) { TCHAR rgch[64];
ASSERT(pmc->hwndEdit);
EVAL(MCGetDateFormatWithTempYear(&pmc->ct, &pmc->st, TEXT("yyyy"), pmc->st.wYear, rgch, ARRAYSIZE(rgch)));
SendMessage(pmc->hwndEdit, WM_SETTEXT, 0, (LPARAM)rgch); }
LRESULT CALLBACK MonthCalWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { MONTHCAL *pmc; LRESULT lres = 0; if (uMsg == WM_NCCREATE) return(MCNcCreateHandler(hwnd)); pmc = MonthCal_GetPtr(hwnd); if (pmc == NULL) return(DefWindowProc(hwnd, uMsg, wParam, lParam));
// Dispatch the various messages we can receive
switch (uMsg) { case WM_CREATE: lres = MCCreateHandler(pmc, hwnd, (LPCREATESTRUCT)lParam); break; HANDLE_MSG(pmc, WM_ERASEBKGND, MCHandleEraseBkgnd);
case WM_PRINTCLIENT: case WM_PAINT: MonthCal_OnPaint(pmc, (HDC)wParam); return(0);
case WM_KEYDOWN: MCHandleKeydown(pmc, wParam, lParam); break;
case WM_KEYUP: switch (wParam) { case VK_CONTROL: pmc->fControl = FALSE; break;
case VK_SHIFT: pmc->fShift = FALSE; break; } break;
case WM_CONTEXTMENU: MCContextMenu(pmc, wParam, lParam); break;
case WM_LBUTTONDOWN: MCLButtonDown(pmc, wParam, lParam); break;
case WM_LBUTTONUP: MCLButtonUp(pmc, wParam, lParam); break;
case WM_MOUSEMOVE: MCMouseMove(pmc, wParam, lParam); break;
case WM_GETFONT: lres = (LRESULT)pmc->hfont; break;
case WM_SETFONT: MCHandleSetFont(pmc, (HFONT)wParam, (BOOL)LOWORD(lParam)); MCSizeHandler(pmc, &pmc->rc); MCUpdateMonthNamePos(pmc); break;
case WM_TIMER: MCHandleTimer(pmc, wParam); break;
case WM_NCDESTROY: MCNcDestroyHandler(hwnd, pmc, wParam, lParam); break;
case WM_ENABLE: { BOOL fEnable = wParam ? TRUE:FALSE; if (pmc->fEnabled != fEnable) { pmc->fEnabled = (WORD) fEnable; InvalidateRect(pmc->ci.hwnd, NULL, TRUE); } break; }
case MCMP_WINDOWPOSCHANGED: case WM_SIZE: { RECT rc;
if (uMsg==MCMP_WINDOWPOSCHANGED) { GetClientRect(pmc->ci.hwnd, &rc); } else { rc.left = 0; rc.top = 0; rc.right = GET_X_LPARAM(lParam); rc.bottom = GET_Y_LPARAM(lParam); }
lres = MCSizeHandler(pmc, &rc); break; }
case WM_CANCELMODE: PostMessage(pmc->ci.hwnd, WM_LBUTTONUP, 0, 0xFFFFFFFF); break;
case WM_SYSCOLORCHANGE: InitGlobalColors(); break;
case WM_WININICHANGE: InitGlobalMetrics(wParam);
if (lParam == 0 || !lstrcmpi((LPTSTR)lParam, TEXT("Intl")) ) { UpdateLocaleInfo(pmc, &pmc->li); MCReloadMenus(pmc); InvalidateRect(hwnd, NULL, TRUE); wParam = 0; // force MCCalcSizes to happen
} if (wParam == 0 || wParam == SPI_SETNONCLIENTMETRICS) { MCCalcSizes(pmc); PostMessage(pmc->ci.hwnd, MCMP_WINDOWPOSCHANGED, 0, 0); }
break;
case WM_THEMECHANGED: if (pmc->hThemeScroll) { CloseThemeData(pmc->hThemeScroll); }
pmc->hThemeScroll = OpenThemeData(pmc->ci.hwnd, L"ScrollBar"); InvalidateRect(pmc->ci.hwnd, NULL, TRUE); break;
case WM_NOTIFYFORMAT: return CIHandleNotifyFormat(&pmc->ci, lParam); break;
case WM_STYLECHANGING: lres = MCOnStyleChanging(pmc, (UINT) wParam, (LPSTYLESTRUCT)lParam); break;
case WM_STYLECHANGED: lres = MCOnStyleChanged(pmc, (UINT) wParam, (LPSTYLESTRUCT)lParam); break;
case WM_NOTIFY: { LPNMHDR pnm = (LPNMHDR)lParam; switch (pnm->code) { case UDN_DELTAPOS: if (pnm->hwndFrom == pmc->hwndUD) { // A notification from the UpDown control buddied
// with the currently popped up monthcal, adjust the
// edit box appropriately. We use UDN_DELTAPOS instad
// of WM_VSCROLL because we care only about the delta and
// not the absolute number. The absolute number causes us
// problems in localized calendars.
LPNM_UPDOWN pnmdp = (LPNM_UPDOWN)lParam; UINT yr = pmc->st.wYear + pnmdp->iDelta; UINT yrMin, yrMax; int delta;
yrMin = pmc->stMin.wYear; if (yr < yrMin) yr = yrMin;
yrMax = pmc->stMax.wYear; if (yr > yrMax) yr = yrMax;
delta = yr - pmc->st.wYear; pmc->st.wYear = (WORD)yr; if (delta) { MCIncrStartMonth(pmc, delta * 12, FALSE); MCNotifySelChange(pmc,MCN_SELCHANGE);
} } break; } } // WM_NOTIFY switch
break;
case WM_VSCROLL: // this must be coming from our UpDown control buddied
// with the currently popped up monthcal, adjust the
// edit box appropriately
// We must do this on WM_VSCROLL rather than UDN_DELTAPOS
// since we need to fix the selection after the updown mangled it
MCUpdateEditYear(pmc); break;
//
// MONTHCAL specific messages
//
// MCM_GETCURSEL wParam=void lParam=LPSYSTEMTIME
// sets *lParam to the currently selected SYSTEMTIME
// returns TRUE on success, FALSE on error (such as multi-select MONTHCAL)
case MCM_GETCURSEL: if (!MonthCal_IsMultiSelect(pmc)) { LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam; if (pst) { ZeroMemory(pst, SIZEOF(SYSTEMTIME)); // Bug#94368 raymondc v6. Need to zero out the time fields instead of
// setting them to garbage. This confuses MFC.
*pst = pmc->st; pst->wDayOfWeek = (DowFromDate(pst)+1) % 7; // this returns 0==sun
lres = 1; } } break;
// MCM_SETCURSEL wParam=void lParam=LPSYSTEMTIME
// sets the currently selected SYSTEMTIME to *lParam
// returns TRUE on success, FALSE on error (such as multi-select MONTHCAL or bad parameters)
case MCM_SETCURSEL: { LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
if (MonthCal_IsMultiSelect(pmc) || !IsValidDate(pst)) { break; }
if (0 == CmpDate(pst, &pmc->st)) { // if no change, just return
lres = 1; break; }
pmc->rcDayOld = pmc->rcDayCur;
pmc->fNoNotify = TRUE; lres = MCSetDate(pmc, pst); pmc->fNoNotify = FALSE;
if (lres) { InvalidateRect(pmc->ci.hwnd, &pmc->rcDayOld, FALSE); // erase old highlight
InvalidateRect(pmc->ci.hwnd, &pmc->rcDayCur, FALSE); // draw new highlight
}
UpdateWindow(pmc->ci.hwnd); break; }
// MCM_GETMAXSELCOUNT wParam=void lParam=void
// returns the max number of selected days allowed
case MCM_GETMAXSELCOUNT: lres = (LRESULT)(MonthCal_IsMultiSelect(pmc) ? pmc->cSelMax : 1); break;
// MCM_SETMAXSELCOUNT wParam=int lParam=void
// sets the maximum selectable date range to wParam days
// returns TRUE on success, FALSE on error (such as single-select MONTHCAL)
case MCM_SETMAXSELCOUNT: if (!MonthCal_IsMultiSelect(pmc) || (int)wParam < 1) break;
pmc->cSelMax = (int)wParam; lres = 1; break;
// MCM_GETSELRANGE wParam=void lParam=LPSYSTEMTIME[2]
// sets *lParam to the first date of the range, *(lParam+1) to the second date
// returns TRUE on success, FALSE otherwise (such as single-select MONTHCAL)
case MCM_GETSELRANGE: { LPSYSTEMTIME pst;
pst = (LPSYSTEMTIME)lParam;
if (!pst) break;
ZeroMemory(pst, 2*SIZEOF(SYSTEMTIME));
if (!MonthCal_IsMultiSelect(pmc)) break;
*pst = pmc->st; pst->wDayOfWeek = (DowFromDate(pst)+1) % 7; // this returns 0==sun
pst++; *pst = pmc->stEndSel; pst->wDayOfWeek = (DowFromDate(pst)+1) % 7; // this returns 0==sun
lres = 1;
break; }
// MCM_SETSELRANGE wParam=void lParam=LPSYSTEMTIME[2]
// sets the currently selected day range to *lparam to *(lParam+1)
// returns TRUE on success, FALSE otherwise (such as single-select MONTHCAL or bad params)
case MCM_SETSELRANGE: { LPSYSTEMTIME pstStart = (LPSYSTEMTIME)lParam; LPSYSTEMTIME pstEnd = &pstStart[1]; SYSTEMTIME stStart; SYSTEMTIME stEnd;
if (!MonthCal_IsMultiSelect(pmc) || !IsValidDate(pstStart) || !IsValidDate(pstEnd)) break;
// IE3 shipped without validating the time portion of this message.
// Make sure our stored systemtimes are always valid (so we will
// always give out valid systemtime structs).
//
if (!IsValidTime(pstStart)) CopyTime(pmc->st, *pstStart); if (!IsValidTime(pstEnd)) CopyTime(pmc->stEndSel, *pstEnd);
if (CmpDate(pstStart, pstEnd) > 0) { stEnd = *pstStart; stStart = *pstEnd; pstStart = &stStart; pstEnd = &stEnd; }
if (CmpDate(pstStart, &pmc->stMin) < 0) break;
if (CmpDate(pstEnd, &pmc->stMax) > 0) break;
if (DaysBetweenDates(pstStart, pstEnd) >= pmc->cSelMax) break;
if (0 == CmpDate(pstStart, &pmc->st) && 0 == CmpDate(pstEnd, &pmc->stEndSel)) { // if no change, just return
lres = 1; break; }
pmc->stStartPrev = pmc->st; pmc->stEndPrev = pmc->stEndSel;
pmc->fNoNotify = TRUE;
lres = MCSetDate(pmc, pstEnd); if (lres) { pmc->st = *pstStart; pmc->stEndSel = *pstEnd;
MCInvalidateDates(pmc, &pmc->stStartPrev, &pmc->stEndPrev); MCInvalidateDates(pmc, &pmc->st, &pmc->stEndSel); UpdateWindow(pmc->ci.hwnd); }
pmc->fNoNotify = FALSE;
break; } // MCM_GETMONTHRANGE wParam=GMR_flags lParam=LPSYSTEMTIME[2]
// if GMR_VISIBLE, returns the range of selectable (non-grayed) displayed
// days. if GMR_DAYSTATE, returns the range of every (incl grayed) days.
// returns the number of months the above range spans.
case MCM_GETMONTHRANGE: { LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
if (pst) { ZeroMemory(pst, 2*SIZEOF(SYSTEMTIME));
if (wParam == GMR_VISIBLE) { pst[0] = pmc->stMonthFirst; pst[1] = pmc->stMonthLast; } else if (wParam == GMR_DAYSTATE) { pst[0] = pmc->stViewFirst; pst[1] = pmc->stViewLast; } }
lres = (LRESULT)pmc->nMonths; if (wParam == GMR_DAYSTATE) lres += 2; break; }
// MCM_SETDAYSTATE wParam=int lParam=LPDAYSTATE
// updates the MONTHCAL's DAYSTATE, only for MONTHCALs with DAYSTATE enabled
// the range of months represented in the DAYSTATE array passed in lParam
// should match that of the MONTHCAL
// wParam count of items in DAYSTATE array
// lParam pointer to array of DAYSTATE items
// returns FALSE if not DAYSTATE enabled or if an error occurs, TRUE otherwise
case MCM_SETDAYSTATE: { MONTHDAYSTATE *pmds = (MONTHDAYSTATE *)lParam; int i;
if (!MonthCal_IsDayState(pmc) || (int)wParam != (pmc->nMonths + 2)) break;
for (i = 0; i < (int)wParam; i++) { pmc->rgdayState[i] = *pmds; pmds++; } MCInvalidateMonthDays(pmc); lres = 1;
break; }
// MCM_GETMINREQRECT wParam=void lParam=LPRECT
// sets *lParam to the minimum size required to display one month in full.
// Note: this is dependent upon the currently selected font.
// Apps can take the returned size and double the width to get two calendars
// displayed.
case MCM_GETMINREQRECT: { LPRECT prc = (LPRECT)lParam;
prc->left = 0; prc->top = 0; prc->right = pmc->dxMonth; prc->bottom = pmc->dyMonth; if (MonthCal_ShowToday(pmc)) { prc->bottom += pmc->dyToday; }
AdjustWindowRect(prc, pmc->ci.style, FALSE);
// This is a bogus message, lParam should really be LPSIZE.
// Make sure left and top are 0 (AdjustWindowRect will make these negative).
prc->right -= prc->left; prc->bottom -= prc->top; prc->left = 0; prc->top = 0;
lres = 1;
break; }
// MCM_GETMAXTODAYWIDTH wParam=void lParam=LPDWORD
// sets *lParam to the width of the "today" string, so apps
// can figure out how big to make the calendar (max of MCM_GETMINREQRECT
// and MCM_GETMAXTODAYWIDTH).
case MCM_GETMAXTODAYWIDTH: { RECT rc;
rc.left = 0; rc.top = 0; rc.right = pmc->dxToday; rc.bottom = pmc->dyToday;
AdjustWindowRect(&rc, pmc->ci.style, FALSE);
lres = rc.right - rc.left; break; }
case MCM_HITTEST: return MCHandleHitTest(pmc, (PMCHITTESTINFO)lParam); case MCM_SETCOLOR:
if (wParam < MCSC_COLORCOUNT) { COLORREF clr = pmc->clr[wParam]; pmc->clr[wParam] = (COLORREF)lParam; InvalidateRect(hwnd, NULL, wParam == MCSC_BACKGROUND); return clr; } return -1; case MCM_GETCOLOR: if (wParam < MCSC_COLORCOUNT) return pmc->clr[wParam]; return -1; case MCM_SETFIRSTDAYOFWEEK: { lres = MAKELONG(pmc->li.dowStartWeek, (BOOL)pmc->fFirstDowSet); if (lParam == (LPARAM)-1) { pmc->fFirstDowSet = FALSE; } else if (lParam < 7) { pmc->fFirstDowSet = TRUE; pmc->li.dowStartWeek = (TCHAR)lParam; } UpdateLocaleInfo(pmc, &pmc->li); InvalidateRect(hwnd, NULL, FALSE); return lres; } case MCM_GETFIRSTDAYOFWEEK: return MAKELONG(pmc->li.dowStartWeek, (BOOL)pmc->fFirstDowSet); case MCM_SETTODAY: MCSetToday(pmc, (SYSTEMTIME*)lParam); break; case MCM_GETTODAY: if (lParam) { *((SYSTEMTIME*)lParam) = pmc->stToday; return TRUE; } return FALSE;
case MCM_GETRANGE: if (lParam) { LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
ZeroMemory(pst, 2*SIZEOF(SYSTEMTIME));
ASSERT(lres == 0); if (pmc->fMinYrSet) { pst[0] = pmc->stMin; lres = GDTR_MIN; } if (pmc->fMaxYrSet) { pst[1] = pmc->stMax; lres |= GDTR_MAX; } } break;
case MCM_SETRANGE: if (lParam) { LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
if (((wParam & GDTR_MIN) && !IsValidDate(pst)) || ((wParam & GDTR_MAX) && !IsValidDate(&pst[1]))) break;
// IE3 did not validate the time portion of this struct
// use stToday time fields cuz pmc->stMin/Max may be zero
if ((wParam & GDTR_MIN) && !IsValidTime(pst)) CopyTime(pmc->stToday, pst[0]); if ((wParam & GDTR_MAX) && !IsValidTime(&pst[1])) CopyTime(pmc->stToday, pst[1]);
if (wParam & GDTR_MIN) { pmc->stMin = *pst; pmc->fMinYrSet = TRUE; } else { pmc->stMin = c_stEpoch; pmc->fMinYrSet = FALSE; } pst++; if (wParam & GDTR_MAX) { pmc->stMax = *pst; pmc->fMaxYrSet = TRUE; } else { pmc->stMax = c_stArmageddon; pmc->fMaxYrSet = FALSE; } if (pmc->fMaxYrSet && pmc->fMinYrSet && CmpDate(&pmc->stMin, &pmc->stMax) > 0) { SYSTEMTIME stTemp = pmc->stMin; pmc->stMin = pmc->stMax; pmc->stMax = stTemp; } lres = TRUE; } break;
case MCM_GETMONTHDELTA: if (pmc->fMonthDelta) lres = pmc->nMonthDelta; else lres = pmc->nMonths; break;
case MCM_SETMONTHDELTA: if (pmc->fMonthDelta) lres = pmc->nMonthDelta; else lres = 0; if ((int)wParam==0) pmc->fMonthDelta = FALSE; else { pmc->fMonthDelta = TRUE; pmc->nMonthDelta = (int)wParam; } break;
default: if (CCWndProc(&pmc->ci, uMsg, wParam, lParam, &lres)) return lres; lres = DefWindowProc(hwnd, uMsg, wParam, lParam); break; } /* switch (uMsg) */
return(lres); }
LRESULT MCNcCreateHandler(HWND hwnd) { MONTHCAL *pmc;
// Allocate storage for the dtpick structure
pmc = (MONTHCAL *)NearAlloc(sizeof(MONTHCAL)); if (!pmc) { DebugMsg(DM_WARNING, TEXT("mc: Out Of Near Memory")); return(0L); }
MonthCal_SetPtr(hwnd, pmc);
return(1L); }
void MCInitColorArray(COLORREF* pclr) { pclr[MCSC_BACKGROUND] = g_clrWindow; pclr[MCSC_MONTHBK] = g_clrWindow; pclr[MCSC_TEXT] = g_clrWindowText; pclr[MCSC_TITLEBK] = GetSysColor(COLOR_ACTIVECAPTION); pclr[MCSC_TITLETEXT] = GetSysColor(COLOR_CAPTIONTEXT); pclr[MCSC_TRAILINGTEXT] = g_clrGrayText; }
LRESULT MCCreateHandler(MONTHCAL *pmc, HWND hwnd, LPCREATESTRUCT lpcs) { HFONT hfont; SYSTEMTIME st;
// Validate data
//
if (lpcs->style & MCS_INVALIDBITS) return(-1);
CIInitialize(&pmc->ci, hwnd, lpcs); UpdateLocaleInfo(pmc, &pmc->li);
// Initialize our data.
//
pmc->hinstance = lpcs->hInstance; pmc->fEnabled = !(pmc->ci.style & WS_DISABLED); pmc->hbrushToday = CreateSolidBrush(CAL_COLOR_TODAY);
MCReloadMenus(pmc);
// Default minimum date is the epoch
pmc->stMin = c_stEpoch;
// Default maximum date is armageddon
pmc->stMax = c_stArmageddon;
GetLocalTime(&pmc->stToday); pmc->st = pmc->stToday; if (MonthCal_IsMultiSelect(pmc)) pmc->stEndSel = pmc->st;
// make sure the time portions of these are valid. they are never
// touched after this point
pmc->stMonthFirst = pmc->st; pmc->stMonthLast = pmc->st; pmc->stViewFirst = pmc->st; pmc->stViewLast = pmc->st;
pmc->cSelMax = CAL_DEF_SELMAX;
hfont = NULL; if (lpcs->hwndParent) hfont = (HFONT)SendMessage(lpcs->hwndParent, WM_GETFONT, 0, 0); if (hfont == NULL) hfont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); MCHandleSetFont(pmc, hfont, FALSE); CopyDate(pmc->st, st); // Can we start at January?
if (st.wMonth <= (pmc->nViewRows * pmc->nViewCols)) st.wMonth = 1; MCUpdateStartEndDates(pmc, &st); pmc->idTimerToday = SetTimer(pmc->ci.hwnd, CAL_TODAYTIMER, CAL_SECTODAYTIMER * 1000, NULL);
MCInitColorArray(pmc->clr);
pmc->hThemeScroll = OpenThemeData(pmc->ci.hwnd, L"ScrollBar");
return(0); }
LRESULT MCOnStyleChanging(MONTHCAL *pmc, UINT gwl, LPSTYLESTRUCT pinfo) { if (gwl == GWL_STYLE) { DWORD changeFlags = pmc->ci.style ^ pinfo->styleNew;
// Don't allow these bits to change
changeFlags &= MCS_MULTISELECT | MCS_DAYSTATE | MCS_INVALIDBITS;
pinfo->styleNew ^= changeFlags; }
return(0); }
LRESULT MCOnStyleChanged(MONTHCAL *pmc, UINT gwl, LPSTYLESTRUCT pinfo) { if (gwl == GWL_STYLE) { DWORD changeFlags = pmc->ci.style ^ pinfo->styleNew;
ASSERT(!(changeFlags & (MCS_MULTISELECT|MCS_DAYSTATE|MCS_INVALIDBITS)));
pmc->ci.style = pinfo->styleNew;
if (changeFlags & MCS_WEEKNUMBERS) { MCCalcSizes(pmc); MCUpdateRcDayCur(pmc, &pmc->st); //MCUpdateToday(pmc);
}
// save a touch of code and share the MCUpdateToday
// call with MCS_WEEKNUMBERS above
if (changeFlags & (MCS_NOTODAY|MCS_NOTODAYCIRCLE|MCS_WEEKNUMBERS)) { MCUpdateToday(pmc); }
if (changeFlags & (WS_BORDER | WS_CAPTION | WS_THICKFRAME)) { // the changing of these bits affect the size of the window
// but not until after this message is handled
// so post ourself a message.
PostMessage(pmc->ci.hwnd, MCMP_WINDOWPOSCHANGED, 0, 0); }
if (changeFlags) InvalidateRect(pmc->ci.hwnd, NULL, TRUE); } else if (gwl == GWL_EXSTYLE) { if ((pinfo->styleOld ^ pinfo->styleNew) & RTL_MIRRORED_WINDOW) { MCUpdateMonthNamePos(pmc); } }
return(0); }
void MCCalcSizes(MONTHCAL *pmc) { HDC hdc; HFONT hfontOrig; int i, dxMax, dyMax, dxExtra; RECT rect; TCHAR szBuf[128]; TCHAR szDateFmt[64];
// get sizing info for bold font...
hdc = GetDC(pmc->ci.hwnd); hfontOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
MGetTextExtent(hdc, g_szTextExtentDef, 2, &dxMax, &dyMax); MGetTextExtent(hdc, g_szTextExtentDef, 4, &pmc->dxYearMax, NULL);
GetDateFormat(pmc->ct.lcid, DATE_SHORTDATE, &pmc->stToday, NULL, szDateFmt, ARRAYSIZE(szDateFmt)); StringCchPrintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s %s"), pmc->li.szToday, szDateFmt); MGetTextExtent(hdc, szBuf, -1, &pmc->dxToday, &pmc->dyToday); // REARCHITECT raymondc - hard-coded numbers are accessibility-incompatible
pmc->dyToday += 4;
//
// Cache these values so we don't go wacko if the app fails to
// forward WM_WININCHANGE messages into us and the user changes
// scrollbar widths. We'll draw with the wrong width, but at
// least they will be consistently wrong.
//
pmc->dxArrowMargin = DX_ARROWMARGIN; pmc->dxCalArrow = DX_CALARROW; pmc->dyCalArrow = DY_CALARROW;
//
// The banner bar consists of
//
// margin + scrollbutton + spacer +
// MonthName yyyy +
// + spacer + scrollbutton + margin
//
// Margin is dxArrowMargin
//
// Scrollbutton = dxCalArrow
//
// Spacer = border + CXVSCROLL + border
//
// The spacer needs to be large enough for us to insert an updown
// control when it comes time to spin the year. We don't need to
// cache the spacer anywhere - its value is implicit from the others.
//
// The actual width is divided by the number of columns we need
// (typically 7, but perhaps 8 if we are also displaying week numbers).
//
// We round the division down - later, we'll add some random futz
// to compensate.
//
dxExtra = pmc->dxArrowMargin + pmc->dxCalArrow + (g_cxBorder + g_cxVScroll + g_cxBorder); dxExtra = dxExtra + dxExtra; // left + right
for (i = 0; i < 12; i++) { int dxTemp;
// REARCHITECT raymondc - not localization safe for languages which change
// month forms based on context
StringCchPrintf(szBuf, ARRAYSIZE(szBuf), pmc->li.rgszMonth[i], g_szTextExtentDef); MGetTextExtent(hdc, szBuf, -1, &dxTemp, NULL); dxTemp += dxExtra; dxTemp = dxTemp / (CALCOLMAX + (MonthCal_ShowWeekNumbers(pmc) ? 1:0)); if (dxTemp > dxMax) dxMax = dxTemp; } SelectObject(hdc, (HGDIOBJ)pmc->hfont); for (i = 0; i < 7; i++) { SIZE size; MGetTextExtent(hdc, pmc->li.rgszDay[i], -1, (LPINT)&size.cx, (LPINT)&size.cy); if (size.cx > dxMax) dxMax = size.cx; if (size.cy > dyMax) dyMax = size.cy; }
if (dyMax < pmc->dyCalArrow / 2) dyMax = pmc->dyCalArrow / 2;
SelectObject(hdc, (HGDIOBJ)hfontOrig); ReleaseDC(pmc->ci.hwnd, hdc);
pmc->dxCol = dxMax + 2; pmc->dyRow = dyMax + 2; pmc->dxMonth = pmc->dxCol * (CALCOLMAX + (MonthCal_ShowWeekNumbers(pmc) ? 1:0)) + 1; pmc->dyMonth = pmc->dyRow * (CALROWMAX + 3) + 1; // we add 2 for the month name and day names
pmc->dxToday += pmc->dxCol+6+CALBORDER; // +2 for -1 at ends and 4 for shift of circle
if (pmc->dxMonth > pmc->dxToday) pmc->dxToday = pmc->dxMonth; // Space for month name (tile bar area of each month)
pmc->rcMonthName.left = 0; pmc->rcMonthName.top = 0; pmc->rcMonthName.right = pmc->dxMonth; pmc->rcMonthName.bottom = pmc->rcMonthName.top + (pmc->dyRow * 2);
// Space for day-of-week
pmc->rcDow.left = 0; pmc->rcDow.top = pmc->rcMonthName.bottom; pmc->rcDow.right = pmc->dxMonth; pmc->rcDow.bottom = pmc->rcDow.top + pmc->dyRow;
// Space for week numbers
if (MonthCal_ShowWeekNumbers(pmc)) { pmc->rcWeekNum.left = pmc->rcDow.left; pmc->rcWeekNum.top = pmc->rcDow.bottom; pmc->rcWeekNum.right = pmc->rcWeekNum.left + pmc->dxCol; pmc->rcWeekNum.bottom = pmc->dyMonth;
pmc->rcDow.left += pmc->dxCol; // shift days of week
}
// Space for the day numbers
pmc->rcDayNum.left = pmc->rcDow.left; pmc->rcDayNum.top = pmc->rcDow.bottom; pmc->rcDayNum.right = pmc->rcDayNum.left + (CALCOLMAX * pmc->dxCol); pmc->rcDayNum.bottom = pmc->dyMonth;
GetClientRect(pmc->ci.hwnd, &rect);
MCRecomputeSizing(pmc, &rect); }
void MCHandleSetFont(MONTHCAL *pmc, HFONT hfont, BOOL fRedraw) { LOGFONT lf; HFONT hfontBold;
if (hfont == NULL) hfont = (HFONT)GetStockObject(SYSTEM_FONT);
GetObject(hfont, sizeof(LOGFONT), (LPVOID)&lf); // we want to make sure that the bold days are obviously different
// from the non-bold days...
CCAdjustForBold(&lf); hfontBold = CreateFontIndirect(&lf);
if (hfontBold == NULL) return;
if (pmc->hfontBold) DeleteObject((HGDIOBJ)pmc->hfontBold);
pmc->hfont = hfont; pmc->hfontBold = hfontBold; pmc->ci.uiCodePage = GetCodePageForFont(hfont);
// calculate the new row and column sizes
MCCalcSizes(pmc);
if (fRedraw) { InvalidateRect(pmc->ci.hwnd, NULL, TRUE); UpdateWindow(pmc->ci.hwnd); } }
void MCDrawTodayCircle(MONTHCAL *pmc, HDC hdc, RECT *prc) { FrameRect(hdc, prc, pmc->hbrushToday); }
void MCInvalidateMonthDays(MONTHCAL *pmc) { InvalidateRect(pmc->ci.hwnd, &pmc->rcCentered, FALSE); }
void MCGetTodayBtnRect(MONTHCAL *pmc, RECT *prc) { if (pmc->dxToday > pmc->rcCentered.right - pmc->rcCentered.left) { prc->left = pmc->rc.left + 1; prc->right = pmc->rc.right - 1; } else { prc->left = pmc->rcCentered.left + 1; prc->right = pmc->rcCentered.right - 1; } prc->top = pmc->rcCentered.bottom - pmc->dyToday; prc->bottom = pmc->rcCentered.bottom;
// center the today rect when we only have 1 col and it will fit in window
if ((pmc->nViewCols == 1) && (pmc->dxToday <= pmc->rc.right - pmc->rc.left)) { int dx = ((pmc->rcCentered.right - pmc->rcCentered.left) - pmc->dxToday) / 2 - 1; prc->left += dx; prc->right -= dx; } }
void MCPaintArrowBtn(MONTHCAL *pmc, HDC hdc, BOOL fPrev, BOOL fPressed) { int iStateId; LPRECT prc; UINT dfcs; if (fPrev) { iStateId = ABS_LEFTNORMAL; dfcs = DFCS_SCROLLLEFT; prc = &pmc->rcPrev; } else { iStateId = ABS_RIGHTNORMAL; dfcs = DFCS_SCROLLRIGHT;
prc = &pmc->rcNext; }
if (pmc->fEnabled) { if (fPressed) { iStateId += 2; dfcs |= DFCS_PUSHED | DFCS_FLAT; } } else { iStateId += 3; // Disabled
dfcs |= DFCS_INACTIVE; }
if (pmc->hThemeScroll) { DrawThemeBackground(pmc->hThemeScroll, hdc, SBP_ARROWBTN, iStateId, prc, 0);
} else { DrawFrameControl(hdc, prc, DFC_SCROLL, dfcs); } }
void MCPaint(MONTHCAL *pmc, HDC hdc) { RECT rc, rcT; int irow, icol, iMonth, iYear, iIndex, dx, dy; HBRUSH hbrSelect; HGDIOBJ hgdiOrig, hpenOrig;
pmc->hpen = CreatePen(PS_SOLID, 0, pmc->clr[MCSC_TEXT]); hbrSelect = CreateSolidBrush(pmc->clr[MCSC_TITLEBK]);
SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, pmc->clr[MCSC_TEXT]); hpenOrig = SelectObject(hdc, GetStockObject(BLACK_PEN));
rc = pmc->rcCentered;
FillRectClr(hdc, &rc, pmc->clr[MCSC_MONTHBK]);
SelectObject(hdc, (HGDIOBJ)pmc->hpen);
// get the place for top left month
rc.left = pmc->rcCentered.left; rc.right = rc.left + pmc->dxMonth; rc.top = pmc->rcCentered.top; rc.bottom = rc.top + pmc->dyMonth;
iMonth = pmc->stMonthFirst.wMonth; iYear = pmc->stMonthFirst.wYear;
dx = pmc->dxMonth + CALBORDER; dy = pmc->dyMonth + CALBORDER;
iIndex = 0; for (irow = 0; irow < pmc->nViewRows; irow++) { rcT = rc; for (icol = 0; icol < pmc->nViewCols; icol++) { if (RectVisible(hdc, &rcT)) { MCPaintMonth(pmc, hdc, &rcT, iMonth, iYear, iIndex, iIndex == 0, iIndex == (pmc->nMonths - 1), hbrSelect); }
rcT.left += dx; rcT.right += dx;
if (++iMonth > 12) { iMonth = 1; iYear++; }
iIndex++; }
rc.top += dy; rc.bottom += dy; }
// draw the today stuff
if (MonthCal_ShowToday(pmc)) { MCGetTodayBtnRect(pmc, &rc); if (RectVisible(hdc, &rc)) { TCHAR szDateFmt[32]; TCHAR szBuf[64];
rcT.right = rc.left + 2; // a bit extra border space
if (MonthCal_ShowTodayCircle(pmc)) // this turns on/off the red circle
{ rcT.left = rcT.right + 2; rcT.right = rcT.left + pmc->dxCol - 2; rcT.top = rc.top + 2; rcT.bottom = rc.bottom - 2; MCDrawTodayCircle(pmc, hdc, &rcT); } rcT.left = rcT.right + 2; rcT.right = rc.right - 2; rcT.top = rc.top; rcT.bottom = rc.bottom; hgdiOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfontBold); SetTextColor(hdc, pmc->clr[MCSC_TEXT]); GetDateFormat(pmc->ct.lcid, DATE_SHORTDATE, &pmc->stToday, NULL, szDateFmt, ARRAYSIZE(szDateFmt)); StringCchPrintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s %s"), pmc->li.szToday, szDateFmt); DrawText(hdc, szBuf, lstrlen(szBuf), &rcT, DT_LEFT | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER); SelectObject(hdc, hgdiOrig); } }
// Draw the spin buttons
if (RectVisible(hdc, &pmc->rcPrev)) MCPaintArrowBtn(pmc, hdc, TRUE, (pmc->idTimer && pmc->fSpinPrev)); if (RectVisible(hdc, &pmc->rcNext)) MCPaintArrowBtn(pmc, hdc, FALSE, (pmc->idTimer && !pmc->fSpinPrev));
SelectObject(hdc, hpenOrig);
DeleteObject((HGDIOBJ)hbrSelect); DeleteObject((HGDIOBJ)pmc->hpen); }
//
// MCGetMonthFormat gets the string to display for the month/year
// in the passed-in SYSTEMTIME. This is tricky because of eras.
// If pmm is non-NULL, it receives the metrics of the
// formatted month/year string.
//
void MCGetMonthFormat(MONTHCAL *pmc, SYSTEMTIME *pst, LPTSTR rgch, UINT cch, PMONTHMETRICS pmm) { // For all months, we display the name appropriate to the first
// day of the month. Note that this means that the title of the
// month in which the era changes may be confusing. If the era
// changes in the middle of a month, we name the month after the
// previous era, even if the current selection belongs to the next
// era. I hope nobody will mind.
pst->wDay = 1;
//
// Get the string (all marked up), then extract the markers
// to locate the month and year substrings.
//
rgch[0] = TEXT('\0'); // In case something horrible happens
GetDateFormat(pmc->ct.lcid, 0, pst, pmc->li.szMonthYearFmt, rgch, cch); MCRemoveMarkers(rgch, pmm); }
void MCPaintMonth(MONTHCAL *pmc, HDC hdc, RECT *prc, int iMonth, int iYear, int iIndex, BOOL fDrawPrev, BOOL fDrawNext, HBRUSH hbrSelect) { BOOL fBold, fView, fReset; RECT rc, rcT; int nDay, cdy, irow, icol, crowShow, nweek, isel; TCHAR rgch[64]; LPTSTR psz; HGDIOBJ hfontOrig; COLORREF clrGrayText, clrHiliteText, clrOld, clrText; SYSTEMTIME st = {0}; int iIndexSave = iIndex;
clrText = pmc->clr[MCSC_TEXT]; clrGrayText = pmc->clr[MCSC_TRAILINGTEXT]; clrHiliteText = pmc->clr[MCSC_TITLETEXT];
hfontOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfont); SelectObject(hdc, (HGDIOBJ)pmc->hpen);
//
// Draw the Month and Year
//
// translate the relative coords to window coords
rc = pmc->rcMonthName; rc.left += prc->left; rc.right += prc->left; rc.top += prc->top; rc.bottom += prc->top; if (RectVisible(hdc, &rc)) { FillRectClr(hdc, &rc, pmc->clr[MCSC_TITLEBK]);
SetTextColor(hdc, pmc->clr[MCSC_TITLETEXT]); SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
st.wYear = (WORD) iYear; st.wMonth = (WORD) iMonth; MCGetMonthFormat(pmc, &st, rgch, ARRAYSIZE(rgch), NULL);
DrawText(hdc, rgch, lstrlen(rgch), &rc, DT_CENTER | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
#ifdef MARKER_DEBUG
//
// When debugging MCInsertMarker and MCRemoveMarker, draw colored
// bars where we think the markers were.
//
{ RECT rcT = rc; rcT.top = rcT.bottom - 2; rcT.left = rc.left + pmc->rgmm[iIndex].rgi[IMM_MONTHSTART]; rcT.right = rc.left + pmc->rgmm[iIndex].rgi[IMM_MONTHEND]; FillRectClr(hdc, &rcT, RGB(0xFF, 0, 0));
rcT.left = rc.left + pmc->rgmm[iIndex].rgi[IMM_YEARSTART]; rcT.right = rc.left + pmc->rgmm[iIndex].rgi[IMM_YEAREND]; FillRectClr(hdc, &rcT, RGB(0, 0xFF, 0)); } #endif
SelectObject(hdc, (HGDIOBJ)pmc->hfont); }
SetTextColor(hdc, pmc->clr[MCSC_TITLEBK]);
//
// Draw the days of the month
//
// translate the relative coords to window coords
rc = pmc->rcDow; rc.left += prc->left; rc.right += prc->left; rc.top += prc->top; rc.bottom += prc->top; if (RectVisible(hdc, &rc)) { MoveToEx(hdc, rc.left + 4, rc.bottom - 1, NULL); LineTo(hdc, rc.right - 4, rc.bottom - 1);
rc.right = rc.left + pmc->dxCol;
for (icol = 0; icol < CALCOLMAX; icol++) { psz = pmc->li.rgszDay[(icol + pmc->li.dowStartWeek) % 7];
DrawText(hdc, psz, lstrlen(psz), &rc, DT_CENTER | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER); rc.left += pmc->dxCol; rc.right += pmc->dxCol; } }
// Check to see how many days from the previous month exist in this months calendar
nDay = pmc->rgnDayUL[iIndex]; // last day in prev month that won't be shown in this month
cdy = pmc->rgcDay[iIndex]; // # of days in prev month
// Calculate the number of weeks to display
if (fDrawNext) crowShow = CALROWMAX; else crowShow = ((cdy - nDay) + pmc->rgcDay[iIndex + 1] + 6/* round up */) / 7;
if (nDay != cdy) { // start at previous month
iMonth--; if(iMonth <= 0) { iMonth = 12; iYear--; } nDay++;
fView = FALSE; } else { // start at this month
iIndex++; // this month
nDay = 1; cdy = pmc->rgcDay[iIndex];
fView = TRUE; }
//
// Draw the week numbers
//
if (MonthCal_ShowWeekNumbers(pmc)) { // translate the relative coords to window coords
rc = pmc->rcWeekNum; rc.left += prc->left; rc.top += prc->top; rc.right += prc->left; rc.bottom = rc.top + (pmc->dyRow * crowShow);
// draw the week numbers
if (RectVisible(hdc, &rc)) { MoveToEx(hdc, rc.right - 1, rc.top + 4, NULL); LineTo(hdc, rc.right - 1, rc.bottom - 4);
st.wYear = (WORD) iYear; st.wMonth = (WORD) iMonth; st.wDay = (WORD) nDay; nweek = GetWeekNumber(&st, pmc->li.dowStartWeek, pmc->li.firstWeek);
rc.bottom = rc.top + pmc->dyRow;
for (irow = 0; irow < crowShow; irow++) { StringCchPrintf(rgch, ARRAYSIZE(rgch), g_szNumFmt, nweek); DrawText(hdc, rgch, (nweek > 9 ? 2 : 1), &rc, DT_CENTER | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER); rc.top += pmc->dyRow; rc.bottom += pmc->dyRow; IncrSystemTime(&st, &st, 1, INCRSYS_WEEK); nweek = GetWeekNumber(&st, pmc->li.dowStartWeek, pmc->li.firstWeek); } } }
if (!fView) SetTextColor(hdc, clrGrayText); else SetTextColor(hdc, clrText);
rc = pmc->rcDayNum; rc.left += prc->left; rc.top += prc->top; rc.right = rc.left + pmc->dxCol; rc.bottom = rc.top + pmc->dyRow;
fReset = FALSE; fBold = FALSE;
for (irow = 0; irow < crowShow; irow++) { rcT = rc;
for (icol = 0; icol < CALCOLMAX; icol++) { if ((fView || fDrawPrev) && RectVisible(hdc, &rcT)) { StringCchPrintf(rgch, ARRAYSIZE(rgch), g_szNumFmt, nDay);
if (MonthCal_IsDayState(pmc)) { // if we're in a dropdown we don't display
if (MCIsBoldOffsetDay(pmc, nDay, iIndex)) { if (!fBold) { SelectObject(hdc, (HGDIOBJ)pmc->hfontBold); fBold = TRUE; } } else { if (fBold) { SelectObject(hdc, (HGDIOBJ)pmc->hfont); fBold = FALSE; } } }
if (isel = MCIsSelectedDayMoYr(pmc, nDay, iMonth, iYear)) { int x1, x2;
clrOld = SetTextColor(hdc, clrHiliteText); fReset = TRUE;
SelectObject(hdc, GetStockObject(NULL_PEN)); x1 = 0; x2 = 0; if (isel & SEL_DOT) { FillRect(hdc, &rcT, hbrSelect); if (isel == SEL_BEGIN) { x1 = rcT.left + (rcT.right - rcT.left) / 2; x2 = rcT.right; } else if (isel == SEL_END) { x1 = rcT.left; x2 = rcT.left + (rcT.right - rcT.left) / 2; } } else { x1 = rcT.left; x2 = rcT.right; }
if (x1 && x2) { Rectangle(hdc, x1, rcT.top + 2, x2 + 1, rcT.bottom - 1); } }
DrawText(hdc, rgch, (nDay > 9 ? 2 : 1), &rcT, DT_CENTER | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
if (MonthCal_ShowTodayCircle(pmc) && pmc->fToday && iIndexSave == pmc->iMonthToday && icol == pmc->iColToday && irow == pmc->iRowToday) { MCDrawTodayCircle(pmc, hdc, &rcT); }
if (fReset) { SetTextColor(hdc, clrOld); fReset = FALSE; } }
rcT.left += pmc->dxCol; rcT.right += pmc->dxCol;
nDay++; if (nDay > cdy) { if (!fDrawNext && iIndex > iIndexSave) goto doneMonth;
nDay = 1; iIndex++; cdy = pmc->rgcDay[iIndex]; iMonth++; if (iMonth > 12) { iMonth = 1; iYear++; }
fView = !fView; SetTextColor(hdc, fView ? clrText : clrGrayText);
fDrawPrev = fDrawNext; } }
rc.top += pmc->dyRow; rc.bottom += pmc->dyRow; }
doneMonth:
SelectObject(hdc, hfontOrig);
return; }
int MCIsSelectedDayMoYr(MONTHCAL *pmc, int iDay, int iMonth, int iYear) { SYSTEMTIME st; int iBegin, iEnd; int iret = 0;
st.wYear = (WORD) iYear; st.wMonth = (WORD) iMonth; st.wDay = (WORD) iDay;
iBegin = CmpDate(&st, &pmc->st);
if (MonthCal_IsMultiSelect(pmc)) { iEnd = CmpDate(&st, &pmc->stEndSel);
if (iBegin > 0 && iEnd< 0) iret = SEL_MID; else { if (iBegin == 0) iret |= SEL_BEGIN; if (iEnd == 0) iret |= SEL_END; } } else if (iBegin == 0) { iret = SEL_DOT; }
return(iret); }
BOOL MCIsBoldOffsetDay(MONTHCAL *pmc, int nDay, int iIndex) { return(pmc->rgdayState && (pmc->rgdayState[iIndex] & (1L << (nDay - 1))) != 0); }
void MCNcDestroyHandler(HWND hwnd, MONTHCAL *pmc, WPARAM wParam, LPARAM lParam) { if (pmc) { if (pmc->hThemeScroll) CloseThemeData(pmc->hThemeScroll);
if (pmc->hbrushToday) DeleteObject((HGDIOBJ)pmc->hbrushToday); if (pmc->hfontBold) DeleteObject((HGDIOBJ)pmc->hfontBold); if (pmc->hmenuCtxt) DestroyMenu(pmc->hmenuCtxt); if (pmc->hmenuMonth) DestroyMenu(pmc->hmenuMonth);
if (pmc->idTimer) KillTimer(pmc->ci.hwnd, pmc->idTimer); if (pmc->idTimerToday) KillTimer(pmc->ci.hwnd, pmc->idTimerToday);
MCFreeCalendarInfo(&pmc->ct);
GlobalFreePtr(pmc); }
// In case rogue messages float through after we have freed the pdtpick, set
// the handle in the window structure to FFFF and test for this value at
// the top of the WndProc
MonthCal_SetPtr(hwnd, NULL);
// Call DefWindowProc32 to free all little chunks of memory such as szName
// and rgwScroll.
DefWindowProc(hwnd, WM_NCDESTROY, wParam, lParam); }
/* Computes the following:
* nViewCols * nViewRows * rcCentered * rcPrev * rcNext */ void MCRecomputeSizing(MONTHCAL *pmc, RECT *prect) { RECT rc; int dx, dy, dCal;
// Space for entire calendar
pmc->rc = *prect;
dx = prect->right - prect->left; dy = prect->bottom - prect->top;
pmc->nViewCols = 1 + (dx - pmc->dxMonth) / (pmc->dxMonth + CALBORDER); pmc->nViewRows = 1 + (dy - pmc->dyMonth - pmc->dyToday) / (pmc->dyMonth + CALBORDER);
// if dx < dxMonth or dy < dyMonth, these can be zero. That's bad...
if (pmc->nViewCols < 1) pmc->nViewCols = 1; if (pmc->nViewRows < 1) pmc->nViewRows = 1;
// Make sure we don't display more than CALMONTHMAX months
while ((pmc->nViewRows * pmc->nViewCols) > CALMONTHMAX) { if (pmc->nViewRows > pmc->nViewCols) pmc->nViewRows--; else pmc->nViewCols--; }
// RC for the months, centered within the client window
dCal = pmc->nViewCols * (pmc->dxMonth + CALBORDER) - CALBORDER; pmc->rcCentered.left = (dx - dCal) / 2; if (pmc->rcCentered.left < 0) pmc->rcCentered.left = 0; pmc->rcCentered.right = pmc->rcCentered.left + dCal;
dCal = pmc->nViewRows * (pmc->dyMonth + CALBORDER) - CALBORDER + pmc->dyToday; pmc->rcCentered.top = (dy - dCal) / 2; if (pmc->rcCentered.top < 0) pmc->rcCentered.top = 0; pmc->rcCentered.bottom = pmc->rcCentered.top + dCal;
// Calculate and set RCs for the spin buttons
rc.top = pmc->rcCentered.top + (pmc->dyRow * 2 - pmc->dyCalArrow) /2; rc.bottom = rc.top + pmc->dyCalArrow;
rc.left = pmc->rcCentered.left + pmc->dxArrowMargin; rc.right = rc.left + pmc->dxCalArrow; pmc->rcPrev = rc;
rc.right = pmc->rcCentered.right - pmc->dxArrowMargin; rc.left = rc.right - pmc->dxCalArrow; pmc->rcNext = rc; }
LRESULT MCSizeHandler(MONTHCAL *pmc, RECT *prc) { int nMax; SYSTEMTIME st; int cmo, dmo;
MCRecomputeSizing(pmc, prc);
nMax = pmc->nViewRows * pmc->nViewCols;
// Compute new start date
CopyDate(pmc->stMonthFirst, st);
// REARCHITECT: this doesn't consider stEndSel
cmo = (pmc->stMonthLast.wYear - (int)pmc->st.wYear) * 12 + (pmc->stMonthLast.wMonth - (int)pmc->st.wMonth); dmo = nMax - pmc->nMonths;
if (-dmo > cmo) { // Selected mon/yr not in view
IncrSystemTime(&st, &st, -(cmo + dmo), INCRSYS_MONTH); cmo = 0; }
// If the # of months being displayed has changed, then lets try to
// start the calendar from January.
if ((dmo != 0) && (cmo + dmo >= pmc->stMonthFirst.wMonth - 1)) st.wMonth = 1;
MCUpdateStartEndDates(pmc, &st);
InvalidateRect(pmc->ci.hwnd, NULL, TRUE); UpdateWindow(pmc->ci.hwnd);
return(0); }
//
// For each month being displayed, compute the precise locations of all
// the gizmos we draw into the month header area.
//
void MCUpdateMonthNamePos(MONTHCAL *pmc) { HDC hdc; int iCount; SYSTEMTIME st; TCHAR rgch[64]; SIZE size; HGDIOBJ hfontOrig;
hdc = GetDC(pmc->ci.hwnd); hfontOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
st = pmc->stMonthFirst;
for (iCount = 0; iCount < pmc->nMonths; iCount++) { PMONTHMETRICS pmm = &pmc->rgmm[iCount]; int i;
MCGetMonthFormat(pmc, &st, rgch, ARRAYSIZE(rgch), pmm);
GetTextExtentPoint32(hdc, rgch, lstrlen(rgch), &size); pmm->rgi[IMM_START] = (pmc->dxMonth - size.cx) / 2;
//
// Now convert the indices into pixels so we can figure out where
// all the strings ended up.
//
for (i = IMM_DATEFIRST; i <= IMM_DATELAST; i++) { SIZE sizeT; // In case of horrible error, pretend the marker was at the
// beginning of the string.
sizeT.cx = 0; GetTextExtentPoint32(hdc, rgch, pmm->rgi[i], &sizeT); pmm->rgi[i] = pmm->rgi[IMM_START] + sizeT.cx; }
//
// Now flip the coordinates for RTL.
//
if (pmc->fHeaderRTL || IS_WINDOW_RTL_MIRRORED(pmc->ci.hwnd)) { int dxStart, dxEnd;
// Flip the month...
dxStart = pmm->rgi[IMM_MONTHSTART] - pmm->rgi[IMM_START]; dxEnd = pmm->rgi[IMM_MONTHEND ] - pmm->rgi[IMM_START]; pmm->rgi[IMM_MONTHSTART] = pmm->rgi[IMM_START] + size.cx - dxEnd; pmm->rgi[IMM_MONTHEND ] = pmm->rgi[IMM_START] + size.cx - dxStart;
// Flip the year...
dxStart = pmm->rgi[IMM_YEARSTART] - pmm->rgi[IMM_START]; dxEnd = pmm->rgi[IMM_YEAREND ] - pmm->rgi[IMM_START]; pmm->rgi[IMM_YEARSTART] = pmm->rgi[IMM_START] + size.cx - dxEnd; pmm->rgi[IMM_YEAREND ] = pmm->rgi[IMM_START] + size.cx - dxStart;
}
// On to the next month
if(++st.wMonth > 12) { st.wMonth = 1; st.wYear++; } }
SelectObject(hdc, hfontOrig); ReleaseDC(pmc->ci.hwnd, hdc); }
/*
* Computes the following, given the number of rows & columns available: * stMonthFirst.wMonth * stMonthFirst.wYear * stMonthLast.wMonth * stMonthLast.wYear * nMonths * * Trashes *pstStart */ void MCUpdateStartEndDates(MONTHCAL *pmc, SYSTEMTIME *pstStart) { int iCount, iMonth, iYear; int nMonthsToEdge;
pmc->nMonths = pmc->nViewRows * pmc->nViewCols;
// make sure pstStart to pstStart+nMonths is within range
nMonthsToEdge = ((int)pmc->stMax.wYear - (int)pstStart->wYear) * 12 + ((int)pmc->stMax.wMonth - (int)pstStart->wMonth) + 1; if (nMonthsToEdge < pmc->nMonths) IncrSystemTime(pstStart, pstStart, nMonthsToEdge - pmc->nMonths, INCRSYS_MONTH);
if (CmpDate(pstStart, &pmc->stMin) < 0) { CopyDate(pmc->stMin, *pstStart); }
nMonthsToEdge = ((int)pmc->stMax.wYear - (int)pstStart->wYear) * 12 + ((int)pmc->stMax.wMonth - (int)pstStart->wMonth) + 1; if (nMonthsToEdge < pmc->nMonths) pmc->nMonths = nMonthsToEdge;
pmc->stMonthFirst.wYear = pstStart->wYear; pmc->stMonthFirst.wMonth = pstStart->wMonth; pmc->stMonthFirst.wDay = 1; if (CmpDate(&pmc->stMonthFirst, &pmc->stMin) < 0) { pmc->stMonthFirst.wDay = pmc->stMin.wDay; ASSERT(0==CmpDate(&pmc->stMonthFirst, &pmc->stMin)); }
// these ranges are CALMONTHMAX+2 and nMonths <= CALMONTHMAX, so we are safe
// index 0 corresponds to stViewFirst (DAYSTATE) info
// index 1..nMonths correspond to stMonthFirst..stMonthLast info
// index nMonths+1 corresponds to stViewLast (DAYSTATE) info
//
iYear = pmc->stMonthFirst.wYear; iMonth = pmc->stMonthFirst.wMonth - 1; if(iMonth == 0) { iMonth = 12; iYear--; } for (iCount = 0; iCount <= pmc->nMonths+1; iCount++) { int cdy, dow, ddow;
// number of days in this month
cdy = GetDaysForMonth(iYear, iMonth); pmc->rgcDay[iCount] = cdy;
// move to "this" month
if(++iMonth > 12) { iMonth = 1; iYear++; }
// last day of this month NOT visible when viewing NEXT month
dow = GetStartDowForMonth(iYear, iMonth); ddow = dow - pmc->li.dowStartWeek; if(ddow < 0) ddow += CALCOLMAX; pmc->rgnDayUL[iCount] = cdy - ddow; }
// we want to always have days visible on the previous month
if (pmc->rgnDayUL[0] == pmc->rgcDay[0]) pmc->rgnDayUL[0] -= CALCOLMAX;
IncrSystemTime(&pmc->stMonthFirst, &pmc->stMonthLast, pmc->nMonths - 1, INCRSYS_MONTH); pmc->stMonthLast.wDay = (WORD) pmc->rgcDay[pmc->nMonths]; if (pmc->fMaxYrSet && CmpDate(&pmc->stMonthLast, &pmc->stMax) > 0) { pmc->stMonthLast.wDay = pmc->stMax.wDay; ASSERT(0==CmpDate(&pmc->stMonthLast, &pmc->stMax)); } pmc->stViewFirst.wYear = pmc->stMonthFirst.wYear; pmc->stViewFirst.wMonth = pmc->stMonthFirst.wMonth - 1; if (pmc->stViewFirst.wMonth == 0) { pmc->stViewFirst.wMonth = 12; pmc->stViewFirst.wYear--; } pmc->stViewFirst.wDay = pmc->rgnDayUL[0] + 1;
pmc->stViewLast.wYear = pmc->stMonthLast.wYear; pmc->stViewLast.wMonth = pmc->stMonthLast.wMonth + 1; if (pmc->stViewLast.wMonth == 13) { pmc->stViewLast.wMonth = 1; pmc->stViewLast.wYear++; } // total days - (days in last month + remaining days in previous month)
pmc->stViewLast.wDay = CALROWMAX * CALCOLMAX - (pmc->rgcDay[pmc->nMonths] + pmc->rgcDay[pmc->nMonths-1] - pmc->rgnDayUL[pmc->nMonths-1]);
MCUpdateDayState(pmc); MCUpdateRcDayCur(pmc, &pmc->st); MCUpdateToday(pmc); MCUpdateMonthNamePos(pmc); }
void MCUpdateToday(MONTHCAL *pmc) { if (MonthCal_ShowTodayCircle(pmc)) { int iMonth;
iMonth = MCGetOffsetForYrMo(pmc, pmc->stToday.wYear, pmc->stToday.wMonth); if (iMonth < 0) { // today is not visible in the displayed months
pmc->fToday = FALSE; } else { int iDay;
// today is visible in the displayed months
pmc->fToday = TRUE; iDay = pmc->rgcDay[iMonth] - pmc->rgnDayUL[iMonth] + pmc->stToday.wDay - 1; pmc->iMonthToday = iMonth; pmc->iRowToday = iDay / CALCOLMAX; pmc->iColToday = iDay % CALCOLMAX; } } }
BOOL FUpdateRcDayCur(MONTHCAL *pmc, POINT pt) { int iRow, iCol; RECT rc; SYSTEMTIME st; if (!FGetDateForPt(pmc, pt, &st, NULL, &iCol, &iRow, &rc)) return FALSE;
if (CmpDate(&st, &pmc->stMin) < 0) return FALSE;
if (CmpDate(&st, &pmc->stMax) > 0) return FALSE;
// calculate the day rc
pmc->rcDayCur.left = rc.left + pmc->rcDayNum.left + iCol * pmc->dxCol; pmc->rcDayCur.top = rc.top + pmc->rcDayNum.top + iRow * pmc->dyRow; pmc->rcDayCur.right = pmc->rcDayCur.left + pmc->dxCol; pmc->rcDayCur.bottom = pmc->rcDayCur.top + pmc->dyRow;
return(TRUE); }
void MCUpdateDayState(MONTHCAL *pmc) { HWND hwndParent;
if (!MonthCal_IsDayState(pmc)) return;
hwndParent = GetParent(pmc->ci.hwnd); if (hwndParent) { int i, mon, yr, cmonths;
yr = pmc->stViewFirst.wYear; mon = pmc->stViewFirst.wMonth; cmonths = pmc->nMonths + 2;
// don't do anything unless we need to
if (cmonths != pmc->cds || mon != pmc->dsMonth || yr != pmc->dsYear) { // this is a small enough to not deal with allocating it
NMDAYSTATE nmds; MONTHDAYSTATE buffer[CALMONTHMAX+2];
ZeroMemory(&nmds, SIZEOF(nmds)); nmds.stStart.wYear = (WORD) yr; nmds.stStart.wMonth = (WORD) mon; nmds.stStart.wDay = 1; nmds.cDayState = cmonths; nmds.prgDayState = buffer;
CCSendNotify(&pmc->ci, MCN_GETDAYSTATE, &nmds.nmhdr);
for (i = 0; i < cmonths; i++) pmc->rgdayState[i] = nmds.prgDayState[i];
pmc->cds = cmonths; pmc->dsMonth = mon; pmc->dsYear = yr; } } }
void MCNotifySelChange(MONTHCAL *pmc, UINT uMsg) { HWND hwndParent;
if (pmc->fNoNotify) return;
hwndParent = GetParent(pmc->ci.hwnd); if (hwndParent) { NMSELCHANGE nmsc; ZeroMemory(&nmsc, SIZEOF(nmsc));
CopyDate(pmc->st, nmsc.stSelStart); if (MonthCal_IsMultiSelect(pmc)) CopyDate(pmc->stEndSel, nmsc.stSelEnd);
CCSendNotify(&pmc->ci, uMsg, &nmsc.nmhdr); } }
void MCUpdateRcDayCur(MONTHCAL *pmc, SYSTEMTIME *pst) { int iOff;
iOff = MCGetOffsetForYrMo(pmc, pst->wYear, pst->wMonth); if (iOff >= 0) MCGetRcForDay(pmc, iOff, pst->wDay, &pmc->rcDayCur); }
// returns zero-based index into DISPLAYED months for month
// if month is not in DISPLAYED months, then -1 is returned...
int MCGetOffsetForYrMo(MONTHCAL *pmc, int iYear, int iMonth) { int iOff;
iOff = ((int)iYear - pmc->stMonthFirst.wYear) * 12 + (int)iMonth - pmc->stMonthFirst.wMonth;
if (iOff < 0 || iOff >= pmc->nMonths) return(-1);
return(iOff); }
// iMonth is a zero-based index relative to the DISPLAYED months.
// iDay is a 1-based index of the day of the month,
void MCGetRcForDay(MONTHCAL *pmc, int iMonth, int iDay, RECT *prc) { RECT rc; int iPlace, iRow, iCol;
MCGetRcForMonth(pmc, iMonth, &rc);
iPlace = pmc->rgcDay[iMonth] - pmc->rgnDayUL[iMonth] + iDay - 1; iRow = iPlace / CALCOLMAX; iCol = iPlace % CALCOLMAX;
prc->left = rc.left + pmc->rcDayNum.left + (pmc->dxCol * iCol); prc->top = rc.top + pmc->rcDayNum.top + (pmc->dyRow * iRow); prc->right = prc->left + pmc->dxCol; prc->bottom = prc->top + pmc->dyRow; }
//
// This routine gets the bounding rect for the iMonth of the displayed months.
// NOTE: iMonth is a zero-based index relative to the DISPLAYED months,
// counting along the rows.
//
void MCGetRcForMonth(MONTHCAL *pmc, int iMonth, RECT *prc) { int iRow, iCol, d;
iRow = iMonth / pmc->nViewCols; iCol = iMonth % pmc->nViewCols;
// intialize the rect to be the bounding rect for the month in the
// top left corner
prc->left = pmc->rcCentered.left; prc->right = prc->left + pmc->dxMonth; prc->top = pmc->rcCentered.top; prc->bottom = prc->top + pmc->dyMonth;
if (iCol) // slide the rect across to the correct column
{ d = (pmc->dxMonth + CALBORDER) * iCol; prc->left += d; prc->right += d; } if (iRow) // slide the rect down to the correct row
{ d = (pmc->dyMonth + CALBORDER) * iRow; prc->top += d; prc->bottom += d; } }
// Changes starting month by nDelta
// returns number of months actually changed
int FIncrStartMonth(MONTHCAL *pmc, int nDelta, BOOL fNoCurDayChange) { SYSTEMTIME stStart;
int nOldStartYear = pmc->stMonthFirst.wYear; int nOldStartMonth = pmc->stMonthFirst.wMonth;
IncrSystemTime(&pmc->stMonthFirst, &stStart, nDelta, INCRSYS_MONTH);
// MCUpdateStartEndDates takes stMin/stMax into account
MCUpdateStartEndDates(pmc, &stStart);
if (!fNoCurDayChange) { int cday;
// REARCHITECT: we arbitrarily set the currently selected day
// to be in the new stMonthFirst, but given the way the
// control works, I doubt we ever hit this code. what's it for??
if (MonthCal_IsMultiSelect(pmc)) cday = DaysBetweenDates(&pmc->st, &pmc->stEndSel);
// need to set date for focus here
pmc->st.wMonth = pmc->stMonthFirst.wMonth; pmc->st.wYear = pmc->stMonthFirst.wYear;
// Check to see if the day is in range, eg, Jan 31 -> Feb 28
if (pmc->st.wDay > pmc->rgcDay[1]) pmc->st.wDay = (WORD) pmc->rgcDay[1];
if (MonthCal_IsMultiSelect(pmc)) IncrSystemTime(&pmc->st, &pmc->stEndSel, cday, INCRSYS_DAY);
MCNotifySelChange(pmc, MCN_SELCHANGE);
MCUpdateRcDayCur(pmc, &pmc->st); }
MCInvalidateMonthDays(pmc);
return((pmc->stMonthFirst.wYear-nOldStartYear)*12 + (pmc->stMonthFirst.wMonth-nOldStartMonth)); }
// FIncrStartMonth with a beep when it doesn't change.
int MCIncrStartMonth(MONTHCAL *pmc, int nDelta, BOOL fDelayDayChange) { int cmoSpun;
// FIncrStartMonth takes stMin/stMax into account
cmoSpun = FIncrStartMonth(pmc, nDelta, fDelayDayChange);
if (cmoSpun==0) MessageBeep(0);
return(cmoSpun); }
//
// Determines in which month the given point lies. In other words, if the
// calendar control is currently sized to show six months, this routine
// determines in which which of those six months the point lies. It returns
// the zero based index of the month, counting along the rows.
//
BOOL FGetOffsetForPt(MONTHCAL *pmc, POINT pt, int *piOffset) { int iRow, iCol, i;
// check to see if point is within the centered months
if (!PtInRect(&pmc->rcCentered, pt)) return(FALSE);
// calculate the month row and column
// (we're really fudging a little here, since the point could
// actually be within the space between months...)
iCol = (pt.x - pmc->rcCentered.left) / (pmc->dxMonth + CALBORDER); iRow = (pt.y - pmc->rcCentered.top) / (pmc->dyMonth + CALBORDER);
i = iRow * pmc->nViewCols + iCol; if (i >= pmc->nMonths) return(FALSE);
*piOffset = i;
return(TRUE); }
//
// This routine returns the row and column of day containing the given point
//
BOOL FGetRowColForRelPt(MONTHCAL *pmc, POINT ptRel, int *piRow, int *piCol) { if (!PtInRect(&pmc->rcDayNum, ptRel)) return(FALSE);
ptRel.x -= pmc->rcDayNum.left; ptRel.y -= pmc->rcDayNum.top;
*piCol = ptRel.x / pmc->dxCol; *piRow = ptRel.y / pmc->dyRow;
return(TRUE); }
//
// This routine returns the month and year of the iMonth in the displayed
// months. NOTE: iMonth is a zero-based index of the displayed months
//
void GetYrMoForOffset(MONTHCAL *pmc, int iMonth, int *piYear, int *piMonth) { SYSTEMTIME st;
st.wDay = 1; st.wMonth = pmc->stMonthFirst.wMonth; st.wYear = pmc->stMonthFirst.wYear; IncrSystemTime(&st, &st, iMonth, INCRSYS_MONTH);
*piYear = st.wYear; *piMonth = st.wMonth; }
//
// This routine returns, the day, month, and year of day containing the
// given point. It will optionally return the day of the month, the row and
// column in the month, and the bounding rect of the month containing the point.
// NOTE: the day returned in piDay can be less than 1 (to indicate a day in the
// previous month) or greater than the number of days in the month (to indicate
// a day in the next month).
//
BOOL FGetDateForPt(MONTHCAL *pmc, POINT pt, SYSTEMTIME *pst, int *piDay, int* piCol, int* piRow, LPRECT prcMonth) { int iOff, iRow, iCol, iDay, iMon, iYear; RECT rcMonth;
if (!FGetOffsetForPt(pmc, pt, &iOff)) return(FALSE);
MCGetRcForMonth(pmc, iOff, &rcMonth); pt.x -= rcMonth.left; pt.y -= rcMonth.top; if (!FGetRowColForRelPt(pmc, pt, &iRow, &iCol)) return(FALSE);
// get the day containing the point by subtracting the number of days
// that are visible from the previous month, and then add one, since
// we are zero-based and the days of the month are 1-based.
//
iDay = iRow * CALCOLMAX + iCol - (pmc->rgcDay[iOff] - pmc->rgnDayUL[iOff]) + 1; if (piDay) *piDay = iDay; if (iDay <= 0) { if (iOff) return(FALSE); // dont accept days in prev month unless
// this happens to be the first month
iDay += pmc->rgcDay[iOff]; // add the cnt of days in the prev month,
--iOff; // then incr the month to get day in new month
} else if (iDay > pmc->rgcDay[iOff+1]) { if (iOff < (pmc->nMonths - 1)) // dont accept days in next month unless
return(FALSE); // this happens to be the last month
++iOff; // increment the month, and then sub the
iDay -= pmc->rgcDay[iOff]; // count of days to get day in new month
}
GetYrMoForOffset(pmc, iOff, &iYear, &iMon); pst->wDay = (WORD) iDay; pst->wMonth = (WORD) iMon; pst->wYear = (WORD) iYear;
if (piCol) *piCol = iCol; if (piRow) *piRow = iRow; if (prcMonth) *prcMonth = rcMonth; return(TRUE); }
BOOL MCSetDate(MONTHCAL *pmc, SYSTEMTIME *pst) { int nDelta = 0; //
// Can't set date outside of min/max range
//
if (CmpDate(pst, &pmc->stMin) < 0) return FALSE; if (CmpDate(pst, &pmc->stMax) > 0) return FALSE;
//
// Set new day
//
pmc->st = *pst; if (MonthCal_IsMultiSelect(pmc)) pmc->stEndSel = *pst;
FScrollIntoView(pmc); MCNotifySelChange(pmc, MCN_SELCHANGE);
MCUpdateRcDayCur(pmc, pst);
return(TRUE); }
void MCSetToday(MONTHCAL* pmc, SYSTEMTIME* pst) { SYSTEMTIME st; RECT rc; if (!pst) { GetLocalTime(&st); pmc->fTodaySet = FALSE; } else { st = *pst; pmc->fTodaySet = TRUE; } if (CmpDate(&st, &pmc->stToday) != 0) { MCGetRcForDay(pmc, pmc->iMonthToday, pmc->stToday.wDay, &rc); InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
pmc->stToday = st;
MCUpdateToday(pmc);
MCGetRcForDay(pmc, pmc->iMonthToday, pmc->stToday.wDay, &rc); InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
if (MonthCal_ShowToday(pmc)) { MCGetTodayBtnRect(pmc, &rc); InvalidateRect(pmc->ci.hwnd, &rc, FALSE); }
UpdateWindow(pmc->ci.hwnd); } }
LRESULT MCHandleTimer(MONTHCAL *pmc, WPARAM wParam) { if (wParam == CAL_IDAUTOSPIN) { int nDelta = pmc->fMonthDelta ? pmc->nMonthDelta : pmc->nMonths;
// REARCHITECT: pass last parameter TRUE if multiselect! else you
// can't multiselect across months
MCIncrStartMonth(pmc, (pmc->fSpinPrev ? -nDelta : nDelta), FALSE);
if (pmc->idTimer == 0) pmc->idTimer = SetTimer(pmc->ci.hwnd, CAL_IDAUTOSPIN, CAL_MSECAUTOSPIN, NULL);
pmc->rcDayOld = pmc->rcDayCur; UpdateWindow(pmc->ci.hwnd); } else if (wParam == CAL_TODAYTIMER) { if (!pmc->fTodaySet) MCSetToday(pmc, NULL); } MCNotifySelChange(pmc, MCN_SELCHANGE); // our date has changed
return((LRESULT)TRUE); }
void MCInvalidateDates(MONTHCAL *pmc, SYSTEMTIME *pst1, SYSTEMTIME *pst2) { int iMonth, ioff, icol, irow; RECT rc, rcMonth; SYSTEMTIME st, stEnd;
if (CmpDate(pst1, &pmc->stViewLast) > 0 || CmpDate(pst2, &pmc->stViewFirst) < 0) return; if (CmpDate(pst1, &pmc->stViewFirst) < 0) CopyDate(pmc->stViewFirst, st); else CopyDate(*pst1, st);
if (CmpDate(pst2, &pmc->stViewLast) > 0) CopyDate(pmc->stViewLast, stEnd); else CopyDate(*pst2, stEnd);
iMonth = MCGetOffsetForYrMo(pmc, st.wYear, st.wMonth); if (iMonth == -1) { if (st.wMonth == pmc->stViewFirst.wMonth) { iMonth = 0; ioff = st.wDay - pmc->rgnDayUL[0] - 1; } else { iMonth = pmc->nMonths - 1; ioff = st.wDay + pmc->rgcDay[pmc->nMonths] + pmc->rgcDay[iMonth] - pmc->rgnDayUL[iMonth] - 1; } } else { ioff = st.wDay + (pmc->rgcDay[iMonth] - pmc->rgnDayUL[iMonth]) - 1; }
MCGetRcForMonth(pmc, iMonth, &rcMonth);
// TODO: make it more efficient...
while (CmpDate(&st, &stEnd) <= 0) { irow = ioff / CALCOLMAX; icol = ioff % CALCOLMAX; rc.left = rcMonth.left + pmc->rcDayNum.left + (pmc->dxCol * icol); rc.top = rcMonth.top + pmc->rcDayNum.top + (pmc->dyRow * irow); rc.right = rc.left + pmc->dxCol; rc.bottom = rc.top + pmc->dyRow;
InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
IncrSystemTime(&st, &st, 1, INCRSYS_DAY); ioff++;
if (st.wDay == 1) { if (st.wMonth != pmc->stMonthFirst.wMonth && st.wMonth != pmc->stViewLast.wMonth) { iMonth++; MCGetRcForMonth(pmc, iMonth, &rcMonth);
ioff = ioff % CALCOLMAX; } } } }
void MCHandleMultiSelect(MONTHCAL *pmc, SYSTEMTIME *pst) { int i; DWORD cday; SYSTEMTIME stStart, stEnd;
if (!pmc->fMultiSelecting) { CopyDate(*pst, stStart); CopyDate(*pst, stEnd);
pmc->fMultiSelecting = TRUE; pmc->fForwardSelect = TRUE;
CopyDate(pmc->st, pmc->stStartPrev); CopyDate(pmc->stEndSel, pmc->stEndPrev); } else { if (pmc->fForwardSelect) { i = CmpDate(pst, &pmc->st); if (i >= 0) { CopyDate(pmc->st, stStart); CopyDate(*pst, stEnd); } else { CopyDate(*pst, stStart); CopyDate(pmc->st, stEnd); pmc->fForwardSelect = FALSE; } } else { i = CmpDate(pst, &pmc->stEndSel); if (i < 0) { CopyDate(*pst, stStart); CopyDate(pmc->stEndSel, stEnd); } else { CopyDate(pmc->stEndSel, stStart); CopyDate(*pst, stEnd); pmc->fForwardSelect = TRUE; } } } // check to make sure not exceeding cSelMax
cday = DaysBetweenDates(&stStart, &stEnd) + 1; if (cday > pmc->cSelMax) { if (pmc->fForwardSelect) IncrSystemTime(&stStart, &stEnd, pmc->cSelMax - 1, INCRSYS_DAY); else IncrSystemTime(&stEnd, &stStart, 1 - pmc->cSelMax, INCRSYS_DAY); }
if (0 == CmpDate(&stStart, &pmc->st) && 0 == CmpDate(&stEnd, &pmc->stEndSel)) return;
// TODO: do this more effeciently..
MCInvalidateDates(pmc, &pmc->st, &pmc->stEndSel); MCInvalidateDates(pmc, &stStart, &stEnd);
CopyDate(stStart, pmc->st); CopyDate(stEnd, pmc->stEndSel);
MCNotifySelChange(pmc, MCN_SELCHANGE);
UpdateWindow(pmc->ci.hwnd); }
void MCGotoToday(MONTHCAL *pmc) { pmc->rcDayOld = pmc->rcDayCur;
// force old selection to get repainted
if (MonthCal_IsMultiSelect(pmc)) MCInvalidateDates(pmc, &pmc->st, &pmc->stEndSel); else InvalidateRect(pmc->ci.hwnd, &pmc->rcDayOld, FALSE);
MCSetDate(pmc, &pmc->stToday); MCNotifySelChange(pmc, MCN_SELECT); // force new selection to get repainted
InvalidateRect(pmc->ci.hwnd, &pmc->rcDayCur, FALSE); UpdateWindow(pmc->ci.hwnd); }
LRESULT MCContextMenu(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam) { POINT pt; int click;
if (!pmc->fEnabled || !MonthCal_ShowToday(pmc)) return(0);
// ignore double click since this makes us advance twice
// since we already had a leftdown before the leftdblclk
if (!pmc->fCapture) { pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam);
//
// If the context menu was generated from the keyboard,
// then put it at the focus rectangle.
//
if (pt.x == -1 && pt.y == -1) { pt.x = (pmc->rcDayCur.left + pmc->rcDayCur.right ) / 2; pt.y = (pmc->rcDayCur.top + pmc->rcDayCur.bottom) / 2; ClientToScreen(pmc->ci.hwnd, &pt); }
click = TrackPopupMenu(pmc->hmenuCtxt, TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, pt.x, pt.y, 0, pmc->ci.hwnd, NULL); if (click >= 1) MCGotoToday(pmc); }
return(0); }
//
// Computes the bounding rects for the month and the year in the title area of
// the month.
//
void MCGetTitleRcsForOffset(MONTHCAL* pmc, int iOffset, LPRECT prcMonth, LPRECT prcYear) { RECT rcT; RECT rc; MCGetRcForMonth(pmc, iOffset, &rc); rcT.top = rc.top + (pmc->dyRow / 2); rcT.bottom = rcT.top + pmc->dyRow;
rcT.left = rc.left + pmc->rcMonthName.left + pmc->rgmm[iOffset].rgi[IMM_MONTHSTART]; rcT.right = rc.left + pmc->rcMonthName.left + pmc->rgmm[iOffset].rgi[IMM_MONTHEND]; *prcMonth = rcT;
rcT.left = rc.left + pmc->rcMonthName.left + pmc->rgmm[iOffset].rgi[IMM_YEARSTART]; rcT.right = rc.left + pmc->rcMonthName.left + pmc->rgmm[iOffset].rgi[IMM_YEAREND]; *prcYear = rcT;
}
LRESULT MCLButtonDown(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam) { HDC hdc; POINT pt; SYSTEMTIME st; RECT rc, rcCal; BOOL fShow; MSG msg; int offset, imonth, iyear;
if (!pmc->fEnabled) return(0);
pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); // treat a shift click like an LMouseDown at the prev location and
// a MouseMove to the new location
if (MonthCal_IsMultiSelect(pmc) && ((wParam & MK_SHIFT) == MK_SHIFT) && (!PtInRect(&pmc->rcDayCur, pt))) { SetCapture(pmc->ci.hwnd); pmc->fCapture = TRUE; pmc->fForwardSelect = (CmpDate(&pmc->stAnchor, &pmc->st) != 0) ? FALSE : TRUE; pmc->fMultiSelecting = TRUE;
hdc = GetDC(pmc->ci.hwnd); DrawFocusRect(hdc, &pmc->rcDayCur); // draw focus rect
pmc->fFocusDrawn = TRUE; ReleaseDC(pmc->ci.hwnd, hdc); MCMouseMove(pmc, wParam, lParam); // draw the highlight to new date
return 0; } // ignore double click since this makes us advance twice
// since we already had a leftdown before the leftdblclk
if (!pmc->fCapture) { SetCapture(pmc->ci.hwnd); pmc->fCapture = TRUE;
// check for spin buttons
if ((pmc->fSpinPrev = (WORD) PtInRect(&pmc->rcPrev, pt)) || PtInRect(&pmc->rcNext, pt)) { MCHandleTimer(pmc, CAL_IDAUTOSPIN);
return(0); } // check for valid day
pmc->rcDayOld = pmc->rcDayCur; // rcDayCur should always be valid now
if (MonthCal_IsMultiSelect(pmc)) { // need to cache these values because these are how
// we determine if the selection has changed and we
// need to notify the parent
CopyDate(pmc->st, pmc->stStartPrev); CopyDate(pmc->stEndSel, pmc->stEndPrev); }
if (FUpdateRcDayCur(pmc, pt)) { if (MonthCal_IsMultiSelect(pmc)) { if (FGetDateForPt(pmc, pt, &st, NULL, NULL, NULL, NULL)) MCHandleMultiSelect(pmc, &st); }
hdc = GetDC(pmc->ci.hwnd); DrawFocusRect(hdc, &pmc->rcDayCur); // draw focus rect
pmc->fFocusDrawn = TRUE; ReleaseDC(pmc->ci.hwnd, hdc);
CopyDate(st, pmc->stAnchor); // new Anchor point
} else { RECT rcMonth, rcYear; int delta, year, month; // is this a click in the today area...
if (MonthCal_ShowToday(pmc)) { MCGetTodayBtnRect(pmc, &rc); if (PtInRect(&rc, pt)) { CCReleaseCapture(&pmc->ci); pmc->fCapture = FALSE; MCGotoToday(pmc); return(0); } }
// figure out if the click was in a month name or a year
if (!FGetOffsetForPt(pmc, pt, &offset)) return(0);
GetYrMoForOffset(pmc, offset, &year, &month);
// calculate where the month name and year are,
// so we can figure out if they clicked in them...
MCGetTitleRcsForOffset(pmc, offset, &rcMonth, &rcYear); delta = 0; if (PtInRect(&rcMonth, pt)) { CCReleaseCapture(&pmc->ci); pmc->fCapture = FALSE;
ClientToScreen(pmc->ci.hwnd, &pt); imonth = TrackPopupMenu(pmc->hmenuMonth, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_RIGHTBUTTON, pt.x, pt.y, 0, pmc->ci.hwnd, NULL); if (imonth >= 1) delta = imonth - month; goto ChangeMonth; }
if (PtInRect(&rcYear, pt)) { HWND hwndEdit, hwndUD, hwndFocus; int yrMin, yrMax; DWORD dwExStyle = 0L; CCReleaseCapture(&pmc->ci); pmc->fCapture = FALSE;
//
// If the year is in a RTL string, then numeric control
// is to the left.
//
if (pmc->fHeaderRTL) { rcYear.left = (rcYear.right - (pmc->dxYearMax + 6)); } else { rcYear.right = rcYear.left + pmc->dxYearMax + 6; } rcYear.top--; rcYear.bottom++; if(((pmc->fHeaderRTL) && !(IS_WINDOW_RTL_MIRRORED(pmc->ci.hwnd))) || (!(pmc->fHeaderRTL) && (IS_WINDOW_RTL_MIRRORED(pmc->ci.hwnd)))) { // not mirrored force RTL, mirrored force LTR (for mirroring RTLis LTR!!)
dwExStyle|= WS_EX_RTLREADING; } hwndEdit = CreateWindowEx(dwExStyle, TEXT("EDIT"), NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_READONLY | ES_LEFT | ES_AUTOHSCROLL, rcYear.left, rcYear.top, rcYear.right - rcYear.left, rcYear.bottom - rcYear.top, pmc->ci.hwnd, (HMENU)0, pmc->hinstance, NULL); if (hwndEdit == NULL) return(0);
pmc->hwndEdit = hwndEdit;
SendMessage(hwndEdit, WM_SETFONT, (WPARAM)pmc->hfontBold, (LPARAM)FALSE); SendMessage(hwndEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, (LPARAM)MAKELONG(1, 1)); MCUpdateEditYear(pmc);
//
// Convert from Gregorian to display years.
//
year = GregorianToOther(&pmc->ct, year); yrMin = GregorianToOther(&pmc->ct, pmc->stMin.wYear); yrMax = 9999; if (pmc->fMaxYrSet) yrMax = GregorianToOther(&pmc->ct, pmc->stMax.wYear);
hwndUD = CreateUpDownControl( WS_CHILD | WS_VISIBLE | WS_BORDER | UDS_NOTHOUSANDS | UDS_ARROWKEYS,// | UDS_SETBUDDYINT,
pmc->fHeaderRTL ? (rcYear.left - 1 - (rcYear.bottom-rcYear.top)): (rcYear.right + 1), rcYear.top, rcYear.bottom - rcYear.top, rcYear.bottom - rcYear.top, pmc->ci.hwnd, 1, pmc->hinstance, hwndEdit, yrMax, yrMin, year); if (hwndUD == NULL) { DestroyWindow(hwndEdit); return(0); }
pmc->hwndUD = hwndUD;
hwndFocus = SetFocus(hwndEdit);
//
// Widen the area depending on the string direction.
//
if (pmc->fHeaderRTL) rcYear.left -= (1 + rcYear.bottom - rcYear.top); else rcYear.right += 1 + rcYear.bottom - rcYear.top; // Use MapWindowRect, It works in a mirrored and unmirrored windows.
MapWindowRect(pmc->ci.hwnd, NULL, (LPPOINT)&rcYear);
rcCal = pmc->rc; MapWindowRect(pmc->ci.hwnd, NULL, (LPPOINT)&rcCal);
fShow = TRUE;
while (fShow && GetFocus() == hwndEdit) { if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { // Check for events that cause the calendar to go away
if (msg.message == WM_KILLFOCUS || (msg.message >= WM_SYSKEYDOWN && msg.message <= WM_SYSDEADCHAR)) { fShow = FALSE; } else if ((msg.message == WM_LBUTTONDOWN || msg.message == WM_NCLBUTTONDOWN || msg.message == WM_RBUTTONDOWN || msg.message == WM_NCRBUTTONDOWN || msg.message == WM_MBUTTONDOWN || msg.message == WM_NCMBUTTONDOWN) && !PtInRect(&rcYear, msg.pt)) { fShow = FALSE; // if its a button down inside the calendar, eat it
// so the calendar doesn't do anything strange when
// the user is just trying to get rid of the year edit
if (PtInRect(&rcCal, msg.pt)) GetMessage(&msg, NULL, 0, 0); break; // do not dispatch
} else if (msg.message == WM_QUIT) { // Don't dispatch a WM_QUIT; leave it in the queue
break; // do not dispatch
} else if (msg.message == WM_CHAR) { if (msg.wParam == VK_ESCAPE) { goto NoYearChange; } else if (msg.wParam == VK_RETURN) { fShow = FALSE; } }
GetMessage(&msg, NULL, 0, 0); TranslateMessage(&msg); DispatchMessage(&msg); } else WaitMessage(); }
iyear = (int) SendMessage(hwndUD, UDM_GETPOS, 0, 0); if (HIWORD(iyear) == 0) delta = (iyear - year) * 12;
NoYearChange: DestroyWindow(hwndUD); DestroyWindow(hwndEdit);
pmc->hwndUD = NULL; pmc->hwndEdit = NULL;
UpdateWindow(pmc->ci.hwnd);
if (hwndFocus != NULL) SetFocus(hwndFocus); } ChangeMonth: if (delta != 0) { MCIncrStartMonth(pmc, delta, FALSE); MCNotifySelChange(pmc,MCN_SELCHANGE); } } }
return(0); }
LRESULT MCLButtonUp(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam) { HDC hdc; SYSTEMTIME st; POINT pt; if (pmc->fCapture) { CCReleaseCapture(&pmc->ci); pmc->fCapture = FALSE;
if (pmc->idTimer) { KillTimer(pmc->ci.hwnd, pmc->idTimer); pmc->idTimer = 0;
hdc = GetDC(pmc->ci.hwnd); MCPaintArrowBtn(pmc, hdc, pmc->fSpinPrev, FALSE); ReleaseDC(pmc->ci.hwnd, hdc);
return(0); }
if (pmc->fFocusDrawn) { hdc = GetDC(pmc->ci.hwnd); DrawFocusRect(hdc, &pmc->rcDayCur); // erase old focus rect
pmc->fFocusDrawn = FALSE; ReleaseDC(pmc->ci.hwnd, hdc); }
pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam);
if (MonthCal_IsMultiSelect(pmc)) { FUpdateRcDayCur(pmc, pt);
if (!EqualRect(&pmc->rcDayOld, &pmc->rcDayCur)) { if (FGetDateForPt(pmc, pt, &st, NULL, NULL, NULL, NULL)) MCHandleMultiSelect(pmc, &st); }
pmc->fMultiSelecting = FALSE; if (0 != CmpDate(&pmc->stStartPrev, &pmc->st) || 0 != CmpDate(&pmc->stEndPrev, &pmc->stEndSel)) { FScrollIntoView(pmc); } MCNotifySelChange(pmc, MCN_SELECT); } else { if (FUpdateRcDayCur(pmc, pt)) { if (!EqualRect(&pmc->rcDayOld, &pmc->rcDayCur) && (FGetDateForPt(pmc, pt, &st, NULL, NULL, NULL, NULL))) { InvalidateRect(pmc->ci.hwnd, &pmc->rcDayOld, FALSE); InvalidateRect(pmc->ci.hwnd, &pmc->rcDayCur, FALSE);
MCSetDate(pmc, &st); } MCNotifySelChange(pmc, MCN_SELECT); } } }
return(0); }
LRESULT MCMouseMove(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam) { BOOL fPrev; HDC hdc; POINT pt; SYSTEMTIME st;
if (pmc->fCapture) { pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam);
// check spin buttons
if ((fPrev = PtInRect(&pmc->rcPrev, pt)) || PtInRect(&pmc->rcNext, pt)) { if (pmc->idTimer == 0) { pmc->fSpinPrev = (WORD) fPrev; MCHandleTimer(pmc, CAL_IDAUTOSPIN); }
return(0); } else { hdc = GetDC(pmc->ci.hwnd);
if (pmc->idTimer) { KillTimer(pmc->ci.hwnd, pmc->idTimer); pmc->idTimer = 0; MCPaintArrowBtn(pmc, hdc, pmc->fSpinPrev, FALSE); } }
// check days
if (!PtInRect(&pmc->rcDayCur, pt)) { if (pmc->fFocusDrawn) DrawFocusRect(hdc, &pmc->rcDayCur); // erase focus rect
if (pmc->fFocusDrawn = (WORD) FUpdateRcDayCur(pmc, pt)) { // moved into a new valid day
if (pmc->fMultiSelecting) { if (FGetDateForPt(pmc, pt, &st, NULL, NULL, NULL, NULL)) MCHandleMultiSelect(pmc, &st); }
DrawFocusRect(hdc, &pmc->rcDayCur); } else { // moved into an invalid position
pmc->rcDayCur = pmc->rcDayOld; } } else if (!pmc->fFocusDrawn) { // handle case where we just moved back into rcDayCur from invalid area
DrawFocusRect(hdc, &pmc->rcDayCur); pmc->fFocusDrawn = TRUE; }
ReleaseDC(pmc->ci.hwnd, hdc); }
return(0); }
LRESULT MCHandleKeydown(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam) { LONG lIncrement; int iDirection; SYSTEMTIME st; BOOL fRet = FALSE; HDC hdc = NULL; RECT rcCurFocus;
// REARCHITECT: raymondc ERA - need to invalidate month title when selection
// moves in/out/within
switch (wParam) { case VK_CONTROL: pmc->fControl = TRUE; // we'll clear this on WM_KEYUP
return TRUE; break;
case VK_SHIFT: pmc->fShift = TRUE; // we'll clear this on WM_KEYUP
return TRUE; break; case VK_LEFT: // goto previous day
iDirection = -1; lIncrement = INCRSYS_DAY; break; case VK_RIGHT: // goto next day
iDirection = 1; lIncrement = INCRSYS_DAY; break;
case VK_UP: // goto previous week
iDirection = -1; lIncrement = INCRSYS_WEEK; break;
case VK_DOWN: // goto next week
iDirection = 1; lIncrement = INCRSYS_WEEK; break;
case VK_NEXT: iDirection = 1; if (pmc->fControl) // goto next year
lIncrement = INCRSYS_YEAR; else // goto next month
lIncrement = INCRSYS_MONTH; break;
case VK_PRIOR: iDirection = -1; if (pmc->fControl) // goto previous year
lIncrement = INCRSYS_YEAR; else lIncrement = INCRSYS_MONTH; // goto next month
break;
case VK_HOME: if (pmc->fControl) // goto first visible month
{ CopyDate(pmc->stMonthFirst, st); } else // goto first day of current month
{ CopyDate(pmc->st, st); st.wDay = 1; } goto setDate; break;
case VK_END: if (pmc->fControl) // goto last visible month
{ CopyDate(pmc->stMonthLast, st); } else // goto last day of current month
{ CopyDate(pmc->st, st); st.wDay = (WORD) GetDaysForMonth(st.wYear, st.wMonth); } goto setDate; break; default: return FALSE; }
// if we're multiselecting, we need to know which "end" of the selection
// the user is moving.
if (pmc->fMultiSelecting && pmc->fForwardSelect) CopyDate(pmc->stEndSel, st); else CopyDate(pmc->st, st);
IncrSystemTime(&st, &st, iDirection, lIncrement);
setDate:
// based on the window style and the shift key state,
// we'll do a multi-select (or not)
if (MonthCal_IsMultiSelect(pmc) && pmc->fShift) { pmc->fForwardSelect = (CmpDate(&pmc->st, &pmc->stAnchor) >= 0) ? TRUE : FALSE; pmc->fMultiSelecting = TRUE; }
// otherwise, we'll end multiselect, and set the new anchor
else { pmc->fMultiSelecting = FALSE; CopyDate(st, pmc->stAnchor); } if (pmc->fFocusDrawn) // erase the focus rect, but don't clear the bit
{ // so we know to put it back
hdc = GetDC(pmc->ci.hwnd); DrawFocusRect(hdc, &pmc->rcDayCur); ReleaseDC(pmc->ci.hwnd, hdc); rcCurFocus = pmc->rcDayCur; } else { pmc->rcDayOld = pmc->rcDayCur; }
if (MonthCal_IsMultiSelect(pmc)) { int nDelta = 0;
MCHandleMultiSelect(pmc, &st);
FScrollIntoView(pmc); } else if (fRet = MCSetDate(pmc, &st)) { InvalidateRect(pmc->ci.hwnd, &pmc->rcDayOld, FALSE); InvalidateRect(pmc->ci.hwnd, &pmc->rcDayCur, FALSE); UpdateWindow(pmc->ci.hwnd); } if (pmc->fFocusDrawn) // put the focus rect back
{ pmc->rcDayOld = pmc->rcDayCur; pmc->rcDayCur = rcCurFocus; hdc = GetDC(pmc->ci.hwnd); DrawFocusRect(hdc, &pmc->rcDayCur); ReleaseDC(pmc->ci.hwnd, hdc); }
return fRet; }
//
// Era information is kept in a DPA of LocalAlloc'd strings.
//
int MCDPAEnumCallback(LPVOID d, LPVOID p) { UNREFERENCED_PARAMETER(p); if (d) LocalFree(d); return TRUE; }
void MCDPADestroy(HDPA hdpa) { if (hdpa) DPA_DestroyCallback(hdpa, MCDPAEnumCallback, 0); }
//
// Collect era information.
//
// Since EnumCalendarInfo is not thread-safe, we have to take the critical
// section.
HDPA g_hdpaCal;
BOOL MCEnumCalInfoProc(LPWSTR psz) { LPWSTR pwszSave = StrDup(psz); if (pwszSave) { if (DPA_AppendPtr(g_hdpaCal, pwszSave) >= 0) { return TRUE; } LocalFree(pwszSave); }
//
// Out of memory. Bail.
//
MCDPADestroy(g_hdpaCal); g_hdpaCal = NULL; return FALSE; }
HDPA MCGetCalInfoDPA(CALID calid, CALTYPE calType) { HDPA hdpa = DPA_Create(4);
ENTERCRITICAL; ASSERT(g_hdpaCal == NULL); g_hdpaCal = hdpa; EnumCalendarInfoW(MCEnumCalInfoProc, LOCALE_USER_DEFAULT, calid, calType); hdpa = g_hdpaCal; g_hdpaCal = NULL; LEAVECRITICAL;
return hdpa; }
void MCFreeCalendarInfo(PCALENDARTYPE pct) { MCDPADestroy(pct->hdpaYears); MCDPADestroy(pct->hdpaEras); pct->hdpaYears = 0; pct->hdpaEras = 0; }
//
// Get all the era info and validate it so we don't fault when we try to
// use them.
//
BOOL MCGetEraInfo(PCALENDARTYPE pct) { int i;
pct->hdpaYears = MCGetCalInfoDPA(pct->calid, CAL_IYEAROFFSETRANGE); if (!pct->hdpaYears) goto Bad;
pct->hdpaEras = MCGetCalInfoDPA(pct->calid, CAL_SERASTRING); if (!pct->hdpaEras) goto Bad;
// There must be at least one era...
if (!DPA_GetPtrCount(pct->hdpaEras)) goto Bad;
// The number of eras must be equal to the number of era names
if (DPA_GetPtrCount(pct->hdpaEras) != DPA_GetPtrCount(pct->hdpaYears)) goto Bad;
// The era dates must be in descending order.
for (i = 1; i < DPA_GetPtrCount(pct->hdpaYears); i++) { if (StrToInt(DPA_FastGetPtr(pct->hdpaYears, i)) > StrToInt(DPA_FastGetPtr(pct->hdpaYears, i - 1))) goto Bad; } return TRUE;
Bad: /*
* Something went wrong, so clean up. */ MCFreeCalendarInfo(pct); return FALSE; }
//
// Check to see if this calendar is not supported currently
//
// Return FALSE for Hijri, Hebrew calendars, since these are
// Lunar calndars. This is hack so that this control behaves well when the calendar
// is any of the non-supported till we add this support to this control. [samera]
//
void MCGetCalendarInfo(PCALENDARTYPE pct) { TCHAR tchCalendar[32]; CALTYPE defCalendar = CAL_GREGORIAN;
if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ICALENDARTYPE, tchCalendar, ARRAYSIZE(tchCalendar))) { defCalendar = StrToInt(tchCalendar); }
//
// Start with a clean slate. Assume we don't have to do funky
// offset stuff (dyrOFfset = 0) or era stuff (hdpaEras = NULL),
// and that we don't need to do locale munging (LOCALE_USER_DEFAULT).
//
MCFreeCalendarInfo(pct); ZeroMemory(pct, SIZEOF(CALTYPE)); pct->calid = defCalendar; pct->lcid = LOCALE_USER_DEFAULT;
switch (pct->calid) { case CAL_GREGORIAN: case CAL_GREGORIAN_US: case CAL_GREGORIAN_ME_FRENCH: case CAL_GREGORIAN_ARABIC: case CAL_GREGORIAN_XLIT_ENGLISH: case CAL_GREGORIAN_XLIT_FRENCH: break; // Gregorian calendars are just fine
case CAL_JAPAN: case CAL_TAIWAN: //
// These are era calendars. Go get the era info. Get hdpaEras
// last so we can use it to test whether we have a supported era
// calendar.
//
// If not enough memory to support traditional calendar, then just
// force Gregorian. Hey, at least we display *something*.
//
if (!MCGetEraInfo(pct)) goto ForceGregorian; break;
case CAL_THAI: pct->dyrOffset = BUDDHIST_BIAS; // You Just Have To Know this number
break;
case CAL_KOREA: pct->dyrOffset = KOREAN_BIAS; // You Just Have To Know this number
break;
default: //
// If the calenday isn't supported, then treat it as Gregorian. [samera]
//
ForceGregorian: pct->calid = CAL_GREGORIAN; pct->lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); break; }
}
//
// Check whether the date string returned accorind to the
// current user-locale and calendar setting is Right-To-Left (RTL),
// and is so the RECTs for month year (@LBUTTONDOWN and NCHITTEST)
// needs to be adjusted. [samera]
//
BOOL MCIsDateStringRTL(TCHAR tch) { WORD wAttrib=0; LCID lcidUserDefault; BOOL fRTL = FALSE;
lcidUserDefault = GetUserDefaultLCID();
if (lcidUserDefault) { //
// Return TRUE if the 1st character is a RTL string.
// A RTL char followed by a european number will
// display visually as "european-num RTL-string" since the
// BiDi layout algorithm of the language-pack will do
// this. [samera]
//
if(GetStringTypeEx(lcidUserDefault, CT_CTYPE2, &tch, 1, &wAttrib)) { if(C2_RIGHTTOLEFT == wAttrib) { fRTL = TRUE; }
} }
return fRTL; }
////////////////////////////////////////////////////////////////////////////
//
// Date/Time Picker
//
////////////////////////////////////////////////////////////////////////////
//
// Subedit wrapper for the various weird things we need to get from NLS.
//
// SE_YEARALT means that the year field is only two digits wide
// in the format string, so we need to perform special Y2K enhancements.
// For these fields, the field is displayed in two-digit format, but
// when you go to edit the field, it temporarily changes to four-digit
// format so you can change the century too. Then when you complete the
// edit, it returns to two-digit format.
//
// The SE_YEARLIKE macro detects either SE_YEAR or SE_YEARALT.
//
// SE_MONTHALT is just like SE_MONTH except that it is used
// when the day (dd) comes before the month (mmm). This is
// important for languages like Russian where "12 October"
// and "October 12" use different strings for the word
// "October". There is no way to get the alternate string
// directly, except by creating a bogus date format that also
// has the day before the month, then throwing away the day.
//
// For example, if the incoming date string is
//
// "MMMM dd yyyy"
//
// we break it up into
//
// "MMMM" SE_MONTH
// " " SE_STATIC
// "dd" SE_DAY
// " " SE_STATIC
// "yyyy" SE_YEAR
//
// However, if the incoming date is
//
// "dd MMMM yyyy"
//
// we break it up into
//
// "dd" SE_DAY
// " " SE_STATIC
// "ddMMMM" SE_MONTHALT
// " " SE_STATIC
// "yyyy" SE_YEAR
//
// The extra "dd" at the beginning of SE_MONTHALT is stripped out below.
//
// Y2K weirdness: If we are getting the date format for the year,
// and the year is being edited (either due to a SUBEDIT_ALL or because
// it is the active subedit), and the format year is only
// two digits, then force a four-digit year for editing purposes.
//
//
void SEGetTimeDateFormat(LPSUBEDIT pse, LPSUBEDITCONTROL psec, LPTSTR pszBuf, DWORD cchBuf) { int cch;
ASSERT(cchBuf >= 2); // We assume it can hold at least space and a null
pszBuf[0] = TEXT('\0'); // In case something fails
if (pse->id == SE_MONTHALT) { TCHAR tszBuf[DTP_FORMATLENGTH + 3]; //
// When we parsed the date string and realized that we needed
// the Alternate Month format, we created a date format of
// the form "ddMMM...", where "MMM..." is the
// the original month format. Here we strip off the digits.
//
cch = GetDateFormat(psec->ct.lcid, 0, &psec->st, pse->pv, tszBuf, ARRAYSIZE(tszBuf)); if (cch >= 2) { // [msadek] For Hebrew calander, day format "dd" is actually
// two OR THREE characters. Don't hardcode it as 2.
int cchDay = GetDateFormat(psec->ct.lcid, 0, &psec->st, TEXT("dd"), NULL, 0); StringCchCopy(pszBuf, cchBuf, tszBuf + cchDay - 1); } } else if (pse->id == SE_YEARALT && (psec->iseCur == SUBEDIT_ALL || pse == &psec->pse[psec->iseCur])) { GetDateFormat(psec->ct.lcid, 0, &psec->st, TEXT("yyyy"), pszBuf, cchBuf); } else if (SE_DATELIKE(pse->id)) { GetDateFormat(psec->ct.lcid, 0, &psec->st, pse->pv, pszBuf, cchBuf);
// Change a blank era to a space so the user can see it
if (pse->id == SE_ERA && pszBuf[0] == TEXT('\0')) { pszBuf[0] = TEXT(' '); pszBuf[1] = TEXT('\0'); }
} else if (pse->id != SE_APP) { GetTimeFormat(LOCALE_USER_DEFAULT, 0, &psec->st, pse->pv, pszBuf, cchBuf); } else { NMDATETIMEFORMAT nmdtf = { 0 }; nmdtf.pszFormat = pse->pv; SECGetSystemtime(psec, &nmdtf.st); nmdtf.pszDisplay = nmdtf.szDisplay;
CCSendNotify(psec->pci, DTN_FORMAT, &nmdtf.nmhdr);
StringCchCopy(pszBuf, cchBuf, nmdtf.pszDisplay);
//
// If the parent is an ANSI window, and pszDisplay
// does not equal szDisplay, the then thunk had to
// allocated memory for pszDisplay. We need to
// free it here.
//
if (!psec->pci->bUnicode && nmdtf.pszDisplay && nmdtf.pszDisplay != nmdtf.szDisplay) { LocalFree ((LPSTR)nmdtf.pszDisplay); } } }
//
// SUBEDIT stuff for DateTimePicker
//
// NOTE: Now that the DatePicker and TimePicker are combined,
// this could be moved back into the parent structure.
//
//
// Used in an era calendar to get the length of the longest era name.
// Also leaves a random heigth in psize->cy because that's what the
// non-era code does, too.
//
int SECGetMaxEraLength(PCALENDARTYPE pct, HDC hdc, PSIZE psize) { int i; int wid = 0; for (i = 0; i < DPA_GetPtrCount(pct->hdpaEras); i++) { LPCTSTR ptsz = DPA_FastGetPtr(pct->hdpaEras, i); if (GetTextExtentPoint32(hdc, ptsz, lstrlen(ptsz), psize) && psize->cx > wid) { wid = psize->cx; } } return wid; }
// SECRecomputeSizing needs to calculate the maximum rectangle each subedit can be. Ugh.
//
// The size of the SE_YEARALT field changes depending on whether or not it is
// the current psec->iseCur. Double ugh.
void SECRecomputeSizing(LPSUBEDITCONTROL psec, LPRECT prc) { HDC hdc; HGDIOBJ hfontOrig; int i; LPSUBEDIT pse; int left = prc->left;
psec->rc = *prc;
hdc = GetDC(psec->pci->hwnd); hfontOrig = SelectObject(hdc, (HGDIOBJ)psec->hfont);
for (i=0, pse=psec->pse; i < psec->cse ; i++, pse++) { TCHAR szTmp[DTP_FORMATLENGTH]; LPCTSTR sz; int min, max; SIZE size; int wid;
min = pse->min; max = pse->max; if (pse->id == SE_STATIC) { ASSERT(pse->fReadOnly); sz = pse->pv; } else { sz = szTmp;
// make some assumptions so we don't loop more than we have to
switch (pse->id) { // we only need seven for the text days of the week
case SE_DAY: min = 10; // make them all double-digit
max = 17; break;
// Assume we only have numeric output with all chars same width
case SE_MARK: min = 11; max = 12; break;
case SE_HOUR: case SE_YEAR: case SE_YEARALT: case SE_MINUTE: case SE_SECOND: min = max; break;
case SE_ERA: if (ISERACALENDAR(&psec->ct)) { wid = SECGetMaxEraLength(&psec->ct, hdc, &size); goto HaveWidth; } else { min = max = *pse->pval; // current value is good enough
} break; } }
// now get max width
if (pse->id == SE_APP) { NMDATETIMEFORMATQUERY nmdtfq = {0};
nmdtfq.pszFormat = pse->pv;
CCSendNotify(psec->pci, DTN_FORMATQUERY, &nmdtfq.nmhdr);
size = nmdtfq.szMax; wid = nmdtfq.szMax.cx; } else { SYSTEMTIME st = psec->st;
/*
* SUBTLE - Munge the month/day to January 1. This solves * lots of problems, such as "Today is Feb 29 1996, and * when we iterate through year = 1997, we get Feb 29 1997, * which is invalid." Or "Today is Jan 31 1999, and when * we iterate through the months, we get Sep 31 1999, which * is invalid." * * We choose "January 1" because * * 1. Every year has a "January 1", so the year can vary. * 2. Every month has a "first", so the month can vary. * 3. Every day up to 31 is valid in January, so the day can vary. */ psec->st.wMonth = psec->st.wDay = 1;
for (wid = 0 ; min <= max ; min++) { if (pse->id != SE_STATIC) { *pse->pval = (WORD) min;
SEGetTimeDateFormat(pse, psec, szTmp, ARRAYSIZE(szTmp)); if (szTmp[0] == TEXT('\0')) { DebugMsg(TF_ERROR, TEXT("SECRecomputeSizing: GetDate/TimeFormat([%s] y=%d m=%d d=%d h=%d m=%d s=%d) = ERROR %d"), pse->pv, psec->st.wYear, psec->st.wMonth, psec->st.wDay, psec->st.wHour, psec->st.wMinute, psec->st.wSecond, GetLastError());
} } if (!GetTextExtentPoint32(hdc, sz, lstrlen(sz), &size)) { size.cx = 0; DebugMsg(TF_MONTHCAL,TEXT("SECRecomputeSizing: GetTextExtentPoint32(%s) = ERROR %d"), sz, GetLastError()); } if (size.cx > wid) wid = size.cx; } psec->st = st; } HaveWidth: // now set up subedit's bounding rectangle
pse->rc.top = prc->top + SECYBORDER; pse->rc.bottom = pse->rc.top + size.cy; pse->rc.left = left; pse->rc.right = left + wid; left = pse->rc.right; }
SelectObject(hdc, hfontOrig); ReleaseDC(psec->pci->hwnd, hdc); }
// InitSubEditControl parses szFormat into psec, setting the time to pst.
TCHAR c_szFormats[] = TEXT("gyMdthHmsX"); BOOL SECParseFormat(DATEPICK* pdp, LPSUBEDITCONTROL psec, LPCTSTR szFormat) { LPCTSTR pFmt; LPTSTR psecFmt; int cse, cchExtra; int nTmp; LPSUBEDIT pse; BOOL fDaySeen = FALSE; BOOL fForceCentury = FALSE; int iLen, i; TCHAR tch; LPTSTR pFmtTemp; TCHAR szFormatTemp[DTP_FORMATLENGTH]; //
// We need to force the century if the format is
// DTS_SHORTDATECENTURYFORMAT.
//
if (pdp->fLocale && (pdp->ci.style & DTS_FORMATMASK) == DTS_SHORTDATECENTURYFORMAT) { fForceCentury = TRUE; }
// [msadek]; If we need to mirror the format and the
// cleint passed a read only buffer, we will AV (W2k bug# 354533)
// Let's copy it first
if (psec->fMirrorSEC) { StringCchPrintf(szFormatTemp, ARRAYSIZE(szFormatTemp), szFormat); szFormat = szFormatTemp; } // count szFormat sections so we know what to allocate
pFmt = szFormat; cse = 0; cchExtra = 0; while (*pFmt) { if (StrChr(c_szFormats, *pFmt)) // format string
{ TCHAR c = *pFmt; while (c == *pFmt) pFmt++; cse++;
// If it was a string Month format, reserve 2 more chars for
// the possible "dd" leader, in case we need SE_MONTHALT.
if (c == TEXT('M')) cchExtra += 2;
} else if (*pFmt == TEXT('\'')) // quoted static string
{ KeepSearching: pFmt++; while (*pFmt && *pFmt != TEXT('\'')) pFmt++; if (*pFmt) // handle poorly quoted strings
{ pFmt++; if (*pFmt == TEXT('\'')) // quoted quote, not end of quote
goto KeepSearching; } cse++; } else // static string probably a delimiter
{ while (*pFmt && *pFmt!=TEXT('\'') && !StrChr(c_szFormats, *pFmt)) pFmt++; cse++; } }
// Allocate space
nTmp = cse + lstrlen(szFormat) + cchExtra + 1; // number of chars
nTmp = nTmp * sizeof(TCHAR); // size in BYTES
nTmp = ROUND_TO_POINTER(nTmp); // round up to POINTER boundary
psecFmt = (LPTSTR)LocalAlloc(LPTR, nTmp + cse * sizeof(SUBEDIT)); if (!psecFmt) { DebugMsg(TF_MONTHCAL, TEXT("SECParseFormat failed to allocate memory")); return FALSE; // use whatever we already have
} if (psec->szFormat) LocalFree(psec->szFormat); psec->szFormat = psecFmt; psec->cDelimeter = '\0'; psec->pse = (LPSUBEDIT)((LPBYTE)psecFmt + nTmp);
// Fill psec
psec->iseCur = SUBEDIT_NONE; psec->cse = cse; pse = psec->pse; ZeroMemory(pse, cse * sizeof(SUBEDIT)); pFmt = szFormat; pdp->fHasMark = FALSE;
//
// Before start parsing the format string, let's mirror it if requested.
//
if (psec->fMirrorSEC) { pFmtTemp = (LPTSTR)pFmt; iLen = lstrlen(pFmtTemp); for( i=0 ; i<iLen/2 ; i++ ) { tch = pFmtTemp[i]; pFmtTemp[i] = pFmtTemp[iLen-i-1]; pFmtTemp[iLen-i-1] = tch; } }
while (*pFmt) { pse->flDrawText = DT_CENTER;
if (*pFmt == TEXT('y') || *pFmt == TEXT('g')) // y=year g=era
{ TCHAR ch = *pFmt;
// If the calendar doesn't use eras, then the era field is just
// for show and can't be changed.
if (ch == TEXT('g') && !ISERACALENDAR(&psec->ct)) { pse->fReadOnly = TRUE; }
pse->id = ch == TEXT('y') ? SE_YEAR : SE_ERA; pse->pval = &psec->st.wYear; pse->min = c_stEpoch.wYear; pse->max = c_stArmageddon.wYear; pse->cchMax = 0; pse->pv = psecFmt; while (*pFmt == ch) { pse->cchMax++; *psecFmt++ = *pFmt++; }
if (pse->id == SE_YEAR) { pse->flDrawText = DT_RIGHT;
if (fForceCentury) { pse->pv = TEXT("yyyy"); pse->cchMax = 4; } else { switch (pse->cchMax) { case 1: // "y" is a SE_YEARALT
case 2: // "yy" is a SE_YEARALT
pse->id = SE_YEARALT; pse->cchMax = 4; // Force four-digit editing
break;
case 3: // "yyy" is an alias for "yyyy".
pse->cchMax = 4; break; } } }
*psecFmt++ = TEXT('\0'); } else if (*pFmt == TEXT('M')) // month
{ pse->pv = psecFmt;
// If the day has been seen, then we need to use the alternate
// month format, so set up the gratuitous "dd" prefix.
// See SEGetTimeDateFormat.
//
if (fDaySeen) { pse->id = SE_MONTHALT; *psecFmt++ = TEXT('d'); *psecFmt++ = TEXT('d'); } else { pse->id = SE_MONTH; } pse->pval = &psec->st.wMonth; pse->min = 1; pse->max = 12; pse->cchMax = 2;
while (*pFmt == TEXT('M')) *psecFmt++ = *pFmt++; if (psecFmt - pse->pv <= 2) pse->flDrawText = DT_RIGHT; *psecFmt++ = TEXT('\0'); } else if (*pFmt == TEXT('d')) // day or day of week
{ fDaySeen = TRUE; // See SEGetTimeDateFormat
pse->id = SE_DAY; pse->pval = &psec->st.wDay; pse->min = 1; pse->max = GetDaysForMonth(psec->st.wYear, psec->st.wMonth); pse->cchMax = 2;
pse->pv = psecFmt; while (*pFmt == TEXT('d')) *psecFmt++ = *pFmt++; if (psecFmt - pse->pv <= 2) pse->flDrawText = DT_RIGHT; // day
else pse->fReadOnly = TRUE; // day of week
*psecFmt++ = TEXT('\0');
} else if (*pFmt == TEXT('t')) // marker
{ pdp->fHasMark = TRUE; pse->id = SE_MARK; pse->pval = &psec->st.wHour; pse->min = 0; pse->max = 23; pse->cIncrement = 12; pse->cchMax = 2;
pse->pv = psecFmt; while (*pFmt == TEXT('t')) *psecFmt++ = *pFmt++; *psecFmt++ = TEXT('\0'); } else if (*pFmt == TEXT('h')) // (12) hour
{ pse->id = SE_HOUR; pse->pval = &psec->st.wHour; pse->min = 0; pse->max = 23; pse->cchMax = 2; pse->flDrawText = DT_RIGHT;
pse->pv = psecFmt; while (*pFmt == TEXT('h')) *psecFmt++ = *pFmt++; *psecFmt++ = TEXT('\0');
} else if (*pFmt == TEXT('H')) // (24) hour
{ pse->id = SE_HOUR; pse->pval = &psec->st.wHour; pse->min = 0; pse->max = 23; pse->cchMax = 2; pse->flDrawText = DT_RIGHT;
pse->pv = psecFmt; while (*pFmt == TEXT('H')) *psecFmt++ = *pFmt++; *psecFmt++ = TEXT('\0'); } else if (*pFmt == TEXT('m')) // minute
{ pse->id = SE_MINUTE; pse->pval = &psec->st.wMinute; pse->min = 0; pse->max = 59; pse->cchMax = 2; pse->flDrawText = DT_RIGHT;
pse->pv = psecFmt; while (*pFmt == TEXT('m')) *psecFmt++ = *pFmt++; *psecFmt++ = TEXT('\0'); } else if (*pFmt == TEXT('s')) // second
{ pse->id = SE_SECOND; pse->pval = &psec->st.wSecond; pse->min = 0; pse->max = 59; pse->cchMax = 2; pse->flDrawText = DT_RIGHT;
pse->pv = psecFmt; while (*pFmt == TEXT('s')) *psecFmt++ = *pFmt++; *psecFmt++ = TEXT('\0'); } else if (*pFmt == TEXT('X')) // app specified field
{ pse->id = SE_APP;
pse->pv = psecFmt; while (*pFmt == TEXT('X')) *psecFmt++ = *pFmt++; *psecFmt++ = TEXT('\0'); } else if (*pFmt == TEXT('\'')) // quoted static string
{ pse->id = SE_STATIC; pse->fReadOnly = TRUE;
pse->pv = psecFmt; SearchSomeMore: pFmt++; while (*pFmt && *pFmt != TEXT('\'')) *psecFmt++ = *pFmt++; if (*pFmt) // handle poorly quoted strings
{ pFmt++; if (*pFmt == TEXT('\'')) // quoted quote, not end of quote
{ *psecFmt++ = *pFmt; goto SearchSomeMore; } } *psecFmt++ = TEXT('\0'); } else // unknown non-editable stuff (most likely a delimeter)
{ // FEATURE: even though it's unknown, we should probably pass
// it off to GetDateFormat so we will be forward compatible
// with future date formats...
//
pse->id = SE_STATIC; pse->fReadOnly = TRUE;
if (!psec->cDelimeter) psec->cDelimeter = *pFmt;
pse->pv = psecFmt; while (*pFmt && *pFmt!=TEXT('\'') && !StrChr(c_szFormats, *pFmt)) *psecFmt++ = *pFmt++; *psecFmt++ = TEXT('\0');
// we'll assume that the first not formatting char is the
// delimeter...maybe not a great assumption, but it will work
// most of the time.
//
} pse++; }
#ifdef DEBUG
{ TCHAR sz[200]; LPTSTR psz; psz = sz; sz[0]=TEXT('\0'); pse = psec->pse; cse = psec->cse; while (cse > 0) { StringCchPrintf(psz, ARRAYSIZE(sz), TEXT("[%s] "), pse->pv); psz = psz + lstrlen(psz); cse--; pse++; } DebugMsg(TF_MONTHCAL, TEXT("SECParseFormat: %s"), sz); } #endif
//
// Let restore the original format
//
if (psec->fMirrorSEC) { pFmtTemp = (LPTSTR)szFormat; for( i=0 ; i<iLen/2 ; i++ ) { tch = pFmtTemp[i]; pFmtTemp[i] = pFmtTemp[iLen-i-1]; pFmtTemp[iLen-i-1] = tch; } }
//
// If this is a time-only DTP control and we need to swap the AM/PM symbol
// to the other side, then let's do it.
//
if (psec->fSwapTimeMarker) { SUBEDIT se; pse = psec->pse; cse = psec->cse;
if ((cse > 1) && (psec->pse[0].id == SE_MARK)) { se = psec->pse[0]; i = 0; while( i < (cse-1) ) { pse[i] = pse[i+1]; i++; } pse[psec->cse-1] = se; } }
// The subedits have changed, recompute sizes
SECRecomputeSizing(psec, &psec->rc);
// We're going to need to redraw this
InvalidateRect(psec->pci->hwnd, NULL, TRUE);
// Changing the format also changes the window text.
NotifyWinEvent(EVENT_OBJECT_VALUECHANGE, pdp->ci.hwnd, OBJID_CLIENT, INDEXID_CONTAINER); return TRUE; }
void SECDestroy(LPSUBEDITCONTROL psec) { if (psec->szFormat) { LocalFree(psec->szFormat); psec->szFormat = NULL; } } void SECSetFont(LPSUBEDITCONTROL psec, HFONT hfont) { if (hfont == NULL) hfont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); psec->hfont = hfont; }
void InvalidateScrollRect(HWND hwnd, RECT *prc, int xScroll) { RECT rc;
if (xScroll) { rc = *prc; OffsetRect(&rc, -xScroll, 0); prc = &rc; } InvalidateRect(hwnd, prc, TRUE); }
void SECSaveResetSubeditEdit(DATEPICK *pdp, BOOL fReset); #define SECSaveSubeditEdit(pdp) SECSaveResetSubeditEdit(pdp, FALSE)
#define SECResetSubeditEdit(pdp) SECSaveResetSubeditEdit(pdp, TRUE)
// Set the current subedit, scrolling things into view as needed
void SECSetCurSubed(DATEPICK *pdp, int isubed) { LPSUBEDITCONTROL psec = &pdp->sec;
// validate the arguments
ASSERT(isubed < psec->cse); // if the subedit is changing, we need to invalidate stuff
if (isubed != psec->iseCur) { int isePre; if (psec->iseCur >= 0) { SECResetSubeditEdit(pdp); InvalidateScrollRect(psec->pci->hwnd, &psec->pse[psec->iseCur].rc, psec->xScroll); }
isePre = psec->iseCur; psec->iseCur = isubed; // For perf reasons, do a full recompute only if SE_YEARALT or
// SUBEDIT_ALL was involved, since those are the only cases
// where SE_YEARALT fields change size.
#define YearAffected(psec, ise) \
(ise == SUBEDIT_ALL || \ (ise >= 0 && psec->pse[ise].id == SE_YEARALT))
if (YearAffected(psec, isePre) || YearAffected(psec, isubed)) { SECRecomputeSizing(psec, &psec->rc); InvalidateRect(psec->pci->hwnd, NULL, TRUE); } #undef YearAffected
if (psec->iseCur >= 0) { RECT rc = psec->pse[psec->iseCur].rc; OffsetRect(&rc, -psec->xScroll, 0); if (rc.left < psec->rc.left) { psec->xScroll += rc.left - psec->rc.left; InvalidateRect(psec->pci->hwnd, NULL, TRUE); } else if (rc.right > psec->rc.right) { psec->xScroll += rc.right - psec->rc.right; InvalidateRect(psec->pci->hwnd, NULL, TRUE); } else { InvalidateRect(psec->pci->hwnd, &rc, TRUE); } } } }
int SECIncrFocus(DATEPICK *pdp, int delta) { int ise, loop; LPSUBEDITCONTROL psec = &pdp->sec;
ASSERT(-1 == delta || 1 == delta);
ise = psec->iseCur; if (ise < 0 && delta < 0) ise = psec->cse;
for (loop = 0 ; loop < psec->cse ; loop++) { int oldise = ise; ise = (ise + delta + psec->cse) % psec->cse; if (ise != oldise+delta && psec->fNone) { // we wrapped and we allow scrolling into SUBEDIT_NONE state
break; } if (!psec->pse[ise].fReadOnly) { goto Found; } } ise = SUBEDIT_NONE; Found: SECSetCurSubed(pdp, ise); return ise; }
void SECInvalidate(LPSUBEDITCONTROL psec, int id);
//
// Given a Gregorian year, get the local name for that year.
//
UINT SECGetYearValue(DATEPICK *pdp, UINT uYear) { UINT uiRc = 0; TCHAR rgch[64]; if (EVAL(MCGetDateFormatWithTempYear(&pdp->sec.ct, &pdp->sec.st, TEXT("yyyy"), uYear, rgch, ARRAYSIZE(rgch)))) { uiRc = StrToInt(rgch); } return uiRc; }
//
// SECAdjustByEra
//
// pct - PCALENDARTYPE structure to use for conversion
//
// uInput - the value the user typed (to be interpreted as local calendar)
//
// The basic idea is that if you type a year, it is interpreted relative
// to the era you were in previously. If the number you typed isn't valid
// for that era, then reject it (by returning the original year unchanged).
//
UINT SECAdjustByEra(DATEPICK *pdp, UINT uInput) { UINT uResult = pdp->sec.st.wYear;
//
// Find the delta between the current local year and the current
// Gregorian year. We don't use any of the era transition dates
// since they aren't reliable at the boundaries. Just convert it
// to a display name and re-parse it back.
//
UINT uDelta = pdp->sec.st.wYear - SECGetYearValue(pdp, pdp->sec.st.wYear);
//
// Apply that delta to the year the user typed in. This converts the
// local year into a Gregorian year.
//
UINT uNewVal = uInput + uDelta;
// uNewVal is the value we want to change it to. If it's valid for that
// era, then use it. We detect that it's okay for the era by converting
// it to a display name and seeing if it matches. It can fail for being
// too large (past the end of the era) or too small (trying to change to
// January 1 local year 1 when the era didn't change until March).
if (SECGetYearValue(pdp, uNewVal) == uInput) { uResult = uNewVal; }
return uResult; }
//
// SECAdjustByType
//
// Some field types are special.
//
// SE_YEAR or SE_YEARALT if user typed only two digits
//
// Use the "implied century for two-digit years" logic
// as described in GetCalendarInfoA.
//
// SE_YEAR or SE_YEARALT if user typed more than two digits
//
// Use that number.
//
// BONUS FEATURE!
//
// Some calendars run parallel to the Gregorian year,
// but with different years. Use GregorianToOther and
// OtherToGregorian to convert.
//
// The input value is the local year (Gregorian, Buddhist, whatever)
// but the return value is always the Gregorian year, since that's
// what SYSTEMTIME uses.
//
// SE_HOUR
//
// If the clock is in 12-hour format, then preserve AM/PM ness of
// the hour. For example, if it was 3pm and somebody is changing
// the hour to 4, use 4pm instead of 4am.
//
UINT SECAdjustByType(DATEPICK *pdp, LPSUBEDIT psubed, UINT uNewValue) {
if (SE_YEARLIKE(psubed->id)) { if (uNewValue < 100) { // Get the preferred century of the preferred calendar
// (in the localized year, not Gregorian.)
DWORD dwMax2DigitYear; if (!GetCalendarInfoA(pdp->sec.ct.lcid, pdp->sec.ct.calid, CAL_RETURN_NUMBER + CAL_ITWODIGITYEARMAX, NULL, 0, &dwMax2DigitYear)) { // default in the absence of all information
dwMax2DigitYear = GregorianToOther(&pdp->sec.ct, 2029);
// if the current year in this era is less than 100, then the 2 digits typed
// may be the real date, so set the max to 99 (i.e., no conversion)
//
if (dwMax2DigitYear < 99) dwMax2DigitYear = 99; }
//
// Copy the century of dwMax2DigitYear into uNewValue.
//
uNewValue += (dwMax2DigitYear - dwMax2DigitYear % 100); //
// If it exceeds the max, then drop to previous century.
//
if (uNewValue > dwMax2DigitYear) uNewValue -= 100;
}
//
// Finally, convert back to Gregorian as necessary.
//
uNewValue = OtherToGregorian(&pdp->sec.ct, uNewValue);
//
// If we are in an Era calendar, then we need to adjust the
// year relative to the ambient era.
//
if (ISERACALENDAR(&pdp->sec.ct)) { uNewValue = SECAdjustByEra(pdp, uNewValue); }
} else if (psubed->id == SE_HOUR && psubed->pv[0] == TEXT('h')) { if (*psubed->pval >= 12 && uNewValue < 12) uNewValue += 12; }
return uNewValue; }
void SECSetSubeditValue(DATEPICK *pdp, LPSUBEDIT psubed, UINT uNewValue, BOOL fForce) { LPSUBEDITCONTROL psec = &pdp->sec; UINT uOldValue;
uNewValue = SECAdjustByType(pdp, psubed, uNewValue);
//
// Must do a full-on range check in addition to the simple psubed->min
// psubed->max range check because the new value might be valid
// for our range but not in the global scheme of things. For example,
// the minimum date is Sep 14 1752, but if today is Jan 1 1995 and
// the user types "1752", that will pass the simple min/max year test,
// but it's not a valid date since Jan 1 1752 is out of range.
//
uOldValue = *psubed->pval; *psubed->pval = (WORD)uNewValue; if (uNewValue >= psubed->min && uNewValue <= psubed->max && CmpSystemtime(&pdp->sec.st, &pdp->stMin) >= 0 && CmpSystemtime(&pdp->sec.st, &pdp->stMax) <= 0) { if (fForce || uNewValue != uOldValue) { SECInvalidate(psec, SE_APP); InvalidateScrollRect(psec->pci->hwnd, &psubed->rc, psec->xScroll);
DPNotifyDateChange(pdp); } } else { // Oops, not valid, put the old value back
*psubed->pval = (WORD)uOldValue; } }
// This saves the current pending value and also resets the edit state
// if fReset is TRUE
void SECSaveResetSubeditEdit(DATEPICK *pdp, BOOL fReset) { LPSUBEDITCONTROL psec = &pdp->sec;
if (psec->iseCur >= 0) { LPSUBEDIT psubed = &psec->pse[psec->iseCur];
if (psubed->cchEdit) { SECSetSubeditValue(pdp, psubed, psubed->valEdit, FALSE); } if (fReset) psubed->cchEdit = 0; } }
// SECInvalidate invalidates the display for each subedit affected by a change
// to ID. NOTE: as a side affect, it recalculates MAX fields for all subedits
// affected by a change to ID.
//
// SE_APP invalidates everything, anything invalidates SE_APP
// SE_MARK (am/pm) invalidate SE_HOUR, SE_HOUR invalides SE_MARK
//
void SECInvalidate(LPSUBEDITCONTROL psec, int id) { BOOL fAdjustDayMax = (id == SE_MONTH || id == SE_MONTHALT || id == SE_YEAR || id == SE_YEARALT || id == SE_APP || id == SE_ERA); LPSUBEDIT pse; int i;
// If we changed any date field and we are in a era-like calendar,
// then invalidate all, since changing the month, day or year may
// change the era or vice versa.
if (ISERACALENDAR(&psec->ct) && SE_DATELIKE(id)) { id = SE_APP; }
for (pse=psec->pse, i=0 ; i < psec->cse ; pse++, i++) { // we need to invalidate all fields that changed
if (id == pse->id || pse->id == SE_APP || id == SE_APP || (id == SE_MARK && pse->id == SE_HOUR) || (id == SE_HOUR && pse->id == SE_MARK)) { InvalidateScrollRect(psec->pci->hwnd, &pse->rc, psec->xScroll); }
// the month or year changed, fix max field for SE_DAY
if (fAdjustDayMax && pse->id == SE_DAY) { pse->max = GetDaysForMonth(psec->st.wYear, psec->st.wMonth); if (*pse->pval > pse->max) { *pse->pval = (WORD) pse->max; } SECInvalidate(psec, SE_DAY); } } }
__inline BOOL SECGetEraName(LPSUBEDITCONTROL psec, LPSUBEDIT pse, UINT uYear, LPTSTR ptszBuf, UINT cchBuf) { return MCGetDateFormatWithTempYear(&psec->ct, &psec->st, pse->pv, uYear, ptszBuf, cchBuf); }
//
// SECIncrementEra increments/decrements the era field. ERAs are strange
// since they aren't a field unto themselves but are rather an artifact
// of the other fields. Returns the new year to use.
//
UINT SECIncrementEra(LPSUBEDITCONTROL psec, LPSUBEDIT pse, int delta) { TCHAR rgch[64]; TCHAR rgch2[64]; int i; int cEras = DPA_GetPtrCount(psec->ct.hdpaEras);
UINT uNewYear;
ASSERT(pse->pval == &psec->st.wYear); uNewYear = psec->st.wYear;
//
// First find the era that encloses the current year.
// Do this by comparing the era string, because it's possible
// for the era to change twice within the same calendar year
// (if an emperor ascends to the throne and then dies the next week)
// so comparing against hdpaYear won't help.
//
SECGetEraName(psec, pse, uNewYear, rgch, ARRAYSIZE(rgch));
//
// If the era string is blank, it means we're in the "before the
// first era" scenario, so we use the "virtual" last element that
// represents "minus infinity".
//
if (rgch[0] == TEXT('\0')) { i = cEras; goto FoundEra; }
for (i = 0; i < cEras; i++) { if (lstrcmp(rgch, DPA_FastGetPtr(psec->ct.hdpaEras, i)) == 0) goto FoundEra; }
//
// Eek! Couldn't find the era! Just increment/decrement the
// year instead.
//
uNewYear += delta; goto Finish;
FoundEra:
//
// The era list is stored backwards, so incrementing the era means
// decrementing the index (i).
//
if (delta > 0) // Incrementing
{ //
// Don't go off the end of the list. Note that if we were in
// the "virtual era" at minus infinity, this decrement will move
// us into the first "real" era.
//
if (--i < 0) goto Finish;
// Increment to first year of the next era.
uNewYear = StrToInt(DPA_FastGetPtr(psec->ct.hdpaYears, i)); } else { //
// Don't go off the end of the list. Note that this also
// catches the "virtual era" at minus infinity.
//
if (i >= cEras) goto Finish;
//
// Move to the last year of the previous era. Do this by
// starting with the first year of the current era and
// decrementing it if necessary.
//
uNewYear = StrToInt(DPA_FastGetPtr(psec->ct.hdpaYears, i)); }
//
// We have a year that might be in the next/prev era. Try it.
// If we're still in the original era, then inc/dec one more time
// to get there for good.
//
SECGetEraName(psec, pse, uNewYear, rgch2, ARRAYSIZE(rgch2)); if (lstrcmp(rgch, rgch2) == 0) uNewYear += delta;
Finish: if (uNewYear < pse->min) uNewYear = pse->min; if (uNewYear > pse->max) uNewYear = pse->max; return uNewYear; }
// SECIncrementSubedit increments currently selected subedit by delta
// Returns TRUE iff the value changed
BOOL SECIncrementSubedit(LPSUBEDITCONTROL psec, int delta) { LPSUBEDIT psubed; UINT val;
if (psec->iseCur < 0) return(FALSE);
psubed = &psec->pse[psec->iseCur];
if (psubed->id == SE_APP) return(FALSE);
//
// Only numeric fields should accelerate. Text fields should always
// increment/decrement by exactly one position.
//
if (psubed->flDrawText & DT_CENTER) { if (delta < 0) delta = -1; if (delta > 0) delta = +1; }
//
// Incrementing/decrementing ERAs is strange.
//
if (psubed->id == SE_ERA) { val = SECIncrementEra(psec, psubed, delta); } else { // delta isn't a REAL delta -- it's a directional thing. Here's the REAL delta:
if (psubed->cIncrement > 0) delta = delta * psubed->cIncrement; if(!psubed->pval) return (FALSE); val = *psubed->pval + delta; while (1) { if ((int)val < (int)psubed->min) { // don't wrap years
if (SE_YEARLIKE(psubed->id)) { val = psubed->min; break; } val = psubed->min - val - 1; val = psubed->max - val; } else if (val > psubed->max) { // don't wrap years
if (SE_YEARLIKE(psubed->id)) { val = psubed->max; break; } val = val - psubed->max - 1; val = psubed->min + val; } else break; } }
if (*psubed->pval != val) { *psubed->pval = (WORD) val;
SECInvalidate(psec, psubed->id); return(TRUE); }
return(FALSE); }
// returns TRUE if a value has changed, FALSE otherwise
BOOL SECHandleKeydown(DATEPICK *pdp, WPARAM wParam, LPARAM lParam) { int delta = 1; LPSUBEDITCONTROL psec = &pdp->sec;
switch (wParam) { case VK_LEFT: delta = -1; // fall through...
case VK_RIGHT: SECResetSubeditEdit(pdp); SECIncrFocus(pdp, delta); return(FALSE); }
if (psec->iseCur >= 0 && psec->pse[psec->iseCur].id == SE_APP) { NMDATETIMEWMKEYDOWN nmdtkd = {0};
nmdtkd.nVirtKey = (int) wParam; nmdtkd.pszFormat = psec->pse[psec->iseCur].pv; SECGetSystemtime(psec,&nmdtkd.st);
CCSendNotify(psec->pci, DTN_WMKEYDOWN, &nmdtkd.nmhdr);
if (psec->st.wYear != nmdtkd.st.wYear || psec->st.wMonth != nmdtkd.st.wMonth || psec->st.wDay != nmdtkd.st.wDay || psec->st.wHour != nmdtkd.st.wHour || psec->st.wMinute != nmdtkd.st.wMinute || psec->st.wSecond != nmdtkd.st.wSecond) // skip wDayOfWeek and wMilliseconds
{ psec->st = nmdtkd.st; SECInvalidate(psec, SE_APP); return(TRUE); } } else { MSG msg; switch (wParam) { case VK_DOWN: case VK_SUBTRACT: delta = -1; // fall through...
case VK_UP: case VK_ADD: PeekMessage(&msg, NULL, WM_CHAR, WM_CHAR, PM_REMOVE); // eat this message
SECResetSubeditEdit(pdp); return(SECIncrementSubedit(psec, delta)); break;
case VK_HOME: case VK_END: if (psec->iseCur >= 0) { LPSUBEDIT psubed; int valT; SECResetSubeditEdit(pdp);
psubed = &psec->pse[psec->iseCur]; valT = *psubed->pval; *psubed->pval = (wParam == VK_HOME ? psubed->min : psubed->max); delta = *psubed->pval - valT; if (delta != 0) { SECInvalidate(psec, psubed->id); return(TRUE); } } break; } } return(FALSE); }
// returns TRUE if a value has changed, FALSE otherwise
// This function performs a DPNotifyDateChange() if applicable.
BOOL SECHandleChar(DATEPICK *pdp, TCHAR ch) { LPSUBEDIT psubed; UINT uCurDigit; // current digit hit
UINT uCurSubValue; // current displayed subvalue in edit field
UINT uCurValue; // current value of the subedit
LPSUBEDITCONTROL psec = &pdp->sec;
// NOTE: In almost all cases, uCurSubValue will be the same as uCurValue
// since most fields don't have shortened displays. However, for years
// we can display two digits of a 4 digit number, which makes for
// complications.
if (psec->iseCur < 0) return(FALSE); psubed = &psec->pse[psec->iseCur];
if (psubed->cchMax == 0) return(FALSE); if (ch == psec->cDelimeter || StrChr(psec->szDelimeters, ch)) { SECResetSubeditEdit(pdp); SECIncrFocus(pdp, 1); return(FALSE); }
// allow 'a' and 'p' to set the AM/PM fields. we need to do some
// funky stuff to get this to work right, so here it is.
else if (psubed->id == SE_MARK) { if ((ch == TEXT('p') || ch == TEXT('P')) && (*psubed->pval < 12)) { int valNew = *psubed->pval+12; ch = (valNew) % 10 + TEXT('0'); psubed->valEdit = (valNew) / 10; psubed->cchEdit = 1; } else if ((ch == TEXT('a') || ch == TEXT('A')) && (*psubed->pval >= 12)) { int valNew = *psubed->pval-12; ch = (valNew) % 10 + TEXT('0'); psubed->valEdit = (valNew) / 10; psubed->cchEdit = 1; } else { return(FALSE); } } else if (ch < TEXT('0') || ch > TEXT('9')) { MessageBeep(MB_ICONHAND); return(FALSE); } else if (psubed->id == SE_ERA) { // I don't know what to do with this field, so bail out
return(FALSE); }
uCurDigit = ch - TEXT('0'); if (psubed->cchEdit) uCurSubValue = psubed->valEdit * 10 + uCurDigit; else uCurSubValue = uCurDigit;
uCurValue = SECAdjustByType(pdp, psubed, uCurSubValue);
// Allow bogus values for years since you might need to type
// in a bogus value on the way to a valid four-digit value.
if (uCurValue > psubed->max && !SE_YEARLIKE(psubed->id)) { // the number has exceeded the max, so no point in continuing
psubed->cchEdit = 0;
// If we're going to exceed the max, then reset the edit
// and make this the first number instead of beeping
uCurValue = uCurValue - uCurSubValue + uCurDigit; uCurSubValue = uCurDigit; }
// Allow 0 to be valEdit for subedits, even though it may be
// illegal for that field (e.g., month).
// This lets people type "09" and get the "expected" result.
SECInvalidate(psec, psubed->id);
psubed->valEdit = uCurSubValue; psubed->cchEdit++; if (psubed->cchEdit == psubed->cchMax) psubed->cchEdit = 0;
if (psubed->cchEdit == 0) { // SECSetSubeditValue will do the validation
SECSetSubeditValue(pdp, psubed, uCurSubValue, TRUE); return(TRUE); }
if(psubed->valEdit != *psubed->pval) InvalidateRect(pdp->ci.hwnd, NULL, TRUE); return(FALSE); }
// SECFormatSubed returns pointer to correct string
LPTSTR SECFormatSubed(LPSUBEDITCONTROL psec, LPSUBEDIT psubed, LPTSTR szTmp, UINT cch) { LPTSTR sz;
if (psubed->id == SE_STATIC) { sz = (LPTSTR)psubed->pv; } else { sz = szTmp; SEGetTimeDateFormat(psubed, psec, szTmp, cch); }
return sz; }
// Returns TRUE if this subedit displays as digits (rather than text).
BOOL SECIsNumeric(LPSUBEDIT psubed) { switch (psubed->id) { case SE_ERA: return FALSE; // g never
case SE_YEAR: return TRUE; // yyyy always digits
case SE_YEARALT: return TRUE; // yy always digits
case SE_MONTH: return lstrlen(psubed->pv) <= 2; // MM yes, but not MMM
case SE_MONTHALT: return lstrlen(psubed->pv) <= 4; // ddMM yes, but not ddMMM
case SE_DAY: return TRUE; // dd always digits
case SE_MARK: return FALSE; // tt never
case SE_HOUR: return TRUE; // hh always digits
case SE_MINUTE: return TRUE; // mm always digits
case SE_SECOND: return TRUE; // ss always digits
case SE_STATIC: return FALSE; // static text
case SE_APP: return FALSE; // app's job to format this
} return FALSE; }
// SECDrawSubedits draws subedits and updates their bounding rectangles
void SECDrawSubedits(HDC hdc, LPSUBEDITCONTROL psec, BOOL fFocus, BOOL fEnabled) { HGDIOBJ hfontOrig; int i, iseCur; LPTSTR sz; TCHAR szTmp[DTP_FORMATLENGTH]; LPSUBEDIT psubed;
hfontOrig = SelectObject(hdc, (HGDIOBJ)psec->hfont);
// Do this cuz the xScroll stuff can send text into visible area that it shouldn't be in
IntersectClipRect(hdc, psec->rc.left, psec->rc.top, psec->rc.right, psec->rc.bottom);
SetBkColor(hdc, g_clrHighlight);
iseCur = psec->iseCur; if (!fFocus) iseCur = SUBEDIT_NONE;
for (i = 0, psubed = psec->pse; i < psec->cse; i++, psubed++) { RECT rc = psubed->rc; if (psec->xScroll) OffsetRect(&rc, -psec->xScroll, 0);
if (!fEnabled) { SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, g_clrGrayText); } else if (iseCur == i) { SetBkMode(hdc, OPAQUE); SetTextColor(hdc, g_clrHighlightText); } else { SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, g_clrWindowText); }
//HACK
//if subedit control is being edited then we display the
//value in psubed->valEdit because it is not being updated
//until psubed->cchMax is reached or SECSave/ResetSubeditEdit is
//called
if(i == psec->iseCur && psubed->cchEdit != 0) { //
// If the field is numeric, then display it raw including the
// leading zero. People really want to see that leading zero,
// so give the public what it wants. (And even if they didn't,
// we need this special case anyway because the value might not
// yet be a valid value because the user is still typing it.
// This is particular true for SE_YEARLIKE fields.)
//
if (SECIsNumeric(psubed)) { TCHAR szFormat[10]; StringCchPrintf(szFormat, ARRAYSIZE(szFormat), TEXT("%%0%dd"), psubed->cchEdit); StringCchPrintf(szTmp, ARRAYSIZE(szTmp), szFormat, psubed->valEdit); sz = szTmp; } else { // The day-of-month might not be valid for the temporary month
// or year in psubed->valEdit, so force the day-of-month to 1
// so the month will always come out okay.
//
// This is tricky, because if the item being edited is the
// day-of-month itself, we want to display valEdit, not 1!
// So we force it to 1, then slam in the valEdit, then do
// our SECFormatSubed, then restore the original values.
//
UINT uTmp = *psubed->pval; //save the original value
WORD wOldDay = psec->st.wDay; psec->st.wDay = 1; // Don't change to zero in case user is typing a leading zero
// into an alphabetic field. (Stranger things have happened.)
if (psubed->valEdit) *psubed->pval = (WORD) psubed->valEdit; sz = SECFormatSubed(psec, psubed, szTmp, ARRAYSIZE(szTmp)); psec->st.wDay = wOldDay; *psubed->pval = (WORD) uTmp; //restore the original value
} } else sz = SECFormatSubed(psec, psubed, szTmp, ARRAYSIZE(szTmp));
DrawText(hdc, sz, -1, &rc, psubed->flDrawText | DT_TOP | DT_NOPREFIX | DT_SINGLELINE); }
// we know no clip region was selected before this function
SelectClipRgn(hdc, NULL);
SelectObject(hdc, hfontOrig); }
// DON'T need to worry about xScroll here because pt is offset
int SECSubeditFromPt(LPSUBEDITCONTROL psec, POINT pt) { int isubed;
for (isubed = psec->cse - 1; isubed >= 0; isubed--) { if (!psec->pse[isubed].fReadOnly && pt.x >= psec->pse[isubed].rc.left) { break; } }
return(isubed); }
void SECGetSystemtime(LPSUBEDITCONTROL psec, LPSYSTEMTIME pst) { *pst = psec->st;
// we don't keep doy up to date, set it now (0==sun, 6==sat)
pst->wDayOfWeek = (DowFromDate(pst)+1) % 7; // this returns 0==sun
}
BOOL SECSetSystemtime(DATEPICK *pdp, LPSYSTEMTIME pst) { pdp->sec.st = *pst;
return TRUE; // assume something changed
}
// SECEdit: Start a free-format edit return result in szOutput.
BOOL SECEdit(DATEPICK *pdp, LPTSTR szOutput, int cchOutput) { HWND hwndEdit; TCHAR szBuf[DTP_FORMATLENGTH]; LPTSTR pszBuf; int cchBuf; int i; int isePrev; LPSUBEDIT pse; BOOL fRet = FALSE; LPSUBEDITCONTROL psec = &pdp->sec;
// Build the string that we hand to the app.
// For the duration of the string build, set the current subedit
// to SUBEDIT_ALL so that
// 1. partial edits are applied before building the string, and
// 2. SE_YEARALT can format appropriately.
isePrev = psec->iseCur; SECSetCurSubed(pdp, SUBEDIT_ALL); pszBuf = szBuf; cchBuf = ARRAYSIZE(szBuf);
//
// Need to mirror the format since the Edit control will take
// of the origianl format with RTL mirroring.
//
if (psec->fMirrorSEC) pse = (psec->pse + (psec->cse - 1)); else pse = psec->pse;
for (i = 0 ; i < psec->cse ; i++) { int nTmp;
if (pse->id == SE_STATIC) { StringCchCopy(pszBuf, cchBuf, pse->pv); } else { SEGetTimeDateFormat(pse, psec, pszBuf, cchBuf); }
nTmp = lstrlen(pszBuf);
cchBuf -= nTmp; pszBuf += nTmp;
//
// If this control is mirrored, then read contents backward.
//
if (psec->fMirrorSEC) pse--; else pse++; } SECSetCurSubed(pdp, isePrev);
hwndEdit = CreateWindowEx(0, TEXT("EDIT"), szBuf, WS_CHILD | ES_AUTOHSCROLL, psec->rc.left + 2, psec->rc.top + 2, psec->rc.right - psec->rc.left, psec->rc.bottom - psec->rc.top, psec->pci->hwnd, NULL, HINST_THISDLL, NULL);
if (hwndEdit) { RECT rcEdit = psec->rc;
MapWindowRect(psec->pci->hwnd, NULL, &rcEdit); // ClientToScreen
pdp->fFreeEditing = TRUE; InvalidateRect(psec->pci->hwnd, NULL, TRUE);
Edit_LimitText(hwndEdit, ARRAYSIZE(szBuf) - 1); FORWARD_WM_SETFONT(hwndEdit, psec->hfont, FALSE, SendMessage); SetFocus(hwndEdit); RescrollEditWindow(hwndEdit); ShowWindow(hwndEdit, SW_SHOWNORMAL);
//
// The basic idea:
//
// Process messages until we receive a cancel message,
// or an accept message, or some implicit accept-like
// thing happens (namely, a sent WM_KILLFOCUS).
//
// If the accept or cancel was implicit, then leave the
// cancelling message in the queue for somebody else
// to process. Otherwise, if the accept/cancel was
// explicit, eat the message so nobody else gets
// confused by it.
//
for (;;) { MSG msg; BOOL fPeek;
fPeek = PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
// That PeekMessage may have dispatched a sent WM_KILLFOCUS,
// in which case the change is considered to have been Accepted.
// Leave the message we peeked in the queue because the accept
// was implicit.
if (GetFocus() != hwndEdit) { DebugMsg(TF_MONTHCAL, TEXT("SECEdit accept (killfocus)")); fRet = TRUE; break;
}
if (fPeek) {
//
// Messages that cause us to cancel implicitly.
// These messages stay in the queue.
//
if (msg.message == WM_SYSCOMMAND || msg.message == WM_SYSCHAR || msg.message == WM_SYSDEADCHAR || msg.message == WM_DEADCHAR || msg.message == WM_SYSKEYDOWN || msg.message == WM_QUIT) { DebugMsg(TF_MONTHCAL, TEXT("SECEdit got a message to terminate (%d)"), msg.message); fRet = FALSE; break; }
//
// Messages that cause us to accept implicitly.
// These messages stay in the queue.
//
if ((msg.message == WM_LBUTTONDOWN || msg.message == WM_NCLBUTTONDOWN || msg.message == WM_RBUTTONDOWN || msg.message == WM_NCRBUTTONDOWN || msg.message == WM_LBUTTONDBLCLK) && !PtInRect(&rcEdit, msg.pt)) { DebugMsg(TF_MONTHCAL, TEXT("SECEdit got a message to accept (%d)"), msg.message); fRet = TRUE; break; }
// We are now committed to eating or processing the message
GetMessage(&msg, NULL, 0, 0);
//
// Messages that cause us to cancel explicitly.
//
if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE) { DebugMsg(TF_MONTHCAL, TEXT("SECEdit explicit cancel (%d)"), msg.message); fRet = FALSE; break;
}
//
// Messages that cause us to accept explicitly.
//
if (msg.message == WM_KEYDOWN && msg.wParam == VK_RETURN) { DebugMsg(TF_MONTHCAL, TEXT("SECEdit explicit accept (%d)"), msg.message); fRet = TRUE; break; }
//
// All other messages just get dispatched.
//
TranslateMessage(&msg); DispatchMessage(&msg); } else { WaitMessage(); } } // for (;;)
if (fRet) { Edit_GetText(hwndEdit, szOutput, cchOutput); } DestroyWindow(hwndEdit); pdp->fFreeEditing = FALSE; InvalidateRect(psec->pci->hwnd, NULL, TRUE); }
return(fRet); }
//
// returns true if months were scrolled, false otherwise
//
BOOL FScrollIntoView(MONTHCAL *pmc) { int nDelta = 0; SYSTEMTIME stEnd;
if (MonthCal_IsMultiSelect(pmc)) CopyDate(pmc->stEndSel, stEnd); else CopyDate(pmc->st, stEnd); //
// If the month/yr for the new date is not in view, bring it
// into view
//
if ((stEnd.wYear < pmc->stMonthFirst.wYear) || ((stEnd.wYear == pmc->stMonthFirst.wYear) && (stEnd.wMonth < pmc->stMonthFirst.wMonth))) { nDelta = - (pmc->stMonthFirst.wYear - (int)stEnd.wYear) * 12 - (pmc->stMonthFirst.wMonth - (int)stEnd.wMonth); } else if ((pmc->st.wYear > pmc->stMonthLast.wYear) || ((pmc->st.wYear == pmc->stMonthLast.wYear) && (pmc->st.wMonth > pmc->stMonthLast.wMonth))) { nDelta = ((int)pmc->st.wYear - pmc->stMonthLast.wYear) * 12 + ((int)pmc->st.wMonth - pmc->stMonthLast.wMonth); } if (nDelta) return FIncrStartMonth(pmc, nDelta, TRUE /* dont change day */); else return FALSE; }
//
// Validates the isubed to make sure we aren't setting it to something
// bogus. If necessary, we pick a field at random.
//
void SECSafeSetCurSubed(DATEPICK *pdp, int ise) { if (ise >= pdp->sec.cse || (ise >= 0 && pdp->sec.pse[ise].fReadOnly)) { SECSetCurSubed(pdp, SUBEDIT_NONE); SECIncrFocus(pdp, 1); } else SECSetCurSubed(pdp, ise); }
LRESULT DTM_OnSetFormat(DATEPICK *pdp, LPCTSTR szFormat) {
// remember the field that has focus so we can restore it later
//
int iseCur = pdp->sec.iseCur;
if (!szFormat || !*szFormat) { pdp->fLocale = TRUE; DPHandleLocaleChange(pdp); } else { pdp->fLocale = FALSE; SECParseFormat(pdp, &pdp->sec, szFormat); }
// restore focus. it might be cool to do extra validation
// to see if iseCur is the same type that it used to be,
// maybe even validating that cse is constant. the case we're
// really trying to fix is changing "1st" to "2nd" to "3rd",
// so only a text portion is really changing...
//
SECSafeSetCurSubed(pdp, iseCur);
return((LRESULT)TRUE); } //
// DATEPICKER stuff
//
LRESULT CALLBACK DatePickWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { DATEPICK *pdp; NMHDR nmhdr; LRESULT lres = 0;
if (message == WM_NCCREATE) return(DPNcCreateHandler(hwnd)); pdp = DatePick_GetPtr(hwnd); if (pdp == NULL) return(DefWindowProc(hwnd, message, wParam, lParam));
// Dispatch the various messages we can receive
switch (message) { case WM_CREATE: lres = DPCreateHandler(pdp, hwnd, (LPCREATESTRUCT)lParam); break;
case WM_ERASEBKGND: if (!pdp->fEnabled) { RECT rc; HDC hdc = (HDC)wParam;
GetClipBox(hdc, &rc); FillRectClr(hdc, &rc, g_clrBtnFace); } else goto DoDefault; break;
case WM_NCPAINT: if (pdp->hThemeCombo && GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_CLIENTEDGE) { HRGN hrgn = (wParam != 1) ? (HRGN)wParam : NULL; HBRUSH hbr = (HBRUSH)GetClassLongPtr(hwnd, GCLP_HBRBACKGROUND);
if (CCDrawNonClientTheme(pdp->hThemeCombo, hwnd, hrgn, hbr, 0, CBXS_NORMAL)) { break; } }
goto DoDefault;
case WM_PRINTCLIENT: case WM_PAINT:
{ PAINTSTRUCT ps; HDC hdc;
hdc = (HDC)wParam;
if (hdc) { DPPaint(pdp, hdc); } else {
hwnd = pdp->ci.hwnd; hdc = BeginPaint(hwnd, &ps); DPPaint(pdp, hdc); EndPaint(hwnd, &ps); } break; }
case WM_LBUTTONDOWN: DPLButtonDown(pdp, wParam, lParam); break;
case WM_NOTIFY: switch (((LPNMHDR)lParam)->code) { case MCN_SELCHANGE: case MCN_SELECT: { LPNMSELECT pnms = (LPNMSELECT)lParam;
DebugMsg(TF_MONTHCAL,TEXT("MonthCal notified DateTimePick of SELECT")); if (!DPSetDate(pdp, &pnms->stSelStart, TRUE)) { DebugMsg(DM_WARNING,TEXT("MonthCal cannot set selected date!")); MessageBeep(MB_ICONHAND); } pdp->fShow = (((LPNMHDR)lParam)->code == MCN_SELCHANGE); break; }
case UDN_DELTAPOS: if ((int)wParam == DATEPICK_UPDOWN) { LPNM_UPDOWN pnmdp = (LPNM_UPDOWN)lParam;
if (!pdp->fFocus) SetFocus(pdp->ci.hwnd);
SECResetSubeditEdit(pdp);
if (SECIncrementSubedit(&pdp->sec, -pnmdp->iDelta)) DPNotifyDateChange(pdp); } break; } // WM_NOTIFY switch
break;
case WM_GETFONT: lres = (LRESULT)pdp->sec.hfont; break;
case WM_SETFONT: DPHandleSetFont(pdp, (HFONT)wParam, (BOOL)LOWORD(lParam)); break;
case WM_DESTROY: DPDestroyHandler(hwnd, pdp, wParam, lParam); break; case WM_KILLFOCUS: case WM_SETFOCUS: { BOOL fGotFocus = (message == WM_SETFOCUS); if (BOOLIFY(fGotFocus) != BOOLIFY(pdp->fFocus)) { pdp->fFocus = (WORD) fGotFocus; if (pdp->sec.iseCur >= 0) { InvalidateScrollRect(pdp->ci.hwnd, &pdp->sec.pse[pdp->sec.iseCur].rc, pdp->sec.xScroll); } else if (DatePick_ShowCheck(pdp)) { pdp->fCheckFocus = (WORD) fGotFocus; InvalidateRect(pdp->ci.hwnd, &pdp->rcCheck, TRUE); } else if (fGotFocus) // nothing has focus, bring it to something
{ SECIncrFocus(pdp, 1); } CCSendNotify(&pdp->ci, (fGotFocus ? NM_SETFOCUS : NM_KILLFOCUS), &nmhdr); }
if (fGotFocus) { // Revalidate iseLastActive because the app might've changed
// the format while we were nonfocus
SECSafeSetCurSubed(pdp, pdp->iseLastActive); } else { pdp->iseLastActive = pdp->sec.iseCur; SECSetCurSubed(pdp, SUBEDIT_NONE); }
break; }
case WM_ENABLE: { BOOL fEnabled = wParam ? TRUE:FALSE; if (BOOLIFY(pdp->fEnabled) != fEnabled) { pdp->fEnabled = (WORD) fEnabled; if (pdp->hwndUD) EnableWindow(pdp->hwndUD, fEnabled); InvalidateRect(pdp->ci.hwnd, NULL, TRUE); } break; }
case DTMP_WINDOWPOSCHANGED: case WM_SIZE: { RECT rc;
if (message == DTMP_WINDOWPOSCHANGED) { GetClientRect(pdp->ci.hwnd, &rc); } else { rc.left = 0; rc.top = 0; rc.right = GET_X_LPARAM(lParam); rc.bottom = GET_Y_LPARAM(lParam); }
DPRecomputeSizing(pdp, &rc);
InvalidateRect(pdp->ci.hwnd, NULL, TRUE); UpdateWindow(pdp->ci.hwnd); break; }
case WM_GETDLGCODE: lres = DLGC_WANTARROWS | DLGC_WANTCHARS; break;
case WM_KEYDOWN: if (pdp->fShow) { SendMessage(pdp->hwndMC, WM_KEYDOWN, wParam, lParam); return 0; } else { lres = DPHandleKeydown(pdp, wParam, lParam); } break;
case WM_KEYUP: if (pdp->fShow) SendMessage(pdp->hwndMC, WM_KEYUP, wParam, lParam); break; case WM_SYSKEYDOWN: if (wParam == VK_DOWN && !pdp->fUseUpDown) { DPLBD_MonthCal(pdp, FALSE); } else goto DoDefault; break;
case WM_CHAR: lres = DPHandleChar(pdp, wParam, lParam); break;
case WM_SYSCOLORCHANGE: InitGlobalColors(); // Don't need to propagate to pdp->hwndMC because it is its own
// top-level window.
break;
case WM_WININICHANGE: if (lParam == 0 || !lstrcmpi((LPTSTR)lParam, TEXT("Intl"))) { DPHandleLocaleChange(pdp); } // Don't need to propagate to pdp->hwndMC because it is its own
// top-level window.
break;
case WM_THEMECHANGED: if (pdp->hThemeCombo) { CloseThemeData(pdp->hThemeCombo); }
if (pdp->hThemeCheck) { CloseThemeData(pdp->hThemeCheck); } pdp->hThemeCombo = OpenThemeData(hwnd, L"Combobox"); // This one kinda looks like a combobox
pdp->hThemeCheck = OpenThemeData(hwnd, L"Button"); InvalidateRect(pdp->ci.hwnd, NULL, TRUE); break;
case WM_NOTIFYFORMAT: return CIHandleNotifyFormat(&pdp->ci, lParam); break;
// Cannot use WM_SETTEXT to change the text of a DTP
case WM_SETTEXT: return -1;
case WM_GETTEXT: if (!lParam || !wParam) { // previously this just failed and returned 0
// in bogus input. should be safe to convert to
// gettextlength
message = WM_GETTEXTLENGTH; } else (*(LPTSTR)lParam) = 0; // fall through
case WM_GETTEXTLENGTH: { TCHAR szTmp[DTP_FORMATLENGTH]; LPSUBEDIT psubed; int i; TCHAR *pszText = (TCHAR *)lParam; UINT nTextLen = 0;
for (i = 0, psubed = pdp->sec.pse; i < pdp->sec.cse; i++, psubed++) { LPTSTR sz; UINT nLen;
sz = SECFormatSubed(&pdp->sec, psubed, szTmp, ARRAYSIZE(szTmp)); nLen = lstrlen(sz);
if (message == WM_GETTEXT) { if (nTextLen + nLen >= wParam) break;
StringCchCopy(pszText, wParam, sz); pszText += nLen; } nTextLen += nLen; } lres = nTextLen; } break;
case WM_STYLECHANGING: lres = DPOnStyleChanging(pdp, (UINT) wParam, (LPSTYLESTRUCT)lParam); break;
case WM_STYLECHANGED: lres = DPOnStyleChanged(pdp, (UINT) wParam, (LPSTYLESTRUCT)lParam); break;
case WM_CONTEXTMENU: if (pdp->hwndMC) lres = SendMessage(pdp->hwndMC, message, wParam, lParam); else goto DoDefault; break;
//
// DATETIMEPICK specific messages
//
// DTM_GETSYSTEMTIME wParam=void lParam=LPSYSTEMTIME
// returns GDT_NONE if no date selected (DTS_SHOWNONE only)
// returns GDT_VALID and modifies *lParam to be the selected date
case DTM_GETSYSTEMTIME: if (!pdp->fCheck) { lres = GDT_NONE; } else { // If there is an edit pending, save it so the app sees
// the absolute latest values. This is important for app
// compat, because IE4 wasn't Y2K compliant and people got
// away with typing just two digits of the year and hitting
// ENTER. The "Find Files" dialog would then ask us for the
// year, and in the Y2K case, we are still waiting for the
// other two digits (for a four-digit year) and return the
// wrong year.
SECSaveSubeditEdit(pdp); SECGetSystemtime(&pdp->sec, (SYSTEMTIME *)lParam); lres = GDT_VALID; } break;
// DTM_SETSYSTEMTIME wParam=GDT_flag lParam=LPSYSTEMTIME
// if wParam==GDT_NONE, sets datepick to None (DTS_SHOWNONE only)
// if wParam==GDT_VALID, sets datepick to *lParam
// returns TRUE on success, FALSE on error (such as bad params)
case DTM_SETSYSTEMTIME: { LPSYSTEMTIME pst = ((LPSYSTEMTIME)lParam);
if ((wParam != GDT_NONE && wParam != GDT_VALID) || (wParam == GDT_NONE && !DatePick_ShowCheck(pdp)) || (wParam == GDT_VALID && !IsValidSystemtime(pst))) { break; }
// reset subed in place edit
SECResetSubeditEdit(pdp);
pdp->fNoNotify = TRUE; if (DatePick_ShowCheck(pdp)) { if ((wParam == GDT_NONE) || (pdp->fCheck)) { // let checkbox have focus
SECSetCurSubed(pdp, SUBEDIT_NONE); pdp->fCheckFocus = 1; }
pdp->fCheck = (wParam == GDT_NONE ? 0 : 1); InvalidateRect(pdp->ci.hwnd, NULL, TRUE); } if (wParam == GDT_VALID) { pdp->fNoNotify = TRUE; DPSetDate(pdp, pst, FALSE); pdp->fNoNotify = FALSE; } lres = TRUE; pdp->fNoNotify = FALSE;
break; }
// DTM_GETRANGE wParam=void lParam=LPSYSTEMTIME[2]
// modifies *lParam to be the minimum ALLOWABLE systemtime (or 0 if no minimum)
// modifies *(lParam+1) to be the maximum ALLOWABLE systemtime (or 0 if no maximum)
// returns GDTR_MIN|GDTR_MAX if there is a minimum|maximum limit
case DTM_GETRANGE: { LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
ZeroMemory(pst, 2*SIZEOF(SYSTEMTIME)); lres = pdp->gdtr; if (lres & GDTR_MIN) pst[0] = pdp->stMin; if (lres & GDTR_MAX) pst[1] = pdp->stMax; break; }
// DTM_SETRANGE wParam=GDR_flags lParam=LPSYSTEMTIME[2]
// if GDTR_MIN, sets the minimum ALLOWABLE systemtime to *lParam, otherwise removes minimum
// if GDTR_MAX, sets the maximum ALLOWABLE systemtime to *(lParam+1), otherwise removes maximum
// returns TRUE on success, FALSE on error (such as invalid parameters)
case DTM_SETRANGE: { LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam; const SYSTEMTIME *pstMin = (wParam & GDTR_MIN) ? pst+0 : &c_stEpoch; const SYSTEMTIME *pstMax = (wParam & GDTR_MAX) ? pst+1 : &c_stArmageddon;
if (!IsValidDate(pstMin) || !IsValidDate(pstMax)) { break; }
// Save the flags so we can tell the app if it asks.
// We personally don't care.
pdp->gdtr = (UINT)wParam & (GDTR_MIN | GDTR_MAX);
if (CmpDate(&pdp->stMin, &pdp->stMax) <= 0) { pdp->stMin = *pstMin; pdp->stMax = *pstMax; } else { pdp->stMin = *pstMax; pdp->stMax = *pstMin; }
// we might now have an invalid date, if so, try to set the current
// date and munge it to a max or min value if out of range.
pdp->fNoNotify = TRUE; DPSetDate(pdp, &pdp->sec.st, TRUE); pdp->fNoNotify = FALSE; lres = TRUE; break; }
// DTM_SETFORMAT wParam=void lParam=LPCTSTR
// Sets the formatting string to a copy of lParam.
case DTM_SETFORMATA: { LPCSTR pszFormat = (LPCSTR)lParam; LPWSTR pwszFormat = NULL;
if (pszFormat && *pszFormat) { pwszFormat = ProduceWFromA(pdp->ci.uiCodePage, pszFormat); }
lres = DTM_OnSetFormat(pdp, pwszFormat);
if (pwszFormat) { FreeProducedString(pwszFormat); } break; }
// DTM_SETFORMAT wParam=void lParam=LPCTSTR
// Sets the formatting string to a copy of lParam.
case DTM_SETFORMAT: { lres = DTM_OnSetFormat(pdp, (LPCTSTR)lParam); break; } case DTM_SETMCCOLOR: if (wParam < MCSC_COLORCOUNT) { COLORREF clr = pdp->clr[wParam]; pdp->clr[wParam] = (COLORREF)lParam; if (pdp->hwndMC) SendMessage(pdp->hwndMC, MCM_SETCOLOR, wParam, lParam); return clr; } return -1; case DTM_GETMCCOLOR: if (wParam < MCSC_COLORCOUNT) return pdp->clr[wParam]; return -1; case DTM_GETMONTHCAL: return (LRESULT)(UINT_PTR)pdp->hwndMC;
// wParam -- HFONT, LOWORD(lParam) -- fRedraw
case DTM_SETMCFONT: pdp->hfontMC = (HFONT)wParam; if (pdp->hwndMC) SendMessage(pdp->hwndMC, WM_SETFONT, wParam, lParam); break;
// returns the font
case DTM_GETMCFONT: return (LRESULT)pdp->hfontMC; break;
default: if (CCWndProc(&pdp->ci, message, wParam, lParam, &lres)) return lres;
DoDefault: lres = DefWindowProc(hwnd, message, wParam, lParam); break; } /* switch (message) */
return(lres); }
LRESULT DPNcCreateHandler(HWND hwnd) { DATEPICK *pdp;
// Sink the datepick -- we may only want to do this if WS_BORDER is set
SetWindowBits(hwnd, GWL_EXSTYLE, WS_EX_CLIENTEDGE, WS_EX_CLIENTEDGE);
// Allocate storage for the dtpick structure
pdp = (DATEPICK *)LocalAlloc(LPTR, sizeof(DATEPICK)); if (pdp) { DatePick_SetPtr(hwnd, pdp); pdp->hThemeCombo = OpenThemeData(hwnd, L"Combobox"); // This one kinda looks like a combobox
pdp->hThemeCheck = OpenThemeData(hwnd, L"Button"); }
return((LRESULT)pdp); }
void DPDestroyHandler(HWND hwnd, DATEPICK *pdp, WPARAM wParam, LPARAM lParam) { if (pdp) { if (pdp->hThemeCombo) { CloseThemeData(pdp->hThemeCombo); }
if (pdp->hThemeCheck) { CloseThemeData(pdp->hThemeCheck); }
SECDestroy(&pdp->sec); MCFreeCalendarInfo(&pdp->sec.ct); LocalFree(pdp); }
DatePick_SetPtr(hwnd, NULL); }
// set any locale-dependent values
#define DTS_TIMEFORMATONLY (DTS_TIMEFORMAT & ~DTS_UPDOWN) // remove the UPDOWN bit for testing
LRESULT DPCreateHandler(DATEPICK *pdp, HWND hwnd, LPCREATESTRUCT lpcs) { HFONT hfont; SYSTEMTIME st; LCID lcid;
// Initialize our data.
CIInitialize(&pdp->ci, hwnd, lpcs);
if (pdp->ci.style & DTS_INVALIDBITS) return(-1);
if (pdp->ci.style & DTS_UPDOWN) { pdp->fUseUpDown = TRUE; pdp->hwndUD = CreateWindow(UPDOWN_CLASS, NULL, WS_CHILD | WS_VISIBLE | (pdp->ci.style & WS_DISABLED), CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwnd, (HMENU)DATEPICK_UPDOWN, HINST_THISDLL, NULL); } if (DatePick_ShowCheck(pdp)) { pdp->sec.fNone = TRUE; // ugly: this SEC stuff should be merged back into DATEPICK
pdp->iseLastActive = SUBEDIT_NONE; }
pdp->fEnabled = !(pdp->ci.style & WS_DISABLED); pdp->fCheck = TRUE; // start checked
// Default minimum date is the epoch
pdp->stMin = c_stEpoch;
// Default maximum date is armageddon
pdp->stMax = c_stArmageddon;
pdp->gdtr = GDTR_MIN; // We marked MIN as set in IE4, go figure
//
// See if the date/time picker supports this calendar. [samera]
//
MCGetCalendarInfo(&pdp->sec.ct);
//
// If the DTP is RTL mirrored and it's a Time-Only field, then
// we need to mirror format string so that it's displayed correctly
// on a RTL mirrored window. In case of Arabic, we need to swap the
// Time-Marker to the other side (visual left) so that it looks ok.
// For the hebrew, we need to swap the field (whether it's date or time)
// bacause unlike Arabic, it doesn't have its own digit so it reads
// from LeftToRight. [samera]
//
lcid = GetUserDefaultLCID(); pdp->sec.fMirrorSEC = pdp->sec.fSwapTimeMarker = FALSE; if (IS_WINDOW_RTL_MIRRORED(hwnd)) { if (pdp->ci.style & DTS_TIMEFORMATONLY) { pdp->sec.fMirrorSEC = TRUE;
if ((PRIMARYLANGID(LANGIDFROMLCID(lcid))) == LANG_ARABIC) pdp->sec.fSwapTimeMarker = TRUE; } else if((PRIMARYLANGID(LANGIDFROMLCID(lcid))) == LANG_HEBREW) { pdp->sec.fMirrorSEC = TRUE; } }
// initialize SUBEDITCONTROL
pdp->sec.pci = &pdp->ci; GetLocalTime(&st); SECSetSystemtime(pdp, &st); SECSetFont(&pdp->sec, NULL); pdp->fLocale = TRUE; DPHandleLocaleChange(pdp); MCLoadString(IDS_DELIMETERS, pdp->sec.szDelimeters, ARRAYSIZE(pdp->sec.szDelimeters));
hfont = NULL; if (lpcs->hwndParent) hfont = (HFONT)SendMessage(lpcs->hwndParent, WM_GETFONT, 0, 0); DPHandleSetFont(pdp, hfont, FALSE); // initialize the colors
MCInitColorArray(pdp->clr); return(0); }
LRESULT DPOnStyleChanging(DATEPICK *pdp, UINT gwl, LPSTYLESTRUCT pinfo) { if (gwl == GWL_STYLE) { DWORD changeFlags = pdp->ci.style ^ pinfo->styleNew;
// Don't allow these bits to change
changeFlags &= DTS_UPDOWN | DTS_SHOWNONE | DTS_INVALIDBITS;
pinfo->styleNew ^= changeFlags; }
return(0); }
LRESULT DPOnStyleChanged(DATEPICK *pdp, UINT gwl, LPSTYLESTRUCT pinfo) { if (gwl == GWL_STYLE) { DWORD changeFlags = pdp->ci.style ^ pinfo->styleNew;
ASSERT(!(changeFlags & (DTS_UPDOWN|DTS_SHOWNONE)));
pdp->ci.style = pinfo->styleNew;
if (changeFlags & (DTS_SHORTDATEFORMAT|DTS_LONGDATEFORMAT|DTS_TIMEFORMAT|DTS_INVALIDBITS)) { DPHandleLocaleChange(pdp); }
if (changeFlags & (WS_BORDER | WS_CAPTION | WS_THICKFRAME)) { // the changing of these bits affect the size of the window
// but not until after this message is handled
// so post ourself a message.
PostMessage(pdp->ci.hwnd, DTMP_WINDOWPOSCHANGED, 0, 0); }
}
return(0); }
void DPHandleLocaleChange(DATEPICK *pdp) { //
// See if the date/time picker supports this new calendar, and refresh
// era names as appropriate.
//
MCGetCalendarInfo(&pdp->sec.ct);
if (pdp->fLocale) { TCHAR szFormat[DTP_FORMATLENGTH];
switch (pdp->ci.style & DTS_FORMATMASK) { case DTS_TIMEFORMATONLY: GetLocaleInfo(pdp->sec.ct.lcid, LOCALE_STIMEFORMAT, szFormat, ARRAYSIZE(szFormat)); break;
case DTS_LONGDATEFORMAT: GetLocaleInfo(pdp->sec.ct.lcid, LOCALE_SLONGDATE, szFormat, ARRAYSIZE(szFormat)); break;
case DTS_SHORTDATEFORMAT: case DTS_SHORTDATECENTURYFORMAT: GetLocaleInfo(pdp->sec.ct.lcid, LOCALE_SSHORTDATE, szFormat, ARRAYSIZE(szFormat)); break; } SECParseFormat(pdp, &pdp->sec, szFormat); } }
void DPHandleSetFont(DATEPICK *pdp, HFONT hfont, BOOL fRedraw) { SECSetFont(&pdp->sec, hfont); SECRecomputeSizing(&pdp->sec, &pdp->rc); pdp->ci.uiCodePage = GetCodePageForFont(hfont);
if (fRedraw) { InvalidateRect(pdp->ci.hwnd, NULL, TRUE); UpdateWindow(pdp->ci.hwnd); } }
void DPPaint(DATEPICK *pdp, HDC hdc) { if (DatePick_ShowCheck(pdp)) { if (RectVisible(hdc, &pdp->rcCheck)) { RECT rc = pdp->rcCheck;
if (pdp->hThemeCheck) { int iStateId = CBS_UNCHECKEDNORMAL; if (pdp->fCheck) iStateId = CBS_CHECKEDNORMAL;
if (pdp->fCheckFocus) iStateId += 1; // Hot == Check focus...
if (!pdp->fEnabled) iStateId += 3; // Disabled
DrawThemeBackground(pdp->hThemeCheck, hdc, BP_CHECKBOX, iStateId, &rc, 0); } else { UINT dfcs = DFCS_BUTTONCHECK; if (pdp->fCheck) dfcs |= DFCS_CHECKED; if (!pdp->fEnabled) dfcs |= DFCS_INACTIVE;
InflateRect(&rc, -1 , -1); if (pdp->fCheckFocus) DrawFocusRect(hdc, &rc); DrawFrameControl(hdc, &rc, DFC_BUTTON, dfcs); }
} }
if (!pdp->fFreeEditing) SECDrawSubedits(hdc, &pdp->sec, pdp->fFocus, pdp->fCheck ? pdp->fEnabled : FALSE);
if (!pdp->fUseUpDown && RectVisible(hdc, &pdp->rcBtn)) DPDrawDropdownButton(pdp, hdc, FALSE); }
void _RecomputeMonthCalRect(DATEPICK *pdp, LPRECT prcCal, LPRECT prcCalT ) { RECT rcCal = *prcCal; RECT rcCalT = *prcCalT; RECT rcWorkArea; MONITORINFO mi = {0}; HMONITOR hMonitor; if (DatePick_RightAlign(pdp)) { rcCal.left = rcCal.right - (rcCalT.right - rcCalT.left); } else { rcCal.right = rcCal.left + (rcCalT.right - rcCalT.left); } rcCal.bottom = rcCal.top + (rcCalT.bottom - rcCalT.top);
// Get the information about the most appropriate monitor.
// (This includes both the work area and the monitor size.
hMonitor = MonitorFromRect(&rcCal, MONITOR_DEFAULTTONEAREST); mi.cbSize = sizeof(mi); GetMonitorInfo(hMonitor, &mi); // we need to know where to fit this rectangle into
if (GetWindowLong(pdp->ci.hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST) { // if we're topmost, our limits are the screen limits (not the working area)
rcWorkArea = mi.rcMonitor; } else { // otherwise it's the limits of the workarea
rcWorkArea = mi.rcWork; }
// slide left if off the right side of area
if (rcCal.right > rcWorkArea.right) { int nTmp = rcCal.right - rcWorkArea.right; rcCal.left -= nTmp; rcCal.right -= nTmp; } // slide right if off the left side of area
if (rcCal.left < rcWorkArea.left) { int nTmp = rcWorkArea.left - rcCal.left; rcCal.left += nTmp; rcCal.right += nTmp; } // move to top of control if off the bottom side of area
if (rcCal.bottom > rcWorkArea.bottom) { RECT rcT = pdp->rc; int nTmp = rcCal.bottom - rcCal.top;
MapWindowRect(pdp->ci.hwnd, NULL, (LPPOINT)&rcT); // 2 ClientToScreen
rcCal.bottom = rcT.top; rcCal.top = rcCal.bottom - nTmp; } *prcCal = rcCal; }
void DPLBD_MonthCal(DATEPICK *pdp, BOOL fLButtonDown) { HDC hdc; HWND hwndMC; RECT rcT, rcCalT; RECT rcBtn, rcCal; BOOL fBtnDown; // Is the button drawn DOWN or UP
BOOL fBtnActive; // Is the button still active
SYSTEMTIME st; SYSTEMTIME stOld; DWORD dwWidth; hdc = GetDC(pdp->ci.hwnd);
// turn datetimepick on but remove all focus -- the MonthCal will have focus
if (!pdp->fCheck) { pdp->fCheck = TRUE; InvalidateRect(pdp->ci.hwnd, NULL, TRUE); DPNotifyDateChange(pdp); } if (pdp->fCheckFocus) { pdp->fCheckFocus = FALSE; InvalidateRect(pdp->ci.hwnd, &pdp->rcCheck, TRUE); } SECSetCurSubed(pdp, SUBEDIT_NONE);
if (fLButtonDown) DPDrawDropdownButton(pdp, hdc, TRUE);
rcT = pdp->rc; MapWindowRect(pdp->ci.hwnd, NULL, &rcT); //2 ClientToScreen
rcBtn = pdp->rcBtn; MapWindowRect(pdp->ci.hwnd, NULL, &rcBtn); //ClientToScreen
rcCal = rcT; // this size is only temp until
rcCal.top = rcCal.bottom + 1; // we ask the monthcal how big it
rcCal.bottom = rcCal.top + 1; // wants to be
hwndMC = CreateWindow(g_rgchMCName, NULL, WS_POPUP | WS_BORDER, rcCal.left, rcCal.top, rcCal.right - rcCal.left, rcCal.bottom - rcCal.top, pdp->ci.hwnd, NULL, HINST_THISDLL, NULL); if (hwndMC == NULL) { DebugMsg(DM_WARNING, TEXT("DPLBD_MonthCal could not create MONTHCAL")); return; }
pdp->hwndMC = hwndMC;
// set all the colors:
{ int i; for (i = 0; i < MCSC_COLORCOUNT; i++) { SendMessage(hwndMC, MCM_SETCOLOR, i, pdp->clr[i]); } }
if (pdp->hfontMC) SendMessage(hwndMC, WM_SETFONT, (WPARAM)pdp->hfontMC, (LPARAM)FALSE);
// set min/max dates
// Relies on HACK! that stMin and stMax are adjacent
MonthCal_SetRange(hwndMC, GDTR_MIN | GDTR_MAX, &pdp->stMin);
SendMessage(hwndMC, MCM_GETMINREQRECT, 0, (LPARAM)&rcCalT); ASSERT(rcCalT.left == 0 && rcCalT.top == 0); dwWidth = (DWORD)SendMessage(hwndMC, MCM_GETMAXTODAYWIDTH, 0, 0); if (dwWidth > (DWORD)rcCalT.right) rcCalT.right = dwWidth;
SECGetSystemtime(&pdp->sec, &st); SendMessage(hwndMC, MCM_SETCURSEL, 0, (LPARAM)&st);
_RecomputeMonthCalRect(pdp, &rcCal, &rcCalT); MoveWindow(hwndMC, rcCal.left, rcCal.top, rcCal.right - rcCal.left, rcCal.bottom - rcCal.top, FALSE);
CCSendNotify(&pdp->ci, DTN_DROPDOWN, NULL);
//
// HACK-- App may have resized the window during DTN_DROPDOWN,
// so we need to get the new rcCal rect
//
{ MONTHCAL *pmc = MonthCal_GetPtr(hwndMC); _RecomputeMonthCalRect(pdp, &rcCal, &pmc->rc); MoveWindow(hwndMC, rcCal.left, rcCal.top, rcCal.right - rcCal.left, rcCal.bottom - rcCal.top, FALSE);
#ifdef DEBUG
if (GetAsyncKeyState(VK_CONTROL) < 0) (pmc)->ci.style |= MCS_MULTISELECT; #endif
} ShowWindow(hwndMC, SW_SHOWNA); pdp->fShow = TRUE; fBtnDown = fLButtonDown; fBtnActive = fLButtonDown;
stOld = pdp->sec.st; while (pdp->fShow) { MSG msg;
pdp->fShow = (WORD) GetMessage(&msg, NULL, 0, 0);
// Here's how button controls work as far as I can tell:
// Until the "final button draw up", the button draws down when the
// mouse is over it and it draws up when the mouse is not over it. This
// entire time, the control is active.
//
// The "final button draw up" occurs at the first opportunity of:
// the user releases the mouse button OR the user moves into the rect
// of the control. The control does it's action on a "mouse up".
if (fBtnActive) { switch (msg.message) { case WM_MOUSEMOVE: if (PtInRect(&rcBtn, msg.pt)) { if (!fBtnDown) { DPDrawDropdownButton(pdp, hdc, TRUE); fBtnDown = TRUE; } } else { if (fBtnDown) { DPDrawDropdownButton(pdp, hdc, FALSE); fBtnDown = FALSE; } if (PtInRect(&rcCal, msg.pt)) { fBtnActive = FALSE; // let MonthCal think it got a button down
FORWARD_WM_LBUTTONDOWN(hwndMC, FALSE, rcCal.left/2 + rcCal.right/2, rcCal.top/2 + rcCal.bottom/2, 0, SendMessage); } } continue; // the MonthCal doesn't need this message
case WM_LBUTTONUP: if (fBtnDown) { DPDrawDropdownButton(pdp, hdc, FALSE); fBtnDown = FALSE; } fBtnActive = FALSE; continue; // the MonthCal doesn't need this message
} } // if (fBtnActive)
// Check for events that cause the calendar to go away
//
// These events mean "I like it". We allow Alt+Up or Enter
// to accept the changes. (Alt+Up for compat with combo boxes.)
//
if (((msg.message == WM_LBUTTONDOWN || msg.message == WM_NCLBUTTONDOWN || msg.message == WM_LBUTTONDBLCLK) && !PtInRect(&rcCal, msg.pt)) || msg.message == WM_SYSCOMMAND || msg.message == WM_COMMAND || (msg.message == WM_SYSKEYDOWN && msg.wParam == VK_UP) || (msg.message == WM_KEYDOWN && msg.wParam == VK_RETURN) || msg.message == WM_KILLFOCUS) { DebugMsg(TF_MONTHCAL,TEXT("DPLBD_MonthCal got a message to accept (%d)"), msg.message); pdp->fShow = FALSE; continue; }
//
// These events mean "I don't like it".
//
else if (((msg.message == WM_RBUTTONDOWN || msg.message == WM_NCRBUTTONDOWN || msg.message == WM_RBUTTONDBLCLK) && !PtInRect(&rcCal, msg.pt)) || (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)) { DebugMsg(TF_MONTHCAL,TEXT("DPLBD_MonthCal got a message to cancel (%d)"), msg.message); pdp->fShow = FALSE; pdp->sec.st = stOld; DPNotifyDateChange(pdp); InvalidateRect(pdp->ci.hwnd, NULL, TRUE); continue; }
TranslateMessage(&msg); DispatchMessage(&msg); } // while(fShow)
CCSendNotify(&pdp->ci, DTN_CLOSEUP, NULL);
pdp->hwndMC = NULL; DestroyWindow(hwndMC); ReleaseDC(pdp->ci.hwnd, hdc); }
void DPHandleSECEdit(DATEPICK *pdp) { TCHAR szBuf[DTP_FORMATLENGTH];
if (SECEdit(pdp, szBuf, ARRAYSIZE(szBuf))) { NMDATETIMESTRING nmdts = {0};
nmdts.pszUserString = szBuf; // just in case the app doesn't parse the string
nmdts.st = pdp->sec.st; nmdts.dwFlags = (pdp->fCheck==1) ? GDT_VALID : GDT_NONE;
CCSendNotify(&pdp->ci, DTN_USERSTRING, &nmdts.nmhdr);
// If the app gives us an invalid date, go back to the old date
if (nmdts.dwFlags == GDT_VALID && !IsValidSystemtime(&nmdts.st)) { nmdts.st = pdp->sec.st; }
if (nmdts.dwFlags == GDT_NONE) { if (DatePick_ShowCheck(pdp)) { pdp->fCheck = FALSE; pdp->fCheckFocus = TRUE; SECSetCurSubed(pdp, SUBEDIT_NONE); InvalidateRect(pdp->ci.hwnd, NULL, TRUE); DPNotifyDateChange(pdp); } } else if (nmdts.dwFlags == GDT_VALID) { DPSetDate(pdp, &nmdts.st, FALSE); } } }
LRESULT DPLButtonDown(DATEPICK *pdp, WPARAM wParam, LPARAM lParam) { POINT pt; BOOL fFocus;
if (!pdp->fEnabled) return(0);
pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam);
// reset subed char count
SECResetSubeditEdit(pdp);
fFocus = pdp->fFocus; if (!fFocus) SetFocus(pdp->ci.hwnd);
// display MONTHCAL iif we're not DTS_UPDOWN
if (!pdp->fUseUpDown && PtInRect(&pdp->rcBtn, pt) && IsWindowVisible(pdp->ci.hwnd)) { DPLBD_MonthCal(pdp, TRUE); } else if (!pdp->fCapture) { // Un/check checkbox
if (DatePick_ShowCheck(pdp) && PtInRect(&pdp->rcCheck, pt)) { pdp->fCheck = !pdp->fCheck; pdp->fCheckFocus = 1; SECSetCurSubed(pdp, SUBEDIT_NONE); InvalidateRect(pdp->ci.hwnd, NULL, TRUE); DPNotifyDateChange(pdp); } // Select a subedit
else if (pdp->fCheck) { if (DatePick_AppCanParse(pdp) && fFocus) { // First click brings focus to a subedit, second click starts editing
DPHandleSECEdit(pdp); } else { int isubed; pt.x += pdp->sec.xScroll; isubed = SECSubeditFromPt(&pdp->sec, pt); if (isubed >= 0) { SECSetCurSubed(pdp, isubed); if (DatePick_ShowCheck(pdp)) { pdp->fCheckFocus = 0; InvalidateRect(pdp->ci.hwnd, &pdp->rcCheck, TRUE); } } } } } return(0); }
void DPRecomputeSizing(DATEPICK *pdp, RECT *prect) { RECT rcTmp;
if (DatePick_ShowCheck(pdp)) { pdp->rcCheck.top = prect->top + 1; pdp->rcCheck.bottom = prect->bottom - 1; pdp->rcCheck.left = prect->left + 1; pdp->rcCheck.right = prect->left + (pdp->rcCheck.bottom - pdp->rcCheck.top); // occupy at most half the width of the window
if (pdp->rcCheck.right > prect->left + (prect->right - prect->left)/2) { pdp->rcCheck.right = prect->left + (prect->right - prect->left)/2; } } else { pdp->rcCheck.top = prect->top; pdp->rcCheck.bottom = prect->top; pdp->rcCheck.left = prect->left; pdp->rcCheck.right = prect->left + DPXBUFFER - 1; }
pdp->rcBtn = *prect; pdp->rcBtn.left = pdp->rcBtn.right - GetSystemMetrics(SM_CXVSCROLL); if (pdp->rcBtn.left < pdp->rcCheck.right) pdp->rcBtn.left = pdp->rcCheck.right; if (pdp->hwndUD) MoveWindow(pdp->hwndUD, pdp->rcBtn.left, pdp->rcBtn.top, pdp->rcBtn.right - pdp->rcBtn.left + 1, pdp->rcBtn.bottom - pdp->rcBtn.top + 1, FALSE);
rcTmp = pdp->rc; pdp->rc.top = prect->top; pdp->rc.bottom = prect->bottom; pdp->rc.left = pdp->rcCheck.right + 1; pdp->rc.right = pdp->rcBtn.left - 1; SECRecomputeSizing(&pdp->sec, &pdp->rc); }
// deal with control codes
LRESULT DPHandleKeydown(DATEPICK *pdp, WPARAM wParam, LPARAM lParam) { int delta = 1; if (wParam == VK_F4 && !pdp->fUseUpDown) { DPLBD_MonthCal(pdp, FALSE); } else if (DatePick_AppCanParse(pdp) && wParam == VK_F2) { DPHandleSECEdit(pdp); } else if (pdp->fCheckFocus) { switch (wParam) { case VK_LEFT: delta = -1; // fall through...
case VK_RIGHT: if (pdp->fCheck) { if (SUBEDIT_NONE != SECIncrFocus(pdp, delta)) { pdp->fCheckFocus = FALSE; InvalidateRect(pdp->ci.hwnd, &pdp->rcCheck, TRUE); } } break; } } else { switch (wParam) { case VK_HOME: if (GetKeyState(VK_CONTROL) < 0) { SYSTEMTIME st; GetLocalTime(&st); DPSetDate(pdp, &st, TRUE); break; } // fall through...
default: if (SECHandleKeydown(pdp, wParam, lParam)) { DPNotifyDateChange(pdp); } else if (DatePick_ShowCheck(pdp)) { if (pdp->sec.iseCur < 0) { pdp->fCheckFocus = TRUE; InvalidateRect(pdp->ci.hwnd, &pdp->rcCheck, TRUE); } } break; } } return(0); }
// deal with characters
LRESULT DPHandleChar(DATEPICK *pdp, WPARAM wParam, LPARAM lParam) { TCHAR ch = (TCHAR)wParam;
if (pdp->fCheckFocus) { // this is the only character we care about in this case
if (ch == TEXT(' ')) { pdp->fCheck = 1-pdp->fCheck; InvalidateRect(pdp->ci.hwnd, NULL, TRUE); DPNotifyDateChange(pdp); } else { MessageBeep(MB_ICONHAND); } } else { // let the subedit handle this -- a value can change
SECHandleChar(pdp, ch); } return(0); }
void DPNotifyDateChange(DATEPICK *pdp) { NMDATETIMECHANGE nmdc = {0}; BOOL fChanged;
if (pdp->fNoNotify) return;
if (pdp->fCheck == 0) { nmdc.dwFlags = GDT_NONE; } else { // validate date - do it here in only one place
if (CmpSystemtime(&pdp->sec.st, &pdp->stMin) < 0) { pdp->sec.st = pdp->stMin; InvalidateRect(pdp->ci.hwnd, NULL, TRUE); SECInvalidate(&pdp->sec, SE_APP); } else if (CmpSystemtime(&pdp->sec.st, &pdp->stMax) > 0) { pdp->sec.st = pdp->stMax; InvalidateRect(pdp->ci.hwnd, NULL, TRUE); SECInvalidate(&pdp->sec, SE_APP); }
nmdc.dwFlags = GDT_VALID; SECGetSystemtime(&pdp->sec, &nmdc.st); }
fChanged = CmpSystemtime(&pdp->stPrev, &nmdc.st); if (fChanged) { NotifyWinEvent(EVENT_OBJECT_VALUECHANGE, pdp->ci.hwnd, OBJID_CLIENT, INDEXID_CONTAINER); }
//
// APP COMPAT: IE4 always notified even if the date didn't change.
// I don't know of any apps that rely on this
// but I'm not gonna risk it.
//
if (fChanged || pdp->ci.iVersion < 5) { pdp->stPrev = nmdc.st; CCSendNotify(&pdp->ci, DTN_DATETIMECHANGE, &nmdc.nmhdr); } }
BOOL DPSetDate(DATEPICK *pdp, SYSTEMTIME *pst, BOOL fMungeDate) { BOOL fChanged = FALSE;
// make sure that the new date is within the valid range
if (CmpSystemtime(pst, &pdp->stMin) < 0) { if (!fMungeDate) return(FALSE); pst = &pdp->stMin; } if (CmpSystemtime(&pdp->stMax, pst) < 0) { if (!fMungeDate) return(FALSE); pst = &pdp->stMax; }
if (fMungeDate) { // only copy the date portion
CopyDate(*pst, pdp->sec.st); fChanged = TRUE; } else { fChanged = SECSetSystemtime(pdp, pst); } if (fChanged) { SECInvalidate(&pdp->sec, SE_APP); // SE_APP invalidates everything
DPNotifyDateChange(pdp); } return(TRUE); }
void DPDrawDropdownButton(DATEPICK *pdp, HDC hdc, BOOL fPressed) { if (pdp->hThemeCombo) { int iStateId = CBXS_NORMAL; if (fPressed) iStateId = CBXS_PRESSED;
if (!pdp->fEnabled) iStateId = CBXS_DISABLED;
DrawThemeBackground(pdp->hThemeCombo, hdc, CP_DROPDOWNBUTTON, iStateId, &pdp->rcBtn, 0); } else { UINT dfcs; dfcs = DFCS_SCROLLDOWN; if (fPressed) dfcs |= DFCS_PUSHED | DFCS_FLAT; if (!pdp->fEnabled) dfcs |= DFCS_INACTIVE; DrawFrameControl(hdc, &pdp->rcBtn, DFC_SCROLL, dfcs); } }
|