Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

5015 lines
140 KiB

#include "ctlspriv.h"
#include "scdttime.h"
#include "monthcal.h"
// private message
#define MCM_DROPOK MCM_FIRST + 200
// 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 MCRButtonDown(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);
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 GetYrMoForOffset(MONTHCAL *pmc, int iOffset, int *piYear, int *piMonth);
// 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);
static TCHAR const g_rgchMCName[] = MONTHCAL_CLASS;
static TCHAR const g_rgchDTPName[] = DATETIMEPICK_CLASS;
// MONTHCAL globals
static TCHAR const g_szTextExtentDef[] = TEXT("0000");
static TCHAR const g_szNumFmt[] = TEXT("%d");
static TCHAR const g_szNumLZFmt[] = TEXT("0%d"); // Assumption: leading zero valid in all locales
void FillRectClr(HDC hdc, LPRECT prc, COLORREF clr)
{
COLORREF clrSave = SetBkColor(hdc, clr);
ExtTextOut(hdc,0,0,ETO_OPAQUE,prc,NULL,0,NULL);
SetBkColor(hdc, clrSave);
}
BOOL InitDateClasses(HINSTANCE hinst)
{
WNDCLASS wndclass;
if (GetClassInfo(hinst, g_rgchMCName, &wndclass))
{
// we're already registered
DebugMsg(TF_MONTHCAL, TEXT("mc: Date Classes already initialized."));
return(TRUE);
}
wndclass.style = CS_GLOBALCLASS;
wndclass.lpfnWndProc = (WNDPROC)MonthCalWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = sizeof(LPVOID);
wndclass.hInstance = hinst;
wndclass.hIcon = NULL;
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = g_rgchMCName;
if (!RegisterClass(&wndclass))
{
DebugMsg(DM_WARNING, TEXT("mc: MonthCalClass failed to initialize"));
return(FALSE);
}
wndclass.lpfnWndProc = (WNDPROC)DatePickWndProc;
wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wndclass.lpszClassName = g_rgchDTPName;
if (!RegisterClass(&wndclass))
{
DebugMsg(DM_WARNING, TEXT("mc: DatePickClass failed to initialize"));
return(FALSE);
}
DebugMsg(TF_MONTHCAL, TEXT("mc: Date Classes initialized successfully."));
return(TRUE);
}
/*
** MonthCal stuff
*/
BOOL UpdateLocaleInfo(MONTHCAL* pmc, PLOCALEINFO pli)
{
int cch, i;
TCHAR rgch[10];
for (i = 0; i < 12; i++)
{
cch = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SMONTHNAME1 + i,
pli->rgszMonth[i], CCHMAXMONTH);
if (cch == 0)
// the calendar is pretty useless without month names...
return(FALSE);
}
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 (!pmc->fFirstDowSet) {
cch = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, rgch, ARRAYSIZE(rgch));
if (cch > 0)
pli->dowStartWeek = rgch[0] - TEXT('0');
}
cch = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, rgch, ARRAYSIZE(rgch));
if (cch > 0)
pli->firstWeek = rgch[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
LoadString(HINST_THISDLL, IDS_TODAY, pli->szToday, ARRAYSIZE(pli->szToday));
LoadString(HINST_THISDLL, 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);
}
BOOL MCHandleEraseBkgnd(MONTHCAL* pmc, HDC hdc)
{
RECT rc;
GetClipBox(hdc, &rc);
FillRectClr(hdc, &rc, pmc->clr[MCSC_BACKGROUND]);
return TRUE;
}
void MCGetTodayBtnRect(MONTHCAL *pmc, RECT *prc);
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)) {
phti->uHit = MCHT_TODAYLINK;
} else if (pmc->fSpinPrev = 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;
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 = month;
phti->st.wYear = year;
if (MonthCal_ShowWeekNumbers(pmc) && PtInRect(&pmc->rcWeekNum, ptRel)) {
phti->uHit |= MCHT_CALENDARWEEKNUM;
phti->pt.x = rcMonth.left + ptRel.x + pmc->rcDayNum.left;
FGetDateForPt(pmc, phti->pt, &phti->st, NULL, NULL, NULL, NULL);
} 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 = iCol;
} 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
// partially shown 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;
}
LRESULT CALLBACK MonthCalWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
MONTHCAL *pmc;
LRESULT lres = 0;
if (message == WM_NCCREATE)
return(MCNcCreateHandler(hwnd));
pmc = MonthCal_GetPtr(hwnd);
if (pmc == NULL)
return(DefWindowProc(hwnd, message, wParam, lParam));
// Dispatch the various messages we can receive
switch (message)
{
case WM_CREATE:
lres = MCCreateHandler(pmc, hwnd, (LPCREATESTRUCT)lParam);
break;
HANDLE_MSG(pmc, WM_ERASEBKGND, MCHandleEraseBkgnd);
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc;
hwnd = pmc->ci.hwnd;
hdc = BeginPaint(hwnd, &ps);
MCPaint(pmc, hdc);
EndPaint(hwnd, &ps);
break;
}
case WM_RBUTTONDOWN:
MCRButtonDown(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 = fEnable;
InvalidateRect(pmc->ci.hwnd, NULL, TRUE);
}
break;
}
case WM_SIZE:
{
RECT rc;
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_WININICHANGE:
if (!lstrcmpi((LPTSTR)lParam, TEXT("Intl")))
{
UpdateLocaleInfo(pmc, &pmc->li);
InvalidateRect(hwnd, NULL, TRUE);
}
break;
case WM_STYLECHANGING:
lres = MCOnStyleChanging(pmc, wParam, (LPSTYLESTRUCT)lParam);
break;
case WM_STYLECHANGED:
lres = MCOnStyleChanged(pmc, wParam, (LPSTYLESTRUCT)lParam);
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));
CopyDate(pmc->st, *pst);
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, sizeof(SYSTEMTIME) * 2);
if (!MonthCal_IsMultiSelect(pmc))
break;
CopyDate(pmc->st, *pst);
pst++;
CopyDate(pmc->stEndSel, *pst);
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;
if (CmpDate(pstStart, pstEnd) > 0)
{
CopyDate(*pstStart, stEnd);
CopyDate(*pstEnd, stStart);
pstStart = &stStart;
pstEnd = &stEnd;
}
if (pmc->fMinYrSet && -1==CmpDate(pstStart, &pmc->stMin))
break;
if (pmc->fMaxYrSet && 1==CmpDate(pstEnd, &pmc->stMax))
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;
}
CopyDate(pmc->st, pmc->stStartPrev);
CopyDate(pmc->stEndSel, pmc->stEndPrev);
pmc->fNoNotify = TRUE;
lres = MCSetDate(pmc, pstEnd);
if (lres)
{
CopyDate(*pstStart, pmc->st);
CopyDate(*pstEnd, pmc->stEndSel);
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)
{
CopyDate(pmc->stMonthFirst, pst[0]);
CopyDate(pmc->stMonthLast, pst[1]);
}
else if (wParam == GMR_DAYSTATE)
{
CopyDate(pmc->stViewFirst, pst[0]);
CopyDate(pmc->stViewLast, pst[1]);
}
}
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.
case MCM_GETMINREQRECT:
{
LPRECT prc = (LPRECT)lParam;
prc->left = 0;
prc->top = 0;
prc->right = pmc->dxMonth;
prc->bottom = pmc->dyMonth + 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;
}
case MCM_HITTEST:
return MCHandleHitTest(pmc, (PMCHITTESTINFO)lParam);
case MCM_SETCOLOR:
if (wParam >= 0 && 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 >= 0 && 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;
CopyDate(pmc->stMin, *pst);
pst++;
CopyDate(pmc->stMax, *pst);
Assert(lres == 0);
if (pmc->fMinYrSet)
lres = GDTR_MIN;
if (pmc->fMaxYrSet)
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;
if (wParam & GDTR_MIN)
{
CopyDate(*pst, pmc->stMin);
pmc->fMinYrSet = TRUE;
}
else
{
ZeroMemory(&pmc->stMin, sizeof(pmc->stMin));
pmc->fMinYrSet = FALSE;
}
pst++;
if (wParam & GDTR_MAX)
{
CopyDate(*pst, pmc->stMax);
pmc->fMaxYrSet = TRUE;
}
else
{
ZeroMemory(&pmc->stMax, sizeof(pmc->stMax));
pmc->fMaxYrSet = FALSE;
}
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:
lres = DefWindowProc(hwnd, message, wParam, lParam);
break;
} /* switch (message) */
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] = GetSysColor(COLOR_3DFACE);
pclr[MCSC_MONTHBK] = GetSysColor(COLOR_WINDOW);
pclr[MCSC_TEXT] = GetSysColor(COLOR_WINDOWTEXT);
pclr[MCSC_TITLEBK] = GetSysColor(COLOR_ACTIVECAPTION);
pclr[MCSC_TITLETEXT] = GetSysColor(COLOR_CAPTIONTEXT);
pclr[MCSC_TRAILINGTEXT] = GetSysColor(COLOR_GRAYTEXT);
}
LRESULT MCCreateHandler(MONTHCAL *pmc, HWND hwnd, LPCREATESTRUCT lpcs)
{
HFONT hfont;
SYSTEMTIME st;
int i;
// 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->hpenToday = CreatePen(PS_SOLID, 2, CAL_COLOR_TODAY);
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]);
}
// the day before 14-sep-1752 was 2-sep-1752 in British and US history.
// avoid ui problems related with this...
pmc->stMin.wDay = 14;
pmc->stMin.wMonth = 9;
pmc->stMin.wYear = 1752;
pmc->fMinYrSet = TRUE;
Assert(pmc->fMaxYrSet == FALSE);
GetLocalTime(&pmc->stToday);
CopyDate(pmc->stToday, pmc->st);
if (MonthCal_IsMultiSelect(pmc))
CopyDate(pmc->st, pmc->stEndSel);
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);
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);
}
if (changeFlags)
InvalidateRect(pmc->ci.hwnd, NULL, TRUE);
}
return(0);
}
void MCCalcSizes(MONTHCAL *pmc)
{
HDC hdc;
HFONT hfontOrig;
SIZE size;
int i, dxMax, dyMax;
RECT rect;
// get sizing info for bold font...
hdc = GetDC(pmc->ci.hwnd);
hfontOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
GetTextExtentPoint(hdc, g_szTextExtentDef, 2, &size);
dxMax = size.cx;
dyMax = size.cy;
GetTextExtentPoint(hdc, g_szTextExtentDef, 4, &size);
pmc->dxYearMax = size.cx;
GetTextExtentPoint(hdc, pmc->li.szToday, lstrlen(pmc->li.szToday), &size);
pmc->dyToday = max(dyMax, size.cy) + 4;
SelectObject(hdc, (HGDIOBJ)pmc->hfont);
for (i = 0; i < 7; i++)
{
GetTextExtentPoint(hdc, pmc->li.rgszDay[i], lstrlen(pmc->li.rgszDay[i]), &size);
if (size.cx > dxMax)
dxMax = size.cx;
if (size.cy > dyMax)
dyMax = size.cy;
}
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
// Space for month name
pmc->rcMonthName.left = 1;
pmc->rcMonthName.top = 1;
pmc->rcMonthName.right = pmc->dxMonth;
pmc->rcMonthName.bottom = pmc->rcMonthName.top + (pmc->dyRow * 2);
// Space for day-of-week
pmc->rcDow.left = 1;
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->rcDow.left += pmc->dxCol;
pmc->rcDow.right -= pmc->dxCol;
pmc->rcWeekNum.top = pmc->rcDow.bottom;
pmc->rcWeekNum.right = pmc->rcWeekNum.left + pmc->dxCol;
pmc->rcWeekNum.bottom = pmc->dyMonth;
}
// 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...
lf.lfWeight = (lf.lfWeight >= 700 ? 1000 : 800);
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);
}
}
// Stolen from the windows tips help file
void DrawTransparentBitmap(HDC hdc, HBITMAP hbmp, RECT *prc, COLORREF cTransparentColor)
{
COLORREF cColor;
BITMAP bm;
HBITMAP hbmAndBack, hbmAndObject, hbmAndMem, hbmSave;
HGDIOBJ hbmBackOld, hbmObjectOld, hbmMemOld, hbmSaveOld;
HDC hdcMem, hdcBack, hdcObject, hdcTemp, hdcSave;
POINT ptSize;
int d;
hdcTemp = CreateCompatibleDC(hdc);
SelectObject(hdcTemp, (HGDIOBJ)hbmp); // Select the bitmap
GetObject(hbmp, sizeof(BITMAP), &bm);
ptSize.x = bm.bmWidth; // Get width of bitmap
ptSize.y = bm.bmHeight; // Get height of bitmap
DPtoLP(hdcTemp, &ptSize, 1); // Convert from device to logical points
d = prc->right - prc->left;
if (d < ptSize.x)
ptSize.x = d;
d = prc->bottom - prc->top;
if (d < ptSize.y)
ptSize.y = d;
// Create some DCs to hold temporary data.
hdcBack = CreateCompatibleDC(hdc);
hdcObject = CreateCompatibleDC(hdc);
hdcMem = CreateCompatibleDC(hdc);
hdcSave = CreateCompatibleDC(hdc);
// Create a bitmap for each DC. DCs are required for a number of
// GDI functions.
// Monochrome DC
hbmAndBack = CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
// Monochrome DC
hbmAndObject = CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
hbmAndMem = CreateCompatibleBitmap(hdc, ptSize.x, ptSize.y);
hbmSave = CreateCompatibleBitmap(hdc, ptSize.x, ptSize.y);
// Each DC must select a bitmap object to store pixel data.
hbmBackOld = SelectObject(hdcBack, (HGDIOBJ)hbmAndBack);
hbmObjectOld = SelectObject(hdcObject, (HGDIOBJ)hbmAndObject);
hbmMemOld = SelectObject(hdcMem, (HGDIOBJ)hbmAndMem);
hbmSaveOld = SelectObject(hdcSave, (HGDIOBJ)hbmSave);
// Set proper mapping mode.
SetMapMode(hdcTemp, GetMapMode(hdc));
// Save the bitmap sent here, because it will be overwritten.
BitBlt(hdcSave, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, SRCCOPY);
// Set the background color of the source DC to the color.
// contained in the parts of the bitmap that should be transparent
cColor = SetBkColor(hdcTemp, cTransparentColor);
// Create the object mask for the bitmap by performing a BitBlt
// from the source bitmap to a monochrome bitmap.
BitBlt(hdcObject, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, SRCCOPY);
// Set the background color of the source DC back to the original
// color.
SetBkColor(hdcTemp, cColor);
// Create the inverse of the object mask.
BitBlt(hdcBack, 0, 0, ptSize.x, ptSize.y, hdcObject, 0, 0, NOTSRCCOPY);
// Copy the background of the main DC to the destination.
BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdc, prc->left, prc->top, SRCCOPY);
// Mask out the places where the bitmap will be placed.
BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdcObject, 0, 0, SRCAND);
// Mask out the transparent colored pixels on the bitmap.
BitBlt(hdcTemp, 0, 0, ptSize.x, ptSize.y, hdcBack, 0, 0, SRCAND);
// XOR the bitmap with the background on the destination DC.
BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, SRCPAINT);
// Copy the destination to the screen.
BitBlt(hdc, prc->left, prc->top, ptSize.x, ptSize.y, hdcMem, 0, 0, SRCCOPY);
// Place the original bitmap back into the bitmap sent here.
BitBlt(hdcTemp, 0, 0, ptSize.x, ptSize.y, hdcSave, 0, 0, SRCCOPY);
// Delete the memory bitmaps.
SelectObject(hdcBack, hbmBackOld);
DeleteObject(hbmAndBack);
SelectObject(hdcObject, hbmObjectOld);
DeleteObject(hbmAndObject);
SelectObject(hdcMem, hbmMemOld);
DeleteObject(hbmAndMem);
SelectObject(hdcSave, hbmSaveOld);
DeleteObject(hbmSave);
// Delete the memory DCs.
DeleteDC(hdcMem);
DeleteDC(hdcBack);
DeleteDC(hdcObject);
DeleteDC(hdcSave);
DeleteDC(hdcTemp);
}
void MCDrawTodayCircle(MONTHCAL *pmc, HDC hdc, RECT *prc)
{
HGDIOBJ hpenOld;
int xBegin, yBegin, yEnd;
xBegin = (prc->right - prc->left) / 2 + prc->left;
yBegin = prc->top + 4;
yEnd = (prc->bottom - prc->top) / 2 + prc->top;
hpenOld = SelectObject(hdc, (HGDIOBJ)pmc->hpenToday);
Arc(hdc, prc->left + 1, yBegin, prc->right, prc->bottom,
xBegin, yBegin, prc->right, yEnd);
Arc(hdc, prc->left - 10, prc->top + 1, prc->right, prc->bottom,
prc->right, yEnd, prc->left + 3, yBegin);
SelectObject(hdc, hpenOld);
}
void MCInvalidateMonthDays(MONTHCAL *pmc)
{
RECT rc;
rc.left = pmc->rcCentered.left;
rc.right = pmc->rcCentered.right;
rc.top = pmc->rcCentered.top + (pmc->dyRow / 2);
rc.bottom = pmc->rcCentered.bottom - pmc->dyToday;
InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
}
void MCGetTodayBtnRect(MONTHCAL *pmc, RECT *prc)
{
prc->left = pmc->rcCentered.left + 1;
prc->right = pmc->rcCentered.right - 1;
prc->top = pmc->rcCentered.bottom - pmc->dyToday;
prc->bottom = pmc->rcCentered.bottom;
}
void MCPaintArrowBtn(MONTHCAL *pmc, HDC hdc, BOOL fPrev, BOOL fPressed)
{
LPRECT prc;
UINT dfcs;
if (fPrev)
{
dfcs = DFCS_SCROLLLEFT;
prc = &pmc->rcPrev;
}
else
{
dfcs = DFCS_SCROLLRIGHT;
prc = &pmc->rcNext;
}
if (pmc->fEnabled)
{
if (fPressed)
{
dfcs |= DFCS_BUTTONPUSH;
}
}
else
{
dfcs |= DFCS_INACTIVE;
}
DrawFrameControl(hdc, prc, DFC_SCROLL, dfcs);
}
void MCPaint(MONTHCAL *pmc, HDC hdc)
{
TCHAR rgchDate[32], rgch[64];
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 LeftTop 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
MCGetTodayBtnRect(pmc, &rc);
if (RectVisible(hdc, &rc))
{
rcT.left = rc.left + 4;
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;
rcT.top = rc.top;
rcT.bottom = rc.bottom;
hgdiOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
SetTextColor(hdc, pmc->clr[MCSC_TEXT]);
GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &pmc->stToday,
NULL, rgchDate, sizeof(rgchDate));
wsprintf(rgch, TEXT("%s %s"), pmc->li.szToday, rgchDate);
DrawText(hdc, rgch, lstrlen(rgch), &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);
}
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, hbrushOld;
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 month and year and days of week
rc = pmc->rcMonthName;
rc.left += prc->left;
rc.right += prc->left - 1;
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);
wsprintf(rgch, TEXT("%s %d"), pmc->li.rgszMonth[iMonth - 1], iYear);
DrawText(hdc, rgch, lstrlen(rgch), &rc, DT_CENTER | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
SelectObject(hdc, (HGDIOBJ)pmc->hfont);
}
SetTextColor(hdc, pmc->clr[MCSC_TITLEBK]);
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;
}
if (MonthCal_ShowWeekNumbers(pmc))
{
rc = pmc->rcWeekNum;
rc.left += prc->left;
rc.top += prc->top;
rc.right += prc->left;
rc.bottom = rc.top + (pmc->dyRow * crowShow);
if (RectVisible(hdc, &rc))
{
MoveToEx(hdc, rc.right - 1, rc.top + 4, NULL);
LineTo(hdc, rc.right - 1, rc.bottom - 4);
st.wYear = iYear;
st.wMonth = iMonth;
st.wDay = nDay;
nweek = GetWeekNumber(&st, pmc->li.dowStartWeek, pmc->li.firstWeek);
rc.bottom = rc.top + pmc->dyRow;
for (irow = 0; irow < crowShow; irow++)
{
wsprintf(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))
{
wsprintf(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);
hbrushOld = SelectObject(hdc, (HGDIOBJ)hbrSelect);
fReset = TRUE;
SelectObject(hdc, GetStockObject(NULL_PEN));
x1 = 0;
x2 = 0;
if (isel & SEL_DOT)
{
Ellipse(hdc, rcT.left + 2, rcT.top + 2, rcT.right - 1, rcT.bottom - 1);
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 (pmc->fToday && iIndexSave == pmc->iMonthToday &&
icol == pmc->iColToday && irow == pmc->iRowToday)
{
MCDrawTodayCircle(pmc, hdc, &rcT);
}
if (fReset)
{
SetTextColor(hdc, clrOld);
SelectObject(hdc, (HGDIOBJ)hbrushOld);
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 = iYear;
st.wMonth = iMonth;
st.wDay = iDay;
iBegin = CmpDate(&st, &pmc->st);
if (MonthCal_IsMultiSelect(pmc))
{
iEnd = CmpDate(&st, &pmc->stEndSel);
if (iBegin == 1 && iEnd == -1)
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->hpenToday)
DeleteObject((HGDIOBJ)pmc->hpenToday);
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);
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 12 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);
rc.bottom = rc.top + DY_CALARROW;
rc.left = pmc->rcCentered.left + 5;
rc.right = rc.left + DX_CALARROW;
pmc->rcPrev = rc;
rc.right = pmc->rcCentered.right - 5;
rc.left = rc.right - DX_CALARROW;
pmc->rcNext = rc;
}
LRESULT MCSizeHandler(MONTHCAL *pmc, RECT *prc)
{
int nOldMax = pmc->nViewRows * pmc->nViewCols;
int nMax;
MCRecomputeSizing(pmc, prc);
nMax = pmc->nViewRows * pmc->nViewCols;
// if nMax==nOldMax, we could move the bits to the new rcCenter
//
//if (nMax != nOldMax)
{
SYSTEMTIME st;
int cmo, dmo;
// Compute new start date
CopyDate(pmc->stMonthFirst, st);
// BUGBUG: 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 have 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);
}
void MCUpdateMonthNamePos(MONTHCAL *pmc)
{
HDC hdc;
int cch, iYear, iMonth, iCount;
TCHAR rgch[64];
SIZE size;
LPTSTR szMonth;
HGDIOBJ hfontOrig;
hdc = GetDC(pmc->ci.hwnd);
hfontOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
iYear = pmc->stMonthFirst.wYear;
iMonth = pmc->stMonthFirst.wMonth;
for (iCount = 0; iCount < pmc->nMonths; iCount++)
{
szMonth = pmc->li.rgszMonth[iMonth - 1];
cch = wsprintf(rgch, TEXT("%s %d"), szMonth, iYear);
GetTextExtentPoint(hdc, rgch, cch, &size);
pmc->rgxMonthBegin[iCount] = (pmc->dxMonth - size.cx) / 2;
pmc->rgxYearEnd[iCount] = pmc->rgxMonthBegin[iCount] + size.cx;
GetTextExtentPoint(hdc, szMonth, lstrlen(szMonth), &size);
pmc->rgxMonthEnd[iCount] = pmc->rgxMonthBegin[iCount] + size.cx;
if(++iMonth > 12)
{
iMonth = 1;
iYear++;
}
}
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;
pmc->nMonths = pmc->nViewRows * pmc->nViewCols;
// make sure pstStart to pstStart+nMonths is within range
if (pmc->fMaxYrSet)
{
int 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 (pmc->fMinYrSet && -1==CmpDate(pstStart, &pmc->stMin))
{
CopyDate(pmc->stMin, *pstStart);
}
if (pmc->fMaxYrSet)
{
int 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 (pmc->fMinYrSet && -1==CmpDate(&pmc->stMonthFirst, &pmc->stMin))
{
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 = pmc->rgcDay[pmc->nMonths];
if (pmc->fMaxYrSet && 1==CmpDate(&pmc->stMonthLast, &pmc->stMax))
{
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_ShowToday(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 (pmc->fMinYrSet && -1==CmpDate(&st, &pmc->stMin))
return FALSE;
if (pmc->fMaxYrSet && 1==CmpDate(&st, &pmc->stMax))
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 = yr;
nmds.stStart.wMonth = 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)
{
NMSELCHANGE nmsc;
HWND hwndParent;
if (pmc->fNoNotify)
return;
hwndParent = GetParent(pmc->ci.hwnd);
if (hwndParent)
{
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;
}
// iMonth is a zero-based index relative to the DISPLAYED months...
void MCGetRcForMonth(MONTHCAL *pmc, int iMonth, RECT *prc)
{
int iRow, iCol, d;
iRow = iMonth / pmc->nViewCols;
iCol = iMonth % pmc->nViewCols;
prc->left = pmc->rcCentered.left;
prc->right = prc->left + pmc->dxMonth;
prc->top = pmc->rcCentered.top;
prc->bottom = prc->top + pmc->dyMonth;
if (iCol)
{
d = (pmc->dxMonth + CALBORDER) * iCol;
prc->left += d;
prc->right += d;
}
if (iRow)
{
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;
// BUGBUG: 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 = 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);
}
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);
}
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);
}
void GetYrMoForOffset(MONTHCAL *pmc, int iOffset, int *piYear, int *piMonth)
{
SYSTEMTIME st;
st.wDay = 1;
st.wMonth = pmc->stMonthFirst.wMonth;
st.wYear = pmc->stMonthFirst.wYear;
IncrSystemTime(&st, &st, iOffset, INCRSYS_MONTH);
*piYear = st.wYear;
*piMonth = st.wMonth;
}
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);
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];
--iOff;
}
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;
iDay -= pmc->rgcDay[iOff];
}
GetYrMoForOffset(pmc, iOff, &iYear, &iMon);
pst->wDay = iDay;
pst->wMonth = iMon;
pst->wYear = 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 (pmc->fMinYrSet && -1==CmpDate(pst, &pmc->stMin))
return FALSE;
if (pmc->fMaxYrSet && 1==CmpDate(pst, &pmc->stMax))
return FALSE;
//
// If the month/yr for the new date is not in view, bring it
// into view
//
if ((pst->wYear < pmc->stMonthFirst.wYear) ||
((pst->wYear == pmc->stMonthFirst.wYear) && (pst->wMonth < pmc->stMonthFirst.wMonth)))
{
nDelta = - (pmc->stMonthFirst.wYear - (int)pst->wYear) * 12 - (pmc->stMonthFirst.wMonth - (int)pst->wMonth);
}
else if ((pst->wYear > pmc->stMonthLast.wYear) ||
((pst->wYear == pmc->stMonthLast.wYear) && (pst->wMonth > pmc->stMonthLast.wMonth)))
{
nDelta = ((int)pst->wYear - pmc->stMonthLast.wYear) * 12 + ((int)pst->wMonth - pmc->stMonthLast.wMonth);
}
if (nDelta)
FIncrStartMonth(pmc, nDelta, TRUE /* dont change day */);
//
// Set new day
//
CopyDate(*pst, pmc->st);
if (MonthCal_IsMultiSelect(pmc))
CopyDate(*pst, pmc->stEndSel);
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))
{
MCGetRcForDay(pmc, pmc->iMonthToday, pmc->stToday.wDay, &rc);
InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
CopyDate(st, pmc->stToday);
MCUpdateToday(pmc);
MCGetRcForDay(pmc, pmc->iMonthToday, pmc->stToday.wDay, &rc);
InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
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;
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);
}
return((LRESULT)TRUE);
}
void MCInvalidateDates(MONTHCAL *pmc, SYSTEMTIME *pst1, SYSTEMTIME *pst2)
{
int iMonth, ioff, icol, irow;
RECT rc, rcMonth;
SYSTEMTIME st, stEnd;
if (1 == CmpDate(pst1, &pmc->stViewLast) ||
-1 == CmpDate(pst2, &pmc->stViewFirst))
return;
if (-1 == CmpDate(pst1, &pmc->stViewFirst))
CopyDate(pmc->stViewFirst, st);
else
CopyDate(*pst1, st);
if (1 == CmpDate(pst2, &pmc->stViewLast))
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: this is bullshit. make it more efficient...
while (CmpDate(&st, &stEnd) != 1)
{
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);
// force new selection to get repainted
InvalidateRect(pmc->ci.hwnd, &pmc->rcDayCur, FALSE);
UpdateWindow(pmc->ci.hwnd);
}
LRESULT MCRButtonDown(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);
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);
}
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->rgxMonthBegin[iOffset];
rcT.right = rc.left + pmc->rcMonthName.left + pmc->rgxMonthEnd[iOffset];
*prcMonth = rcT;
rcT.left = rcT.right;
rcT.right = rc.left + pmc->rcMonthName.left + pmc->rgxYearEnd[iOffset];
*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);
// 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;
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
// check for spin buttons
if ((pmc->fSpinPrev = 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))
{
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);
}
else
{
RECT rcMonth, rcYear;
int delta, year, month;
// is this a click in the today area...
MCGetTodayBtnRect(pmc, &rc);
if (PtInRect(&rc, pt))
{
ReleaseCapture();
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))
{
ReleaseCapture();
pmc->fCapture = FALSE;
pt.x = rcMonth.right;
pt.y = rcMonth.bottom;
ClientToScreen(pmc->ci.hwnd, &pt);
imonth = TrackPopupMenu(pmc->hmenuMonth,
TPM_RIGHTALIGN | 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;
ReleaseCapture();
pmc->fCapture = FALSE;
rcYear.left++;
rcYear.right = rcYear.left + pmc->dxYearMax + 6;
rcYear.top--;
rcYear.bottom++;
hwndEdit = CreateWindow(TEXT("EDIT"), NULL,
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_READONLY | ES_LEFT | ES_NUMBER | 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);
SendMessage(hwndEdit, WM_SETFONT, (WPARAM)pmc->hfontBold, (LPARAM)FALSE);
SendMessage(hwndEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
(LPARAM)MAKELONG(1, 1));
yrMin = 1753;
if (pmc->fMinYrSet)
yrMin = pmc->stMin.wYear;
yrMax = 2999;
if (pmc->fMaxYrSet)
yrMax = pmc->stMax.wYear;
hwndUD = CreateUpDownControl(
WS_CHILD | WS_VISIBLE | WS_BORDER | UDS_SETBUDDYINT |
UDS_NOTHOUSANDS | UDS_ARROWKEYS, 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);
}
hwndFocus = SetFocus(hwndEdit);
rcYear.right += 1 + rcYear.bottom - rcYear.top;
ClientToScreen(pmc->ci.hwnd, (LPPOINT)&rcYear.left);
ClientToScreen(pmc->ci.hwnd, (LPPOINT)&rcYear.right);
rcCal = pmc->rc;
ClientToScreen(pmc->ci.hwnd, (LPPOINT)&rcCal.left);
ClientToScreen(pmc->ci.hwnd, (LPPOINT)&rcCal.right);
fShow = TRUE;
while (fShow)
{
while (fShow && 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;
}
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);
}
}
iyear = SendMessage(hwndUD, UDM_GETPOS, 0, 0);
if (HIWORD(iyear) == 0)
delta = (iyear - year) * 12;
NoYearChange:
DestroyWindow(hwndUD);
DestroyWindow(hwndEdit);
UpdateWindow(pmc->ci.hwnd);
if (hwndFocus != NULL)
SetFocus(hwndFocus);
}
ChangeMonth:
if (delta != 0)
MCIncrStartMonth(pmc, delta, FALSE);
}
}
return(0);
}
LRESULT MCLButtonUp(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
SYSTEMTIME st;
POINT pt;
int nDelta;
if (pmc->fCapture)
{
ReleaseCapture();
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))
{
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))
{
nDelta = 0;
//
// If the month/yr for the new date is not in view, bring it
// into view
//
if ((pmc->stEndSel.wYear < pmc->stMonthFirst.wYear) ||
((pmc->stEndSel.wYear == pmc->stMonthFirst.wYear) && (pmc->stEndSel.wMonth < pmc->stMonthFirst.wMonth)))
{
nDelta = -1;
}
else if ((pmc->st.wYear > pmc->stMonthLast.wYear) ||
((pmc->st.wYear == pmc->stMonthLast.wYear) && (pmc->st.wMonth > pmc->stMonthLast.wMonth)))
{
nDelta = 1;
}
if (nDelta)
FIncrStartMonth(pmc, nDelta, TRUE /* dont change day */);
}
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 = 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 = FUpdateRcDayCur(pmc, pt))
{
// moved into a new valid day
if (pmc->fMultiSelecting)
{
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);
}
//
// SUBEDIT stuff for DateTimePicker
//
// NOTE: Now that the DatePicker and TimePicker are combined, this could be moved back into the parent structure.
//
// SECRecomputeSizing needs to calculate the maximum rectangle each subedit can be. Ugh.
void SECRecomputeSizing(PSUBEDITCONTROL psec, LPRECT prc)
{
HDC hdc;
HGDIOBJ hfontOrig;
int i;
PSUBEDIT 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->fStatic)
{
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_HOUR:
if (*pse->pv == TEXT('t'))
{
// should be SE_MARK, but then (more) other code would be special-cased
min = 11;
max = 12;
break;
}
case SE_YEAR:
case SE_MINUTE:
case SE_SECOND:
min = max;
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;
for (wid = 0 ; min <= max ; min++)
{
if (!pse->fStatic)
{
int cch;
*pse->pval = min;
if (pse->id < SE_MARK)
cch = GetDateFormat(LOCALE_USER_DEFAULT, 0, &psec->st, pse->pv, szTmp, ARRAYSIZE(szTmp));
else
cch = GetTimeFormat(LOCALE_USER_DEFAULT, 0, &psec->st, pse->pv, szTmp, ARRAYSIZE(szTmp));
if (cch == 0)
{
*szTmp = TEXT('\0');
DebugMsg(TF_MONTHCAL, 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;
}
// 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("yMdthHmsX");
BOOL PASCAL SECParseFormat(PSUBEDITCONTROL psec, LPCTSTR szFormat)
{
LPCTSTR pFmt;
LPTSTR psecFmt;
int cse;
int nTmp;
PSUBEDIT pse;
// count szFormat sections so we know what to allocate
pFmt = szFormat;
cse = 0;
while (*pFmt)
{
if (StrChr(c_szFormats, *pFmt)) // format string
{
TCHAR c = *pFmt;
while (c == *pFmt)
pFmt++;
cse++;
}
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 (illegal?)
{
while (*pFmt && *pFmt!=TEXT('\'') && !StrChr(c_szFormats, *pFmt))
pFmt++;
cse++;
}
}
// Allocate space
nTmp = cse + lstrlen(szFormat) + 1; // number of chars
nTmp = nTmp * sizeof(TCHAR); // size in BYTES
nTmp = (nTmp + 3) & (~3); // round up to DWORD boundary (MIPS alignment issue)
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->pse = (PSUBEDIT)((LPBYTE)psecFmt + nTmp);
// Fill psec
psec->iseCur = SUBEDIT_NONE;
psec->cse = cse;
pse = psec->pse;
ZeroMemory(pse, cse * sizeof(SUBEDIT));
pFmt = szFormat;
while (*pFmt)
{
if (*pFmt == TEXT('y')) // year
{
pse->id = SE_YEAR;
pse->pval = &psec->st.wYear;
pse->min = 1601; // GetDateFormat screws up on years less than this
pse->max = 9999;
// It would be cool to allow subedit editing for this. More smarts are needed:
// "y" -> decade editing: '5' results in 199 + 5
// "yy" -> century editing: '95' results in 19 + 95
// "yyyy" -> full year editing.
pse->pv = psecFmt;
while (*pFmt == TEXT('y'))
*psecFmt++ = *pFmt++;
*psecFmt++ = TEXT('\0');
}
else if (*pFmt == TEXT('M')) // month
{
// BUGBUG: NLS date functions have two month names: one for the month when the day appears
// after it, and another for the month when the day appears before it. Breaking the format
// string into subedits like this breaks this functionality. Nothing we can do about it
// without a big hack, as far as I can tell...
pse->id = SE_MONTH;
pse->pval = &psec->st.wMonth;
pse->min = 1;
pse->max = 12;
pse->cchMax = 2;
pse->pv = psecFmt;
while (*pFmt == TEXT('M'))
*psecFmt++ = *pFmt++;
*psecFmt++ = TEXT('\0');
}
#if 0 // if we ever do week-of-year format
else if (*pFmt == TEXT('w')) // week
{
// BUGBUG: NLS date functions can modify the displayed YEAR if the displayed WEEK overflows
// into the next or prior year!
// BUGBUG: We need to make the week increment move beyond month boundries...
// In order to do this, we need to add an "overflow" flag so day increments will
// carry into month increments etc.
pse->id = SE_DAY;
pse->pval = &psec->st.wDay;
pse->min = 1;
pse->max = GetDaysForMonth(psec->st.wYear, psec->st.wMonth);
pse->cIncrement = 7;
pse->cchMax = 2;
pse->pv = psecFmt;
while (*pFmt == TEXT('w'))
*psecFmt++ = *pFmt++;
*psecFmt++ = TEXT('\0');
}
#endif
else if (*pFmt == TEXT('d')) // day
{
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++;
*psecFmt++ = TEXT('\0');
}
else if (*pFmt == TEXT('t')) // marker
{
pse->id = SE_HOUR; // should be SE_MARK, but then "did change affect this" code wouldn't work
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->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->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->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->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->fStatic = 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
{
pse->id = SE_STATIC;
pse->fStatic = TRUE;
pse->pv = psecFmt;
while (*pFmt && *pFmt!=TEXT('\'') && !StrChr(c_szFormats, *pFmt))
*psecFmt++ = *pFmt++;
*psecFmt++ = TEXT('\0');
}
pse++;
}
#ifdef DEBUG
{
TCHAR sz[200];
LPTSTR psz;
psz = sz;
sz[0]=TEXT('\0');
pse = psec->pse;
cse = psec->cse;
while (cse > 0)
{
wsprintf(psz, TEXT("[%s] "), pse->pv);
psz = psz + lstrlen(psz);
cse--;
pse++;
}
DebugMsg(TF_MONTHCAL, TEXT("SECParseFormat: %s"), sz);
}
#endif
// The subedits have changed, recompute sizes
SECRecomputeSizing(psec, &psec->rc);
// We're going to need to redraw this
InvalidateRect(psec->pci->hwnd, NULL, TRUE);
}
void SECDestroy(PSUBEDITCONTROL psec)
{
if (psec->szFormat)
{
LocalFree(psec->szFormat);
psec->szFormat = NULL;
}
}
void SECSetFont(PSUBEDITCONTROL 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);
}
// Set the current subedit, scrolling things into view as needed
void SECSetCurSubed(PSUBEDITCONTROL psec, int isubed)
{
// if the subedit is changing, we need to invalidate stuff
if (isubed != psec->iseCur)
{
if (psec->iseCur != SUBEDIT_NONE)
{
InvalidateScrollRect(psec->pci->hwnd, &psec->pse[psec->iseCur].rc, psec->xScroll);
}
psec->iseCur = isubed;
if (psec->iseCur != SUBEDIT_NONE)
{
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(PSUBEDITCONTROL psec, int delta)
{
int ise, loop;
Assert(-1 == delta || 1 == delta);
ise = psec->iseCur;
if (ise == SUBEDIT_NONE && 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].fStatic)
{
goto Found;
}
}
ise = SUBEDIT_NONE;
Found:
SECSetCurSubed(psec, ise);
return ise;
}
void SECResetSubeditEdit(PSUBEDITCONTROL psec)
{
if (psec->iseCur != SUBEDIT_NONE)
{
PSUBEDIT psubed = &psec->pse[psec->iseCur];
if (psubed->cchEdit && psubed->valEdit != *psubed->pval)
InvalidateScrollRect(psec->pci->hwnd, &psubed->rc, psec->xScroll);
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.
void SECInvalidate(PSUBEDITCONTROL psec, int id)
{
BOOL fAdjustDayMax = (id == SE_MONTH || id == SE_YEAR || id == SE_APP);
PSUBEDIT pse;
int i;
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)
{
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 = pse->max;
SECInvalidate(psec, SE_DAY);
}
}
}
}
// SECIncrementSubedit increments currently selected subedit by delta
// Returns TRUE iff the value changed
BOOL SECIncrementSubedit(PSUBEDITCONTROL psec, int delta)
{
PSUBEDIT psubed;
UINT val;
if (psec->iseCur == SUBEDIT_NONE)
return(FALSE);
psubed = &psec->pse[psec->iseCur];
if (psubed->id == SE_APP)
return(FALSE);
// delta isn't a REAL delta -- it's a directional thing. Here's the REAL delta:
if (psubed->cIncrement > 0)
delta = delta * psubed->cIncrement;
val = *psubed->pval + delta;
if ((int)val < (int)psubed->min)
{
val = psubed->min - val - 1;
val = psubed->max - val;
}
else if (val > psubed->max)
{
val = val - psubed->max - 1;
val = psubed->min + val;
}
if (*psubed->pval != val)
{
*psubed->pval = val;
SECInvalidate(psec, psubed->id);
return(TRUE);
}
return(FALSE);
}
// returns TRUE if a value has changed, FALSE otherwise
BOOL SECHandleKeydown(PSUBEDITCONTROL psec, WPARAM wParam, LPARAM lParam)
{
int delta = 1;
switch (wParam)
{
case VK_LEFT:
delta = -1;
// fall through...
case VK_RIGHT:
SECResetSubeditEdit(psec);
SECIncrFocus(psec, delta);
return(FALSE);
}
if (psec->iseCur != SUBEDIT_NONE &&
psec->pse[psec->iseCur].id == SE_APP)
{
NMDATETIMEWMKEYDOWN nmdtkd = {0};
nmdtkd.nVirtKey = wParam;
nmdtkd.pszFormat = psec->pse[psec->iseCur].pv;
nmdtkd.st = psec->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
{
switch (wParam)
{
case VK_DOWN:
case VK_SUBTRACT:
delta = -1;
// fall through...
case VK_UP:
case VK_ADD:
SECResetSubeditEdit(psec);
return(SECIncrementSubedit(psec, delta));
break;
case VK_HOME:
case VK_END:
if (psec->iseCur != SUBEDIT_NONE)
{
PSUBEDIT psubed;
int valT;
SECResetSubeditEdit(psec);
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
BOOL SECHandleChar(PSUBEDITCONTROL psec, TCHAR ch)
{
PSUBEDIT psubed;
UINT num;
if (psec->iseCur == SUBEDIT_NONE)
return(FALSE);
psubed = &psec->pse[psec->iseCur];
if (psubed->cchMax == 0)
return(FALSE);
if (ch < TEXT('0') || ch > TEXT('9'))
{
MessageBeep(MB_ICONHAND);
return(FALSE);
}
num = ch - TEXT('0');
if (psubed->cchEdit)
num = psubed->valEdit * 10 + num;
if (num > psubed->max)
{
// the number has exceeded the max, so no point in continuing
psubed->cchEdit = 0;
MessageBeep(MB_ICONHAND);
return(FALSE);
}
SECInvalidate(psec, psubed->id);
psubed->valEdit = num;
psubed->cchEdit++;
if (psubed->cchEdit == psubed->cchMax)
psubed->cchEdit = 0;
if (num >= psubed->min)
{
// a valid value has been entered
*psubed->pval = num;
return(TRUE);
}
return(FALSE);
}
// SECFormatSubed returns pointer to correct string
LPTSTR SECFormatSubed(PSUBEDITCONTROL psec, PSUBEDIT psubed, LPTSTR szTmp, UINT cch)
{
LPTSTR sz;
if (psubed->fStatic)
{
sz = (LPTSTR)psubed->pv;
}
else
{
sz = szTmp;
if (psubed->id < SE_MARK)
GetDateFormat(LOCALE_USER_DEFAULT, 0, &psec->st, psubed->pv, szTmp, cch);
else if (psubed->id != SE_APP)
GetTimeFormat(LOCALE_USER_DEFAULT, 0, &psec->st, psubed->pv, szTmp, cch);
else
{
NMDATETIMEFORMAT nmdtf = {0};
nmdtf.pszFormat = psubed->pv;
nmdtf.st = psec->st;
nmdtf.pszDisplay = nmdtf.szDisplay;
CCSendNotify(psec->pci, DTN_FORMAT, &nmdtf.nmhdr);
lstrcpyn(szTmp, nmdtf.pszDisplay, ARRAYSIZE(szTmp));
#ifdef UNICODE
//
// 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);
}
#endif
}
}
return sz;
}
// SECDrawSubedits draws subedits and updates their bounding rectangles
void SECDrawSubedits(HDC hdc, PSUBEDITCONTROL psec, BOOL fFocus, BOOL fEnabled)
{
HGDIOBJ hfontOrig;
DWORD gray, hilite, text;
int i, iseCur;
LPTSTR sz;
TCHAR szTmp[DTP_FORMATLENGTH];
PSUBEDIT 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);
gray = GetSysColor(COLOR_GRAYTEXT);
hilite = GetSysColor(COLOR_HIGHLIGHTTEXT);
text = GetSysColor(COLOR_WINDOWTEXT);
SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
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 (RectVisible(hdc, &rc))
{
if (!fEnabled)
{
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, gray);
}
else if (iseCur == i)
{
SetBkMode(hdc, OPAQUE);
SetTextColor(hdc, hilite);
}
else
{
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, text);
}
sz = SECFormatSubed(psec, psubed, szTmp, ARRAYSIZE(szTmp));
DrawText(hdc, sz, -1, &rc,
DT_TOP | DT_CENTER | 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(PSUBEDITCONTROL psec, POINT pt)
{
int isubed;
for (isubed = psec->cse - 1; isubed >= 0; isubed--)
{
if (!psec->pse[isubed].fStatic &&
pt.x >= psec->pse[isubed].rc.left)
{
break;
}
}
return(isubed);
}
void SECGetSystemtime(PSUBEDITCONTROL psec, LPSYSTEMTIME pst)
{
*pst = psec->st;
// we don't keep doy up to date, set it now (0==sun, 6==sat)
pst->wDayOfWeek = 0; // BUGBUG: How the f*** do we figure this out???
}
BOOL SECSetSystemtime(PSUBEDITCONTROL psec, LPSYSTEMTIME pst)
{
psec->st = *pst;
return TRUE; // assume something changed
}
// SECEdit: Start a free-format edit return result in szOutput.
BOOL SECEdit(PSUBEDITCONTROL psec, LPTSTR szOutput, int cchOutput)
{
RECT rcEdit;
HWND hwndEdit;
TCHAR szBuf[DTP_FORMATLENGTH];
LPTSTR pszBuf;
int cchBuf;
int i;
PSUBEDIT pse;
BOOL fRet = FALSE;
pszBuf = szBuf;
cchBuf = ARRAYSIZE(szBuf);
for (i = 0, pse = psec->pse ; i < psec->cse ; i++, pse++)
{
int nTmp;
if (pse->fStatic)
{
lstrcpyn(pszBuf, pse->pv, cchBuf);
}
else
{
if (pse->id < SE_MARK)
GetDateFormat(LOCALE_USER_DEFAULT, 0, &psec->st, pse->pv, pszBuf, cchBuf);
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;
nmdtf.st = psec->st;
nmdtf.pszDisplay = nmdtf.szDisplay;
CCSendNotify(psec->pci, DTN_FORMAT, &nmdtf.nmhdr);
lstrcpyn(pszBuf, nmdtf.pszDisplay, cchBuf);
#ifdef UNICODE
//
// 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);
}
#endif
}
}
nTmp = lstrlen(pszBuf);
cchBuf -= nTmp;
pszBuf += nTmp;
}
rcEdit = psec->rc;
ClientToScreen(psec->pci->hwnd, (LPPOINT)&rcEdit.left);
ClientToScreen(psec->pci->hwnd, (LPPOINT)&rcEdit.right);
hwndEdit = CreateWindowEx(0, TEXT("EDIT"), szBuf, WS_CHILD | ES_AUTOHSCROLL,
2, 2, rcEdit.right - rcEdit.left, rcEdit.bottom - rcEdit.top,
psec->pci->hwnd, NULL, HINST_THISDLL, NULL);
if (hwndEdit)
{
BOOL fShow;
Edit_LimitText(hwndEdit, ARRAYSIZE(szBuf));
FORWARD_WM_SETFONT(hwndEdit, psec->hfont, FALSE, SendMessage);
SetFocus(hwndEdit);
Edit_SetSel(hwndEdit, 32000, 32000); // move to the end
Edit_SetSel(hwndEdit, 0, 32000); // select all text
ShowWindow(hwndEdit, SW_SHOWNORMAL);
fShow = TRUE;
while (fShow)
{
MSG msg;
fShow = GetMessage(&msg, NULL, 0, 0);
// Check for termination events
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)) ||
msg.message == WM_KILLFOCUS ||
(msg.message == WM_KEYDOWN &&
msg.wParam == VK_RETURN))
{
DebugMsg(TF_MONTHCAL, TEXT("SECEdit got a message to accept (%d)"), msg.message);
fShow = FALSE;
fRet = TRUE;
break;
}
if (msg.message == WM_SYSCOMMAND ||
msg.message == WM_SYSCHAR ||
msg.message == WM_SYSDEADCHAR ||
msg.message == WM_DEADCHAR ||
msg.message == WM_SYSKEYDOWN ||
(msg.message == WM_KEYDOWN &&
msg.wParam == VK_ESCAPE))
{
DebugMsg(TF_MONTHCAL, TEXT("SECEdit got a message to terminate (%d)"), msg.message);
fShow = FALSE;
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
} // while(fShow)
if (fRet)
{
Edit_GetText(hwndEdit, szOutput, cchOutput);
}
DestroyWindow(hwndEdit);
}
return(fRet);
}
//
// 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_PAINT:
{
PAINTSTRUCT ps;
HDC hdc;
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_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 = FALSE;
break;
}
case UDN_DELTAPOS:
if ((int)wParam == DATEPICK_UPDOWN)
{
LPNM_UPDOWN pnmdp = (LPNM_UPDOWN)lParam;
if (!pdp->fFocus)
SetFocus(pdp->ci.hwnd);
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 (fGotFocus != pdp->fFocus)
{
pdp->fFocus = fGotFocus;
if (pdp->sec.iseCur != SUBEDIT_NONE)
{
InvalidateScrollRect(pdp->ci.hwnd, &pdp->sec.pse[pdp->sec.iseCur].rc, pdp->sec.xScroll);
}
else if (DatePick_ShowCheck(pdp))
{
pdp->fCheckFocus = fGotFocus;
InvalidateRect(pdp->ci.hwnd, &pdp->rcCheck, TRUE);
}
else if (fGotFocus) // nothing has focus, bring it to something
{
SECIncrFocus(&pdp->sec, 1);
}
CCSendNotify(&pdp->ci, (fGotFocus ? NM_SETFOCUS : NM_KILLFOCUS), &nmhdr);
}
break;
}
case WM_ENABLE:
{
BOOL fEnabled = wParam ? TRUE:FALSE;
if (pdp->fEnabled != fEnabled)
{
pdp->fEnabled = fEnabled;
if (pdp->hwndUD)
EnableWindow(pdp->hwndUD, fEnabled);
InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
}
break;
}
case WM_SIZE:
{
RECT rc;
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:
lres = DPHandleKeydown(pdp, wParam, lParam);
break;
case WM_CHAR:
lres = DPHandleChar(pdp, wParam, lParam);
break;
case WM_WININICHANGE:
if (!lstrcmpi((LPTSTR)lParam, TEXT("Intl")))
DPHandleLocaleChange(pdp);
break;
case WM_GETTEXTLENGTH:
{
TCHAR szTmp[DTP_FORMATLENGTH];
PSUBEDIT psubed;
int i;
int nTextLen = 0;
for (i = 0, psubed = pdp->sec.pse; i < pdp->sec.cse; i++, psubed++)
{
nTextLen += lstrlen(SECFormatSubed(&pdp->sec, psubed, szTmp, ARRAYSIZE(szTmp)));
}
lres = nTextLen;
break;
}
case WM_GETTEXT:
if (lParam && wParam)
{
TCHAR szTmp[DTP_FORMATLENGTH];
PSUBEDIT psubed;
int i;
LPTSTR pszText = (LPTSTR)lParam;
UINT nTextLen = 0;
*pszText = TEXT('\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 (nTextLen + nLen >= wParam)
break;
lstrcpy(pszText, sz);
pszText += nLen;
nTextLen += nLen;
}
lres = nTextLen;
}
break;
case WM_STYLECHANGING:
lres = DPOnStyleChanging(pdp, wParam, (LPSTYLESTRUCT)lParam);
break;
case WM_STYLECHANGED:
lres = DPOnStyleChanged(pdp, wParam, (LPSTYLESTRUCT)lParam);
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
{
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->sec);
pdp->fNoNotify = TRUE;
if (DatePick_ShowCheck(pdp))
{
if ((wParam == GDT_NONE) ^ (pdp->fCheck))
{
// let checkbox have focus
SECSetCurSubed(&pdp->sec, SUBEDIT_NONE);
pdp->fCheckFocus = 1;
InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
}
pdp->fCheck = (wParam == GDT_NONE ? 0 : 1);
}
if (wParam == GDT_VALID)
{
DPSetDate(pdp, pst, 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));
if (pdp->fMin)
{
pst[0] = pdp->stMin;
lres |= GDTR_MIN;
}
if (pdp->fMax)
{
pst[1] = pdp->stMax;
lres |= GDTR_MAX;
}
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;
if (((wParam & GDTR_MIN) && !IsValidDate(pst)) ||
((wParam & GDTR_MAX) && !IsValidDate(&pst[1])))
{
break;
}
lres = TRUE;
pdp->fMin = (wParam & GDTR_MIN) ? 1:0;
if (pdp->fMin)
{
pdp->stMin = pst[0];
}
pdp->fMax = (wParam & GDTR_MAX) ? 1:0;
if (pdp->fMax)
{
pdp->stMax = pst[1];
}
break;
}
#ifdef UNICODE
// DTM_SETFORMAT wParam=void lParam=LPCTSTR
// Sets the formatting string to a copy of lParam.
case DTM_SETFORMATA:
{
LPCSTR szFormat = (LPCSTR)lParam;
if (!szFormat || !*szFormat)
{
pdp->fLocale = TRUE;
DPHandleLocaleChange(pdp);
}
else
{
LPWSTR lpFormatW;
lpFormatW = ProduceWFromA(pdp->ci.uiCodePage, szFormat);
pdp->fLocale = FALSE;
SECParseFormat(&pdp->sec, lpFormatW);
FreeProducedString(lpFormatW);
}
break;
}
#endif
// DTM_SETFORMAT wParam=void lParam=LPCTSTR
// Sets the formatting string to a copy of lParam.
case DTM_SETFORMAT:
{
LPCTSTR szFormat = (LPCTSTR)lParam;
if (!szFormat || !*szFormat)
{
pdp->fLocale = TRUE;
DPHandleLocaleChange(pdp);
}
else
{
pdp->fLocale = FALSE;
SECParseFormat(&pdp->sec, szFormat);
}
break;
}
case DTM_SETMCCOLOR:
if (wParam >= 0 && 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 >= 0 && wParam < MCSC_COLORCOUNT)
return pdp->clr[wParam];
return -1;
case DTM_GETMONTHCAL:
return (LRESULT)(UINT)pdp->hwndMC;
default:
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
SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CLIENTEDGE);
// Allocate storage for the dtpick structure
pdp = (DATEPICK *)NearAlloc(sizeof(DATEPICK));
if (pdp)
DatePick_SetPtr(hwnd, pdp);
return((LRESULT)pdp);
}
void DPDestroyHandler(HWND hwnd, DATEPICK *pdp, WPARAM wParam, LPARAM lParam)
{
if (pdp)
{
SECDestroy(&pdp->sec);
GlobalFreePtr(pdp);
}
DatePick_SetPtr(hwnd, NULL);
}
LRESULT DPCreateHandler(DATEPICK *pdp, HWND hwnd, LPCREATESTRUCT lpcs)
{
HFONT hfont;
SYSTEMTIME st;
// 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->fEnabled = !(pdp->ci.style & WS_DISABLED);
pdp->fCheck = TRUE; // start checked
// the day before 14-sep-1752 was 2-sep-1752 in British and US history.
// avoid ui problems related with this...
pdp->stMin.wDay = 14;
pdp->stMin.wMonth = 9;
pdp->stMin.wYear = 1752;
pdp->fMin = TRUE;
pdp->fMax = FALSE;
// initialize SUBEDITCONTROL
pdp->sec.pci = &pdp->ci;
GetLocalTime(&st);
SECSetSystemtime(&pdp->sec, &st);
SECSetFont(&pdp->sec, NULL);
pdp->fLocale = TRUE;
DPHandleLocaleChange(pdp);
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);
}
}
return(0);
}
// set any locale-dependent values
#define DTS_TIMEFORMATONLY (DTS_TIMEFORMAT & ~DTS_UPDOWN) // remove the UPDOWN bit for testing
void DPHandleLocaleChange(DATEPICK *pdp)
{
if (pdp->fLocale)
{
TCHAR szFormat[DTP_FORMATLENGTH];
if (pdp->ci.style & DTS_TIMEFORMATONLY)
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STIMEFORMAT, szFormat, ARRAYSIZE(szFormat));
else if (pdp->ci.style & DTS_LONGDATEFORMAT)
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SLONGDATE, szFormat, ARRAYSIZE(szFormat));
else
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SSHORTDATE, szFormat, ARRAYSIZE(szFormat));
SECParseFormat(&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;
UINT dfcs;
rc = pdp->rcCheck;
if (pdp->fCheckFocus)
DrawFocusRect(hdc, &rc);
InflateRect(&rc, -1 , -1);
dfcs = DFCS_BUTTONCHECK;
if (pdp->fCheck)
dfcs |= DFCS_CHECKED;
if (!pdp->fEnabled)
dfcs |= DFCS_INACTIVE;
DrawFrameControl(hdc, &rc, DFC_BUTTON, dfcs);
}
}
SECDrawSubedits(hdc, &pdp->sec, pdp->fFocus, pdp->fCheck ? pdp->fEnabled : FALSE);
if (!pdp->fUseUpDown && RectVisible(hdc, &pdp->rcBtn))
DPDrawDropdownButton(pdp, hdc, FALSE);
}
void DPLBD_MonthCal(DATEPICK *pdp, BOOL fLButtonDown)
{
HDC hdc;
HWND hwndMC;
RECT rcT, rcCalT;
RECT rcBtn, rcCal;
RECT rcWorkArea;
BOOL fBtnDown; // Is the button drawn DOWN or UP
BOOL fBtnActive; // Is the button still active
SYSTEMTIME st;
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->sec, SUBEDIT_NONE);
if (fLButtonDown)
DPDrawDropdownButton(pdp, hdc, TRUE);
rcT = pdp->rc;
ClientToScreen(pdp->ci.hwnd, (LPPOINT)&rcT.left);
ClientToScreen(pdp->ci.hwnd, (LPPOINT)&rcT.right);
rcBtn = pdp->rcBtn;
ClientToScreen(pdp->ci.hwnd, (LPPOINT)&rcBtn.left);
ClientToScreen(pdp->ci.hwnd, (LPPOINT)&rcBtn.right);
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,
rcCal.left, rcCal.top,
rcCal.right - rcCal.left, rcCal.bottom - rcCal.top,
pdp->ci.hwnd, NULL, HINST_THISDLL, NULL);
if (hwndMC == NULL)
{
// BUGBUG: we are left with the button drawn DOWN
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]);
}
}
// set min/max dates
{
DWORD dw = 0;
if (pdp->fMin)
dw |= GDTR_MIN;
if (pdp->fMax)
dw |= GDTR_MIN;
MonthCal_SetRange(hwndMC, dw, &pdp->stMin);
}
SendMessage(hwndMC, MCM_GETMINREQRECT, 0, (LPARAM)&rcCalT);
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);
// 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
rcWorkArea.left = 0;
rcWorkArea.top = 0;
rcWorkArea.right = GetSystemMetrics(SM_CXSCREEN);
rcWorkArea.bottom = GetSystemMetrics(SM_CYSCREEN);
} else {
// otherwise it's the limits of the workarea
SystemParametersInfo(SPI_GETWORKAREA, FALSE, &rcWorkArea, 0);
}
// 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)
{
int nTmp = rcCal.bottom - rcCal.top;
rcCal.bottom = rcT.top;
rcCal.top = rcCal.bottom - nTmp;
}
MoveWindow(hwndMC, rcCal.left, rcCal.top,
rcCal.right - rcCal.left, rcCal.bottom - rcCal.top, FALSE);
SECGetSystemtime(&pdp->sec, &st);
SendMessage(hwndMC, MCM_SETCURSEL, 0, (LPARAM)&st);
CCSendNotify(&pdp->ci, DTN_DROPDOWN, NULL);
//
// BUGBUG -- App may have resized the window during DTN_DROPDOWN,
// so we need to get the new rcCal rect
//
ShowWindow(hwndMC, SW_SHOWNORMAL);
pdp->fShow = TRUE;
fBtnDown = fLButtonDown;
fBtnActive = fLButtonDown;
while (pdp->fShow)
{
MSG msg;
pdp->fShow = 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, // in the middle so as to not hit the year
rcCal.top/2 + rcCal.bottom/2, // or the month or the today thing
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
if (((msg.message == WM_LBUTTONDOWN ||
msg.message == WM_NCLBUTTONDOWN ||
msg.message == WM_RBUTTONDOWN ||
msg.message == WM_NCRBUTTONDOWN ||
msg.message == WM_LBUTTONDBLCLK) &&
!PtInRect(&rcCal, msg.pt)) ||
msg.message == WM_SYSCOMMAND ||
msg.message == WM_COMMAND ||
msg.message == WM_CHAR ||
msg.message == WM_SYSCHAR ||
msg.message == WM_SYSDEADCHAR ||
msg.message == WM_DEADCHAR ||
msg.message == WM_KILLFOCUS ||
msg.message == WM_SYSKEYDOWN ||
msg.message == WM_KEYDOWN)
{
DebugMsg(TF_MONTHCAL,TEXT("DPLBD_MonthCal got a message to terminate (%d)"), msg.message);
pdp->fShow = FALSE;
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->sec, 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 (nmdts.dwFlags == GDT_NONE)
{
if (DatePick_ShowCheck(pdp))
{
pdp->fCheck = FALSE;
pdp->fCheckFocus = TRUE;
SECSetCurSubed(&pdp->sec, 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->sec);
fFocus = pdp->fFocus;
if (!fFocus)
SetFocus(pdp->ci.hwnd);
// display MONTHCAL
if (PtInRect(&pdp->rcBtn, pt))
{
DPLBD_MonthCal(pdp, TRUE);
}
else if (!pdp->fCapture)
{
pt.x -= pdp->sec.xScroll;
// Un/check checkbox
if (DatePick_ShowCheck(pdp) && PtInRect(&pdp->rcCheck, pt))
{
pdp->fCheck = 1-pdp->fCheck;
pdp->fCheckFocus = 1;
SECSetCurSubed(&pdp->sec, 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 = SECSubeditFromPt(&pdp->sec, pt);
if (isubed != SUBEDIT_NONE)
{
SECSetCurSubed(&pdp->sec, 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);
// don't take up too much width in a tall narrow window
if (pdp->rcCheck.right-prect->left > prect->right/2 - prect->left/2)
{
pdp->rcCheck.right = prect->right/2 - 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 (pdp->fCheckFocus)
{
switch (wParam)
{
case VK_LEFT:
delta = -1;
// fall through...
case VK_RIGHT:
if (pdp->fCheck)
{
if (SUBEDIT_NONE != SECIncrFocus(&pdp->sec, 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->sec, wParam, lParam))
{
DPNotifyDateChange(pdp);
}
else if (DatePick_ShowCheck(pdp))
{
if (pdp->sec.iseCur == SUBEDIT_NONE)
{
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
if (SECHandleChar(&pdp->sec, ch))
{
DPNotifyDateChange(pdp);
}
}
return(0);
}
void DPNotifyDateChange(DATEPICK *pdp)
{
NMDATETIMECHANGE nmdc = {0};
if (pdp->fNoNotify)
return;
if (pdp->fCheck == 0)
{
nmdc.dwFlags = GDT_NONE;
}
else
{
// validate date - do it here in only one place
if ((pdp->fMin && -1==CmpSystemtime(&pdp->sec.st, &pdp->stMin)))
{
pdp->sec.st = pdp->stMin;
InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
}
else if ((pdp->fMax && 1==CmpSystemtime(&pdp->sec.st, &pdp->stMax)))
{
pdp->sec.st = pdp->stMax;
InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
}
nmdc.dwFlags = GDT_VALID;
SECGetSystemtime(&pdp->sec, &nmdc.st);
}
CCSendNotify(&pdp->ci, DTN_DATETIMECHANGE, &nmdc.nmhdr);
}
BOOL DPSetDate(DATEPICK *pdp, SYSTEMTIME *pst, BOOL fMungeDate)
{
BOOL fNotify = FALSE;
// make sure that the new date is within the valid range
if (pdp->fMin && CmpSystemtime(pst, &pdp->stMin) < 0)
{
if (!fMungeDate)
return(FALSE);
pst = &pdp->stMin;
}
if (pdp->fMax && CmpSystemtime(&pdp->stMax, pst) < 0)
{
if (!fMungeDate)
return(FALSE);
pst = &pdp->stMax;
}
if (fMungeDate)
{
// only copy the date portion
CopyDate(*pst, pdp->sec.st);
fNotify = TRUE;
}
else
{
fNotify = SECSetSystemtime(&pdp->sec, pst);
}
if (fNotify)
{
SECInvalidate(&pdp->sec, SE_APP); // SE_APP invalidates everything
DPNotifyDateChange(pdp);
}
return(TRUE);
}
void DPDrawDropdownButton(DATEPICK *pdp, HDC hdc, BOOL fPressed)
{
UINT dfcs;
dfcs = DFCS_SCROLLDOWN;
if (fPressed)
dfcs |= DFCS_PUSHED;
if (!pdp->fEnabled)
dfcs |= DFCS_INACTIVE;
DrawFrameControl(hdc, &pdp->rcBtn, DFC_SCROLL, dfcs);
}