monthcal.c
Upload User: xhy777
Upload Date: 2007-02-14
Package Size: 24088k
Code Size: 223k
Category:

Windows Kernel

Development Platform:

Visual C++

  1. #include "ctlspriv.h"
  2. #include "scdttime.h"
  3. #include "monthcal.h"
  4. #include "prshti.h"         // for StrDup_AtoW
  5. // TODO
  6. //
  7. // #6329: When Min/Max range is set, then dates before the min
  8. // or after the max are painted in the normal date color. They
  9. // should be painted with MCSC_TRAILINGTEXT color. (Or we should
  10. // add a new color to cover this case.) Feature requested by Jobi George
  11. //
  12. // 9577: We want a DAYSTATE like structure for the background
  13. // color of dates. For highlighting. Perhaps a COLORSTATE per
  14. // registered background color.
  15. //
  16. // private message
  17. #define MCMP_WINDOWPOSCHANGED (MCM_FIRST - 1) // MCM_FIRST is way over WM_USER
  18. #define DTMP_WINDOWPOSCHANGED (DTM_FIRST - 1) // DTM_FIRST is way over WM_USER
  19. // MONTHCAL
  20. LRESULT CALLBACK MonthCalWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
  21. LRESULT MCNcCreateHandler(HWND hwnd);
  22. LRESULT MCCreateHandler(MONTHCAL *pmc, HWND hwnd, LPCREATESTRUCT lpcs);
  23. LRESULT MCOnStyleChanging(MONTHCAL *pmc, UINT gwl, LPSTYLESTRUCT pinfo);
  24. LRESULT MCOnStyleChanged(MONTHCAL *pmc, UINT gwl, LPSTYLESTRUCT pinfo);
  25. void MCCalcSizes(MONTHCAL *pmc);
  26. void MCHandleSetFont(MONTHCAL *pmc, HFONT hfont, BOOL fRedraw);
  27. void MCPaint(MONTHCAL *pmc, HDC hdc);
  28. void MCPaintMonth(MONTHCAL *pmc, HDC hdc, RECT *prc, int iMonth, int iYear, int iIndex,
  29.                   BOOL fDrawPrev, BOOL fDrawNext, HBRUSH hbrSelect);
  30. void MCNcDestroyHandler(HWND hwnd, MONTHCAL *pmc, WPARAM wParam, LPARAM lParam);
  31. void MCRecomputeSizing(MONTHCAL *pmc, RECT *prect);
  32. LRESULT MCSizeHandler(MONTHCAL *pmc, RECT *prc);
  33. void MCUpdateMonthNamePos(MONTHCAL *pmc);
  34. void MCUpdateStartEndDates(MONTHCAL *pmc, SYSTEMTIME *pstStart);
  35. void MCGetRcForDay(MONTHCAL *pmc, int iMonth, int iDay, RECT *prc);
  36. void MCGetRcForMonth(MONTHCAL *pmc, int iMonth, RECT *prc);
  37. void MCUpdateToday(MONTHCAL *pmc);
  38. void MCUpdateRcDayCur(MONTHCAL *pmc, SYSTEMTIME *pst);
  39. void MCUpdateDayState(MONTHCAL *pmc);
  40. int MCGetOffsetForYrMo(MONTHCAL *pmc, int iYear, int iMonth);
  41. int MCIsSelectedDayMoYr(MONTHCAL *pmc, int iDay, int iMonth, int iYear);
  42. BOOL MCIsBoldOffsetDay(MONTHCAL *pmc, int nDay, int iIndex);
  43. BOOL FGetOffsetForPt(MONTHCAL *pmc, POINT pt, int *piOffset);
  44. BOOL FGetRowColForRelPt(MONTHCAL *pmc, POINT ptRel, int *piRow, int *piCol);
  45. BOOL FGetDateForPt(MONTHCAL *pmc, POINT pt, SYSTEMTIME *pst, 
  46.                    int* piDay, int* piCol, int* piRow, LPRECT prcMonth);
  47. LRESULT MCContextMenu(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam);
  48. LRESULT MCLButtonDown(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam);
  49. LRESULT MCLButtonUp(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam);
  50. LRESULT MCMouseMove(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam);
  51. LRESULT MCHandleTimer(MONTHCAL *pmc, WPARAM wParam);
  52. LRESULT MCHandleKeydown(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam);
  53. LRESULT MCHandleChar(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam);
  54. int MCIncrStartMonth(MONTHCAL *pmc, int nDelta, BOOL fDelayDayChange);
  55. void MCGetTitleRcsForOffset(MONTHCAL* pmc, int iOffset, LPRECT prcMonth, LPRECT prcYear);
  56. BOOL MCSetDate(MONTHCAL *pmc, SYSTEMTIME *pst);
  57. void MCNotifySelChange(MONTHCAL *pmc, UINT uMsg);
  58. void MCInvalidateDates(MONTHCAL *pmc, SYSTEMTIME *pst1, SYSTEMTIME *pst2);
  59. void MCInvalidateMonthDays(MONTHCAL *pmc);
  60. void MCSetToday(MONTHCAL* pmc, SYSTEMTIME* pst);
  61. void MCGetTodayBtnRect(MONTHCAL *pmc, RECT *prc);
  62. void GetYrMoForOffset(MONTHCAL *pmc, int iOffset, int *piYear, int *piMonth);
  63. BOOL FScrollIntoView(MONTHCAL *pmc);
  64. void MCFreeCalendarInfo(PCALENDARTYPE pct);
  65. void MCGetCalendarInfo(PCALENDARTYPE pct);
  66. BOOL MCIsDateStringRTL(TCHAR tch);
  67. // DATEPICK
  68. LRESULT CALLBACK DatePickWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
  69. LRESULT DPNcCreateHandler(HWND hwnd);
  70. LRESULT DPCreateHandler(DATEPICK *pdp, HWND hwnd, LPCREATESTRUCT lpcs);
  71. LRESULT DPOnStyleChanging(DATEPICK *pdp, UINT gwl, LPSTYLESTRUCT pinfo);
  72. LRESULT DPOnStyleChanged(DATEPICK *pdp, UINT gwl, LPSTYLESTRUCT pinfo);
  73. void DPHandleLocaleChange(DATEPICK *pdp);
  74. void DPDestroyHandler(HWND hwnd, DATEPICK *pdp, WPARAM wParam, LPARAM lParam);
  75. void DPHandleSetFont(DATEPICK *pdp, HFONT hfont, BOOL fRedraw);
  76. void DPPaint(DATEPICK *pdp, HDC hdc);
  77. void DPLBD_MonthCal(DATEPICK *pdp, BOOL fLButtonDown);
  78. LRESULT DPLButtonDown(DATEPICK *pdp, WPARAM wParam, LPARAM lParam);
  79. LRESULT DPLButtonUp(DATEPICK *pdp, WPARAM wParam, LPARAM lParam);
  80. void DPRecomputeSizing(DATEPICK *pdp, RECT *prect);
  81. LRESULT DPHandleKeydown(DATEPICK *pdp, WPARAM wParam, LPARAM lParam);
  82. LRESULT DPHandleChar(DATEPICK *pdp, WPARAM wParam, LPARAM lParam);
  83. void DPNotifyDateChange(DATEPICK *pdp);
  84. BOOL DPSetDate(DATEPICK *pdp, SYSTEMTIME *pst, BOOL fMungeDate);
  85. void DPDrawDropdownButton(DATEPICK *pdp, HDC hdc, BOOL fPressed);
  86. void SECGetSystemtime(LPSUBEDITCONTROL psec, LPSYSTEMTIME pst);
  87. static TCHAR const g_rgchMCName[] = MONTHCAL_CLASS;
  88. static TCHAR const g_rgchDTPName[] = DATETIMEPICK_CLASS;
  89. // MONTHCAL globals
  90. #define g_szTextExtentDef TEXT("0000")
  91. #define g_szNumFmt TEXT("%d")
  92. //
  93. //  Epoch = the beginning of the universe (the earliest date we support)
  94. //  Armageddon = the end of the universe (the latest date we support)
  95. //
  96. //  Epoch is 14-sep-1752 because that's when the Gregorian calendar
  97. //  kicked in.  The day before 14-sep-1752 was 2-sep-1752 (in British
  98. //  and US history; other countries switched at other times).
  99. //
  100. //  Armageddon is 31-dec-9999 because we assume four digits for years
  101. //  is enough.  (Oh no, the Y10K problem...)
  102. //
  103. const SYSTEMTIME c_stEpoch      = { 1752,  9, 0, 14,  0,  0,  0,   0 };
  104. const SYSTEMTIME c_stArmageddon = { 9999, 12, 0, 31, 23, 59, 59, 999 };
  105. void FillRectClr(HDC hdc, LPRECT prc, COLORREF clr)
  106. {
  107.     COLORREF clrSave = SetBkColor(hdc, clr);
  108.     ExtTextOut(hdc,0,0,ETO_OPAQUE,prc,NULL,0,NULL);
  109.     SetBkColor(hdc, clrSave);
  110. }
  111. BOOL InitDateClasses(HINSTANCE hinst)
  112. {
  113.     WNDCLASS wndclass;
  114.     
  115.     if (GetClassInfo(hinst, g_rgchMCName, &wndclass))
  116.     {
  117.         // we're already registered
  118.         DebugMsg(TF_MONTHCAL, TEXT("mc: Date Classes already initialized."));
  119.         return(TRUE);
  120.     }
  121.     
  122.     wndclass.style          = CS_GLOBALCLASS;
  123.     wndclass.lpfnWndProc    = (WNDPROC)MonthCalWndProc;
  124.     wndclass.cbClsExtra     = 0;
  125.     wndclass.cbWndExtra     = sizeof(LPVOID);
  126.     wndclass.hInstance      = hinst;
  127.     wndclass.hIcon          = NULL;
  128.     wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
  129.     wndclass.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
  130.     wndclass.lpszMenuName   = NULL;
  131.     wndclass.lpszClassName  = g_rgchMCName;
  132.     if (!RegisterClass(&wndclass))
  133.     {
  134.         DebugMsg(DM_WARNING, TEXT("mc: MonthCalClass failed to initialize"));
  135.         return(FALSE);
  136.     }
  137.     wndclass.lpfnWndProc    = (WNDPROC)DatePickWndProc;
  138.     wndclass.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
  139.     wndclass.lpszClassName  = g_rgchDTPName;
  140.     if (!RegisterClass(&wndclass))
  141.     {
  142.         DebugMsg(DM_WARNING, TEXT("mc: DatePickClass failed to initialize"));
  143.         return(FALSE);
  144.     }
  145.     DebugMsg(TF_MONTHCAL, TEXT("mc: Date Classes initialized successfully."));
  146.     return(TRUE);
  147. }
  148. ////////////////////////////////////////////////////////////////////////////
  149. //
  150. // MonthCal stuff
  151. //
  152. ////////////////////////////////////////////////////////////////////////////
  153. ////////////////////////////////////
  154. //
  155. // MCInsert/RemoveMarkers
  156. //
  157. // QuickSummary:  Convert the string "MMMM yyyy" into "1MMMM3 2yyyy4".
  158. //
  159. // In order to lay out the month/year info in the header, we have to be
  160. // able to extract the month and year out of the formatted string so we
  161. // know what their rectangles are.  We do this by wrapping the month and
  162. // year inserts with markers so we can extract them after formatting.
  163. //
  164. // Since 1 through 4 are control characters, they won't conflict with
  165. // displayable characters in the actual format string.  And just to play it
  166. // safe, if we actually see a format character, we erase it from the string.
  167. //
  168. // MCInsertMarkers inserts the markers into the output string so we can
  169. // extract the substrings later.  Quotation marks are funky since you can
  170. // write a format of "'The' mm'''th month of' yyyy".  Note that a simple
  171. // even-odd test works for detecting whether we are inside or outside
  172. // quotation marks, even in the nested quotation mark case.
  173. //
  174. void MCInsertMarkers(LPTSTR pszOut, LPCTSTR pszIn)
  175. {
  176.     BOOL fInQuote = FALSE;
  177.     UINT flSeen = 0;
  178.     UINT flThis;
  179.     for (;;)
  180.     {
  181.         TCHAR ch = *pszIn;
  182.         switch (ch) {
  183.         // At end of string, terminate the output buffer and go home
  184.         case TEXT(''):
  185.             *pszOut = TEXT('');
  186.             return;
  187.         case TEXT('m'):
  188.         case TEXT('M'):
  189.             flThis = IMM_MONTHSTART;
  190.             goto CheckMarker;
  191.         case TEXT('y'):
  192.             flThis = IMM_YEARSTART;
  193.             goto CheckMarker;
  194.         CheckMarker:
  195.             // If inside a quotation mark or we've already done this guy,
  196.             // then just treat it as a regular character.
  197.             if (fInQuote || (flSeen & flThis))
  198.                 goto CopyChar;
  199.             flSeen |= flThis;
  200.             *pszOut++ = (TCHAR)flThis;
  201.             // Don't need to use CharNext because we know *pszIn is "m" "M" or "y"
  202.             for ( ; *pszIn == ch; pszIn++)
  203.             {
  204.                 *pszOut++ = ch;
  205.             }
  206.             *pszOut++ = (TCHAR)(flThis + DMM_STARTEND);
  207.             // Restart the loop so we re-parse the character at *pszIn
  208.             continue;
  209.         // Toggle the quotation mark gizmo if we see one, and then just
  210.         // copy it.
  211.         case ''':
  212.             fInQuote ^= TRUE;
  213.             goto CopyChar;
  214.         //
  215.         //  Don't let these sneak into the output format or it
  216.         //  will confuse us.
  217.         //
  218.         case IMM_MONTHSTART:
  219.         case IMM_YEARSTART:
  220.         case IMM_MONTHEND:
  221.         case IMM_YEAREND:
  222.             break;
  223.         default:
  224.         CopyChar:
  225.             *pszOut++ = ch;
  226. #ifndef UNICODE
  227.             if (IsDBCSLeadByte(ch) && pszIn[1]) {
  228.                 *pszOut++ = *++pszIn;
  229.             }
  230. #endif
  231.             break;
  232.         }
  233.         pszIn++;            // We handled the DBCS case already
  234.     }
  235.     // NOTREACHED
  236. }
  237. //
  238. //  MCRemoveMarkers hunts down the marker characters and strips them out,
  239. //  recording their locations in the optional MONTHMETRICS (as character
  240. //  indices).
  241. //
  242. void MCRemoveMarkers(LPTSTR pszBuf, PMONTHMETRICS pmm)
  243. {
  244.     int iWrite, iRead;
  245.     //
  246.     //  If by some horrid error we can't find our markers, just pretend
  247.     //  they were at the start of the string.
  248.     //
  249.     if (pmm) {
  250.         pmm->rgi[IMM_MONTHSTART] = 0;
  251.         pmm->rgi[IMM_YEARSTART ] = 0;
  252.         pmm->rgi[IMM_MONTHEND  ] = 0;
  253.         pmm->rgi[IMM_YEAREND   ] = 0;
  254.     }
  255.     iWrite = iRead = 0;
  256.     for (;;)
  257.     {
  258.         TCHAR ch = pszBuf[iRead];
  259.         switch (ch)
  260.         {
  261.         // At end of string, terminate the output buffer and go home
  262.         case TEXT(''):
  263.             pszBuf[iWrite] = TEXT('');
  264.             return;
  265.         // If we find a marker, eat it and remember its location
  266.         case IMM_MONTHSTART:
  267.         case IMM_YEARSTART:
  268.         case IMM_MONTHEND:
  269.         case IMM_YEAREND:
  270.             if (pmm)
  271.                 pmm->rgi[ch] = iWrite;
  272.             break;
  273.         // Otherwise, just copy it to the output
  274.         default:
  275.             pszBuf[iWrite++] = ch;
  276. #ifndef UNICODE
  277.             if (IsDBCSLeadByte(ch) && pszBuf[iRead+1]) {
  278.                 pszBuf[iWrite++] = pszBuf[++iRead];
  279.             }
  280. #endif
  281.             break;
  282.         }
  283.         iRead++;
  284.     }
  285.     // NOTREACHED
  286. }
  287. ////////////////////////////////////
  288. //
  289. // Like LocalizedLoadString, except that we get the string from
  290. // LOCAL_USER_DEFAULT instead of GetUserDefaultUILanguage().
  291. //
  292. // LOCALE_USER_DEFAULT is the same as GetUserDefaultLCID(), and
  293. // LANGIDFROMLCID(GetUserDefaultLCID()) is the same as GetUserDefaultLangID().
  294. //
  295. // So we pass GetUserDefaultLangID() as the language.
  296. //
  297. int MCLoadString(UINT uID, LPWSTR lpBuffer, int nBufferMax)
  298. {
  299.     return CCLoadStringEx(uID, lpBuffer, nBufferMax, GetUserDefaultLangID());
  300. }
  301. ////////////////////////////////////
  302. //
  303. // Get the localized calendar info
  304. //
  305. BOOL UpdateLocaleInfo(MONTHCAL* pmc, LPLOCALEINFO pli)
  306. {
  307.     int    i;
  308.     TCHAR  szBuf[64];
  309.     int    cch;
  310.     LPTSTR pc = szBuf;
  311.     //
  312.     // Get information about the calendar (e.g., is it supported?)
  313.     //
  314.     MCGetCalendarInfo(&pmc->ct);
  315.     //
  316.     // Check if the calendar title is an RTL string
  317.     //
  318.     GetDateFormat(pmc->ct.lcid, 0, NULL, TEXT("MMMM"), szBuf, ARRAYSIZE(szBuf));
  319.     pmc->fHeaderRTL = (WORD) MCIsDateStringRTL(szBuf[0]);
  320.     //
  321.     // get the short date format and sniff it to see if it displays the year
  322.     // or month first
  323.     //
  324.     MCLoadString(IDS_MONTHFMT, pli->szMonthFmt, ARRAYSIZE(pli->szMonthFmt));
  325.     //
  326.     //  Try to get the MONTHYEAR format from NLS.  If not supported by NLS,
  327.     //  then use the hard-coded value in our resources.  Note that we
  328.     //  subtract 4 from the buffer size because we may insert up to four
  329.     //  marker characters.
  330.     //
  331.     COMPILETIME_ASSERT(ARRAYSIZE(szBuf) >= ARRAYSIZE(pli->szMonthYearFmt));
  332.     szBuf[0] = TEXT('');
  333.     GetLocaleInfo(pmc->ct.lcid, LOCALE_SYEARMONTH,
  334.                  szBuf, ARRAYSIZE(pli->szMonthYearFmt) - CCH_MARKERS);
  335.     if (!szBuf[0]) {
  336.         MCLoadString(IDS_MONTHYEARFMT, szBuf, ARRAYSIZE(pli->szMonthYearFmt) - CCH_MARKERS);
  337.     }
  338.     MCInsertMarkers(pli->szMonthYearFmt, szBuf);
  339.     //
  340.     //  BUGBUG - this code needs to change to use CAL_ values when we
  341.     //  want to support multiple calendars.
  342.     //
  343.     //
  344.     // Get the month names
  345.     //
  346.     for (i = 0; i < 12; i++)
  347.     {
  348.         cch = GetLocaleInfo(pmc->ct.lcid, LOCALE_SMONTHNAME1 + i,
  349.                             pli->rgszMonth[i], CCHMAXMONTH);
  350.         if (cch == 0)
  351.             // the calendar is pretty useless without month names...
  352.             return(FALSE);
  353.     }
  354.     //
  355.     // Get the days of the week
  356.     //
  357.     for (i = 0; i < 7; i++)
  358.     {
  359.         cch = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1 + i,
  360.                             pli->rgszDay[i], CCHMAXABBREVDAY);
  361.         if (cch == 0)
  362.             // the calendar is pretty useless without day names...
  363.             return(FALSE);
  364.     }
  365.     //
  366.     // If we haven't already set what the first day of the week is, get the
  367.     // localized setting.
  368.     // 
  369.     if (!pmc->fFirstDowSet)
  370.     {
  371.         cch = GetLocaleInfo(pmc->ct.lcid, LOCALE_IFIRSTDAYOFWEEK, szBuf, ARRAYSIZE(szBuf));
  372.         if (cch > 0)
  373.             pli->dowStartWeek = szBuf[0] - TEXT('0');
  374.     }
  375.     //
  376.     // Get the first week of the year
  377.     //
  378.     cch = GetLocaleInfo(pmc->ct.lcid, LOCALE_IFIRSTWEEKOFYEAR, szBuf, ARRAYSIZE(szBuf));
  379.     if (cch > 0)
  380.         pli->firstWeek = szBuf[0] - TEXT('0');
  381.     
  382.     // Set up pointers
  383.     for (i = 0; i < 12; i++)
  384.         pli->rgpszMonth[i] = pli->rgszMonth[i];
  385.     for (i = 0; i < 7; i++)
  386.         pli->rgpszDay[i] = pli->rgszDay[i];
  387.     // Get static strings
  388.     MCLoadString(IDS_TODAY, pli->szToday, ARRAYSIZE(pli->szToday));
  389.     MCLoadString(IDS_GOTOTODAY, pli->szGoToToday, ARRAYSIZE(pli->szGoToToday));
  390.     // if we've been initialized
  391.     if (pmc->hinstance)
  392.     {
  393.         SYSTEMTIME st;
  394.         CopyDate(pmc->stMonthFirst, st);
  395.         MCUpdateStartEndDates(pmc, &st);
  396.     }
  397.     return(TRUE);
  398. }
  399. void MCReloadMenus(MONTHCAL *pmc)
  400. {
  401.     int i;
  402.     if (pmc->hmenuCtxt)
  403.         DestroyMenu(pmc->hmenuCtxt);
  404.     if (pmc->hmenuMonth)
  405.         DestroyMenu(pmc->hmenuMonth);
  406.     pmc->hmenuCtxt = CreatePopupMenu();
  407.     if (pmc->hmenuCtxt)
  408.         AppendMenu(pmc->hmenuCtxt, MF_STRING, 1, pmc->li.szGoToToday);
  409.     pmc->hmenuMonth = CreatePopupMenu();
  410.     if (pmc->hmenuMonth)
  411.     {
  412.         for (i = 0; i < 12; i++)
  413.             AppendMenu(pmc->hmenuMonth, MF_STRING, i + 1, pmc->li.rgszMonth[i]);
  414.     }
  415. }
  416. BOOL MCHandleEraseBkgnd(MONTHCAL* pmc, HDC hdc)
  417. {
  418.     RECT rc;
  419.     GetClipBox(hdc, &rc);
  420.     FillRectClr(hdc, &rc, pmc->clr[MCSC_BACKGROUND]);
  421.     return TRUE;   
  422. }
  423. LRESULT MCHandleHitTest(MONTHCAL* pmc, PMCHITTESTINFO phti)
  424. {
  425.     int iMonth;
  426.     RECT rc;
  427.     
  428.     if (!phti || phti->cbSize != sizeof(MCHITTESTINFO))
  429.         return -1;
  430.     phti->uHit = MCHT_NOWHERE;
  431.     MCGetTodayBtnRect(pmc, &rc);
  432.     if (PtInRect(&rc, phti->pt) && MonthCal_ShowToday(pmc))
  433.     {
  434.         phti->uHit = MCHT_TODAYLINK;
  435.     }
  436.     else if (pmc->fSpinPrev = (WORD) PtInRect(&pmc->rcPrev, phti->pt))
  437.     {
  438.         phti->uHit = MCHT_TITLEBTNPREV;        
  439.     }
  440.     else if (PtInRect(&pmc->rcNext, phti->pt))
  441.     {
  442.         phti->uHit = MCHT_TITLEBTNNEXT;        
  443.     }
  444.     else if (FGetOffsetForPt(pmc, phti->pt, &iMonth))
  445.     {
  446.         RECT  rcMonth;   // bounding rect for month containg phti->pt
  447.         POINT ptRel;     // relative point in a month
  448.         int   month;
  449.         int   year;
  450.         MCGetRcForMonth(pmc, iMonth, &rcMonth);
  451.         ptRel.x = phti->pt.x - rcMonth.left;
  452.         ptRel.y = phti->pt.y - rcMonth.top;
  453.         GetYrMoForOffset(pmc, iMonth, &year, &month);
  454.         phti->st.wMonth = (WORD) month;
  455.         phti->st.wYear  = (WORD) year;
  456.             
  457.         // 
  458.         // if calendar is showing week numbers and the point lies in the
  459.         // the week numbers, get the date for day immediately to the right
  460.         // of the week number containing the point
  461.         //
  462.         if (MonthCal_ShowWeekNumbers(pmc) && PtInRect(&pmc->rcWeekNum, ptRel))
  463.         {            
  464.             phti->uHit |= MCHT_CALENDARWEEKNUM;
  465.             phti->pt.x += pmc->rcDayNum.left;
  466.             FGetDateForPt(pmc, phti->pt, &phti->st, NULL, NULL, NULL, NULL);
  467.         }
  468.         //
  469.         // if the point lies in the days of the week header, then return
  470.         // the day of the week containing the point
  471.         //
  472.         else if (PtInRect(&pmc->rcDow, ptRel))
  473.         {            
  474.             int iRow;
  475.             int iCol;
  476.             
  477.             phti->uHit |= MCHT_CALENDARDAY;
  478.             ptRel.y = pmc->rcDayNum.top;
  479.             FGetRowColForRelPt(pmc, ptRel, &iRow, &iCol);
  480.             phti->st.wDayOfWeek = (WORD) iCol;            
  481.         }
  482.         //
  483.         // if the point lies in the actually calendar part, then return the
  484.         // date containg the point
  485.         //
  486.         else if (PtInRect(&pmc->rcDayNum, ptRel))
  487.         {
  488.             int iDay;
  489.             
  490.             // we're in the calendar part!
  491.             phti->uHit |= MCHT_CALENDAR;
  492.             if (FGetDateForPt(pmc, phti->pt, &phti->st, &iDay, NULL, NULL, NULL))
  493.             {                
  494.                 phti->uHit |= MCHT_CALENDARDATE;
  495.                 
  496.                 // if it was beyond the bounds of the days we're showing
  497.                 // and also FGetDateForPt returns TRUE, then we're on the boundary
  498.                 // of the displayed months
  499.                 if (iDay <= 0)
  500.                 {
  501.                     phti->uHit |= MCHT_PREV;
  502.                 }
  503.                 else if (iDay > pmc->rgcDay[iMonth + 1])
  504.                 {
  505.                     phti->uHit |= MCHT_NEXT;
  506.                 }
  507.             }            
  508.         }
  509.         else
  510.         {
  511.             RECT rcMonthTitle;
  512.             RECT rcYearTitle;
  513.             // otherwise we're in the title
  514.             
  515.             phti->uHit |= MCHT_TITLE;
  516.             MCGetTitleRcsForOffset(pmc, iMonth, &rcMonthTitle, &rcYearTitle);
  517.             if (PtInRect(&rcMonthTitle, phti->pt))
  518.             {
  519.                 phti->uHit |= MCHT_TITLEMONTH;
  520.             }
  521.             else if (PtInRect(&rcYearTitle, phti->pt))
  522.             {
  523.                 phti->uHit |= MCHT_TITLEYEAR;
  524.             }
  525.         }
  526.     }    
  527.     DebugMsg(TF_MONTHCAL, TEXT("mc: Hittest returns : %d %d %d %d)"), 
  528.              (int)phti->st.wDay,
  529.              (int)phti->st.wMonth, 
  530.              (int)phti->st.wYear,
  531.              (int)phti->st.wDayOfWeek
  532.              );
  533.     
  534.     return phti->uHit;
  535. }
  536. void MonthCal_OnPaint(MONTHCAL *pmc, HDC hdc)
  537. {
  538.     if (hdc)
  539.     {
  540.         MCPaint(pmc, hdc);
  541.     }
  542.     else
  543.     {
  544.         PAINTSTRUCT ps;
  545.         hdc = BeginPaint(pmc->ci.hwnd, &ps);
  546.         MCPaint(pmc, hdc);
  547.         EndPaint(pmc->ci.hwnd, &ps);
  548.     }
  549. }
  550. BOOL MCGetDateFormatWithTempYear(PCALENDARTYPE pct, SYSTEMTIME *pst, LPCTSTR pszFormat, UINT uYear, LPTSTR pszBuf, UINT cchBuf)
  551. {
  552.     BOOL fRc;
  553.     WORD wYear = pst->wYear;
  554.     pst->wYear = (WORD)uYear;
  555.     fRc = GetDateFormat(pct->lcid, 0, pst, pszFormat, pszBuf, cchBuf);
  556.     if (!fRc)
  557.     {
  558.         // AIGH!  I hate Feburary 29.  In case we are Feb 29 1996 and the
  559.         // user changes to a non-leap year, force the day to something valid
  560.         // in February 1997 (or whatever year the user finally picked).
  561.         //
  562.         // We can't blindly smash the day to 1 because the era might change
  563.         // in the middle of the month.
  564.         WORD wDay = pst->wDay;
  565.         ASSERT(pst->wDay == 29);
  566.         pst->wDay = 28;
  567.         fRc = GetDateFormat(pct->lcid, 0, pst, pszFormat, pszBuf, cchBuf);
  568.         pst->wDay = wDay;
  569.     }
  570.     pst->wYear = wYear;
  571.     return fRc;
  572. }
  573. void MCUpdateEditYear(MONTHCAL *pmc)
  574. {
  575.     TCHAR rgch[64];
  576.     ASSERT(pmc->hwndEdit);
  577.     EVAL(MCGetDateFormatWithTempYear(&pmc->ct, &pmc->st, TEXT("yyyy"), pmc->st.wYear, rgch, ARRAYSIZE(rgch)));
  578.     SendMessage(pmc->hwndEdit, WM_SETTEXT, 0, (LPARAM)rgch);
  579. }
  580. LRESULT CALLBACK MonthCalWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  581. {
  582.     MONTHCAL *pmc;
  583.     LRESULT lres = 0;
  584.         
  585.     if (uMsg == WM_NCCREATE)
  586.         return(MCNcCreateHandler(hwnd));
  587.     
  588.     pmc = MonthCal_GetPtr(hwnd);
  589.     if (pmc == NULL)
  590.         return(DefWindowProc(hwnd, uMsg, wParam, lParam));
  591.     // Dispatch the various messages we can receive
  592.     switch (uMsg)
  593.     {
  594.         
  595.     case WM_CREATE:
  596.         lres = MCCreateHandler(pmc, hwnd, (LPCREATESTRUCT)lParam);
  597.         break;
  598.         
  599.         HANDLE_MSG(pmc, WM_ERASEBKGND, MCHandleEraseBkgnd);
  600.     case WM_PRINTCLIENT:
  601.     case WM_PAINT:
  602.         MonthCal_OnPaint(pmc, (HDC)wParam);
  603.         return(0);
  604.     case WM_KEYDOWN:
  605.         MCHandleKeydown(pmc, wParam, lParam);
  606.         break;
  607.     case WM_KEYUP:
  608.         switch (wParam)
  609.         {   
  610.         case VK_CONTROL:
  611.             pmc->fControl = FALSE;
  612.             break;
  613.         case VK_SHIFT:
  614.             pmc->fShift = FALSE;
  615.             break;
  616.         }
  617.         break;    
  618.         
  619. #if 0
  620.     case WM_CHAR:
  621.         MCHandleChar(pmc, wParam, lParam);
  622.         break;
  623. #endif
  624.                 
  625.     case WM_CONTEXTMENU:
  626.         MCContextMenu(pmc, wParam, lParam);
  627.         break;
  628.     case WM_LBUTTONDOWN:
  629.         MCLButtonDown(pmc, wParam, lParam);
  630.         break;
  631.     case WM_LBUTTONUP:
  632.         MCLButtonUp(pmc, wParam, lParam);
  633.         break;
  634.     case WM_MOUSEMOVE:
  635.         MCMouseMove(pmc, wParam, lParam);
  636.         break;
  637.     case WM_GETFONT:
  638.         lres = (LRESULT)pmc->hfont;
  639.         break;
  640.     case WM_SETFONT:
  641.         MCHandleSetFont(pmc, (HFONT)wParam, (BOOL)LOWORD(lParam));
  642.         MCSizeHandler(pmc, &pmc->rc);
  643.         MCUpdateMonthNamePos(pmc);
  644.         break;
  645.     case WM_TIMER:
  646.         MCHandleTimer(pmc, wParam);
  647.         break;
  648.     case WM_NCDESTROY:
  649.         MCNcDestroyHandler(hwnd, pmc, wParam, lParam);
  650.         break;
  651.     case WM_ENABLE:
  652.     {
  653.         BOOL fEnable = wParam ? TRUE:FALSE;
  654.         if (pmc->fEnabled != fEnable)
  655.         {
  656.             pmc->fEnabled = (WORD) fEnable;
  657.             InvalidateRect(pmc->ci.hwnd, NULL, TRUE);
  658.         }
  659.         break;
  660.     }
  661.     case MCMP_WINDOWPOSCHANGED:
  662.     case WM_SIZE:
  663.     {
  664.         RECT rc;
  665.         if (uMsg==MCMP_WINDOWPOSCHANGED)
  666.         {
  667.             GetClientRect(pmc->ci.hwnd, &rc);
  668.         }
  669.         else
  670.         {
  671.             rc.left   = 0;
  672.             rc.top    = 0;
  673.             rc.right  = GET_X_LPARAM(lParam);
  674.             rc.bottom = GET_Y_LPARAM(lParam);
  675.         }
  676.         lres = MCSizeHandler(pmc, &rc);
  677.         break;
  678.     }
  679.     case WM_CANCELMODE:
  680.         PostMessage(pmc->ci.hwnd, WM_LBUTTONUP, 0, 0xFFFFFFFF);
  681.         break;
  682.     case WM_SYSCOLORCHANGE:
  683.         InitGlobalColors();
  684.         break;
  685.     case WM_WININICHANGE:
  686.         InitGlobalMetrics(wParam);
  687.         if (lParam == 0 ||
  688. #ifdef UNICODE_WIN9x
  689.             !lstrcmpiA((LPSTR)lParam, "Intl")
  690. #else
  691.             !lstrcmpi((LPTSTR)lParam, TEXT("Intl"))
  692. #endif
  693.            )
  694.         {
  695.             UpdateLocaleInfo(pmc, &pmc->li);
  696.             MCReloadMenus(pmc);
  697.             InvalidateRect(hwnd, NULL, TRUE);
  698.             wParam = 0;             // force MCCalcSizes to happen
  699.         }
  700.         if (wParam == 0 || wParam == SPI_SETNONCLIENTMETRICS)
  701.         {
  702.             MCCalcSizes(pmc);
  703.             PostMessage(pmc->ci.hwnd, MCMP_WINDOWPOSCHANGED, 0, 0);
  704.         }
  705.         break;
  706.     case WM_NOTIFYFORMAT:
  707.         return CIHandleNotifyFormat(&pmc->ci, lParam);
  708.         break;
  709.     case WM_STYLECHANGING:
  710.         lres = MCOnStyleChanging(pmc, (UINT) wParam, (LPSTYLESTRUCT)lParam);
  711.         break;
  712.     case WM_STYLECHANGED:
  713.         lres = MCOnStyleChanged(pmc, (UINT) wParam, (LPSTYLESTRUCT)lParam);
  714.         break;
  715.     case WM_NOTIFY: {
  716.         LPNMHDR pnm = (LPNMHDR)lParam;
  717.         switch (pnm->code)
  718.         {
  719.         case UDN_DELTAPOS:
  720.             if (pnm->hwndFrom == pmc->hwndUD)
  721.             {
  722.                 // A notification from the UpDown control buddied
  723.                 // with the currently popped up monthcal, adjust the
  724.                 // edit box appropriately.  We use UDN_DELTAPOS instad
  725.                 // of WM_VSCROLL because we care only about the delta and
  726.                 // not the absolute number. The absolute number causes us
  727.                 // problems in localized calendars.
  728.                 LPNM_UPDOWN pnmdp = (LPNM_UPDOWN)lParam;
  729.                 UINT yr = pmc->st.wYear + pnmdp->iDelta;
  730.                 UINT yrMin, yrMax;
  731.                 int delta;
  732.                 yrMin = pmc->stMin.wYear;
  733.                 if (yr < yrMin)
  734.                     yr = yrMin;
  735.                 yrMax = pmc->stMax.wYear;
  736.                 if (yr > yrMax)
  737.                     yr = yrMax;
  738.                 delta = yr - pmc->st.wYear;
  739.                 pmc->st.wYear = (WORD)yr;
  740.                 if (delta) {
  741.                     MCIncrStartMonth(pmc, delta * 12, FALSE);
  742.                     MCNotifySelChange(pmc,MCN_SELCHANGE);
  743.                 }
  744.             }
  745.             break;
  746.         }
  747.     } // WM_NOTIFY switch
  748.         break;
  749.     case WM_VSCROLL:
  750.         // this must be coming from our UpDown control buddied
  751.         // with the currently popped up monthcal, adjust the
  752.         // edit box appropriately
  753.         // We must do this on WM_VSCROLL rather than UDN_DELTAPOS
  754.         // since we need to fix the selection after the updown mangled it
  755.         MCUpdateEditYear(pmc);
  756.         break;
  757.     //
  758.     // MONTHCAL specific messages
  759.     //
  760.     // MCM_GETCURSEL wParam=void lParam=LPSYSTEMTIME
  761.     //   sets *lParam to the currently selected SYSTEMTIME
  762.     //   returns TRUE on success, FALSE on error (such as multi-select MONTHCAL)
  763.     case MCM_GETCURSEL:
  764.         if (!MonthCal_IsMultiSelect(pmc))
  765.         {
  766.             LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
  767.             if (pst)
  768.             {
  769.                 ZeroMemory(pst, sizeof(SYSTEMTIME));
  770.                 // BUGBUG raymondc v6. Need to zero out the time fields instead of
  771.                 // setting them to garbage.  This confuses MFC.
  772.                 *pst = pmc->st;
  773.                 pst->wDayOfWeek = (DowFromDate(pst)+1) % 7;  // this returns 0==sun
  774.                 lres = 1;
  775.             }
  776.         }
  777.         break;
  778.     // MCM_SETCURSEL wParam=void lParam=LPSYSTEMTIME
  779.     //   sets the currently selected SYSTEMTIME to *lParam
  780.     //   returns TRUE on success, FALSE on error (such as multi-select MONTHCAL or bad parameters)
  781.     case MCM_SETCURSEL:
  782.     {
  783.         LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
  784.         if (MonthCal_IsMultiSelect(pmc) ||
  785.             !IsValidDate(pst))
  786.         {
  787.             break;
  788.         }
  789.         if (0 == CmpDate(pst, &pmc->st))
  790.         {
  791.             // if no change, just return
  792.             lres = 1;
  793.             break;
  794.         }
  795.         pmc->rcDayOld = pmc->rcDayCur;
  796.         pmc->fNoNotify = TRUE;
  797.         lres = MCSetDate(pmc, pst);
  798.         pmc->fNoNotify = FALSE;
  799.         if (lres)
  800.         {
  801.             InvalidateRect(pmc->ci.hwnd, &pmc->rcDayOld, FALSE);     // erase old highlight
  802.             InvalidateRect(pmc->ci.hwnd, &pmc->rcDayCur, FALSE);     // draw new highlight
  803.         }
  804.         UpdateWindow(pmc->ci.hwnd);
  805.         break;
  806.     }
  807.     // MCM_GETMAXSELCOUNT wParam=void lParam=void
  808.     //   returns the max number of selected days allowed
  809.     case MCM_GETMAXSELCOUNT:
  810.         lres = (LRESULT)(MonthCal_IsMultiSelect(pmc) ? pmc->cSelMax : 1);
  811.         break;
  812.     // MCM_SETMAXSELCOUNT wParam=int lParam=void
  813.     //   sets the maximum selectable date range to wParam days
  814.     //   returns TRUE on success, FALSE on error (such as single-select MONTHCAL)
  815.     case MCM_SETMAXSELCOUNT:
  816.         if (!MonthCal_IsMultiSelect(pmc) || (int)wParam < 1)
  817.             break;
  818.         pmc->cSelMax = (int)wParam;
  819.         lres = 1;
  820.         break;
  821.     // MCM_GETSELRANGE wParam=void lParam=LPSYSTEMTIME[2]
  822.     //   sets *lParam to the first date of the range, *(lParam+1) to the second date
  823.     //   returns TRUE on success, FALSE otherwise (such as single-select MONTHCAL)
  824.     case MCM_GETSELRANGE:
  825.     {
  826.         LPSYSTEMTIME pst;
  827.         pst = (LPSYSTEMTIME)lParam;
  828.         if (!pst)
  829.             break;
  830.         ZeroMemory(pst, sizeof(SYSTEMTIME) * 2);
  831.         if (!MonthCal_IsMultiSelect(pmc))
  832.             break;
  833.         *pst = pmc->st;
  834.         pst->wDayOfWeek = (DowFromDate(pst)+1) % 7;  // this returns 0==sun
  835.         pst++;
  836.         *pst = pmc->stEndSel;
  837.         pst->wDayOfWeek = (DowFromDate(pst)+1) % 7;  // this returns 0==sun
  838.         lres = 1;
  839.         break;
  840.     }
  841.     // MCM_SETSELRANGE wParam=void lParam=LPSYSTEMTIME[2]
  842.     //   sets the currently selected day range to *lparam to *(lParam+1)
  843.     //   returns TRUE on success, FALSE otherwise (such as single-select MONTHCAL or bad params)
  844.     case MCM_SETSELRANGE:
  845.     {
  846.         LPSYSTEMTIME pstStart = (LPSYSTEMTIME)lParam;
  847.         LPSYSTEMTIME pstEnd = &pstStart[1];
  848.         SYSTEMTIME stStart;
  849.         SYSTEMTIME stEnd;
  850.         if (!MonthCal_IsMultiSelect(pmc) ||
  851.             !IsValidDate(pstStart) ||
  852.             !IsValidDate(pstEnd))
  853.             break;
  854.         // IE3 shipped without validating the time portion of this message.
  855.         // Make sure our stored systemtimes are always valid (so we will
  856.         // always give out valid systemtime structs).
  857.         //
  858.         if (!IsValidTime(pstStart))
  859.             CopyTime(pmc->st, *pstStart);
  860.         if (!IsValidTime(pstEnd))
  861.             CopyTime(pmc->stEndSel, *pstEnd);
  862.         if (CmpDate(pstStart, pstEnd) > 0)
  863.         {
  864.             stEnd = *pstStart;
  865.             stStart = *pstEnd;
  866.             pstStart = &stStart;
  867.             pstEnd = &stEnd;
  868.         }
  869.         if (CmpDate(pstStart, &pmc->stMin) < 0)
  870.             break;
  871.         if (CmpDate(pstEnd, &pmc->stMax) > 0)
  872.             break;
  873.         if (DaysBetweenDates(pstStart, pstEnd) >= pmc->cSelMax)
  874.             break;
  875.         if (0 == CmpDate(pstStart, &pmc->st) &&
  876.             0 == CmpDate(pstEnd, &pmc->stEndSel))
  877.         {
  878.             // if no change, just return
  879.             lres = 1;
  880.             break;
  881.         }
  882.         pmc->stStartPrev = pmc->st;
  883.         pmc->stEndPrev = pmc->stEndSel;
  884.         pmc->fNoNotify = TRUE;
  885.         lres = MCSetDate(pmc, pstEnd);
  886.         if (lres)
  887.         {
  888.             pmc->st = *pstStart;
  889.             pmc->stEndSel = *pstEnd;
  890.             MCInvalidateDates(pmc, &pmc->stStartPrev, &pmc->stEndPrev);
  891.             MCInvalidateDates(pmc, &pmc->st, &pmc->stEndSel);
  892.             UpdateWindow(pmc->ci.hwnd);
  893.         }
  894.         pmc->fNoNotify = FALSE;
  895.         break;
  896.     }
  897.     
  898.     // MCM_GETMONTHRANGE wParam=GMR_flags lParam=LPSYSTEMTIME[2]
  899.     // if GMR_VISIBLE, returns the range of selectable (non-grayed) displayed
  900.     // days. if GMR_DAYSTATE, returns the range of every (incl grayed) days.
  901.     // returns the number of months the above range spans.
  902.     case MCM_GETMONTHRANGE:
  903.     {
  904.         LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
  905.         if (pst)
  906.         {
  907.             ZeroMemory(pst, 2 * sizeof(SYSTEMTIME));
  908.             if (wParam == GMR_VISIBLE)
  909.             {
  910.                 pst[0] = pmc->stMonthFirst;
  911.                 pst[1] = pmc->stMonthLast;
  912.             }
  913.             else if (wParam == GMR_DAYSTATE)
  914.             {
  915.                 pst[0] = pmc->stViewFirst;
  916.                 pst[1] = pmc->stViewLast;
  917.             }
  918.         }
  919.         lres = (LRESULT)pmc->nMonths;
  920.         if (wParam == GMR_DAYSTATE)
  921.             lres += 2;
  922.         
  923.         break;
  924.     }
  925.     // MCM_SETDAYSTATE wParam=int lParam=LPDAYSTATE
  926.     // updates the MONTHCAL's DAYSTATE, only for MONTHCALs with DAYSTATE enabled
  927.     // the range of months represented in the DAYSTATE array passed in lParam 
  928.     // should match that of the MONTHCAL
  929.     // wParam count of items in DAYSTATE array
  930.     // lParam pointer to array of DAYSTATE items 
  931.     // returns FALSE if not DAYSTATE enabled or if an error occurs, TRUE otherwise
  932.     case MCM_SETDAYSTATE:
  933.     {
  934.         MONTHDAYSTATE *pmds = (MONTHDAYSTATE *)lParam;
  935.         int i;
  936.         if (!MonthCal_IsDayState(pmc) ||
  937.             (int)wParam != (pmc->nMonths + 2))
  938.             break;
  939.         for (i = 0; i < (int)wParam; i++)
  940.         {
  941.             pmc->rgdayState[i] = *pmds;
  942.             pmds++;
  943.         }
  944.         MCInvalidateMonthDays(pmc);
  945.         lres = 1;
  946.         break;
  947.     }
  948.     // MCM_GETMINREQRECT wParam=void lParam=LPRECT
  949.     //   sets *lParam to the minimum size required to display one month in full.
  950.     //   Note: this is dependent upon the currently selected font.
  951.     //   Apps can take the returned size and double the width to get two calendars
  952.     //   displayed.
  953.     case MCM_GETMINREQRECT:
  954.     {
  955.         LPRECT prc = (LPRECT)lParam;
  956.         prc->left   = 0;
  957.         prc->top    = 0;
  958.         prc->right  = pmc->dxMonth;
  959.         prc->bottom = pmc->dyMonth;
  960.         if (MonthCal_ShowToday(pmc))
  961.         {
  962.             prc->bottom += pmc->dyToday;
  963.         }
  964.         AdjustWindowRect(prc, pmc->ci.style, FALSE);
  965.         // This is a bogus message, lParam should really be LPSIZE.
  966.         // Make sure left and top are 0 (AdjustWindowRect will make these negative).
  967.         prc->right  -= prc->left;
  968.         prc->bottom -= prc->top;
  969.         prc->left    = 0;
  970.         prc->top     = 0;
  971.         lres = 1;
  972.         break;
  973.     }
  974.     // MCM_GETMAXTODAYWIDTH wParam=void lParam=LPDWORD
  975.     //   sets *lParam to the width of the "today" string, so apps
  976.     //   can figure out how big to make the calendar (max of MCM_GETMINREQRECT
  977.     //   and MCM_GETMAXTODAYWIDTH).
  978.     case MCM_GETMAXTODAYWIDTH:
  979.     {
  980.         RECT rc;
  981.         rc.left = 0;
  982.         rc.top = 0;
  983.         rc.right = pmc->dxToday;
  984.         rc.bottom = pmc->dyToday;
  985.         AdjustWindowRect(&rc, pmc->ci.style, FALSE);
  986.         lres = rc.right - rc.left;
  987.         break;
  988.     }
  989.     case MCM_HITTEST:
  990.         return MCHandleHitTest(pmc, (PMCHITTESTINFO)lParam);
  991.         
  992.     case MCM_SETCOLOR:
  993.         if (wParam >= 0 && wParam < MCSC_COLORCOUNT) 
  994.         {
  995.             COLORREF clr = pmc->clr[wParam];
  996.             pmc->clr[wParam] = (COLORREF)lParam;
  997.             InvalidateRect(hwnd, NULL, wParam == MCSC_BACKGROUND);
  998.             return clr;
  999.         }
  1000.         return -1;
  1001.         
  1002.     case MCM_GETCOLOR:
  1003.         if (wParam >= 0 && wParam < MCSC_COLORCOUNT) 
  1004.             return pmc->clr[wParam];
  1005.         return -1;
  1006.         
  1007.     case MCM_SETFIRSTDAYOFWEEK:
  1008.     {
  1009.         lres = MAKELONG(pmc->li.dowStartWeek, (BOOL)pmc->fFirstDowSet);
  1010.         if (lParam == (LPARAM)-1) {
  1011.             pmc->fFirstDowSet = FALSE;
  1012.         } else if (lParam < 7) {
  1013.             pmc->fFirstDowSet = TRUE;
  1014.             pmc->li.dowStartWeek = (TCHAR)lParam;
  1015.         }
  1016.         UpdateLocaleInfo(pmc, &pmc->li);
  1017.         InvalidateRect(hwnd, NULL, FALSE);
  1018.         return lres;
  1019.     }
  1020.         
  1021.     case MCM_GETFIRSTDAYOFWEEK:
  1022.         return MAKELONG(pmc->li.dowStartWeek, (BOOL)pmc->fFirstDowSet);
  1023.         
  1024.     case MCM_SETTODAY:
  1025.         MCSetToday(pmc, (SYSTEMTIME*)lParam);
  1026.         break;
  1027.         
  1028.     case MCM_GETTODAY:
  1029.         if (lParam) {
  1030.             *((SYSTEMTIME*)lParam) = pmc->stToday;
  1031.             return TRUE;
  1032.         }
  1033.         return FALSE;
  1034.     case MCM_GETRANGE:
  1035.         if (lParam)
  1036.         {
  1037.             LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
  1038.             ZeroMemory(pst, sizeof(SYSTEMTIME)*2);
  1039.             ASSERT(lres == 0);
  1040.             if (pmc->fMinYrSet)
  1041.             {
  1042.                 pst[0] = pmc->stMin;
  1043.                 lres = GDTR_MIN;
  1044.             }
  1045.             if (pmc->fMaxYrSet)
  1046.             {
  1047.                 pst[1] = pmc->stMax;
  1048.                 lres |= GDTR_MAX;
  1049.             }
  1050.         }
  1051.         break;
  1052.     case MCM_SETRANGE:
  1053.         if (lParam)
  1054.         {
  1055.             LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
  1056.             if (((wParam & GDTR_MIN) && !IsValidDate(pst)) ||
  1057.                 ((wParam & GDTR_MAX) && !IsValidDate(&pst[1])))
  1058.                 break;
  1059.             // IE3 did not validate the time portion of this struct
  1060.             // use stToday time fields cuz pmc->stMin/Max may be zero
  1061.             if ((wParam & GDTR_MIN) && !IsValidTime(pst))
  1062.                 CopyTime(pmc->stToday, pst[0]);
  1063.             if ((wParam & GDTR_MAX) && !IsValidTime(&pst[1]))
  1064.                 CopyTime(pmc->stToday, pst[1]);
  1065.             if (wParam & GDTR_MIN)
  1066.             {
  1067.                 pmc->stMin = *pst;
  1068.                 pmc->fMinYrSet = TRUE;
  1069.             }
  1070.             else
  1071.             {
  1072.                 pmc->stMin = c_stEpoch;
  1073.                 pmc->fMinYrSet = FALSE;
  1074.             }
  1075.             pst++;
  1076.             if (wParam & GDTR_MAX)
  1077.             {
  1078.                 pmc->stMax = *pst;
  1079.                 pmc->fMaxYrSet = TRUE;
  1080.             }
  1081.             else
  1082.             {
  1083.                 pmc->stMax = c_stArmageddon;
  1084.                 pmc->fMaxYrSet = FALSE;
  1085.             }
  1086.             
  1087.             if (pmc->fMaxYrSet && pmc->fMinYrSet && CmpDate(&pmc->stMin, &pmc->stMax) > 0)
  1088.             {
  1089.                 SYSTEMTIME stTemp = pmc->stMin;
  1090.                 pmc->stMin = pmc->stMax;
  1091.                 pmc->stMax = stTemp;
  1092.             }
  1093.             lres = TRUE;
  1094.         }
  1095.         break;
  1096.     case MCM_GETMONTHDELTA:
  1097.         if (pmc->fMonthDelta)
  1098.             lres = pmc->nMonthDelta;
  1099.         else
  1100.             lres = pmc->nMonths;
  1101.         break;
  1102.     case MCM_SETMONTHDELTA:
  1103.         if (pmc->fMonthDelta)
  1104.             lres = pmc->nMonthDelta;
  1105.         else
  1106.             lres = 0;
  1107.         if ((int)wParam==0)
  1108.             pmc->fMonthDelta = FALSE;
  1109.         else
  1110.         {
  1111.             pmc->fMonthDelta = TRUE;
  1112.             pmc->nMonthDelta = (int)wParam;
  1113.         }
  1114.         break;
  1115.     default:
  1116.         if (CCWndProc(&pmc->ci, uMsg, wParam, lParam, &lres))
  1117.             return lres;
  1118.         
  1119.         lres = DefWindowProc(hwnd, uMsg, wParam, lParam);
  1120.         break;
  1121.     } /* switch (uMsg) */
  1122.     return(lres);
  1123. }
  1124. LRESULT MCNcCreateHandler(HWND hwnd)
  1125. {
  1126.     MONTHCAL *pmc;
  1127.     // Allocate storage for the dtpick structure
  1128.     pmc = (MONTHCAL *)NearAlloc(sizeof(MONTHCAL));
  1129.     if (!pmc)
  1130.     {
  1131.         DebugMsg(DM_WARNING, TEXT("mc: Out Of Near Memory"));
  1132.         return(0L);
  1133.     }
  1134.     MonthCal_SetPtr(hwnd, pmc);
  1135.     return(1L);
  1136. }
  1137. void MCInitColorArray(COLORREF* pclr)
  1138. {
  1139.     pclr[MCSC_BACKGROUND]   = g_clrWindow;
  1140.     pclr[MCSC_MONTHBK]      = g_clrWindow;
  1141.     pclr[MCSC_TEXT]         = g_clrWindowText;
  1142.     pclr[MCSC_TITLEBK]      = GetSysColor(COLOR_ACTIVECAPTION);
  1143.     pclr[MCSC_TITLETEXT]    = GetSysColor(COLOR_CAPTIONTEXT);
  1144.     pclr[MCSC_TRAILINGTEXT] = g_clrGrayText;
  1145. }
  1146. LRESULT MCCreateHandler(MONTHCAL *pmc, HWND hwnd, LPCREATESTRUCT lpcs)
  1147. {
  1148.     HFONT      hfont;
  1149.     SYSTEMTIME st;
  1150.     // Validate data
  1151.     //
  1152.     if (lpcs->style & MCS_INVALIDBITS)
  1153.         return(-1);
  1154.     CIInitialize(&pmc->ci, hwnd, lpcs);
  1155.     UpdateLocaleInfo(pmc, &pmc->li);   
  1156.     // Initialize our data.
  1157.     //
  1158.     pmc->hinstance = lpcs->hInstance;
  1159.     
  1160.     pmc->fEnabled  = !(pmc->ci.style & WS_DISABLED);
  1161.     
  1162.     pmc->hpenToday = CreatePen(PS_SOLID, 2, CAL_COLOR_TODAY);
  1163.     MCReloadMenus(pmc);
  1164.     // Default minimum date is the epoch
  1165.     pmc->stMin = c_stEpoch;
  1166.     // Default maximum date is armageddon
  1167.     pmc->stMax = c_stArmageddon;
  1168.     GetLocalTime(&pmc->stToday);
  1169.     pmc->st = pmc->stToday;
  1170.     if (MonthCal_IsMultiSelect(pmc))
  1171.         pmc->stEndSel = pmc->st;
  1172.     // make sure the time portions of these are valid. they are never
  1173.     // touched after this point
  1174.     pmc->stMonthFirst = pmc->st;
  1175.     pmc->stMonthLast = pmc->st;
  1176.     pmc->stViewFirst = pmc->st;
  1177.     pmc->stViewLast = pmc->st;
  1178.     pmc->cSelMax = CAL_DEF_SELMAX;
  1179.     hfont = NULL;
  1180.     if (lpcs->hwndParent)
  1181.         hfont = (HFONT)SendMessage(lpcs->hwndParent, WM_GETFONT, 0, 0);
  1182.     if (hfont == NULL)
  1183.         hfont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
  1184.     MCHandleSetFont(pmc, hfont, FALSE);
  1185.     
  1186.     CopyDate(pmc->st, st);
  1187.     // Can we start at January?
  1188.     if (st.wMonth <= (pmc->nViewRows * pmc->nViewCols))
  1189.         st.wMonth = 1;
  1190.     
  1191.     MCUpdateStartEndDates(pmc, &st);
  1192.     
  1193.     pmc->idTimerToday = SetTimer(pmc->ci.hwnd, CAL_TODAYTIMER, CAL_SECTODAYTIMER * 1000, NULL);
  1194.     MCInitColorArray(pmc->clr);
  1195.     return(0);
  1196. }
  1197. LRESULT MCOnStyleChanging(MONTHCAL *pmc, UINT gwl, LPSTYLESTRUCT pinfo)
  1198. {
  1199.     if (gwl == GWL_STYLE)
  1200.     {
  1201.         DWORD changeFlags = pmc->ci.style ^ pinfo->styleNew;
  1202.         // Don't allow these bits to change
  1203.         changeFlags &= MCS_MULTISELECT | MCS_DAYSTATE | MCS_INVALIDBITS;
  1204.         pinfo->styleNew ^= changeFlags;
  1205.     }
  1206.     return(0);
  1207. }
  1208. LRESULT MCOnStyleChanged(MONTHCAL *pmc, UINT gwl, LPSTYLESTRUCT pinfo)
  1209. {
  1210.     if (gwl == GWL_STYLE)
  1211.     {
  1212.         DWORD changeFlags = pmc->ci.style ^ pinfo->styleNew;
  1213.         ASSERT(!(changeFlags & (MCS_MULTISELECT|MCS_DAYSTATE|MCS_INVALIDBITS)));
  1214.         pmc->ci.style = pinfo->styleNew;
  1215.         if (changeFlags & MCS_WEEKNUMBERS)
  1216.         {
  1217.             MCCalcSizes(pmc);
  1218.             MCUpdateRcDayCur(pmc, &pmc->st);
  1219.             //MCUpdateToday(pmc);
  1220.         }
  1221.         // save a touch of code and share the MCUpdateToday
  1222.         // call with MCS_WEEKNUMBERS above
  1223.         if (changeFlags & MCS_NOTODAY|MCS_NOTODAYCIRCLE|MCS_WEEKNUMBERS)
  1224.         {
  1225.             MCUpdateToday(pmc);
  1226.         }
  1227.         if (changeFlags & (WS_BORDER | WS_CAPTION | WS_THICKFRAME)) {
  1228.             // the changing of these bits affect the size of the window
  1229.             // but not until after this message is handled
  1230.             // so post ourself a message.
  1231.             PostMessage(pmc->ci.hwnd, MCMP_WINDOWPOSCHANGED, 0, 0);
  1232.         }
  1233.         if (changeFlags)
  1234.             InvalidateRect(pmc->ci.hwnd, NULL, TRUE);
  1235.     }
  1236.     else if (gwl == GWL_EXSTYLE)
  1237.     {
  1238.         if ((pinfo->styleOld ^ pinfo->styleNew) & RTL_MIRRORED_WINDOW)
  1239.         {
  1240.             MCUpdateMonthNamePos(pmc);
  1241.         }
  1242.     }
  1243.     return(0);
  1244. }
  1245. void MCCalcSizes(MONTHCAL *pmc)
  1246. {
  1247.     HDC   hdc;
  1248.     HFONT hfontOrig;
  1249.     int   i, dxMax, dyMax, dxExtra;
  1250.     RECT  rect;
  1251.     TCHAR szBuf[128];
  1252.     TCHAR szDateFmt[64];
  1253.     // get sizing info for bold font...
  1254.     hdc = GetDC(pmc->ci.hwnd);
  1255.     hfontOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
  1256.     MGetTextExtent(hdc, g_szTextExtentDef, 2, &dxMax, &dyMax);
  1257.     MGetTextExtent(hdc, g_szTextExtentDef, 4, &pmc->dxYearMax, NULL);
  1258.     GetDateFormat(pmc->ct.lcid, DATE_SHORTDATE, &pmc->stToday,
  1259.         NULL, szDateFmt, sizeof(szDateFmt));
  1260.     wsprintf(szBuf,TEXT("%s %s"),pmc->li.szToday,szDateFmt);
  1261.     MGetTextExtent(hdc, szBuf, -1, &pmc->dxToday, &pmc->dyToday);
  1262.     // BUGBUG raymondc - hard-coded numbers are accessibility-incompatible
  1263.     pmc->dyToday += 4;
  1264.     //
  1265.     //  Cache these values so we don't go wacko if the app fails to
  1266.     //  forward WM_WININCHANGE messages into us and the user changes
  1267.     //  scrollbar widths.  We'll draw with the wrong width, but at
  1268.     //  least they will be consistently wrong.
  1269.     //
  1270.     pmc->dxArrowMargin = DX_ARROWMARGIN;
  1271.     pmc->dxCalArrow    = DX_CALARROW;
  1272.     pmc->dyCalArrow    = DY_CALARROW;
  1273.     //
  1274.     //  The banner bar consists of
  1275.     //
  1276.     //  margin + scrollbutton + spacer +
  1277.     //                      MonthName yyyy +
  1278.     //                        + spacer + scrollbutton + margin
  1279.     //
  1280.     //  Margin is dxArrowMargin
  1281.     //
  1282.     //  Scrollbutton = dxCalArrow
  1283.     //
  1284.     //  Spacer = border + CXVSCROLL + border
  1285.     //
  1286.     //  The spacer needs to be large enough for us to insert an updown
  1287.     //  control when it comes time to spin the year.  We don't need to
  1288.     //  cache the spacer anywhere - its value is implicit from the others.
  1289.     //
  1290.     //  The actual width is divided by the number of columns we need
  1291.     //  (typically 7, but perhaps 8 if we are also displaying week numbers).
  1292.     //
  1293.     //  We round the division down - later, we'll add some random futz
  1294.     //  to compensate.
  1295.     //
  1296.     dxExtra = pmc->dxArrowMargin + pmc->dxCalArrow +
  1297.                         (g_cxBorder + g_cxVScroll + g_cxBorder);
  1298.     dxExtra = dxExtra + dxExtra; // left + right
  1299.     for (i = 0; i < 12; i++)
  1300.     {
  1301.         int dxTemp;
  1302.         // BUGBUG raymondc - not localization safe for languages which change
  1303.         // month forms based on context
  1304.         wsprintf(szBuf,TEXT("%s %s"),pmc->li.rgszMonth[i],g_szTextExtentDef);
  1305.         
  1306.         MGetTextExtent(hdc, szBuf, -1, &dxTemp, NULL);
  1307.         dxTemp += dxExtra;
  1308.         dxTemp = dxTemp / (CALCOLMAX + (MonthCal_ShowWeekNumbers(pmc) ? 1:0));
  1309.         if (dxTemp > dxMax)
  1310.             dxMax = dxTemp;                    
  1311.     }
  1312.         
  1313.     SelectObject(hdc, (HGDIOBJ)pmc->hfont);
  1314.     for (i = 0; i < 7; i++)
  1315.     {
  1316.         SIZE  size;
  1317.         MGetTextExtent(hdc, pmc->li.rgszDay[i], -1, (LPINT)&size.cx, (LPINT)&size.cy);
  1318.         if (size.cx > dxMax)
  1319.             dxMax = size.cx;
  1320.         if (size.cy > dyMax)
  1321.             dyMax = size.cy;
  1322.     }
  1323.     if (dyMax < pmc->dyCalArrow / 2)
  1324.         dyMax = pmc->dyCalArrow / 2;
  1325.     SelectObject(hdc, (HGDIOBJ)hfontOrig);
  1326.     ReleaseDC(pmc->ci.hwnd, hdc);
  1327.     pmc->dxCol = dxMax + 2;
  1328.     pmc->dyRow = dyMax + 2;
  1329.     pmc->dxMonth = pmc->dxCol * (CALCOLMAX + (MonthCal_ShowWeekNumbers(pmc) ? 1:0)) + 1;
  1330.     pmc->dyMonth = pmc->dyRow * (CALROWMAX + 3) + 1; // we add 2 for the month name and day names
  1331.     pmc->dxToday += pmc->dxCol+6+CALBORDER; // +2 for -1 at ends and 4 for shift of circle 
  1332.     if (pmc->dxMonth > pmc->dxToday)
  1333.         pmc->dxToday = pmc->dxMonth;
  1334.      
  1335.     // Space for month name (tile bar area of each month)
  1336.     pmc->rcMonthName.left   = 0;
  1337.     pmc->rcMonthName.top    = 0;
  1338.     pmc->rcMonthName.right  = pmc->dxMonth;
  1339.     pmc->rcMonthName.bottom = pmc->rcMonthName.top + (pmc->dyRow * 2);
  1340.     // Space for day-of-week
  1341.     pmc->rcDow.left   = 0;
  1342.     pmc->rcDow.top    = pmc->rcMonthName.bottom;
  1343.     pmc->rcDow.right  = pmc->dxMonth;
  1344.     pmc->rcDow.bottom = pmc->rcDow.top + pmc->dyRow;
  1345.     // Space for week numbers
  1346.     if (MonthCal_ShowWeekNumbers(pmc))
  1347.     {
  1348.         pmc->rcWeekNum.left   = pmc->rcDow.left;
  1349.         pmc->rcWeekNum.top    = pmc->rcDow.bottom;
  1350.         pmc->rcWeekNum.right  = pmc->rcWeekNum.left + pmc->dxCol;
  1351.         pmc->rcWeekNum.bottom = pmc->dyMonth;
  1352.         pmc->rcDow.left  += pmc->dxCol;          // shift days of week
  1353.     }
  1354.     // Space for the day numbers
  1355.     pmc->rcDayNum.left   = pmc->rcDow.left;
  1356.     pmc->rcDayNum.top    = pmc->rcDow.bottom;
  1357.     pmc->rcDayNum.right  = pmc->rcDayNum.left + (CALCOLMAX * pmc->dxCol);
  1358.     pmc->rcDayNum.bottom = pmc->dyMonth;
  1359.     GetClientRect(pmc->ci.hwnd, &rect);
  1360.     MCRecomputeSizing(pmc, &rect);
  1361. }
  1362. void MCHandleSetFont(MONTHCAL *pmc, HFONT hfont, BOOL fRedraw)
  1363. {
  1364.     LOGFONT lf;
  1365.     HFONT   hfontBold;
  1366.     if (hfont == NULL)
  1367.         hfont = (HFONT)GetStockObject(SYSTEM_FONT);
  1368.     GetObject(hfont, sizeof(LOGFONT), (LPVOID)&lf);
  1369.     // we want to make sure that the bold days are obviously different
  1370.     // from the non-bold days...
  1371.     lf.lfWeight = (lf.lfWeight >= 700 ? 1000 : 800);
  1372.     hfontBold = CreateFontIndirect(&lf);
  1373.     if (hfontBold == NULL)
  1374.         return;
  1375.     if (pmc->hfontBold)
  1376.         DeleteObject((HGDIOBJ)pmc->hfontBold);
  1377.     pmc->hfont     = hfont;
  1378.     pmc->hfontBold = hfontBold;
  1379.     pmc->ci.uiCodePage = GetCodePageForFont(hfont);
  1380.     // calculate the new row and column sizes
  1381.     MCCalcSizes(pmc);
  1382.     if (fRedraw)
  1383.     {
  1384.         InvalidateRect(pmc->ci.hwnd, NULL, TRUE);
  1385.         UpdateWindow(pmc->ci.hwnd);
  1386.     }
  1387. }
  1388. #if 0       // why is this here?  it's not called anywhere??
  1389. // Stolen from the windows tips help file 
  1390. void DrawTransparentBitmap(HDC hdc, HBITMAP hbmp, RECT *prc, COLORREF cTransparentColor)
  1391. {
  1392.     COLORREF cColor;
  1393.     BITMAP bm;
  1394.     HBITMAP hbmAndBack, hbmAndObject, hbmAndMem, hbmSave;
  1395.     HGDIOBJ hbmBackOld, hbmObjectOld, hbmMemOld, hbmSaveOld;
  1396.     HDC hdcMem, hdcBack, hdcObject, hdcTemp, hdcSave;
  1397.     POINT ptSize;
  1398.     int d;
  1399.     hdcTemp = CreateCompatibleDC(hdc);
  1400.     SelectObject(hdcTemp, (HGDIOBJ)hbmp);   // Select the bitmap
  1401.     GetObject(hbmp, sizeof(BITMAP), &bm);
  1402.     ptSize.x = bm.bmWidth;            // Get width of bitmap
  1403.     ptSize.y = bm.bmHeight;           // Get height of bitmap
  1404.     DPtoLP(hdcTemp, &ptSize, 1);      // Convert from device to logical points
  1405.     d = prc->right - prc->left;
  1406.     if (d < ptSize.x)
  1407.         ptSize.x = d;
  1408.     d = prc->bottom - prc->top;
  1409.     if (d < ptSize.y)
  1410.         ptSize.y = d;
  1411.     // Create some DCs to hold temporary data.
  1412.     hdcBack = CreateCompatibleDC(hdc);
  1413.     hdcObject = CreateCompatibleDC(hdc);
  1414.     hdcMem = CreateCompatibleDC(hdc);
  1415.     hdcSave = CreateCompatibleDC(hdc);
  1416.     // Create a bitmap for each DC. DCs are required for a number of
  1417.     // GDI functions.
  1418.     // Monochrome DC
  1419.     hbmAndBack = CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
  1420.     // Monochrome DC
  1421.     hbmAndObject = CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
  1422.     hbmAndMem = CreateCompatibleBitmap(hdc, ptSize.x, ptSize.y);
  1423.     hbmSave = CreateCompatibleBitmap(hdc, ptSize.x, ptSize.y);
  1424.     // Each DC must select a bitmap object to store pixel data.
  1425.     hbmBackOld = SelectObject(hdcBack, (HGDIOBJ)hbmAndBack);
  1426.     hbmObjectOld = SelectObject(hdcObject, (HGDIOBJ)hbmAndObject);
  1427.     hbmMemOld = SelectObject(hdcMem, (HGDIOBJ)hbmAndMem);
  1428.     hbmSaveOld = SelectObject(hdcSave, (HGDIOBJ)hbmSave);
  1429.     // Set proper mapping mode.
  1430.     SetMapMode(hdcTemp, GetMapMode(hdc));
  1431.     // Save the bitmap sent here, because it will be overwritten.
  1432.     BitBlt(hdcSave, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, SRCCOPY);
  1433.     // Set the background color of the source DC to the color.
  1434.     // contained in the parts of the bitmap that should be transparent
  1435.     cColor = SetBkColor(hdcTemp, cTransparentColor);
  1436.     // Create the object mask for the bitmap by performing a BitBlt
  1437.     // from the source bitmap to a monochrome bitmap.
  1438.     BitBlt(hdcObject, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, SRCCOPY);
  1439.     // Set the background color of the source DC back to the original
  1440.     // color.
  1441.     SetBkColor(hdcTemp, cColor);
  1442.     // Create the inverse of the object mask.
  1443.     BitBlt(hdcBack, 0, 0, ptSize.x, ptSize.y, hdcObject, 0, 0, NOTSRCCOPY);
  1444.     // Copy the background of the main DC to the destination.
  1445.     BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdc, prc->left, prc->top, SRCCOPY);
  1446.     // Mask out the places where the bitmap will be placed.
  1447.     BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdcObject, 0, 0, SRCAND);
  1448.     // Mask out the transparent colored pixels on the bitmap.
  1449.     BitBlt(hdcTemp, 0, 0, ptSize.x, ptSize.y, hdcBack, 0, 0, SRCAND);
  1450.     // XOR the bitmap with the background on the destination DC.
  1451.     BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, SRCPAINT);
  1452.     // Copy the destination to the screen.
  1453.     BitBlt(hdc, prc->left, prc->top, ptSize.x, ptSize.y, hdcMem, 0, 0, SRCCOPY);
  1454.     // Place the original bitmap back into the bitmap sent here.
  1455.     BitBlt(hdcTemp, 0, 0, ptSize.x, ptSize.y, hdcSave, 0, 0, SRCCOPY);
  1456.     // Delete the memory bitmaps.
  1457.     SelectObject(hdcBack, hbmBackOld);
  1458.     DeleteObject(hbmAndBack);
  1459.     SelectObject(hdcObject, hbmObjectOld);
  1460.     DeleteObject(hbmAndObject);
  1461.     SelectObject(hdcMem, hbmMemOld);
  1462.     DeleteObject(hbmAndMem);
  1463.     SelectObject(hdcSave, hbmSaveOld);
  1464.     DeleteObject(hbmSave);
  1465.     // Delete the memory DCs.
  1466.     DeleteDC(hdcMem);
  1467.     DeleteDC(hdcBack);
  1468.     DeleteDC(hdcObject);
  1469.     DeleteDC(hdcSave);
  1470.     DeleteDC(hdcTemp);
  1471. }
  1472. #endif      // DEAD CODE
  1473. void MCDrawTodayCircle(MONTHCAL *pmc, HDC hdc, RECT *prc)
  1474. {
  1475.     HGDIOBJ hpenOld;
  1476.     int xBegin, yBegin, yEnd;
  1477.     xBegin = (prc->right - prc->left) / 2 + prc->left;
  1478.     yBegin = prc->top + 4;
  1479.     yEnd = (prc->bottom - prc->top) / 2 + prc->top;
  1480.     hpenOld = SelectObject(hdc, (HGDIOBJ)pmc->hpenToday);
  1481.     Arc(hdc, prc->left + 1, yBegin, prc->right, prc->bottom,
  1482.         xBegin, yBegin, prc->right, yEnd); 
  1483.     Arc(hdc, prc->left - 10, prc->top + 1, prc->right, prc->bottom,
  1484.         prc->right, yEnd, prc->left + 3, yBegin);
  1485.     SelectObject(hdc, hpenOld);
  1486. }
  1487. void MCInvalidateMonthDays(MONTHCAL *pmc)
  1488. {
  1489.     InvalidateRect(pmc->ci.hwnd, &pmc->rcCentered, FALSE);
  1490. }
  1491. void MCGetTodayBtnRect(MONTHCAL *pmc, RECT *prc)
  1492. {    
  1493.     if (pmc->dxToday > pmc->rcCentered.right - pmc->rcCentered.left)
  1494.     {
  1495.         prc->left   = pmc->rc.left + 1;
  1496.         prc->right  = pmc->rc.right - 1;
  1497.     }
  1498.     else
  1499.     {
  1500.         prc->left   = pmc->rcCentered.left + 1;
  1501.         prc->right  = pmc->rcCentered.right - 1;
  1502.     }
  1503.     prc->top    = pmc->rcCentered.bottom - pmc->dyToday;
  1504.     prc->bottom = pmc->rcCentered.bottom;
  1505.     // center the today rect when we only have 1 col and it will fit in window
  1506.     if ((pmc->nViewCols == 1) && (pmc->dxToday <= pmc->rc.right - pmc->rc.left))
  1507.     {
  1508.         int dx =  ((pmc->rcCentered.right - pmc->rcCentered.left) - pmc->dxToday) / 2 - 1;
  1509.         prc->left   += dx;
  1510.         prc->right  -= dx;
  1511.     }
  1512. }
  1513. void MCPaintArrowBtn(MONTHCAL *pmc, HDC hdc, BOOL fPrev, BOOL fPressed)
  1514. {
  1515.     LPRECT prc;
  1516.     UINT   dfcs;
  1517.     BOOL   bMirrored = FALSE;
  1518.     // This is to work around DrawFrameControl() mirroring bug fixed on W2k (bld 2042 or higher)
  1519. #ifndef WINNT    
  1520.     bMirrored = IS_DC_RTL_MIRRORED(hdc);
  1521. #endif // WINNT    
  1522.     if (fPrev)
  1523.     {
  1524.         if(bMirrored)
  1525.         {
  1526.             dfcs = DFCS_SCROLLRIGHT;        
  1527.         }
  1528.         else
  1529.         {
  1530.             dfcs = DFCS_SCROLLLEFT;        
  1531.         }
  1532.         prc  = &pmc->rcPrev;
  1533.     }
  1534.     else
  1535.     {
  1536.         if(bMirrored)
  1537.         {
  1538.             dfcs = DFCS_SCROLLLEFT;        
  1539.         }
  1540.         else
  1541.         {
  1542.             dfcs = DFCS_SCROLLRIGHT;        
  1543.         }
  1544.         prc  = &pmc->rcNext;
  1545.     }
  1546.     if (pmc->fEnabled)
  1547.     {
  1548.         if (fPressed)
  1549.         {
  1550.             dfcs |= DFCS_PUSHED | DFCS_FLAT;
  1551.         }
  1552.     }
  1553.     else
  1554.     {
  1555.         dfcs |= DFCS_INACTIVE;
  1556.     }
  1557.     DrawFrameControl(hdc, prc, DFC_SCROLL, dfcs);
  1558. }
  1559. void MCPaint(MONTHCAL *pmc, HDC hdc)
  1560. {
  1561.     RECT    rc, rcT;
  1562.     int     irow, icol, iMonth, iYear, iIndex, dx, dy;
  1563.     HBRUSH  hbrSelect;
  1564.     HGDIOBJ hgdiOrig, hpenOrig;
  1565.     pmc->hpen = CreatePen(PS_SOLID, 0, pmc->clr[MCSC_TEXT]);
  1566.     hbrSelect = CreateSolidBrush(pmc->clr[MCSC_TITLEBK]);
  1567.     SetBkMode(hdc, TRANSPARENT);
  1568.     SetTextColor(hdc, pmc->clr[MCSC_TEXT]);
  1569.     hpenOrig = SelectObject(hdc, GetStockObject(BLACK_PEN));
  1570.     rc = pmc->rcCentered;
  1571.     FillRectClr(hdc, &rc, pmc->clr[MCSC_MONTHBK]);
  1572.     SelectObject(hdc, (HGDIOBJ)pmc->hpen);
  1573.     // get the place for top left month
  1574.     rc.left   = pmc->rcCentered.left;
  1575.     rc.right  = rc.left + pmc->dxMonth;
  1576.     rc.top    = pmc->rcCentered.top;
  1577.     rc.bottom = rc.top + pmc->dyMonth;
  1578.     iMonth = pmc->stMonthFirst.wMonth;
  1579.     iYear  = pmc->stMonthFirst.wYear;
  1580.     dx = pmc->dxMonth + CALBORDER;
  1581.     dy = pmc->dyMonth + CALBORDER;
  1582.     iIndex = 0;
  1583.     for (irow = 0; irow < pmc->nViewRows; irow++)
  1584.     {
  1585.         rcT = rc;
  1586.         for (icol = 0; icol < pmc->nViewCols; icol++)
  1587.         {                
  1588.             if (RectVisible(hdc, &rcT))
  1589.             {
  1590.                 MCPaintMonth(pmc, hdc, &rcT, iMonth, iYear, iIndex,
  1591.                     iIndex == 0, 
  1592.                     iIndex == (pmc->nMonths - 1), hbrSelect);
  1593.             }
  1594.             rcT.left  += dx;
  1595.             rcT.right += dx;
  1596.             if (++iMonth > 12)
  1597.             {
  1598.                 iMonth = 1;
  1599.                 iYear++;
  1600.             }
  1601.             iIndex++;
  1602.         }
  1603.         rc.top    += dy;
  1604.         rc.bottom += dy;
  1605.     }
  1606.     // draw the today stuff
  1607.     if (MonthCal_ShowToday(pmc))
  1608.     {
  1609.         MCGetTodayBtnRect(pmc, &rc);
  1610.         if (RectVisible(hdc, &rc))
  1611.         {
  1612.             TCHAR   szDateFmt[32];
  1613.             TCHAR   szBuf[64];
  1614.             rcT.right = rc.left + 2; // a bit extra border space
  1615.     
  1616.             if (MonthCal_ShowTodayCircle(pmc)) // this turns on/off the red circle
  1617.             {
  1618.                 rcT.left   = rcT.right + 2;
  1619.                 rcT.right  = rcT.left + pmc->dxCol - 2;
  1620.                 rcT.top    = rc.top + 2;
  1621.                 rcT.bottom = rc.bottom - 2;
  1622.                 MCDrawTodayCircle(pmc, hdc, &rcT);
  1623.             }
  1624.     
  1625.             rcT.left   = rcT.right + 2;
  1626.             rcT.right  = rc.right - 2;
  1627.             rcT.top    = rc.top;
  1628.             rcT.bottom = rc.bottom;
  1629.             hgdiOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
  1630.             SetTextColor(hdc, pmc->clr[MCSC_TEXT]);
  1631.     
  1632.             GetDateFormat(pmc->ct.lcid, DATE_SHORTDATE, &pmc->stToday,
  1633.                             NULL, szDateFmt, sizeof(szDateFmt));
  1634.             wsprintf(szBuf, TEXT("%s %s"), pmc->li.szToday, szDateFmt);
  1635.             DrawText(hdc, szBuf, lstrlen(szBuf), &rcT,
  1636.                         DT_LEFT | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
  1637.             
  1638.             SelectObject(hdc, hgdiOrig);
  1639.         }
  1640.     }
  1641.     // Draw the spin buttons
  1642.     if (RectVisible(hdc, &pmc->rcPrev))
  1643.         MCPaintArrowBtn(pmc, hdc, TRUE, (pmc->idTimer && pmc->fSpinPrev));
  1644.     if (RectVisible(hdc, &pmc->rcNext))
  1645.         MCPaintArrowBtn(pmc, hdc, FALSE, (pmc->idTimer && !pmc->fSpinPrev));
  1646.     SelectObject(hdc, hpenOrig);
  1647.     DeleteObject((HGDIOBJ)hbrSelect);
  1648.     DeleteObject((HGDIOBJ)pmc->hpen);
  1649. }
  1650. //
  1651. //  MCGetMonthFormat gets the string to display for the month/year
  1652. //  in the passed-in SYSTEMTIME.  This is tricky because of eras.
  1653. //  If pmm is non-NULL, it receives the metrics of the
  1654. //  formatted month/year string.
  1655. //
  1656. void MCGetMonthFormat(MONTHCAL *pmc, SYSTEMTIME *pst, LPTSTR rgch, UINT cch, PMONTHMETRICS pmm)
  1657. {
  1658.     // For all months, we display the name appropriate to the first
  1659.     // day of the month.  Note that this means that the title of the
  1660.     // month in which the era changes may be confusing.  If the era
  1661.     // changes in the middle of a month, we name the month after the
  1662.     // previous era, even if the current selection belongs to the next
  1663.     // era.  I hope nobody will mind.
  1664. #if 0 // code that tried to track the era based on where the selection is
  1665.       // but before you can turn this on, you have to find everybody who
  1666.       // changes the selection, and that's hard because the monthcal control
  1667.       // doesn't have a centralizesd selection changer; people just party on
  1668.       // the selection directly
  1669.     if (pst->wMonth == pmc->st.wMonth &&
  1670.         pst->wYear == pmc->st.wYear) {
  1671.         pst->wDay = pmc->st.wDay;
  1672.     } else {
  1673.         pst->wDay = 1;
  1674.     }
  1675. #else
  1676.     pst->wDay = 1;
  1677. #endif
  1678.     //
  1679.     //  Get the string (all marked up), then extract the markers
  1680.     //  to locate the month and year substrings.
  1681.     //
  1682.     rgch[0] = TEXT('');       // In case something horrible happens
  1683.     GetDateFormat(pmc->ct.lcid, 0, pst,
  1684.                   pmc->li.szMonthYearFmt,
  1685.                   rgch, cch);
  1686.     MCRemoveMarkers(rgch, pmm);
  1687. }
  1688. void MCPaintMonth(MONTHCAL *pmc, HDC hdc, RECT *prc, int iMonth, int iYear, int iIndex,
  1689.                     BOOL fDrawPrev, BOOL fDrawNext, HBRUSH hbrSelect)
  1690. {
  1691.     BOOL fBold, fView, fReset;
  1692.     RECT rc, rcT;
  1693.     int nDay, cdy, irow, icol, crowShow, nweek, isel;
  1694.     TCHAR rgch[64];
  1695.     LPTSTR psz;
  1696.     HGDIOBJ hfontOrig, hbrushOld;
  1697.     COLORREF clrGrayText, clrHiliteText, clrOld, clrText;
  1698.     SYSTEMTIME st = {0};
  1699.     int iIndexSave = iIndex;
  1700.     clrText       = pmc->clr[MCSC_TEXT];
  1701.     clrGrayText   = pmc->clr[MCSC_TRAILINGTEXT];
  1702.     clrHiliteText = pmc->clr[MCSC_TITLETEXT];
  1703.     hfontOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfont);
  1704.     SelectObject(hdc, (HGDIOBJ)pmc->hpen);
  1705.     //
  1706.     // Draw the Month and Year
  1707.     //
  1708.     // translate the relative coords to window coords 
  1709.     rc = pmc->rcMonthName;
  1710.     rc.left   += prc->left;
  1711.     rc.right  += prc->left;
  1712.     rc.top    += prc->top;
  1713.     rc.bottom += prc->top;
  1714.     if (RectVisible(hdc, &rc))
  1715.     {
  1716.         FillRectClr(hdc, &rc, pmc->clr[MCSC_TITLEBK]);
  1717.         SetTextColor(hdc, pmc->clr[MCSC_TITLETEXT]);
  1718.         SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
  1719.         st.wYear = (WORD) iYear;
  1720.         st.wMonth = (WORD) iMonth;
  1721.         MCGetMonthFormat(pmc, &st, rgch, ARRAYSIZE(rgch), NULL);
  1722.         DrawText(hdc, rgch, lstrlen(rgch), &rc, DT_CENTER | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
  1723. #ifdef MARKER_DEBUG
  1724.         //
  1725.         //  When debugging MCInsertMarker and MCRemoveMarker, draw colored
  1726.         //  bars where we think the markers were.
  1727.         //
  1728.         { RECT rcT = rc;
  1729.             rcT.top = rcT.bottom - 2;
  1730.             rcT.left  = rc.left + pmc->rgmm[iIndex].rgi[IMM_MONTHSTART];
  1731.             rcT.right = rc.left + pmc->rgmm[iIndex].rgi[IMM_MONTHEND];
  1732.             FillRectClr(hdc, &rcT, RGB(0xFF, 0, 0));
  1733.             rcT.left  = rc.left + pmc->rgmm[iIndex].rgi[IMM_YEARSTART];
  1734.             rcT.right = rc.left + pmc->rgmm[iIndex].rgi[IMM_YEAREND];
  1735.             FillRectClr(hdc, &rcT, RGB(0, 0xFF, 0));
  1736.         }
  1737. #endif
  1738.         SelectObject(hdc, (HGDIOBJ)pmc->hfont);
  1739.     }
  1740.     SetTextColor(hdc, pmc->clr[MCSC_TITLEBK]);
  1741.     //
  1742.     // Draw the days of the month
  1743.     //
  1744.     // translate the relative coords to window coords 
  1745.     rc = pmc->rcDow;
  1746.     rc.left   += prc->left;
  1747.     rc.right  += prc->left;
  1748.     rc.top    += prc->top;
  1749.     rc.bottom += prc->top;
  1750.     if (RectVisible(hdc, &rc))
  1751.     {
  1752.         MoveToEx(hdc, rc.left + 4, rc.bottom - 1, NULL);
  1753.         LineTo(hdc, rc.right - 4, rc.bottom - 1);
  1754.         rc.right = rc.left + pmc->dxCol;
  1755.         for (icol = 0; icol < CALCOLMAX; icol++)
  1756.         {
  1757.             psz = pmc->li.rgszDay[(icol + pmc->li.dowStartWeek) % 7];
  1758.             DrawText(hdc, psz, lstrlen(psz), &rc, DT_CENTER | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
  1759.             rc.left  += pmc->dxCol;
  1760.             rc.right += pmc->dxCol;
  1761.         }
  1762.     }
  1763.     
  1764.     // Check to see how many days from the previous month exist in this months calendar
  1765.     nDay = pmc->rgnDayUL[iIndex];   // last day in prev month that won't be shown in this month
  1766.     cdy  = pmc->rgcDay[iIndex];     // # of days in prev month
  1767.     // Calculate the number of weeks to display
  1768.     if (fDrawNext)
  1769.         crowShow = CALROWMAX;
  1770.     else
  1771.         crowShow = ((cdy - nDay) + pmc->rgcDay[iIndex + 1] + 6/* round up */) / 7;
  1772.     if (nDay != cdy)
  1773.     {
  1774.         // start at previous month
  1775.         iMonth--;
  1776.         if(iMonth <= 0)
  1777.         {
  1778.             iMonth = 12;
  1779.             iYear--;
  1780.         }
  1781.         nDay++;
  1782.         fView = FALSE;
  1783.     }
  1784.     else
  1785.     {
  1786.         // start at this month
  1787.         iIndex++;                   // this month
  1788.         nDay = 1;
  1789.         cdy = pmc->rgcDay[iIndex];
  1790.         fView = TRUE;
  1791.     }
  1792.     //
  1793.     // Draw the week numbers
  1794.     //
  1795.     if (MonthCal_ShowWeekNumbers(pmc))
  1796.     {
  1797.         // translate the relative coords to window coords 
  1798.         rc = pmc->rcWeekNum;
  1799.         rc.left   += prc->left;
  1800.         rc.top    += prc->top;
  1801.         rc.right  += prc->left;
  1802.         rc.bottom = rc.top + (pmc->dyRow * crowShow);
  1803.         // draw the week numbers
  1804.         if (RectVisible(hdc, &rc))
  1805.         {
  1806.             MoveToEx(hdc, rc.right - 1, rc.top + 4, NULL);
  1807.             LineTo(hdc, rc.right - 1, rc.bottom - 4);
  1808.             st.wYear  = (WORD) iYear;
  1809.             st.wMonth = (WORD) iMonth;
  1810.             st.wDay   = (WORD) nDay;
  1811.             nweek = GetWeekNumber(&st, pmc->li.dowStartWeek, pmc->li.firstWeek);
  1812.             rc.bottom = rc.top + pmc->dyRow;
  1813.             for (irow = 0; irow < crowShow; irow++)
  1814.             {
  1815.                 wsprintf(rgch, g_szNumFmt, nweek);
  1816.                 DrawText(hdc, rgch, (nweek > 9 ? 2 : 1), &rc,
  1817.                         DT_CENTER | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
  1818.                 
  1819.                 rc.top    += pmc->dyRow;
  1820.                 rc.bottom += pmc->dyRow;
  1821.                 IncrSystemTime(&st, &st, 1, INCRSYS_WEEK);
  1822.                 nweek = GetWeekNumber(&st, pmc->li.dowStartWeek, pmc->li.firstWeek);
  1823.             }
  1824.         }
  1825.     }
  1826.     if (!fView)
  1827.         SetTextColor(hdc, clrGrayText);
  1828.     else
  1829.         SetTextColor(hdc, clrText);
  1830.     rc = pmc->rcDayNum;
  1831.     rc.left   += prc->left;
  1832.     rc.top    += prc->top;
  1833.     rc.right  =  rc.left + pmc->dxCol;
  1834.     rc.bottom =  rc.top  + pmc->dyRow;
  1835.     fReset = FALSE;
  1836.     fBold  = FALSE;
  1837.     for (irow = 0; irow < crowShow; irow++)
  1838.     {
  1839.         rcT = rc;
  1840.         for (icol = 0; icol < CALCOLMAX; icol++)
  1841.         {
  1842.             if ((fView || fDrawPrev) && RectVisible(hdc, &rcT))
  1843.             {
  1844.                 wsprintf(rgch, g_szNumFmt, nDay);
  1845.                 if (MonthCal_IsDayState(pmc))
  1846.                 {
  1847.                     // if we're in a dropdown we don't display 
  1848.                     if (MCIsBoldOffsetDay(pmc, nDay, iIndex))
  1849.                     {
  1850.                         if (!fBold)
  1851.                         {
  1852.                             SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
  1853.                             fBold = TRUE;
  1854.                         }
  1855.                     }
  1856.                     else
  1857.                     {
  1858.                         if (fBold)
  1859.                         {
  1860.                             SelectObject(hdc, (HGDIOBJ)pmc->hfont);
  1861.                             fBold = FALSE;
  1862.                         }
  1863.                     }
  1864.                 }
  1865.                 if (isel = MCIsSelectedDayMoYr(pmc, nDay, iMonth, iYear))
  1866.                 {
  1867.                     int x1, x2;
  1868.                     clrOld    = SetTextColor(hdc, clrHiliteText);
  1869.                     hbrushOld = SelectObject(hdc, (HGDIOBJ)hbrSelect);
  1870.                     fReset    = TRUE;
  1871.                     SelectObject(hdc, GetStockObject(NULL_PEN));
  1872.                      
  1873.                     x1 = 0;
  1874.                     x2 = 0;
  1875.                     if (isel & SEL_DOT)
  1876.                     {
  1877.                         Ellipse(hdc, rcT.left + 2, rcT.top + 2, rcT.right - 1, rcT.bottom - 1);
  1878.                         if (isel == SEL_BEGIN)
  1879.                         {
  1880.                             x1 = rcT.left + (rcT.right - rcT.left) / 2;
  1881.                             x2 = rcT.right;
  1882.                         }
  1883.                         else if (isel == SEL_END)
  1884.                         {
  1885.                             x1 = rcT.left;
  1886.                             x2 = rcT.left + (rcT.right - rcT.left) / 2;
  1887.                         }
  1888.                     }
  1889.                     else
  1890.                     {
  1891.                         x1 = rcT.left;
  1892.                         x2 = rcT.right;
  1893.                     }
  1894.                     if (x1 && x2)
  1895.                     {
  1896.                         Rectangle(hdc, x1, rcT.top + 2, x2 + 1, rcT.bottom - 1);
  1897.                     }
  1898.                 }
  1899.                 DrawText(hdc, rgch, (nDay > 9 ? 2 : 1), &rcT,
  1900.                         DT_CENTER | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
  1901.                 if (MonthCal_ShowTodayCircle(pmc) && pmc->fToday && iIndexSave == pmc->iMonthToday &&
  1902.                     icol == pmc->iColToday && irow == pmc->iRowToday)
  1903.                 {
  1904.                     MCDrawTodayCircle(pmc, hdc, &rcT);
  1905.                 }
  1906.                 if (fReset)
  1907.                 {
  1908.                     SetTextColor(hdc, clrOld);
  1909.                     SelectObject(hdc, (HGDIOBJ)hbrushOld);
  1910.                     fReset = FALSE;
  1911.                 }
  1912.             }
  1913.             rcT.left  += pmc->dxCol;
  1914.             rcT.right += pmc->dxCol;
  1915.             nDay++;
  1916.             if (nDay > cdy)
  1917.             {
  1918.                 if (!fDrawNext && iIndex > iIndexSave)
  1919.                     goto doneMonth;
  1920.                 nDay = 1;
  1921.                 iIndex++;
  1922.                 cdy = pmc->rgcDay[iIndex];
  1923.                 iMonth++;
  1924.                 if (iMonth > 12)
  1925.                 {
  1926.                     iMonth = 1;
  1927.                     iYear++;
  1928.                 }
  1929.                 fView = !fView;
  1930.                 SetTextColor(hdc, fView ? clrText : clrGrayText);
  1931.                 fDrawPrev = fDrawNext;
  1932.             }
  1933.         }
  1934.         rc.top    += pmc->dyRow;
  1935.         rc.bottom += pmc->dyRow;
  1936.     }
  1937. doneMonth:
  1938.     SelectObject(hdc, hfontOrig);
  1939.     return;
  1940. }
  1941. int MCIsSelectedDayMoYr(MONTHCAL *pmc, int iDay, int iMonth, int iYear)
  1942. {
  1943.     SYSTEMTIME st;
  1944.     int iBegin, iEnd;
  1945.     int iret = 0;
  1946.     st.wYear  = (WORD) iYear;
  1947.     st.wMonth = (WORD) iMonth;
  1948.     st.wDay   = (WORD) iDay;
  1949.     iBegin = CmpDate(&st, &pmc->st);
  1950.     if (MonthCal_IsMultiSelect(pmc))
  1951.     {
  1952.         iEnd = CmpDate(&st, &pmc->stEndSel);
  1953.         if (iBegin > 0 && iEnd< 0)
  1954.             iret = SEL_MID;
  1955.         else
  1956.         {
  1957.             if (iBegin == 0)
  1958.                 iret |= SEL_BEGIN;
  1959.         
  1960.             if (iEnd == 0)
  1961.                 iret |= SEL_END;
  1962.         }
  1963.     }
  1964.     else if (iBegin == 0)
  1965.     {
  1966.         iret = SEL_DOT;
  1967.     }
  1968.     return(iret);
  1969. }
  1970. BOOL MCIsBoldOffsetDay(MONTHCAL *pmc, int nDay, int iIndex)
  1971. {
  1972.     return(pmc->rgdayState && (pmc->rgdayState[iIndex] & (1L << (nDay - 1))) != 0);
  1973. }
  1974. void MCNcDestroyHandler(HWND hwnd, MONTHCAL *pmc, WPARAM wParam, LPARAM lParam)
  1975. {
  1976.     if (pmc)
  1977.     {
  1978.         if (pmc->hpenToday)
  1979.             DeleteObject((HGDIOBJ)pmc->hpenToday);
  1980.         if (pmc->hfontBold)
  1981.             DeleteObject((HGDIOBJ)pmc->hfontBold);
  1982.         
  1983.         if (pmc->hmenuCtxt)
  1984.             DestroyMenu(pmc->hmenuCtxt);
  1985.         if (pmc->hmenuMonth)
  1986.             DestroyMenu(pmc->hmenuMonth);
  1987.         if (pmc->idTimer)
  1988.             KillTimer(pmc->ci.hwnd, pmc->idTimer);
  1989.         if (pmc->idTimerToday)
  1990.             KillTimer(pmc->ci.hwnd, pmc->idTimerToday);
  1991.         MCFreeCalendarInfo(&pmc->ct);
  1992.         GlobalFreePtr(pmc);
  1993.     }
  1994.     // In case rogue messages float through after we have freed the pdtpick, set
  1995.     // the handle in the window structure to FFFF and test for this value at 
  1996.     // the top of the WndProc 
  1997.     MonthCal_SetPtr(hwnd, NULL);
  1998.     // Call DefWindowProc32 to free all little chunks of memory such as szName 
  1999.     // and rgwScroll.  
  2000.     DefWindowProc(hwnd, WM_NCDESTROY, wParam, lParam);
  2001. }
  2002. /* Computes the following:
  2003.  *  nViewCols
  2004.  *  nViewRows
  2005.  *  rcCentered
  2006.  *  rcPrev
  2007.  *  rcNext
  2008.  */
  2009. void MCRecomputeSizing(MONTHCAL *pmc, RECT *prect)
  2010. {
  2011.     RECT rc;
  2012.     int dx, dy, dCal;
  2013.     // Space for entire calendar
  2014.     pmc->rc = *prect;
  2015.     dx = prect->right  - prect->left;
  2016.     dy = prect->bottom - prect->top;
  2017.     pmc->nViewCols = 1 + (dx - pmc->dxMonth) / (pmc->dxMonth + CALBORDER);
  2018.     pmc->nViewRows = 1 + (dy - pmc->dyMonth - pmc->dyToday) / (pmc->dyMonth + CALBORDER);
  2019.     // if dx < dxMonth or dy < dyMonth, these can be zero. That's bad...
  2020.     if (pmc->nViewCols < 1)
  2021.         pmc->nViewCols = 1;
  2022.     if (pmc->nViewRows < 1)
  2023.         pmc->nViewRows = 1;
  2024.     // Make sure we don't display more than CALMONTHMAX months
  2025.     while ((pmc->nViewRows * pmc->nViewCols) > CALMONTHMAX)
  2026.     {
  2027.         if (pmc->nViewRows > pmc->nViewCols)
  2028.             pmc->nViewRows--;
  2029.         else
  2030.             pmc->nViewCols--;
  2031.     }
  2032.     // RC for the months, centered within the client window
  2033.     dCal = pmc->nViewCols * (pmc->dxMonth + CALBORDER) - CALBORDER;
  2034.     pmc->rcCentered.left = (dx - dCal) / 2;
  2035.     if (pmc->rcCentered.left < 0)
  2036.         pmc->rcCentered.left = 0;
  2037.     pmc->rcCentered.right = pmc->rcCentered.left + dCal;
  2038.     dCal = pmc->nViewRows * (pmc->dyMonth + CALBORDER) - CALBORDER + pmc->dyToday;
  2039.     pmc->rcCentered.top = (dy - dCal) / 2;
  2040.     if (pmc->rcCentered.top < 0)
  2041.         pmc->rcCentered.top = 0;
  2042.     pmc->rcCentered.bottom = pmc->rcCentered.top + dCal;
  2043.     // Calculate and set RCs for the spin buttons
  2044.     rc.top    = pmc->rcCentered.top + (pmc->dyRow * 2 - pmc->dyCalArrow) /2;
  2045.     rc.bottom = rc.top + pmc->dyCalArrow;
  2046.     rc.left  = pmc->rcCentered.left + pmc->dxArrowMargin;
  2047.     rc.right = rc.left + pmc->dxCalArrow;
  2048.     pmc->rcPrev = rc;
  2049.     rc.right = pmc->rcCentered.right - pmc->dxArrowMargin;
  2050.     rc.left  = rc.right - pmc->dxCalArrow;
  2051.     pmc->rcNext = rc;
  2052. }
  2053. LRESULT MCSizeHandler(MONTHCAL *pmc, RECT *prc)
  2054. {
  2055.     int nMax;
  2056.     SYSTEMTIME st;
  2057.     int cmo, dmo;
  2058.     MCRecomputeSizing(pmc, prc);
  2059.     nMax = pmc->nViewRows * pmc->nViewCols;
  2060.     
  2061.     // Compute new start date
  2062.     CopyDate(pmc->stMonthFirst, st);
  2063.     // BUGBUG: this doesn't consider stEndSel
  2064.     cmo = (pmc->stMonthLast.wYear - (int)pmc->st.wYear) * 12 +
  2065.         (pmc->stMonthLast.wMonth - (int)pmc->st.wMonth);
  2066.     dmo = nMax - pmc->nMonths;
  2067.     if (-dmo > cmo)
  2068.     {
  2069.         // Selected mon/yr not in view
  2070.         IncrSystemTime(&st, &st, -(cmo + dmo), INCRSYS_MONTH);
  2071.         cmo = 0;
  2072.     }
  2073.     // If the # of months being displayed has changed, then lets try to
  2074.     // start the calendar from January.
  2075.     if ((dmo != 0) && (cmo + dmo >= pmc->stMonthFirst.wMonth - 1))
  2076.         st.wMonth = 1;
  2077.     MCUpdateStartEndDates(pmc, &st);
  2078.     InvalidateRect(pmc->ci.hwnd, NULL, TRUE);
  2079.     UpdateWindow(pmc->ci.hwnd);
  2080.     return(0);
  2081. }
  2082. //
  2083. //  For each month being displayed, compute the precise locations of all
  2084. //  the gizmos we draw into the month header area.
  2085. //
  2086. void MCUpdateMonthNamePos(MONTHCAL *pmc)
  2087. {
  2088.     HDC hdc;
  2089.     int iCount;
  2090.     SYSTEMTIME st;
  2091.     TCHAR rgch[64];
  2092.     SIZE size;
  2093.     HGDIOBJ hfontOrig;
  2094.     hdc = GetDC(pmc->ci.hwnd);
  2095.     hfontOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
  2096.     st = pmc->stMonthFirst;
  2097.     for (iCount = 0; iCount < pmc->nMonths; iCount++)
  2098.     {
  2099.         PMONTHMETRICS pmm = &pmc->rgmm[iCount];
  2100.         int i;
  2101.         MCGetMonthFormat(pmc, &st, rgch, ARRAYSIZE(rgch), pmm);
  2102.         GetTextExtentPoint32(hdc, rgch, lstrlen(rgch), &size);
  2103.         pmm->rgi[IMM_START] = (pmc->dxMonth - size.cx) / 2;
  2104.         //
  2105.         //  Now convert the indices into pixels so we can figure out where
  2106.         //  all the strings ended up.
  2107.         //
  2108.         for (i = IMM_DATEFIRST; i <= IMM_DATELAST; i++) {
  2109.             SIZE sizeT;
  2110.             // In case of horrible error, pretend the marker was at the
  2111.             // beginning of the string.
  2112.             sizeT.cx = 0;
  2113.             GetTextExtentPoint32(hdc, rgch, pmm->rgi[i], &sizeT);
  2114.             pmm->rgi[i] = pmm->rgi[IMM_START] + sizeT.cx;
  2115.         }
  2116.         //
  2117.         //  Now flip the coordinates for RTL.
  2118.         //
  2119.         if (pmc->fHeaderRTL || IS_WINDOW_RTL_MIRRORED(pmc->ci.hwnd))
  2120.         {
  2121.             int dxStart, dxEnd;
  2122.             // Flip the month...
  2123.             dxStart = pmm->rgi[IMM_MONTHSTART] - pmm->rgi[IMM_START];
  2124.             dxEnd   = pmm->rgi[IMM_MONTHEND  ] - pmm->rgi[IMM_START];
  2125.             pmm->rgi[IMM_MONTHSTART] = pmm->rgi[IMM_START] + size.cx - dxEnd;
  2126.             pmm->rgi[IMM_MONTHEND  ] = pmm->rgi[IMM_START] + size.cx - dxStart;
  2127.             // Flip the year...
  2128.             dxStart = pmm->rgi[IMM_YEARSTART] - pmm->rgi[IMM_START];
  2129.             dxEnd   = pmm->rgi[IMM_YEAREND  ] - pmm->rgi[IMM_START];
  2130.             pmm->rgi[IMM_YEARSTART] = pmm->rgi[IMM_START] + size.cx - dxEnd;
  2131.             pmm->rgi[IMM_YEAREND  ] = pmm->rgi[IMM_START] + size.cx - dxStart;
  2132.         }
  2133.         //  On to the next month
  2134.         if(++st.wMonth > 12)
  2135.         {
  2136.             st.wMonth = 1;
  2137.             st.wYear++;
  2138.         }
  2139.     }
  2140.     SelectObject(hdc, hfontOrig);
  2141.     ReleaseDC(pmc->ci.hwnd, hdc);
  2142. }
  2143. /*
  2144.  * Computes the following, given the number of rows & columns available:
  2145.  *        stMonthFirst.wMonth
  2146.  *        stMonthFirst.wYear
  2147.  *        stMonthLast.wMonth
  2148.  *        stMonthLast.wYear
  2149.  *        nMonths
  2150.  *
  2151.  * Trashes *pstStart
  2152.  */
  2153. void MCUpdateStartEndDates(MONTHCAL *pmc, SYSTEMTIME *pstStart)
  2154. {
  2155.     int iCount, iMonth, iYear;
  2156.     int nMonthsToEdge;
  2157.     pmc->nMonths = pmc->nViewRows * pmc->nViewCols;
  2158.     // make sure pstStart to pstStart+nMonths is within range
  2159.     nMonthsToEdge = ((int)pmc->stMax.wYear - (int)pstStart->wYear) * 12 +
  2160.                         ((int)pmc->stMax.wMonth - (int)pstStart->wMonth) + 1;
  2161.     if (nMonthsToEdge < pmc->nMonths)
  2162.         IncrSystemTime(pstStart, pstStart, nMonthsToEdge - pmc->nMonths, INCRSYS_MONTH);
  2163.     if (CmpDate(pstStart, &pmc->stMin) < 0)
  2164.     {
  2165.         CopyDate(pmc->stMin, *pstStart);
  2166.     }
  2167.     nMonthsToEdge = ((int)pmc->stMax.wYear - (int)pstStart->wYear) * 12 +
  2168.                         ((int)pmc->stMax.wMonth - (int)pstStart->wMonth) + 1;
  2169.     if (nMonthsToEdge < pmc->nMonths)
  2170.         pmc->nMonths = nMonthsToEdge;
  2171.     pmc->stMonthFirst.wYear  = pstStart->wYear;
  2172.     pmc->stMonthFirst.wMonth = pstStart->wMonth;
  2173.     pmc->stMonthFirst.wDay   = 1;
  2174.     if (CmpDate(&pmc->stMonthFirst, &pmc->stMin) < 0)
  2175.     {
  2176.         pmc->stMonthFirst.wDay = pmc->stMin.wDay;
  2177.         ASSERT(0==CmpDate(&pmc->stMonthFirst, &pmc->stMin));
  2178.     }
  2179.     // these ranges are CALMONTHMAX+2 and nMonths <= CALMONTHMAX, so we are safe
  2180.     // index 0 corresponds to stViewFirst (DAYSTATE) info
  2181.     // index 1..nMonths correspond to stMonthFirst..stMonthLast info
  2182.     // index nMonths+1 corresponds to stViewLast (DAYSTATE) info
  2183.     //
  2184.     iYear  = pmc->stMonthFirst.wYear;
  2185.     iMonth = pmc->stMonthFirst.wMonth - 1;
  2186.     if(iMonth == 0)
  2187.     {
  2188.         iMonth = 12;
  2189.         iYear--;
  2190.     }
  2191.     for (iCount = 0; iCount <= pmc->nMonths+1; iCount++)
  2192.     {
  2193.         int cdy, dow, ddow;
  2194.         // number of days in this month
  2195.         cdy = GetDaysForMonth(iYear, iMonth);
  2196.         pmc->rgcDay[iCount] = cdy;
  2197.         // move to "this" month
  2198.         if(++iMonth > 12)
  2199.         {
  2200.             iMonth = 1;
  2201.             iYear++;
  2202.         }
  2203.         // last day of this month NOT visible when viewing NEXT month
  2204.         dow = GetStartDowForMonth(iYear, iMonth);
  2205.         ddow = dow - pmc->li.dowStartWeek;
  2206.         if(ddow < 0)
  2207.             ddow += CALCOLMAX;
  2208.         pmc->rgnDayUL[iCount] = cdy  - ddow;
  2209.     }
  2210.     // we want to always have days visible on the previous month
  2211.     if (pmc->rgnDayUL[0] == pmc->rgcDay[0])
  2212.         pmc->rgnDayUL[0] -= CALCOLMAX;
  2213.     IncrSystemTime(&pmc->stMonthFirst, &pmc->stMonthLast, pmc->nMonths - 1, INCRSYS_MONTH);
  2214.     pmc->stMonthLast.wDay = (WORD) pmc->rgcDay[pmc->nMonths];
  2215.     if (pmc->fMaxYrSet && CmpDate(&pmc->stMonthLast, &pmc->stMax) > 0)
  2216.     {
  2217.         pmc->stMonthLast.wDay = pmc->stMax.wDay;
  2218.         ASSERT(0==CmpDate(&pmc->stMonthLast, &pmc->stMax));
  2219.     }
  2220.     
  2221.     pmc->stViewFirst.wYear  = pmc->stMonthFirst.wYear;
  2222.     pmc->stViewFirst.wMonth = pmc->stMonthFirst.wMonth - 1;
  2223.     if (pmc->stViewFirst.wMonth == 0)
  2224.     {
  2225.         pmc->stViewFirst.wMonth = 12;
  2226.         pmc->stViewFirst.wYear--;
  2227.     }
  2228.     pmc->stViewFirst.wDay = pmc->rgnDayUL[0] + 1;
  2229.     pmc->stViewLast.wYear  = pmc->stMonthLast.wYear;
  2230.     pmc->stViewLast.wMonth = pmc->stMonthLast.wMonth + 1;
  2231.     if (pmc->stViewLast.wMonth == 13)
  2232.     {
  2233.         pmc->stViewLast.wMonth = 1;
  2234.         pmc->stViewLast.wYear++;
  2235.     }
  2236.     // total days - (days in last month + remaining days in previous month)
  2237.     pmc->stViewLast.wDay = CALROWMAX * CALCOLMAX -
  2238.         (pmc->rgcDay[pmc->nMonths] +
  2239.          pmc->rgcDay[pmc->nMonths-1] - pmc->rgnDayUL[pmc->nMonths-1]);
  2240.     MCUpdateDayState(pmc);
  2241.     MCUpdateRcDayCur(pmc, &pmc->st);
  2242.     MCUpdateToday(pmc);
  2243.     MCUpdateMonthNamePos(pmc);
  2244. }
  2245. void MCUpdateToday(MONTHCAL *pmc)
  2246. {
  2247.     if (MonthCal_ShowTodayCircle(pmc))
  2248.     {
  2249.         int iMonth;
  2250.         iMonth = MCGetOffsetForYrMo(pmc, pmc->stToday.wYear, pmc->stToday.wMonth);
  2251.         if (iMonth < 0)
  2252.         {
  2253.             // today is not visible in the displayed months
  2254.             pmc->fToday = FALSE;
  2255.         }
  2256.         else
  2257.         {
  2258.             int iDay;
  2259.             // today is visible in the displayed months
  2260.             pmc->fToday = TRUE;
  2261.             
  2262.             iDay = pmc->rgcDay[iMonth] - pmc->rgnDayUL[iMonth] + pmc->stToday.wDay - 1;
  2263.     
  2264.             pmc->iMonthToday = iMonth;
  2265.             pmc->iRowToday   = iDay / CALCOLMAX;
  2266.             pmc->iColToday   = iDay % CALCOLMAX;
  2267.         }
  2268.     }
  2269. }
  2270. BOOL FUpdateRcDayCur(MONTHCAL *pmc, POINT pt)
  2271. {
  2272.     int iRow, iCol;
  2273.     RECT rc;
  2274.     SYSTEMTIME st;
  2275.     
  2276.     if (!FGetDateForPt(pmc, pt, &st, NULL, &iCol, &iRow, &rc))
  2277.         return FALSE;
  2278.     if (CmpDate(&st, &pmc->stMin) < 0)
  2279.         return FALSE;
  2280.     if (CmpDate(&st, &pmc->stMax) > 0)
  2281.         return FALSE;
  2282.     // calculate the day rc
  2283.     pmc->rcDayCur.left   = rc.left + pmc->rcDayNum.left + iCol * pmc->dxCol;
  2284.     pmc->rcDayCur.top    = rc.top + pmc->rcDayNum.top + iRow * pmc->dyRow;
  2285.     pmc->rcDayCur.right  = pmc->rcDayCur.left + pmc->dxCol;
  2286.     pmc->rcDayCur.bottom = pmc->rcDayCur.top + pmc->dyRow;
  2287.     return(TRUE);
  2288. }
  2289. void MCUpdateDayState(MONTHCAL *pmc)
  2290. {
  2291.     HWND hwndParent;
  2292.     if (!MonthCal_IsDayState(pmc))
  2293.         return;
  2294.     hwndParent = GetParent(pmc->ci.hwnd);
  2295.     if (hwndParent)
  2296.     {
  2297.         int i, mon, yr, cmonths;
  2298.         yr      = pmc->stViewFirst.wYear;
  2299.         mon     = pmc->stViewFirst.wMonth;
  2300.         cmonths = pmc->nMonths + 2;
  2301.         // don't do anything unless we need to
  2302.         if (cmonths != pmc->cds || mon != pmc->dsMonth || yr != pmc->dsYear)
  2303.         {
  2304.             // this is a small enough to not deal with allocating it
  2305.             NMDAYSTATE    nmds;
  2306.             MONTHDAYSTATE buffer[CALMONTHMAX+2];
  2307.             ZeroMemory(&nmds, sizeof(nmds));
  2308.             nmds.stStart.wYear  = (WORD) yr;
  2309.             nmds.stStart.wMonth = (WORD) mon;
  2310.             nmds.stStart.wDay   = 1;
  2311.             nmds.cDayState      = cmonths;
  2312.             nmds.prgDayState    = buffer;
  2313.             CCSendNotify(&pmc->ci, MCN_GETDAYSTATE, &nmds.nmhdr);
  2314.             for (i = 0; i < cmonths; i++)
  2315.                 pmc->rgdayState[i] = nmds.prgDayState[i];
  2316.             pmc->cds     = cmonths;
  2317.             pmc->dsMonth = mon;
  2318.             pmc->dsYear  = yr;
  2319.         }
  2320.     }
  2321. }
  2322. void MCNotifySelChange(MONTHCAL *pmc, UINT uMsg)
  2323. {
  2324.     HWND hwndParent;
  2325.     if (pmc->fNoNotify)
  2326.         return;
  2327.     hwndParent = GetParent(pmc->ci.hwnd);
  2328.     if (hwndParent)
  2329.     {
  2330.         NMSELCHANGE nmsc;
  2331.         ZeroMemory(&nmsc, sizeof(nmsc));
  2332.         CopyDate(pmc->st, nmsc.stSelStart);
  2333.         if (MonthCal_IsMultiSelect(pmc))
  2334.             CopyDate(pmc->stEndSel, nmsc.stSelEnd);
  2335.         CCSendNotify(&pmc->ci, uMsg, &nmsc.nmhdr);
  2336.     }
  2337. }
  2338. void MCUpdateRcDayCur(MONTHCAL *pmc, SYSTEMTIME *pst)
  2339. {
  2340.     int iOff;
  2341.     iOff = MCGetOffsetForYrMo(pmc, pst->wYear, pst->wMonth);
  2342.     if (iOff >= 0)
  2343.         MCGetRcForDay(pmc, iOff, pst->wDay, &pmc->rcDayCur);
  2344. }
  2345. // returns zero-based index into DISPLAYED months for month
  2346. // if month is not in DISPLAYED months, then -1 is returned...
  2347. int MCGetOffsetForYrMo(MONTHCAL *pmc, int iYear, int iMonth)
  2348. {
  2349.     int iOff;
  2350.     iOff = ((int)iYear - pmc->stMonthFirst.wYear) * 12 + (int)iMonth - pmc->stMonthFirst.wMonth;
  2351.     if (iOff < 0 || iOff >= pmc->nMonths)
  2352.         return(-1);
  2353.     return(iOff);
  2354. }
  2355. // iMonth is a zero-based index relative to the DISPLAYED months.
  2356. // iDay is a 1-based index of the day of the month,
  2357. void MCGetRcForDay(MONTHCAL *pmc, int iMonth, int iDay, RECT *prc)
  2358. {
  2359.     RECT rc;
  2360.     int iPlace, iRow, iCol;
  2361.     MCGetRcForMonth(pmc, iMonth, &rc);
  2362.     iPlace = pmc->rgcDay[iMonth] - pmc->rgnDayUL[iMonth] + iDay - 1;
  2363.     iRow = iPlace / CALCOLMAX;
  2364.     iCol = iPlace % CALCOLMAX;
  2365.     prc->left   = rc.left   + pmc->rcDayNum.left + (pmc->dxCol * iCol);
  2366.     prc->top    = rc.top    + pmc->rcDayNum.top  + (pmc->dyRow * iRow);
  2367.     prc->right  = prc->left + pmc->dxCol;
  2368.     prc->bottom = prc->top  + pmc->dyRow;
  2369. }
  2370. //
  2371. // This routine gets the bounding rect for the iMonth of the displayed months.
  2372. // NOTE: iMonth is a zero-based index relative to the DISPLAYED months,
  2373. // counting along the rows.
  2374. //
  2375. void MCGetRcForMonth(MONTHCAL *pmc, int iMonth, RECT *prc)
  2376. {
  2377.     int iRow, iCol, d;
  2378.     iRow = iMonth / pmc->nViewCols;
  2379.     iCol = iMonth % pmc->nViewCols;
  2380.     // intialize the rect to be the bounding rect for the month in the
  2381.     // top left corner
  2382.     prc->left   = pmc->rcCentered.left;
  2383.     prc->right  = prc->left + pmc->dxMonth;
  2384.     prc->top    = pmc->rcCentered.top;
  2385.     prc->bottom = prc->top + pmc->dyMonth;
  2386.     if (iCol)       // slide the rect across to the correct column
  2387.     {
  2388.         d = (pmc->dxMonth + CALBORDER) * iCol;
  2389.         prc->left  += d;
  2390.         prc->right += d;
  2391.     }
  2392.     if (iRow)       // slide the rect down to the correct row
  2393.     {
  2394.         d = (pmc->dyMonth + CALBORDER) * iRow;
  2395.         prc->top    += d;
  2396.         prc->bottom += d;
  2397.     }
  2398. }
  2399. // Changes starting month by nDelta
  2400. // returns number of months actually changed
  2401. int FIncrStartMonth(MONTHCAL *pmc, int nDelta, BOOL fNoCurDayChange)
  2402. {
  2403.     SYSTEMTIME stStart;
  2404.     int nOldStartYear  = pmc->stMonthFirst.wYear;
  2405.     int nOldStartMonth = pmc->stMonthFirst.wMonth;
  2406.     IncrSystemTime(&pmc->stMonthFirst, &stStart, nDelta, INCRSYS_MONTH);
  2407.     // MCUpdateStartEndDates takes stMin/stMax into account
  2408.     MCUpdateStartEndDates(pmc, &stStart);
  2409.     if (!fNoCurDayChange)
  2410.     {
  2411.         int cday;
  2412.         // BUGBUG: we arbitrarily set the currently selected day
  2413.         // to be in the new stMonthFirst, but given the way the
  2414.         // control works, I doubt we ever hit this code. what's it for??
  2415.         if (MonthCal_IsMultiSelect(pmc))
  2416.             cday = DaysBetweenDates(&pmc->st, &pmc->stEndSel);
  2417.         // need to set date for focus here
  2418.         pmc->st.wMonth = pmc->stMonthFirst.wMonth;
  2419.         pmc->st.wYear  = pmc->stMonthFirst.wYear;
  2420.         // Check to see if the day is in range, eg, Jan 31 -> Feb 28
  2421.         if (pmc->st.wDay > pmc->rgcDay[1])
  2422.             pmc->st.wDay = (WORD) pmc->rgcDay[1];
  2423.         if (MonthCal_IsMultiSelect(pmc))
  2424.             IncrSystemTime(&pmc->st, &pmc->stEndSel, cday, INCRSYS_DAY);
  2425.         MCNotifySelChange(pmc, MCN_SELCHANGE);
  2426.         MCUpdateRcDayCur(pmc, &pmc->st);
  2427.     }
  2428.     MCInvalidateMonthDays(pmc);
  2429.     return((pmc->stMonthFirst.wYear-nOldStartYear)*12 + (pmc->stMonthFirst.wMonth-nOldStartMonth));
  2430. }
  2431. // FIncrStartMonth with a beep when it doesn't change.
  2432. int MCIncrStartMonth(MONTHCAL *pmc, int nDelta, BOOL fDelayDayChange)
  2433. {
  2434.     int cmoSpun;
  2435.     // FIncrStartMonth takes stMin/stMax into account
  2436.     cmoSpun = FIncrStartMonth(pmc, nDelta, fDelayDayChange);
  2437.     if (cmoSpun==0)
  2438.         MessageBeep(0);
  2439.     return(cmoSpun);
  2440. }
  2441. //
  2442. // Determines in which month the given point lies.  In other words, if the
  2443. // calendar control is currently sized to show six months, this routine
  2444. // determines in which which of those six months the point lies.  It returns
  2445. // the zero based index of the month, counting along the rows.
  2446. //
  2447. BOOL FGetOffsetForPt(MONTHCAL *pmc, POINT pt, int *piOffset)
  2448. {
  2449.     int iRow, iCol, i;
  2450.     // check to see if point is within the centered months
  2451.     if (!PtInRect(&pmc->rcCentered, pt))
  2452.         return(FALSE);
  2453.     // calculate the month row and column
  2454.     // (we're really fudging a little here, since the point could
  2455.     // actually be within the space between months...)
  2456.     iCol = (pt.x - pmc->rcCentered.left) / (pmc->dxMonth + CALBORDER);
  2457.     iRow = (pt.y - pmc->rcCentered.top) / (pmc->dyMonth + CALBORDER);
  2458.     i = iRow * pmc->nViewCols + iCol;
  2459.     if (i >= pmc->nMonths)
  2460.         return(FALSE);
  2461.     *piOffset = i;
  2462.     return(TRUE);
  2463. }
  2464. //
  2465. // This routine returns the row and column of day containing the given point
  2466. //
  2467. BOOL FGetRowColForRelPt(MONTHCAL *pmc, POINT ptRel, int *piRow, int *piCol)
  2468. {
  2469.     if (!PtInRect(&pmc->rcDayNum, ptRel))
  2470.         return(FALSE);
  2471.     ptRel.x -= pmc->rcDayNum.left;
  2472.     ptRel.y -= pmc->rcDayNum.top;
  2473.     *piCol = ptRel.x / pmc->dxCol;
  2474.     *piRow = ptRel.y / pmc->dyRow;
  2475.     return(TRUE);
  2476. }
  2477. //
  2478. // This routine returns the month and year of the iMonth in the displayed
  2479. // months.  NOTE: iMonth is a zero-based index of the displayed months
  2480. //
  2481. void GetYrMoForOffset(MONTHCAL *pmc, int iMonth, int *piYear, int *piMonth)
  2482. {
  2483.     SYSTEMTIME st;
  2484.     st.wDay   = 1;
  2485.     st.wMonth = pmc->stMonthFirst.wMonth;
  2486.     st.wYear  = pmc->stMonthFirst.wYear;
  2487.     
  2488.     IncrSystemTime(&st, &st, iMonth, INCRSYS_MONTH);
  2489.     *piYear  = st.wYear;
  2490.     *piMonth = st.wMonth;
  2491. }
  2492. //
  2493. // This routine returns, the day, month, and year of day containing the
  2494. // given point.  It will optionally return the day of the month, the row and
  2495. // column in the month, and the bounding rect of the month containing the point.
  2496. // NOTE: the day returned in piDay can be less than 1 (to indicate a day in the
  2497. // previous month) or greater than the number of days in the month (to indicate
  2498. // a day in the next month).
  2499. //
  2500. BOOL FGetDateForPt(MONTHCAL *pmc, POINT pt, SYSTEMTIME *pst, int *piDay,
  2501.                    int* piCol, int* piRow, LPRECT prcMonth)
  2502. {
  2503.     int iOff, iRow, iCol, iDay, iMon, iYear;
  2504.     RECT rcMonth;
  2505.     if (!FGetOffsetForPt(pmc, pt, &iOff))
  2506.         return(FALSE);
  2507.     MCGetRcForMonth(pmc, iOff, &rcMonth);
  2508.     pt.x -= rcMonth.left;
  2509.     pt.y -= rcMonth.top;
  2510.     if (!FGetRowColForRelPt(pmc, pt, &iRow, &iCol))
  2511.         return(FALSE);
  2512.     // get the day containing the point by subtracting the number of days
  2513.     // that are visible from the previous month, and then add one, since
  2514.     // we are zero-based and the days of the month are 1-based.
  2515.     //
  2516.     iDay = iRow * CALCOLMAX + iCol - (pmc->rgcDay[iOff] - pmc->rgnDayUL[iOff]) + 1;
  2517.     if (piDay)
  2518.         *piDay = iDay;
  2519.     
  2520.     if (iDay <= 0)
  2521.     {
  2522.         if (iOff)
  2523.             return(FALSE);      // dont accept days in prev month unless
  2524.                                 // this happens to be the first month
  2525.                                 
  2526.         iDay += pmc->rgcDay[iOff];  // add the cnt of days in the prev month,
  2527.         --iOff;                     // then incr the month to get day in new month
  2528.     }
  2529.     else if (iDay > pmc->rgcDay[iOff+1])
  2530.     {
  2531.         if (iOff < (pmc->nMonths - 1))  // dont accept days in next month unless
  2532.             return(FALSE);              // this happens to be the last month
  2533.         
  2534.         ++iOff;                         // increment the month, and then sub the
  2535.         iDay -= pmc->rgcDay[iOff];      // count of days to get day in new month
  2536.     }
  2537.     GetYrMoForOffset(pmc, iOff, &iYear, &iMon);
  2538.     pst->wDay   = (WORD) iDay;
  2539.     pst->wMonth = (WORD) iMon;
  2540.     pst->wYear  = (WORD) iYear;
  2541.     if (piCol)
  2542.         *piCol = iCol;
  2543.     
  2544.     if (piRow)
  2545.         *piRow = iRow;
  2546.     
  2547.     if (prcMonth)
  2548.         *prcMonth = rcMonth;
  2549.     
  2550.     return(TRUE);
  2551. }
  2552. BOOL MCSetDate(MONTHCAL *pmc, SYSTEMTIME *pst)
  2553. {
  2554.     int nDelta = 0;
  2555.     
  2556.     //
  2557.     // Can't set date outside of min/max range
  2558.     //
  2559.     if (CmpDate(pst, &pmc->stMin) < 0)
  2560.         return FALSE;
  2561.     if (CmpDate(pst, &pmc->stMax) > 0)
  2562.         return FALSE;
  2563.     //
  2564.     // Set new day
  2565.     //
  2566.     pmc->st = *pst;
  2567.     if (MonthCal_IsMultiSelect(pmc))
  2568.         pmc->stEndSel = *pst;
  2569.     FScrollIntoView(pmc);
  2570.     
  2571.     MCNotifySelChange(pmc, MCN_SELCHANGE);
  2572.     MCUpdateRcDayCur(pmc, pst);
  2573.     return(TRUE);
  2574. }
  2575. void MCSetToday(MONTHCAL* pmc, SYSTEMTIME* pst)
  2576. {
  2577.     SYSTEMTIME st;
  2578.     RECT rc;
  2579.     
  2580.     if (!pst)
  2581.     {
  2582.         GetLocalTime(&st);
  2583.         pmc->fTodaySet = FALSE;
  2584.     }
  2585.     else
  2586.     {
  2587.         st = *pst;
  2588.         pmc->fTodaySet = TRUE;
  2589.     }
  2590.     
  2591.     if (CmpDate(&st, &pmc->stToday) != 0)
  2592.     {
  2593.         MCGetRcForDay(pmc, pmc->iMonthToday, pmc->stToday.wDay, &rc);
  2594.         InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
  2595.         pmc->stToday = st;
  2596.         MCUpdateToday(pmc);
  2597.         MCGetRcForDay(pmc, pmc->iMonthToday, pmc->stToday.wDay, &rc);
  2598.         InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
  2599.         if (MonthCal_ShowToday(pmc))
  2600.         {
  2601.             MCGetTodayBtnRect(pmc, &rc);
  2602.             InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
  2603.         }
  2604.         UpdateWindow(pmc->ci.hwnd);
  2605.     }
  2606. }
  2607. LRESULT MCHandleTimer(MONTHCAL *pmc, WPARAM wParam)
  2608. {
  2609.     if (wParam == CAL_IDAUTOSPIN)
  2610.     {
  2611.         int nDelta = pmc->fMonthDelta ? pmc->nMonthDelta : pmc->nMonths;
  2612.         // BUGBUG pass last parameter TRUE if multiselect! else you
  2613.         // can't multiselect across months
  2614.         MCIncrStartMonth(pmc, (pmc->fSpinPrev ? -nDelta : nDelta), FALSE);
  2615.         if (pmc->idTimer == 0)
  2616.             pmc->idTimer = SetTimer(pmc->ci.hwnd, CAL_IDAUTOSPIN, CAL_MSECAUTOSPIN, NULL);
  2617.         pmc->rcDayOld = pmc->rcDayCur;
  2618.         UpdateWindow(pmc->ci.hwnd);
  2619.     }
  2620.     else if (wParam == CAL_TODAYTIMER)
  2621.     {
  2622.         if (!pmc->fTodaySet)
  2623.             MCSetToday(pmc, NULL);
  2624.     }
  2625.     
  2626.     MCNotifySelChange(pmc, MCN_SELCHANGE);     // our date has changed
  2627.     
  2628.     return((LRESULT)TRUE);
  2629. }
  2630. void MCInvalidateDates(MONTHCAL *pmc, SYSTEMTIME *pst1, SYSTEMTIME *pst2)
  2631. {
  2632.     int iMonth, ioff, icol, irow;
  2633.     RECT rc, rcMonth;
  2634.     SYSTEMTIME st, stEnd;
  2635.     if (CmpDate(pst1, &pmc->stViewLast) > 0 ||
  2636.         CmpDate(pst2, &pmc->stViewFirst) < 0)
  2637.         return;
  2638.         
  2639.     if (CmpDate(pst1, &pmc->stViewFirst) < 0)
  2640.         CopyDate(pmc->stViewFirst, st);
  2641.     else
  2642.         CopyDate(*pst1, st);
  2643.     if (CmpDate(pst2, &pmc->stViewLast) > 0)
  2644.         CopyDate(pmc->stViewLast, stEnd);
  2645.     else
  2646.         CopyDate(*pst2, stEnd);
  2647.     iMonth = MCGetOffsetForYrMo(pmc, st.wYear, st.wMonth);
  2648.     if (iMonth == -1)
  2649.     {
  2650.         if (st.wMonth == pmc->stViewFirst.wMonth)
  2651.         {
  2652.             iMonth = 0;
  2653.             ioff = st.wDay - pmc->rgnDayUL[0] - 1;
  2654.         }
  2655.         else
  2656.         {
  2657.             iMonth = pmc->nMonths - 1;
  2658.             ioff = st.wDay + pmc->rgcDay[pmc->nMonths] +
  2659.                 pmc->rgcDay[iMonth] - pmc->rgnDayUL[iMonth] - 1;
  2660.         }
  2661.     }
  2662.     else
  2663.     {
  2664.         ioff = st.wDay + (pmc->rgcDay[iMonth] - pmc->rgnDayUL[iMonth]) - 1;
  2665.     }
  2666.     MCGetRcForMonth(pmc, iMonth, &rcMonth);
  2667.     // TODO: this is bullshit. make it more efficient...
  2668.     while (CmpDate(&st, &stEnd) <= 0)
  2669.     {
  2670.         irow = ioff / CALCOLMAX;
  2671.         icol = ioff % CALCOLMAX;
  2672.         rc.left   = rcMonth.left + pmc->rcDayNum.left + (pmc->dxCol * icol);
  2673.         rc.top    = rcMonth.top  + pmc->rcDayNum.top  + (pmc->dyRow * irow);
  2674.         rc.right  = rc.left      + pmc->dxCol;
  2675.         rc.bottom = rc.top       + pmc->dyRow;
  2676.         InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
  2677.         IncrSystemTime(&st, &st, 1, INCRSYS_DAY);
  2678.         ioff++;
  2679.         if (st.wDay == 1)
  2680.         {
  2681.             if (st.wMonth != pmc->stMonthFirst.wMonth &&
  2682.                 st.wMonth != pmc->stViewLast.wMonth)
  2683.             {
  2684.                 iMonth++;
  2685.                 MCGetRcForMonth(pmc, iMonth, &rcMonth);
  2686.                 ioff = ioff % CALCOLMAX;
  2687.             }
  2688.         }
  2689.     }
  2690. }
  2691. void MCHandleMultiSelect(MONTHCAL *pmc, SYSTEMTIME *pst)
  2692. {
  2693.     int i;
  2694.     DWORD cday;
  2695.     SYSTEMTIME stStart, stEnd;
  2696.     if (!pmc->fMultiSelecting)
  2697.     {
  2698.         CopyDate(*pst, stStart);
  2699.         CopyDate(*pst, stEnd);
  2700.         pmc->fMultiSelecting = TRUE;
  2701.         pmc->fForwardSelect = TRUE;
  2702.         CopyDate(pmc->st, pmc->stStartPrev);
  2703.         CopyDate(pmc->stEndSel, pmc->stEndPrev);
  2704.     }
  2705.     else
  2706.     {
  2707.         if (pmc->fForwardSelect)
  2708.         {
  2709.             i = CmpDate(pst, &pmc->st);
  2710.             if (i >= 0)
  2711.             {
  2712.                 CopyDate(pmc->st, stStart);
  2713.                 CopyDate(*pst, stEnd);
  2714.             }
  2715.             else
  2716.             {
  2717.                 CopyDate(*pst, stStart);
  2718.                 CopyDate(pmc->st, stEnd);
  2719.                 pmc->fForwardSelect = FALSE;
  2720.             }
  2721.         }
  2722.         else
  2723.         {
  2724.             i = CmpDate(pst, &pmc->stEndSel);
  2725.             if (i < 0)
  2726.             {
  2727.                 CopyDate(*pst, stStart);
  2728.                 CopyDate(pmc->stEndSel, stEnd);
  2729.             }
  2730.             else
  2731.             {
  2732.                 CopyDate(pmc->stEndSel, stStart);
  2733.                 CopyDate(*pst, stEnd);
  2734.                 pmc->fForwardSelect = TRUE;
  2735.             }
  2736.         }
  2737.     }
  2738.     
  2739.     // check to make sure not exceeding cSelMax
  2740.     cday = DaysBetweenDates(&stStart, &stEnd) + 1;
  2741.     if (cday > pmc->cSelMax)
  2742.     {
  2743.         if (pmc->fForwardSelect)
  2744.             IncrSystemTime(&stStart, &stEnd, pmc->cSelMax - 1, INCRSYS_DAY);
  2745.         else
  2746.             IncrSystemTime(&stEnd, &stStart, 1 - pmc->cSelMax, INCRSYS_DAY);
  2747.     }
  2748.     if (0 == CmpDate(&stStart, &pmc->st) &&
  2749.         0 == CmpDate(&stEnd, &pmc->stEndSel))
  2750.         return;
  2751.     // TODO: do this more effeciently..
  2752.     MCInvalidateDates(pmc, &pmc->st, &pmc->stEndSel);
  2753.     MCInvalidateDates(pmc, &stStart, &stEnd);
  2754.     CopyDate(stStart, pmc->st);
  2755.     CopyDate(stEnd, pmc->stEndSel);
  2756.     MCNotifySelChange(pmc, MCN_SELCHANGE);
  2757.     UpdateWindow(pmc->ci.hwnd);
  2758. }
  2759. void MCGotoToday(MONTHCAL *pmc)
  2760. {
  2761.     pmc->rcDayOld = pmc->rcDayCur;
  2762.     // force old selection to get repainted
  2763.     if (MonthCal_IsMultiSelect(pmc))
  2764.         MCInvalidateDates(pmc, &pmc->st, &pmc->stEndSel);
  2765.     else
  2766.         InvalidateRect(pmc->ci.hwnd, &pmc->rcDayOld, FALSE);
  2767.     MCSetDate(pmc, &pmc->stToday);
  2768.     
  2769.     MCNotifySelChange(pmc, MCN_SELECT);    
  2770.     
  2771.     // force new selection to get repainted
  2772.     InvalidateRect(pmc->ci.hwnd, &pmc->rcDayCur, FALSE);
  2773.     UpdateWindow(pmc->ci.hwnd);
  2774. }
  2775. LRESULT MCContextMenu(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam)
  2776. {
  2777.     POINT pt;
  2778.     int click;
  2779.     if (!pmc->fEnabled || !MonthCal_ShowToday(pmc))
  2780.         return(0);
  2781.     // ignore double click since this makes us advance twice
  2782.     // since we already had a leftdown before the leftdblclk
  2783.     if (!pmc->fCapture)
  2784.     {
  2785.         pt.x = GET_X_LPARAM(lParam);
  2786.         pt.y = GET_Y_LPARAM(lParam);
  2787.         //
  2788.         //  If the context menu was generated from the keyboard,
  2789.         //  then put it at the focus rectangle.
  2790.         //
  2791.         if (pt.x == -1 && pt.y == -1)
  2792.         {
  2793.             pt.x = (pmc->rcDayCur.left + pmc->rcDayCur.right ) / 2;
  2794.             pt.y = (pmc->rcDayCur.top  + pmc->rcDayCur.bottom) / 2;
  2795.             ClientToScreen(pmc->ci.hwnd, &pt);
  2796.         }
  2797.         click = TrackPopupMenu(pmc->hmenuCtxt,
  2798.                     TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY,
  2799.                     pt.x, pt.y, 0, pmc->ci.hwnd, NULL);
  2800.         if (click >= 1)
  2801.             MCGotoToday(pmc);
  2802.     }
  2803.     return(0);
  2804. }
  2805. //
  2806. // Computes the bounding rects for the month and the year in the title area of
  2807. // the month.
  2808. //
  2809. void MCGetTitleRcsForOffset(MONTHCAL* pmc, int iOffset, LPRECT prcMonth, LPRECT prcYear)
  2810. {
  2811.     RECT rcT;
  2812.     RECT rc;
  2813.     MCGetRcForMonth(pmc, iOffset, &rc);
  2814.     
  2815.     rcT.top    = rc.top + (pmc->dyRow / 2);
  2816.     rcT.bottom = rcT.top + pmc->dyRow;
  2817.     rcT.left  = rc.left + pmc->rcMonthName.left + pmc->rgmm[iOffset].rgi[IMM_MONTHSTART];
  2818.     rcT.right = rc.left + pmc->rcMonthName.left + pmc->rgmm[iOffset].rgi[IMM_MONTHEND];
  2819.     *prcMonth = rcT;
  2820.     rcT.left  = rc.left + pmc->rcMonthName.left + pmc->rgmm[iOffset].rgi[IMM_YEARSTART];
  2821.     rcT.right = rc.left + pmc->rcMonthName.left + pmc->rgmm[iOffset].rgi[IMM_YEAREND];
  2822.     *prcYear  = rcT;
  2823. }
  2824. LRESULT MCLButtonDown(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam)
  2825. {
  2826.     HDC        hdc;
  2827.     POINT      pt;
  2828.     SYSTEMTIME st;
  2829.     RECT       rc, rcCal;
  2830.     BOOL       fShow;
  2831.     MSG        msg;
  2832.     int        offset, imonth, iyear;
  2833.     if (!pmc->fEnabled)
  2834.         return(0);
  2835.     pt.x = GET_X_LPARAM(lParam);
  2836.     pt.y = GET_Y_LPARAM(lParam);
  2837.   
  2838.     // treat a shift click like an LMouseDown at the prev location and
  2839.     // a MouseMove to the new location
  2840.     if (MonthCal_IsMultiSelect(pmc) && ((wParam & MK_SHIFT) == MK_SHIFT) && (!PtInRect(&pmc->rcDayCur, pt)))
  2841.     {
  2842.         SetCapture(pmc->ci.hwnd);
  2843.         pmc->fCapture = TRUE;
  2844.         
  2845.         pmc->fForwardSelect = (CmpDate(&pmc->stAnchor, &pmc->st) != 0) ? FALSE : TRUE;
  2846.         pmc->fMultiSelecting = TRUE;
  2847.         hdc = GetDC(pmc->ci.hwnd);
  2848.         DrawFocusRect(hdc, &pmc->rcDayCur);    // draw focus rect
  2849.         pmc->fFocusDrawn = TRUE;
  2850.         ReleaseDC(pmc->ci.hwnd, hdc);
  2851.         
  2852.         MCMouseMove(pmc, wParam, lParam);      // draw the highlight to new date
  2853.         return 0;
  2854.     }
  2855.     
  2856.     // ignore double click since this makes us advance twice
  2857.     // since we already had a leftdown before the leftdblclk
  2858.     if (!pmc->fCapture)
  2859.     {
  2860.         SetCapture(pmc->ci.hwnd);
  2861.         pmc->fCapture = TRUE;
  2862.         // check for spin buttons
  2863.         if ((pmc->fSpinPrev = (WORD) PtInRect(&pmc->rcPrev, pt)) || PtInRect(&pmc->rcNext, pt))
  2864.         {
  2865.             MCHandleTimer(pmc, CAL_IDAUTOSPIN);
  2866.             return(0);
  2867.         }
  2868.                       
  2869.         // check for valid day
  2870.         pmc->rcDayOld = pmc->rcDayCur;   // rcDayCur should always be valid now
  2871.         if (MonthCal_IsMultiSelect(pmc))
  2872.         {
  2873.             // need to cache these values because these are how
  2874.             // we determine if the selection has changed and we
  2875.             // need to notify the parent
  2876.             CopyDate(pmc->st, pmc->stStartPrev);
  2877.             CopyDate(pmc->stEndSel, pmc->stEndPrev);
  2878.         }
  2879.                 
  2880.         if (FUpdateRcDayCur(pmc, pt))
  2881.         {
  2882.             if (MonthCal_IsMultiSelect(pmc))
  2883.             {
  2884.                 if (FGetDateForPt(pmc, pt, &st, NULL, NULL, NULL, NULL))
  2885.                     MCHandleMultiSelect(pmc, &st);
  2886.             }
  2887.             hdc = GetDC(pmc->ci.hwnd);
  2888.             DrawFocusRect(hdc, &pmc->rcDayCur);    // draw focus rect
  2889.             pmc->fFocusDrawn = TRUE;
  2890.             ReleaseDC(pmc->ci.hwnd, hdc);
  2891.             CopyDate(st, pmc->stAnchor);           // new Anchor point
  2892.         }
  2893.         else
  2894.         {
  2895.             RECT rcMonth, rcYear;
  2896.             int delta, year, month;
  2897.             
  2898.             // is this a click in the today area...
  2899.             if (MonthCal_ShowToday(pmc))
  2900.             {
  2901.                 MCGetTodayBtnRect(pmc, &rc);
  2902.                 if (PtInRect(&rc, pt))
  2903.                 {
  2904.                     CCReleaseCapture(&pmc->ci);
  2905.                     pmc->fCapture = FALSE;
  2906.     
  2907.                     MCGotoToday(pmc);
  2908.                     return(0);
  2909.                 }
  2910.             }
  2911.             // figure out if the click was in a month name or a year
  2912.             if (!FGetOffsetForPt(pmc, pt, &offset))
  2913.                 return(0);
  2914.             GetYrMoForOffset(pmc, offset, &year, &month);
  2915.             // calculate where the month name and year are,
  2916.             // so we can figure out if they clicked in them...
  2917.             MCGetTitleRcsForOffset(pmc, offset, &rcMonth, &rcYear);
  2918.             
  2919.             delta = 0;
  2920.             if (PtInRect(&rcMonth, pt))
  2921.             {
  2922.                 CCReleaseCapture(&pmc->ci);
  2923.                 pmc->fCapture = FALSE;
  2924.                 ClientToScreen(pmc->ci.hwnd, &pt);
  2925.                 imonth = TrackPopupMenu(pmc->hmenuMonth,
  2926.                     TPM_LEFTALIGN | TPM_TOPALIGN |
  2927.                     TPM_NONOTIFY | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_RIGHTBUTTON,
  2928.                     pt.x, pt.y, 0, pmc->ci.hwnd, NULL);
  2929.                 if (imonth >= 1)
  2930.                     delta = imonth - month;
  2931.                 goto ChangeMonth;
  2932.             }                
  2933.             if (PtInRect(&rcYear, pt))
  2934.             {
  2935.                 HWND hwndEdit, hwndUD, hwndFocus;
  2936.                 int yrMin, yrMax;
  2937.                 DWORD dwExStyle = 0L;
  2938.                 CCReleaseCapture(&pmc->ci);
  2939.                 pmc->fCapture = FALSE;
  2940.                 
  2941.                 //
  2942.                 // If the year is in a RTL string, then numeric control
  2943.                 // is to the left. 
  2944.                 //
  2945.                 if (pmc->fHeaderRTL)
  2946.                 {
  2947.                     rcYear.left = (rcYear.right - (pmc->dxYearMax + 6));
  2948.                 }
  2949.                 else
  2950.                 {
  2951.                     rcYear.right = rcYear.left + pmc->dxYearMax + 6;
  2952.                 }
  2953.                 rcYear.top--;
  2954.                 rcYear.bottom++;
  2955.                 if(((pmc->fHeaderRTL) && !(IS_WINDOW_RTL_MIRRORED(pmc->ci.hwnd))) ||
  2956.                   (!(pmc->fHeaderRTL) && (IS_WINDOW_RTL_MIRRORED(pmc->ci.hwnd))))
  2957.                 {
  2958.                     // not mirrored force RTL, mirrored force LTR (for mirroring RTLis LTR!!)
  2959.                     dwExStyle|= WS_EX_RTLREADING;
  2960.                 }
  2961.                 hwndEdit = CreateWindowEx(dwExStyle, TEXT("EDIT"), NULL,
  2962.                     WS_CHILD | WS_VISIBLE | WS_BORDER | ES_READONLY | ES_LEFT | ES_AUTOHSCROLL,
  2963.                     rcYear.left, rcYear.top, rcYear.right - rcYear.left, rcYear.bottom - rcYear.top,
  2964.                     pmc->ci.hwnd, (HMENU)0, pmc->hinstance, NULL);
  2965.                 if (hwndEdit == NULL)
  2966.                     return(0);
  2967.                 pmc->hwndEdit = hwndEdit;
  2968.                 SendMessage(hwndEdit, WM_SETFONT, (WPARAM)pmc->hfontBold, (LPARAM)FALSE);
  2969.                 SendMessage(hwndEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
  2970.                             (LPARAM)MAKELONG(1, 1));
  2971.                 MCUpdateEditYear(pmc);
  2972.                 //
  2973.                 //  Convert from Gregorian to display years.
  2974.                 //
  2975.                 year = GregorianToOther(&pmc->ct, year);
  2976.                 yrMin = GregorianToOther(&pmc->ct, pmc->stMin.wYear);
  2977.                 yrMax = 9999;
  2978.                 if (pmc->fMaxYrSet)
  2979.                     yrMax = GregorianToOther(&pmc->ct, pmc->stMax.wYear);
  2980.                 hwndUD = CreateUpDownControl(
  2981.                     WS_CHILD | WS_VISIBLE | WS_BORDER | 
  2982.                     UDS_NOTHOUSANDS | UDS_ARROWKEYS,// | UDS_SETBUDDYINT,
  2983.                     pmc->fHeaderRTL ? (rcYear.left - 1 - (rcYear.bottom-rcYear.top)): (rcYear.right + 1), 
  2984.                     rcYear.top, 
  2985.                     rcYear.bottom - rcYear.top, rcYear.bottom - rcYear.top, pmc->ci.hwnd,
  2986.                     1, pmc->hinstance, hwndEdit, yrMax, yrMin, year);
  2987.                 if (hwndUD == NULL)
  2988.                 {
  2989.                     DestroyWindow(hwndEdit);
  2990.                     return(0);
  2991.                 }
  2992.                 pmc->hwndUD = hwndUD;
  2993.                 hwndFocus = SetFocus(hwndEdit);
  2994.                 //
  2995.                 // Widen the area depending on the string direction.
  2996.                 //
  2997.                 if (pmc->fHeaderRTL)
  2998.                     rcYear.left -= (1 + rcYear.bottom - rcYear.top);
  2999.                 else
  3000.                     rcYear.right += 1 + rcYear.bottom - rcYear.top;
  3001.                 // Use MapWindowRect, It works in a mirrored and unmirrored windows.
  3002.                 MapWindowRect(pmc->ci.hwnd, NULL, (LPPOINT)&rcYear);
  3003.                 rcCal = pmc->rc;
  3004.                 MapWindowRect(pmc->ci.hwnd, NULL, (LPPOINT)&rcCal);
  3005.                 fShow = TRUE;
  3006.                 while (fShow && GetFocus() == hwndEdit)
  3007.                 {
  3008.                     if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
  3009.                     {
  3010.                         // Check for events that cause the calendar to go away
  3011.                         if (msg.message == WM_KILLFOCUS ||
  3012.                             (msg.message >= WM_SYSKEYDOWN &&
  3013.                             msg.message <= WM_SYSDEADCHAR))
  3014.                         {
  3015.                             fShow = FALSE;
  3016.                         }
  3017.                         else if ((msg.message == WM_LBUTTONDOWN ||
  3018.                             msg.message == WM_NCLBUTTONDOWN ||
  3019.                             msg.message == WM_RBUTTONDOWN ||
  3020.                             msg.message == WM_NCRBUTTONDOWN ||
  3021.                             msg.message == WM_MBUTTONDOWN ||
  3022.                             msg.message == WM_NCMBUTTONDOWN) &&
  3023.                             !PtInRect(&rcYear, msg.pt))
  3024.                         {
  3025.                             fShow = FALSE;
  3026.                             
  3027.                             // if its a button down inside the calendar, eat it
  3028.                             // so the calendar doesn't do anything strange when
  3029.                             // the user is just trying to get rid of the year edit
  3030.                             if (PtInRect(&rcCal, msg.pt))
  3031.                                 GetMessage(&msg, NULL, 0, 0);
  3032.                             
  3033.                             break;    // do not dispatch
  3034.                         }
  3035.                         else if (msg.message == WM_QUIT)
  3036.                         {   // Don't dispatch a WM_QUIT; leave it in the queue
  3037.                             break;    // do not dispatch
  3038.                         }
  3039.                         else if (msg.message == WM_CHAR)
  3040.                         {
  3041.                             if (msg.wParam == VK_ESCAPE)
  3042.                             {
  3043.                                 goto NoYearChange;
  3044.                             }
  3045.                             else if (msg.wParam == VK_RETURN)
  3046.                             {
  3047.                                 fShow = FALSE;
  3048.                             }
  3049.                         }
  3050.                         GetMessage(&msg, NULL, 0, 0);
  3051.                         TranslateMessage(&msg);
  3052.                         DispatchMessage(&msg);
  3053.                     }
  3054.                     else
  3055.                         WaitMessage();
  3056.                 }
  3057.                 iyear = (int) SendMessage(hwndUD, UDM_GETPOS, 0, 0);
  3058.                 if (HIWORD(iyear) == 0)
  3059.                     delta = (iyear - year) * 12;
  3060. NoYearChange:
  3061.                 DestroyWindow(hwndUD);
  3062.                 DestroyWindow(hwndEdit);
  3063.                 pmc->hwndUD = NULL;
  3064.                 pmc->hwndEdit = NULL;
  3065.                 UpdateWindow(pmc->ci.hwnd);
  3066.                 if (hwndFocus != NULL)
  3067.                     SetFocus(hwndFocus);
  3068.             }
  3069. ChangeMonth:
  3070.             if (delta != 0)
  3071.             {
  3072.                 MCIncrStartMonth(pmc, delta, FALSE);
  3073.                 MCNotifySelChange(pmc,MCN_SELCHANGE);
  3074.             }
  3075.             
  3076.         }
  3077.     }
  3078.     return(0);
  3079. }
  3080. LRESULT MCLButtonUp(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam)
  3081. {
  3082.     HDC hdc;
  3083.     SYSTEMTIME st;
  3084.     POINT pt;
  3085.         
  3086.     if (pmc->fCapture)
  3087.     {
  3088.         CCReleaseCapture(&pmc->ci);
  3089.         pmc->fCapture = FALSE;
  3090.         if (pmc->idTimer)
  3091.         {
  3092.             KillTimer(pmc->ci.hwnd, pmc->idTimer);
  3093.             pmc->idTimer = 0;
  3094.             hdc = GetDC(pmc->ci.hwnd);
  3095.             MCPaintArrowBtn(pmc, hdc, pmc->fSpinPrev, FALSE);
  3096.             ReleaseDC(pmc->ci.hwnd, hdc);
  3097.             return(0);
  3098.         }
  3099.         if (pmc->fFocusDrawn)
  3100.         {
  3101.             hdc = GetDC(pmc->ci.hwnd);
  3102.             DrawFocusRect(hdc, &pmc->rcDayCur); // erase old focus rect
  3103.             pmc->fFocusDrawn = FALSE;
  3104.             ReleaseDC(pmc->ci.hwnd, hdc);
  3105.         }
  3106.         pt.x = GET_X_LPARAM(lParam);
  3107.         pt.y = GET_Y_LPARAM(lParam);
  3108.         if (MonthCal_IsMultiSelect(pmc))
  3109.         {
  3110.             FUpdateRcDayCur(pmc, pt);
  3111.             if (!EqualRect(&pmc->rcDayOld, &pmc->rcDayCur))
  3112.             {
  3113.                 if (FGetDateForPt(pmc, pt, &st, NULL, NULL, NULL, NULL))
  3114.                     MCHandleMultiSelect(pmc, &st);
  3115.             }
  3116.             pmc->fMultiSelecting = FALSE;
  3117.             if (0 != CmpDate(&pmc->stStartPrev, &pmc->st) ||
  3118.                 0 != CmpDate(&pmc->stEndPrev, &pmc->stEndSel))
  3119.             {
  3120.                 FScrollIntoView(pmc);
  3121.             }
  3122.             MCNotifySelChange(pmc, MCN_SELECT);
  3123.         }
  3124.         else
  3125.         {
  3126.             if (FUpdateRcDayCur(pmc, pt))
  3127.             {
  3128.                 if (!EqualRect(&pmc->rcDayOld, &pmc->rcDayCur) && (FGetDateForPt(pmc, pt, &st, NULL, NULL, NULL, NULL)))
  3129.                 {
  3130.                     InvalidateRect(pmc->ci.hwnd, &pmc->rcDayOld, FALSE);
  3131.                     InvalidateRect(pmc->ci.hwnd, &pmc->rcDayCur, FALSE);
  3132.                     MCSetDate(pmc, &st);
  3133.                 }
  3134.                 
  3135.                 MCNotifySelChange(pmc, MCN_SELECT);
  3136.             }
  3137.         }
  3138.     }
  3139.     return(0);
  3140. }
  3141. LRESULT MCMouseMove(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam)
  3142. {
  3143.     BOOL fPrev;
  3144.     HDC hdc;
  3145.     POINT pt;
  3146.     SYSTEMTIME st;
  3147.     if (pmc->fCapture)
  3148.     {
  3149.         pt.x = GET_X_LPARAM(lParam);
  3150.         pt.y = GET_Y_LPARAM(lParam);
  3151.         // check spin buttons
  3152.         if ((fPrev = PtInRect(&pmc->rcPrev, pt)) || PtInRect(&pmc->rcNext, pt))
  3153.         {
  3154.             if (pmc->idTimer == 0)
  3155.             {
  3156.                 pmc->fSpinPrev = (WORD) fPrev;
  3157.                 MCHandleTimer(pmc, CAL_IDAUTOSPIN);
  3158.             }
  3159.             return(0);
  3160.         }