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

7523 lines
217 KiB

  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. BOOL InitDateClasses(HINSTANCE hinst)
  106. {
  107. WNDCLASS wndclass;
  108. wndclass.style = CS_GLOBALCLASS;
  109. wndclass.lpfnWndProc = MonthCalWndProc;
  110. wndclass.cbClsExtra = 0;
  111. wndclass.cbWndExtra = sizeof(LPVOID);
  112. wndclass.hInstance = hinst;
  113. wndclass.hIcon = NULL;
  114. wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
  115. wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  116. wndclass.lpszMenuName = NULL;
  117. wndclass.lpszClassName = g_rgchMCName;
  118. if (!RegisterClass(&wndclass))
  119. {
  120. return GetClassInfo(hinst, g_rgchMCName, &wndclass);
  121. }
  122. wndclass.lpfnWndProc = DatePickWndProc;
  123. wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  124. wndclass.lpszClassName = g_rgchDTPName;
  125. if (!RegisterClass(&wndclass))
  126. {
  127. return GetClassInfo(hinst, g_rgchDTPName, &wndclass);
  128. }
  129. return(TRUE);
  130. }
  131. ////////////////////////////////////////////////////////////////////////////
  132. //
  133. // MonthCal stuff
  134. //
  135. ////////////////////////////////////////////////////////////////////////////
  136. ////////////////////////////////////
  137. //
  138. // MCInsert/RemoveMarkers
  139. //
  140. // QuickSummary: Convert the string "MMMM yyyy" into "\1MMMM\3 \2yyyy\4".
  141. //
  142. // In order to lay out the month/year info in the header, we have to be
  143. // able to extract the month and year out of the formatted string so we
  144. // know what their rectangles are. We do this by wrapping the month and
  145. // year inserts with markers so we can extract them after formatting.
  146. //
  147. // Since \1 through \4 are control characters, they won't conflict with
  148. // displayable characters in the actual format string. And just to play it
  149. // safe, if we actually see a format character, we erase it from the string.
  150. //
  151. // MCInsertMarkers inserts the markers into the output string so we can
  152. // extract the substrings later. Quotation marks are funky since you can
  153. // write a format of "'The' mm'''th month of' yyyy". Note that a simple
  154. // even-odd test works for detecting whether we are inside or outside
  155. // quotation marks, even in the nested quotation mark case.
  156. //
  157. void MCInsertMarkers(LPTSTR pszOut, LPCTSTR pszIn)
  158. {
  159. BOOL fInQuote = FALSE;
  160. UINT flSeen = 0;
  161. UINT flThis;
  162. for (;;)
  163. {
  164. TCHAR ch = *pszIn;
  165. switch (ch) {
  166. // At end of string, terminate the output buffer and go home
  167. case TEXT('\0'):
  168. *pszOut = TEXT('\0');
  169. return;
  170. case TEXT('m'):
  171. case TEXT('M'):
  172. flThis = IMM_MONTHSTART;
  173. goto CheckMarker;
  174. case TEXT('y'):
  175. flThis = IMM_YEARSTART;
  176. goto CheckMarker;
  177. CheckMarker:
  178. // If inside a quotation mark or we've already done this guy,
  179. // then just treat it as a regular character.
  180. if (fInQuote || (flSeen & flThis))
  181. goto CopyChar;
  182. flSeen |= flThis;
  183. *pszOut++ = (TCHAR)flThis;
  184. // Don't need to use CharNext because we know *pszIn is "m" "M" or "y"
  185. for ( ; *pszIn == ch; pszIn++)
  186. {
  187. *pszOut++ = ch;
  188. }
  189. *pszOut++ = (TCHAR)(flThis + DMM_STARTEND);
  190. // Restart the loop so we re-parse the character at *pszIn
  191. continue;
  192. // Toggle the quotation mark gizmo if we see one, and then just
  193. // copy it.
  194. case '\'':
  195. fInQuote ^= TRUE;
  196. goto CopyChar;
  197. //
  198. // Don't let these sneak into the output format or it
  199. // will confuse us.
  200. //
  201. case IMM_MONTHSTART:
  202. case IMM_YEARSTART:
  203. case IMM_MONTHEND:
  204. case IMM_YEAREND:
  205. break;
  206. default:
  207. CopyChar:
  208. *pszOut++ = ch;
  209. #ifndef UNICODE
  210. if (IsDBCSLeadByte(ch) && pszIn[1]) {
  211. *pszOut++ = *++pszIn;
  212. }
  213. #endif
  214. break;
  215. }
  216. pszIn++; // We handled the DBCS case already
  217. }
  218. // NOTREACHED
  219. }
  220. //
  221. // MCRemoveMarkers hunts down the marker characters and strips them out,
  222. // recording their locations in the optional MONTHMETRICS (as character
  223. // indices).
  224. //
  225. void MCRemoveMarkers(LPTSTR pszBuf, PMONTHMETRICS pmm)
  226. {
  227. int iWrite, iRead;
  228. //
  229. // If by some horrid error we can't find our markers, just pretend
  230. // they were at the start of the string.
  231. //
  232. if (pmm) {
  233. pmm->rgi[IMM_MONTHSTART] = 0;
  234. pmm->rgi[IMM_YEARSTART ] = 0;
  235. pmm->rgi[IMM_MONTHEND ] = 0;
  236. pmm->rgi[IMM_YEAREND ] = 0;
  237. }
  238. iWrite = iRead = 0;
  239. for (;;)
  240. {
  241. TCHAR ch = pszBuf[iRead];
  242. switch (ch)
  243. {
  244. // At end of string, terminate the output buffer and go home
  245. case TEXT('\0'):
  246. pszBuf[iWrite] = TEXT('\0');
  247. return;
  248. // If we find a marker, eat it and remember its location
  249. case IMM_MONTHSTART:
  250. case IMM_YEARSTART:
  251. case IMM_MONTHEND:
  252. case IMM_YEAREND:
  253. if (pmm)
  254. pmm->rgi[ch] = iWrite;
  255. break;
  256. // Otherwise, just copy it to the output
  257. default:
  258. pszBuf[iWrite++] = ch;
  259. #ifndef UNICODE
  260. if (IsDBCSLeadByte(ch) && pszBuf[iRead+1]) {
  261. pszBuf[iWrite++] = pszBuf[++iRead];
  262. }
  263. #endif
  264. break;
  265. }
  266. iRead++;
  267. }
  268. // NOTREACHED
  269. }
  270. ////////////////////////////////////
  271. //
  272. // Like LocalizedLoadString, except that we get the string from
  273. // LOCAL_USER_DEFAULT instead of GetUserDefaultUILanguage().
  274. //
  275. // LOCALE_USER_DEFAULT is the same as GetUserDefaultLCID(), and
  276. // LANGIDFROMLCID(GetUserDefaultLCID()) is the same as GetUserDefaultLangID().
  277. //
  278. // So we pass GetUserDefaultLangID() as the language.
  279. //
  280. int MCLoadString(UINT uID, LPWSTR lpBuffer, int nBufferMax)
  281. {
  282. return CCLoadStringEx(uID, lpBuffer, nBufferMax, GetUserDefaultLangID());
  283. }
  284. ////////////////////////////////////
  285. //
  286. // Get the localized calendar info
  287. //
  288. BOOL UpdateLocaleInfo(MONTHCAL* pmc, LPLOCALEINFO pli)
  289. {
  290. int i;
  291. TCHAR szBuf[64];
  292. int cch;
  293. LPTSTR pc = szBuf;
  294. //
  295. // Get information about the calendar (e.g., is it supported?)
  296. //
  297. MCGetCalendarInfo(&pmc->ct);
  298. //
  299. // Check if the calendar title is an RTL string
  300. //
  301. GetDateFormat(pmc->ct.lcid, 0, NULL, TEXT("MMMM"), szBuf, ARRAYSIZE(szBuf));
  302. pmc->fHeaderRTL = (WORD) MCIsDateStringRTL(szBuf[0]);
  303. //
  304. // get the short date format and sniff it to see if it displays the year
  305. // or month first
  306. //
  307. MCLoadString(IDS_MONTHFMT, pli->szMonthFmt, ARRAYSIZE(pli->szMonthFmt));
  308. //
  309. // Try to get the MONTHYEAR format from NLS. If not supported by NLS,
  310. // then use the hard-coded value in our resources. Note that we
  311. // subtract 4 from the buffer size because we may insert up to four
  312. // marker characters.
  313. //
  314. COMPILETIME_ASSERT(ARRAYSIZE(szBuf) >= ARRAYSIZE(pli->szMonthYearFmt));
  315. szBuf[0] = TEXT('\0');
  316. GetLocaleInfo(pmc->ct.lcid, LOCALE_SYEARMONTH,
  317. szBuf, ARRAYSIZE(pli->szMonthYearFmt) - CCH_MARKERS);
  318. if (!szBuf[0]) {
  319. MCLoadString(IDS_MONTHYEARFMT, szBuf, ARRAYSIZE(pli->szMonthYearFmt) - CCH_MARKERS);
  320. }
  321. MCInsertMarkers(pli->szMonthYearFmt, szBuf);
  322. //
  323. // REARCHITECT this code needs to change to use CAL_ values when we
  324. // want to support multiple calendars.
  325. //
  326. //
  327. // Get the month names
  328. //
  329. for (i = 0; i < 12; i++)
  330. {
  331. cch = GetLocaleInfo(pmc->ct.lcid, LOCALE_SMONTHNAME1 + i,
  332. pli->rgszMonth[i], CCHMAXMONTH);
  333. if (cch == 0)
  334. // the calendar is pretty useless without month names...
  335. return(FALSE);
  336. }
  337. //
  338. // Get the days of the week
  339. //
  340. for (i = 0; i < 7; i++)
  341. {
  342. cch = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVDAYNAME1 + i,
  343. pli->rgszDay[i], CCHMAXABBREVDAY);
  344. if (cch == 0)
  345. // the calendar is pretty useless without day names...
  346. return(FALSE);
  347. }
  348. //
  349. // If we haven't already set what the first day of the week is, get the
  350. // localized setting.
  351. //
  352. if (!pmc->fFirstDowSet)
  353. {
  354. cch = GetLocaleInfo(pmc->ct.lcid, LOCALE_IFIRSTDAYOFWEEK, szBuf, ARRAYSIZE(szBuf));
  355. if (cch > 0)
  356. pli->dowStartWeek = szBuf[0] - TEXT('0');
  357. }
  358. //
  359. // Get the first week of the year
  360. //
  361. cch = GetLocaleInfo(pmc->ct.lcid, LOCALE_IFIRSTWEEKOFYEAR, szBuf, ARRAYSIZE(szBuf));
  362. if (cch > 0)
  363. pli->firstWeek = szBuf[0] - TEXT('0');
  364. // Set up pointers
  365. for (i = 0; i < 12; i++)
  366. pli->rgpszMonth[i] = pli->rgszMonth[i];
  367. for (i = 0; i < 7; i++)
  368. pli->rgpszDay[i] = pli->rgszDay[i];
  369. // Get static strings
  370. MCLoadString(IDS_TODAY, pli->szToday, ARRAYSIZE(pli->szToday));
  371. MCLoadString(IDS_GOTOTODAY, pli->szGoToToday, ARRAYSIZE(pli->szGoToToday));
  372. // if we've been initialized
  373. if (pmc->hinstance)
  374. {
  375. SYSTEMTIME st;
  376. CopyDate(pmc->stMonthFirst, st);
  377. MCUpdateStartEndDates(pmc, &st);
  378. }
  379. return(TRUE);
  380. }
  381. void MCReloadMenus(MONTHCAL *pmc)
  382. {
  383. int i;
  384. if (pmc->hmenuCtxt)
  385. DestroyMenu(pmc->hmenuCtxt);
  386. if (pmc->hmenuMonth)
  387. DestroyMenu(pmc->hmenuMonth);
  388. pmc->hmenuCtxt = CreatePopupMenu();
  389. if (pmc->hmenuCtxt)
  390. AppendMenu(pmc->hmenuCtxt, MF_STRING, 1, pmc->li.szGoToToday);
  391. pmc->hmenuMonth = CreatePopupMenu();
  392. if (pmc->hmenuMonth)
  393. {
  394. for (i = 0; i < 12; i++)
  395. AppendMenu(pmc->hmenuMonth, MF_STRING, i + 1, pmc->li.rgszMonth[i]);
  396. }
  397. }
  398. BOOL MCHandleEraseBkgnd(MONTHCAL* pmc, HDC hdc)
  399. {
  400. RECT rc;
  401. GetClipBox(hdc, &rc);
  402. FillRectClr(hdc, &rc, pmc->clr[MCSC_BACKGROUND]);
  403. return TRUE;
  404. }
  405. LRESULT MCHandleHitTest(MONTHCAL* pmc, PMCHITTESTINFO phti)
  406. {
  407. int iMonth;
  408. RECT rc;
  409. if (!phti || phti->cbSize != sizeof(MCHITTESTINFO))
  410. return -1;
  411. phti->uHit = MCHT_NOWHERE;
  412. MCGetTodayBtnRect(pmc, &rc);
  413. if (PtInRect(&rc, phti->pt) && MonthCal_ShowToday(pmc))
  414. {
  415. phti->uHit = MCHT_TODAYLINK;
  416. }
  417. else if (pmc->fSpinPrev = (WORD) PtInRect(&pmc->rcPrev, phti->pt))
  418. {
  419. phti->uHit = MCHT_TITLEBTNPREV;
  420. }
  421. else if (PtInRect(&pmc->rcNext, phti->pt))
  422. {
  423. phti->uHit = MCHT_TITLEBTNNEXT;
  424. }
  425. else if (FGetOffsetForPt(pmc, phti->pt, &iMonth))
  426. {
  427. RECT rcMonth; // bounding rect for month containg phti->pt
  428. POINT ptRel; // relative point in a month
  429. int month;
  430. int year;
  431. MCGetRcForMonth(pmc, iMonth, &rcMonth);
  432. ptRel.x = phti->pt.x - rcMonth.left;
  433. ptRel.y = phti->pt.y - rcMonth.top;
  434. GetYrMoForOffset(pmc, iMonth, &year, &month);
  435. phti->st.wMonth = (WORD) month;
  436. phti->st.wYear = (WORD) year;
  437. //
  438. // if calendar is showing week numbers and the point lies in the
  439. // the week numbers, get the date for day immediately to the right
  440. // of the week number containing the point
  441. //
  442. if (MonthCal_ShowWeekNumbers(pmc) && PtInRect(&pmc->rcWeekNum, ptRel))
  443. {
  444. phti->uHit |= MCHT_CALENDARWEEKNUM;
  445. phti->pt.x += pmc->rcDayNum.left;
  446. FGetDateForPt(pmc, phti->pt, &phti->st, NULL, NULL, NULL, NULL);
  447. }
  448. //
  449. // if the point lies in the days of the week header, then return
  450. // the day of the week containing the point
  451. //
  452. else if (PtInRect(&pmc->rcDow, ptRel))
  453. {
  454. int iRow;
  455. int iCol;
  456. phti->uHit |= MCHT_CALENDARDAY;
  457. ptRel.y = pmc->rcDayNum.top;
  458. FGetRowColForRelPt(pmc, ptRel, &iRow, &iCol);
  459. phti->st.wDayOfWeek = (WORD) iCol;
  460. }
  461. //
  462. // if the point lies in the actually calendar part, then return the
  463. // date containg the point
  464. //
  465. else if (PtInRect(&pmc->rcDayNum, ptRel))
  466. {
  467. int iDay;
  468. // we're in the calendar part!
  469. phti->uHit |= MCHT_CALENDAR;
  470. if (FGetDateForPt(pmc, phti->pt, &phti->st, &iDay, NULL, NULL, NULL))
  471. {
  472. phti->uHit |= MCHT_CALENDARDATE;
  473. // if it was beyond the bounds of the days we're showing
  474. // and also FGetDateForPt returns TRUE, then we're on the boundary
  475. // of the displayed months
  476. if (iDay <= 0)
  477. {
  478. phti->uHit |= MCHT_PREV;
  479. }
  480. else if (iDay > pmc->rgcDay[iMonth + 1])
  481. {
  482. phti->uHit |= MCHT_NEXT;
  483. }
  484. }
  485. }
  486. else
  487. {
  488. RECT rcMonthTitle;
  489. RECT rcYearTitle;
  490. // otherwise we're in the title
  491. phti->uHit |= MCHT_TITLE;
  492. MCGetTitleRcsForOffset(pmc, iMonth, &rcMonthTitle, &rcYearTitle);
  493. if (PtInRect(&rcMonthTitle, phti->pt))
  494. {
  495. phti->uHit |= MCHT_TITLEMONTH;
  496. }
  497. else if (PtInRect(&rcYearTitle, phti->pt))
  498. {
  499. phti->uHit |= MCHT_TITLEYEAR;
  500. }
  501. }
  502. }
  503. DebugMsg(TF_MONTHCAL, TEXT("mc: Hittest returns : %d %d %d %d)"),
  504. (int)phti->st.wDay,
  505. (int)phti->st.wMonth,
  506. (int)phti->st.wYear,
  507. (int)phti->st.wDayOfWeek
  508. );
  509. return phti->uHit;
  510. }
  511. void MonthCal_OnPaint(MONTHCAL *pmc, HDC hdc)
  512. {
  513. if (hdc)
  514. {
  515. MCPaint(pmc, hdc);
  516. }
  517. else
  518. {
  519. PAINTSTRUCT ps;
  520. hdc = BeginPaint(pmc->ci.hwnd, &ps);
  521. MCPaint(pmc, hdc);
  522. EndPaint(pmc->ci.hwnd, &ps);
  523. }
  524. }
  525. BOOL MCGetDateFormatWithTempYear(PCALENDARTYPE pct, SYSTEMTIME *pst, LPCTSTR pszFormat, UINT uYear, LPTSTR pszBuf, UINT cchBuf)
  526. {
  527. BOOL fRc;
  528. WORD wYear = pst->wYear;
  529. pst->wYear = (WORD)uYear;
  530. fRc = GetDateFormat(pct->lcid, 0, pst, pszFormat, pszBuf, cchBuf);
  531. if (!fRc)
  532. {
  533. // AIGH! I hate Feburary 29. In case we are Feb 29 1996 and the
  534. // user changes to a non-leap year, force the day to something valid
  535. // in February 1997 (or whatever year the user finally picked).
  536. //
  537. // We can't blindly smash the day to 1 because the era might change
  538. // in the middle of the month.
  539. WORD wDay = pst->wDay;
  540. ASSERT(pst->wDay == 29);
  541. pst->wDay = 28;
  542. fRc = GetDateFormat(pct->lcid, 0, pst, pszFormat, pszBuf, cchBuf);
  543. pst->wDay = wDay;
  544. }
  545. pst->wYear = wYear;
  546. return fRc;
  547. }
  548. void MCUpdateEditYear(MONTHCAL *pmc)
  549. {
  550. TCHAR rgch[64];
  551. ASSERT(pmc->hwndEdit);
  552. EVAL(MCGetDateFormatWithTempYear(&pmc->ct, &pmc->st, TEXT("yyyy"), pmc->st.wYear, rgch, ARRAYSIZE(rgch)));
  553. SendMessage(pmc->hwndEdit, WM_SETTEXT, 0, (LPARAM)rgch);
  554. }
  555. LRESULT CALLBACK MonthCalWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  556. {
  557. MONTHCAL *pmc;
  558. LRESULT lres = 0;
  559. if (uMsg == WM_NCCREATE)
  560. return(MCNcCreateHandler(hwnd));
  561. pmc = MonthCal_GetPtr(hwnd);
  562. if (pmc == NULL)
  563. return(DefWindowProc(hwnd, uMsg, wParam, lParam));
  564. // Dispatch the various messages we can receive
  565. switch (uMsg)
  566. {
  567. case WM_CREATE:
  568. lres = MCCreateHandler(pmc, hwnd, (LPCREATESTRUCT)lParam);
  569. break;
  570. HANDLE_MSG(pmc, WM_ERASEBKGND, MCHandleEraseBkgnd);
  571. case WM_PRINTCLIENT:
  572. case WM_PAINT:
  573. MonthCal_OnPaint(pmc, (HDC)wParam);
  574. return(0);
  575. case WM_KEYDOWN:
  576. MCHandleKeydown(pmc, wParam, lParam);
  577. break;
  578. case WM_KEYUP:
  579. switch (wParam)
  580. {
  581. case VK_CONTROL:
  582. pmc->fControl = FALSE;
  583. break;
  584. case VK_SHIFT:
  585. pmc->fShift = FALSE;
  586. break;
  587. }
  588. break;
  589. #if 0
  590. case WM_CHAR:
  591. MCHandleChar(pmc, wParam, lParam);
  592. break;
  593. #endif
  594. case WM_CONTEXTMENU:
  595. MCContextMenu(pmc, wParam, lParam);
  596. break;
  597. case WM_LBUTTONDOWN:
  598. MCLButtonDown(pmc, wParam, lParam);
  599. break;
  600. case WM_LBUTTONUP:
  601. MCLButtonUp(pmc, wParam, lParam);
  602. break;
  603. case WM_MOUSEMOVE:
  604. MCMouseMove(pmc, wParam, lParam);
  605. break;
  606. case WM_GETFONT:
  607. lres = (LRESULT)pmc->hfont;
  608. break;
  609. case WM_SETFONT:
  610. MCHandleSetFont(pmc, (HFONT)wParam, (BOOL)LOWORD(lParam));
  611. MCSizeHandler(pmc, &pmc->rc);
  612. MCUpdateMonthNamePos(pmc);
  613. break;
  614. case WM_TIMER:
  615. MCHandleTimer(pmc, wParam);
  616. break;
  617. case WM_NCDESTROY:
  618. MCNcDestroyHandler(hwnd, pmc, wParam, lParam);
  619. break;
  620. case WM_ENABLE:
  621. {
  622. BOOL fEnable = wParam ? TRUE:FALSE;
  623. if (pmc->fEnabled != fEnable)
  624. {
  625. pmc->fEnabled = (WORD) fEnable;
  626. InvalidateRect(pmc->ci.hwnd, NULL, TRUE);
  627. }
  628. break;
  629. }
  630. case MCMP_WINDOWPOSCHANGED:
  631. case WM_SIZE:
  632. {
  633. RECT rc;
  634. if (uMsg==MCMP_WINDOWPOSCHANGED)
  635. {
  636. GetClientRect(pmc->ci.hwnd, &rc);
  637. }
  638. else
  639. {
  640. rc.left = 0;
  641. rc.top = 0;
  642. rc.right = GET_X_LPARAM(lParam);
  643. rc.bottom = GET_Y_LPARAM(lParam);
  644. }
  645. lres = MCSizeHandler(pmc, &rc);
  646. break;
  647. }
  648. case WM_CANCELMODE:
  649. PostMessage(pmc->ci.hwnd, WM_LBUTTONUP, 0, 0xFFFFFFFF);
  650. break;
  651. case WM_SYSCOLORCHANGE:
  652. InitGlobalColors();
  653. break;
  654. case WM_WININICHANGE:
  655. InitGlobalMetrics(wParam);
  656. if (lParam == 0 ||
  657. #ifdef UNICODE_WIN9x
  658. !lstrcmpiA((LPSTR)lParam, "Intl")
  659. #else
  660. !lstrcmpi((LPTSTR)lParam, TEXT("Intl"))
  661. #endif
  662. )
  663. {
  664. UpdateLocaleInfo(pmc, &pmc->li);
  665. MCReloadMenus(pmc);
  666. InvalidateRect(hwnd, NULL, TRUE);
  667. wParam = 0; // force MCCalcSizes to happen
  668. }
  669. if (wParam == 0 || wParam == SPI_SETNONCLIENTMETRICS)
  670. {
  671. MCCalcSizes(pmc);
  672. PostMessage(pmc->ci.hwnd, MCMP_WINDOWPOSCHANGED, 0, 0);
  673. }
  674. break;
  675. case WM_THEMECHANGED:
  676. if (pmc->hThemeScroll)
  677. {
  678. CloseThemeData(pmc->hThemeScroll);
  679. }
  680. pmc->hThemeScroll = OpenThemeData(pmc->ci.hwnd, L"ScrollBar");
  681. InvalidateRect(pmc->ci.hwnd, NULL, TRUE);
  682. break;
  683. case WM_NOTIFYFORMAT:
  684. return CIHandleNotifyFormat(&pmc->ci, lParam);
  685. break;
  686. case WM_STYLECHANGING:
  687. lres = MCOnStyleChanging(pmc, (UINT) wParam, (LPSTYLESTRUCT)lParam);
  688. break;
  689. case WM_STYLECHANGED:
  690. lres = MCOnStyleChanged(pmc, (UINT) wParam, (LPSTYLESTRUCT)lParam);
  691. break;
  692. case WM_NOTIFY: {
  693. LPNMHDR pnm = (LPNMHDR)lParam;
  694. switch (pnm->code)
  695. {
  696. case UDN_DELTAPOS:
  697. if (pnm->hwndFrom == pmc->hwndUD)
  698. {
  699. // A notification from the UpDown control buddied
  700. // with the currently popped up monthcal, adjust the
  701. // edit box appropriately. We use UDN_DELTAPOS instad
  702. // of WM_VSCROLL because we care only about the delta and
  703. // not the absolute number. The absolute number causes us
  704. // problems in localized calendars.
  705. LPNM_UPDOWN pnmdp = (LPNM_UPDOWN)lParam;
  706. UINT yr = pmc->st.wYear + pnmdp->iDelta;
  707. UINT yrMin, yrMax;
  708. int delta;
  709. yrMin = pmc->stMin.wYear;
  710. if (yr < yrMin)
  711. yr = yrMin;
  712. yrMax = pmc->stMax.wYear;
  713. if (yr > yrMax)
  714. yr = yrMax;
  715. delta = yr - pmc->st.wYear;
  716. pmc->st.wYear = (WORD)yr;
  717. if (delta) {
  718. MCIncrStartMonth(pmc, delta * 12, FALSE);
  719. MCNotifySelChange(pmc,MCN_SELCHANGE);
  720. }
  721. }
  722. break;
  723. }
  724. } // WM_NOTIFY switch
  725. break;
  726. case WM_VSCROLL:
  727. // this must be coming from our UpDown control buddied
  728. // with the currently popped up monthcal, adjust the
  729. // edit box appropriately
  730. // We must do this on WM_VSCROLL rather than UDN_DELTAPOS
  731. // since we need to fix the selection after the updown mangled it
  732. MCUpdateEditYear(pmc);
  733. break;
  734. //
  735. // MONTHCAL specific messages
  736. //
  737. // MCM_GETCURSEL wParam=void lParam=LPSYSTEMTIME
  738. // sets *lParam to the currently selected SYSTEMTIME
  739. // returns TRUE on success, FALSE on error (such as multi-select MONTHCAL)
  740. case MCM_GETCURSEL:
  741. if (!MonthCal_IsMultiSelect(pmc))
  742. {
  743. LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
  744. if (pst)
  745. {
  746. ZeroMemory(pst, sizeof(SYSTEMTIME));
  747. // Bug#94368 raymondc v6. Need to zero out the time fields instead of
  748. // setting them to garbage. This confuses MFC.
  749. *pst = pmc->st;
  750. pst->wDayOfWeek = (DowFromDate(pst)+1) % 7; // this returns 0==sun
  751. lres = 1;
  752. }
  753. }
  754. break;
  755. // MCM_SETCURSEL wParam=void lParam=LPSYSTEMTIME
  756. // sets the currently selected SYSTEMTIME to *lParam
  757. // returns TRUE on success, FALSE on error (such as multi-select MONTHCAL or bad parameters)
  758. case MCM_SETCURSEL:
  759. {
  760. LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
  761. if (MonthCal_IsMultiSelect(pmc) ||
  762. !IsValidDate(pst))
  763. {
  764. break;
  765. }
  766. if (0 == CmpDate(pst, &pmc->st))
  767. {
  768. // if no change, just return
  769. lres = 1;
  770. break;
  771. }
  772. pmc->rcDayOld = pmc->rcDayCur;
  773. pmc->fNoNotify = TRUE;
  774. lres = MCSetDate(pmc, pst);
  775. pmc->fNoNotify = FALSE;
  776. if (lres)
  777. {
  778. InvalidateRect(pmc->ci.hwnd, &pmc->rcDayOld, FALSE); // erase old highlight
  779. InvalidateRect(pmc->ci.hwnd, &pmc->rcDayCur, FALSE); // draw new highlight
  780. }
  781. UpdateWindow(pmc->ci.hwnd);
  782. break;
  783. }
  784. // MCM_GETMAXSELCOUNT wParam=void lParam=void
  785. // returns the max number of selected days allowed
  786. case MCM_GETMAXSELCOUNT:
  787. lres = (LRESULT)(MonthCal_IsMultiSelect(pmc) ? pmc->cSelMax : 1);
  788. break;
  789. // MCM_SETMAXSELCOUNT wParam=int lParam=void
  790. // sets the maximum selectable date range to wParam days
  791. // returns TRUE on success, FALSE on error (such as single-select MONTHCAL)
  792. case MCM_SETMAXSELCOUNT:
  793. if (!MonthCal_IsMultiSelect(pmc) || (int)wParam < 1)
  794. break;
  795. pmc->cSelMax = (int)wParam;
  796. lres = 1;
  797. break;
  798. // MCM_GETSELRANGE wParam=void lParam=LPSYSTEMTIME[2]
  799. // sets *lParam to the first date of the range, *(lParam+1) to the second date
  800. // returns TRUE on success, FALSE otherwise (such as single-select MONTHCAL)
  801. case MCM_GETSELRANGE:
  802. {
  803. LPSYSTEMTIME pst;
  804. pst = (LPSYSTEMTIME)lParam;
  805. if (!pst)
  806. break;
  807. ZeroMemory(pst, sizeof(SYSTEMTIME) * 2);
  808. if (!MonthCal_IsMultiSelect(pmc))
  809. break;
  810. *pst = pmc->st;
  811. pst->wDayOfWeek = (DowFromDate(pst)+1) % 7; // this returns 0==sun
  812. pst++;
  813. *pst = pmc->stEndSel;
  814. pst->wDayOfWeek = (DowFromDate(pst)+1) % 7; // this returns 0==sun
  815. lres = 1;
  816. break;
  817. }
  818. // MCM_SETSELRANGE wParam=void lParam=LPSYSTEMTIME[2]
  819. // sets the currently selected day range to *lparam to *(lParam+1)
  820. // returns TRUE on success, FALSE otherwise (such as single-select MONTHCAL or bad params)
  821. case MCM_SETSELRANGE:
  822. {
  823. LPSYSTEMTIME pstStart = (LPSYSTEMTIME)lParam;
  824. LPSYSTEMTIME pstEnd = &pstStart[1];
  825. SYSTEMTIME stStart;
  826. SYSTEMTIME stEnd;
  827. if (!MonthCal_IsMultiSelect(pmc) ||
  828. !IsValidDate(pstStart) ||
  829. !IsValidDate(pstEnd))
  830. break;
  831. // IE3 shipped without validating the time portion of this message.
  832. // Make sure our stored systemtimes are always valid (so we will
  833. // always give out valid systemtime structs).
  834. //
  835. if (!IsValidTime(pstStart))
  836. CopyTime(pmc->st, *pstStart);
  837. if (!IsValidTime(pstEnd))
  838. CopyTime(pmc->stEndSel, *pstEnd);
  839. if (CmpDate(pstStart, pstEnd) > 0)
  840. {
  841. stEnd = *pstStart;
  842. stStart = *pstEnd;
  843. pstStart = &stStart;
  844. pstEnd = &stEnd;
  845. }
  846. if (CmpDate(pstStart, &pmc->stMin) < 0)
  847. break;
  848. if (CmpDate(pstEnd, &pmc->stMax) > 0)
  849. break;
  850. if (DaysBetweenDates(pstStart, pstEnd) >= pmc->cSelMax)
  851. break;
  852. if (0 == CmpDate(pstStart, &pmc->st) &&
  853. 0 == CmpDate(pstEnd, &pmc->stEndSel))
  854. {
  855. // if no change, just return
  856. lres = 1;
  857. break;
  858. }
  859. pmc->stStartPrev = pmc->st;
  860. pmc->stEndPrev = pmc->stEndSel;
  861. pmc->fNoNotify = TRUE;
  862. lres = MCSetDate(pmc, pstEnd);
  863. if (lres)
  864. {
  865. pmc->st = *pstStart;
  866. pmc->stEndSel = *pstEnd;
  867. MCInvalidateDates(pmc, &pmc->stStartPrev, &pmc->stEndPrev);
  868. MCInvalidateDates(pmc, &pmc->st, &pmc->stEndSel);
  869. UpdateWindow(pmc->ci.hwnd);
  870. }
  871. pmc->fNoNotify = FALSE;
  872. break;
  873. }
  874. // MCM_GETMONTHRANGE wParam=GMR_flags lParam=LPSYSTEMTIME[2]
  875. // if GMR_VISIBLE, returns the range of selectable (non-grayed) displayed
  876. // days. if GMR_DAYSTATE, returns the range of every (incl grayed) days.
  877. // returns the number of months the above range spans.
  878. case MCM_GETMONTHRANGE:
  879. {
  880. LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
  881. if (pst)
  882. {
  883. ZeroMemory(pst, 2 * sizeof(SYSTEMTIME));
  884. if (wParam == GMR_VISIBLE)
  885. {
  886. pst[0] = pmc->stMonthFirst;
  887. pst[1] = pmc->stMonthLast;
  888. }
  889. else if (wParam == GMR_DAYSTATE)
  890. {
  891. pst[0] = pmc->stViewFirst;
  892. pst[1] = pmc->stViewLast;
  893. }
  894. }
  895. lres = (LRESULT)pmc->nMonths;
  896. if (wParam == GMR_DAYSTATE)
  897. lres += 2;
  898. break;
  899. }
  900. // MCM_SETDAYSTATE wParam=int lParam=LPDAYSTATE
  901. // updates the MONTHCAL's DAYSTATE, only for MONTHCALs with DAYSTATE enabled
  902. // the range of months represented in the DAYSTATE array passed in lParam
  903. // should match that of the MONTHCAL
  904. // wParam count of items in DAYSTATE array
  905. // lParam pointer to array of DAYSTATE items
  906. // returns FALSE if not DAYSTATE enabled or if an error occurs, TRUE otherwise
  907. case MCM_SETDAYSTATE:
  908. {
  909. MONTHDAYSTATE *pmds = (MONTHDAYSTATE *)lParam;
  910. int i;
  911. if (!MonthCal_IsDayState(pmc) ||
  912. (int)wParam != (pmc->nMonths + 2))
  913. break;
  914. for (i = 0; i < (int)wParam; i++)
  915. {
  916. pmc->rgdayState[i] = *pmds;
  917. pmds++;
  918. }
  919. MCInvalidateMonthDays(pmc);
  920. lres = 1;
  921. break;
  922. }
  923. // MCM_GETMINREQRECT wParam=void lParam=LPRECT
  924. // sets *lParam to the minimum size required to display one month in full.
  925. // Note: this is dependent upon the currently selected font.
  926. // Apps can take the returned size and double the width to get two calendars
  927. // displayed.
  928. case MCM_GETMINREQRECT:
  929. {
  930. LPRECT prc = (LPRECT)lParam;
  931. prc->left = 0;
  932. prc->top = 0;
  933. prc->right = pmc->dxMonth;
  934. prc->bottom = pmc->dyMonth;
  935. if (MonthCal_ShowToday(pmc))
  936. {
  937. prc->bottom += pmc->dyToday;
  938. }
  939. AdjustWindowRect(prc, pmc->ci.style, FALSE);
  940. // This is a bogus message, lParam should really be LPSIZE.
  941. // Make sure left and top are 0 (AdjustWindowRect will make these negative).
  942. prc->right -= prc->left;
  943. prc->bottom -= prc->top;
  944. prc->left = 0;
  945. prc->top = 0;
  946. lres = 1;
  947. break;
  948. }
  949. // MCM_GETMAXTODAYWIDTH wParam=void lParam=LPDWORD
  950. // sets *lParam to the width of the "today" string, so apps
  951. // can figure out how big to make the calendar (max of MCM_GETMINREQRECT
  952. // and MCM_GETMAXTODAYWIDTH).
  953. case MCM_GETMAXTODAYWIDTH:
  954. {
  955. RECT rc;
  956. rc.left = 0;
  957. rc.top = 0;
  958. rc.right = pmc->dxToday;
  959. rc.bottom = pmc->dyToday;
  960. AdjustWindowRect(&rc, pmc->ci.style, FALSE);
  961. lres = rc.right - rc.left;
  962. break;
  963. }
  964. case MCM_HITTEST:
  965. return MCHandleHitTest(pmc, (PMCHITTESTINFO)lParam);
  966. case MCM_SETCOLOR:
  967. if (wParam < MCSC_COLORCOUNT)
  968. {
  969. COLORREF clr = pmc->clr[wParam];
  970. pmc->clr[wParam] = (COLORREF)lParam;
  971. InvalidateRect(hwnd, NULL, wParam == MCSC_BACKGROUND);
  972. return clr;
  973. }
  974. return -1;
  975. case MCM_GETCOLOR:
  976. if (wParam < MCSC_COLORCOUNT)
  977. return pmc->clr[wParam];
  978. return -1;
  979. case MCM_SETFIRSTDAYOFWEEK:
  980. {
  981. lres = MAKELONG(pmc->li.dowStartWeek, (BOOL)pmc->fFirstDowSet);
  982. if (lParam == (LPARAM)-1) {
  983. pmc->fFirstDowSet = FALSE;
  984. } else if (lParam < 7) {
  985. pmc->fFirstDowSet = TRUE;
  986. pmc->li.dowStartWeek = (TCHAR)lParam;
  987. }
  988. UpdateLocaleInfo(pmc, &pmc->li);
  989. InvalidateRect(hwnd, NULL, FALSE);
  990. return lres;
  991. }
  992. case MCM_GETFIRSTDAYOFWEEK:
  993. return MAKELONG(pmc->li.dowStartWeek, (BOOL)pmc->fFirstDowSet);
  994. case MCM_SETTODAY:
  995. MCSetToday(pmc, (SYSTEMTIME*)lParam);
  996. break;
  997. case MCM_GETTODAY:
  998. if (lParam) {
  999. *((SYSTEMTIME*)lParam) = pmc->stToday;
  1000. return TRUE;
  1001. }
  1002. return FALSE;
  1003. case MCM_GETRANGE:
  1004. if (lParam)
  1005. {
  1006. LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
  1007. ZeroMemory(pst, sizeof(SYSTEMTIME)*2);
  1008. ASSERT(lres == 0);
  1009. if (pmc->fMinYrSet)
  1010. {
  1011. pst[0] = pmc->stMin;
  1012. lres = GDTR_MIN;
  1013. }
  1014. if (pmc->fMaxYrSet)
  1015. {
  1016. pst[1] = pmc->stMax;
  1017. lres |= GDTR_MAX;
  1018. }
  1019. }
  1020. break;
  1021. case MCM_SETRANGE:
  1022. if (lParam)
  1023. {
  1024. LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
  1025. if (((wParam & GDTR_MIN) && !IsValidDate(pst)) ||
  1026. ((wParam & GDTR_MAX) && !IsValidDate(&pst[1])))
  1027. break;
  1028. // IE3 did not validate the time portion of this struct
  1029. // use stToday time fields cuz pmc->stMin/Max may be zero
  1030. if ((wParam & GDTR_MIN) && !IsValidTime(pst))
  1031. CopyTime(pmc->stToday, pst[0]);
  1032. if ((wParam & GDTR_MAX) && !IsValidTime(&pst[1]))
  1033. CopyTime(pmc->stToday, pst[1]);
  1034. if (wParam & GDTR_MIN)
  1035. {
  1036. pmc->stMin = *pst;
  1037. pmc->fMinYrSet = TRUE;
  1038. }
  1039. else
  1040. {
  1041. pmc->stMin = c_stEpoch;
  1042. pmc->fMinYrSet = FALSE;
  1043. }
  1044. pst++;
  1045. if (wParam & GDTR_MAX)
  1046. {
  1047. pmc->stMax = *pst;
  1048. pmc->fMaxYrSet = TRUE;
  1049. }
  1050. else
  1051. {
  1052. pmc->stMax = c_stArmageddon;
  1053. pmc->fMaxYrSet = FALSE;
  1054. }
  1055. if (pmc->fMaxYrSet && pmc->fMinYrSet && CmpDate(&pmc->stMin, &pmc->stMax) > 0)
  1056. {
  1057. SYSTEMTIME stTemp = pmc->stMin;
  1058. pmc->stMin = pmc->stMax;
  1059. pmc->stMax = stTemp;
  1060. }
  1061. lres = TRUE;
  1062. }
  1063. break;
  1064. case MCM_GETMONTHDELTA:
  1065. if (pmc->fMonthDelta)
  1066. lres = pmc->nMonthDelta;
  1067. else
  1068. lres = pmc->nMonths;
  1069. break;
  1070. case MCM_SETMONTHDELTA:
  1071. if (pmc->fMonthDelta)
  1072. lres = pmc->nMonthDelta;
  1073. else
  1074. lres = 0;
  1075. if ((int)wParam==0)
  1076. pmc->fMonthDelta = FALSE;
  1077. else
  1078. {
  1079. pmc->fMonthDelta = TRUE;
  1080. pmc->nMonthDelta = (int)wParam;
  1081. }
  1082. break;
  1083. default:
  1084. if (CCWndProc(&pmc->ci, uMsg, wParam, lParam, &lres))
  1085. return lres;
  1086. lres = DefWindowProc(hwnd, uMsg, wParam, lParam);
  1087. break;
  1088. } /* switch (uMsg) */
  1089. return(lres);
  1090. }
  1091. LRESULT MCNcCreateHandler(HWND hwnd)
  1092. {
  1093. MONTHCAL *pmc;
  1094. // Allocate storage for the dtpick structure
  1095. pmc = (MONTHCAL *)NearAlloc(sizeof(MONTHCAL));
  1096. if (!pmc)
  1097. {
  1098. DebugMsg(DM_WARNING, TEXT("mc: Out Of Near Memory"));
  1099. return(0L);
  1100. }
  1101. MonthCal_SetPtr(hwnd, pmc);
  1102. return(1L);
  1103. }
  1104. void MCInitColorArray(COLORREF* pclr)
  1105. {
  1106. pclr[MCSC_BACKGROUND] = g_clrWindow;
  1107. pclr[MCSC_MONTHBK] = g_clrWindow;
  1108. pclr[MCSC_TEXT] = g_clrWindowText;
  1109. pclr[MCSC_TITLEBK] = GetSysColor(COLOR_ACTIVECAPTION);
  1110. pclr[MCSC_TITLETEXT] = GetSysColor(COLOR_CAPTIONTEXT);
  1111. pclr[MCSC_TRAILINGTEXT] = g_clrGrayText;
  1112. }
  1113. LRESULT MCCreateHandler(MONTHCAL *pmc, HWND hwnd, LPCREATESTRUCT lpcs)
  1114. {
  1115. HFONT hfont;
  1116. SYSTEMTIME st;
  1117. // Validate data
  1118. //
  1119. if (lpcs->style & MCS_INVALIDBITS)
  1120. return(-1);
  1121. CIInitialize(&pmc->ci, hwnd, lpcs);
  1122. UpdateLocaleInfo(pmc, &pmc->li);
  1123. // Initialize our data.
  1124. //
  1125. pmc->hinstance = lpcs->hInstance;
  1126. pmc->fEnabled = !(pmc->ci.style & WS_DISABLED);
  1127. pmc->hbrushToday = CreateSolidBrush(CAL_COLOR_TODAY);
  1128. MCReloadMenus(pmc);
  1129. // Default minimum date is the epoch
  1130. pmc->stMin = c_stEpoch;
  1131. // Default maximum date is armageddon
  1132. pmc->stMax = c_stArmageddon;
  1133. GetLocalTime(&pmc->stToday);
  1134. pmc->st = pmc->stToday;
  1135. if (MonthCal_IsMultiSelect(pmc))
  1136. pmc->stEndSel = pmc->st;
  1137. // make sure the time portions of these are valid. they are never
  1138. // touched after this point
  1139. pmc->stMonthFirst = pmc->st;
  1140. pmc->stMonthLast = pmc->st;
  1141. pmc->stViewFirst = pmc->st;
  1142. pmc->stViewLast = pmc->st;
  1143. pmc->cSelMax = CAL_DEF_SELMAX;
  1144. hfont = NULL;
  1145. if (lpcs->hwndParent)
  1146. hfont = (HFONT)SendMessage(lpcs->hwndParent, WM_GETFONT, 0, 0);
  1147. if (hfont == NULL)
  1148. hfont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
  1149. MCHandleSetFont(pmc, hfont, FALSE);
  1150. CopyDate(pmc->st, st);
  1151. // Can we start at January?
  1152. if (st.wMonth <= (pmc->nViewRows * pmc->nViewCols))
  1153. st.wMonth = 1;
  1154. MCUpdateStartEndDates(pmc, &st);
  1155. pmc->idTimerToday = SetTimer(pmc->ci.hwnd, CAL_TODAYTIMER, CAL_SECTODAYTIMER * 1000, NULL);
  1156. MCInitColorArray(pmc->clr);
  1157. pmc->hThemeScroll = OpenThemeData(pmc->ci.hwnd, L"ScrollBar");
  1158. return(0);
  1159. }
  1160. LRESULT MCOnStyleChanging(MONTHCAL *pmc, UINT gwl, LPSTYLESTRUCT pinfo)
  1161. {
  1162. if (gwl == GWL_STYLE)
  1163. {
  1164. DWORD changeFlags = pmc->ci.style ^ pinfo->styleNew;
  1165. // Don't allow these bits to change
  1166. changeFlags &= MCS_MULTISELECT | MCS_DAYSTATE | MCS_INVALIDBITS;
  1167. pinfo->styleNew ^= changeFlags;
  1168. }
  1169. return(0);
  1170. }
  1171. LRESULT MCOnStyleChanged(MONTHCAL *pmc, UINT gwl, LPSTYLESTRUCT pinfo)
  1172. {
  1173. if (gwl == GWL_STYLE)
  1174. {
  1175. DWORD changeFlags = pmc->ci.style ^ pinfo->styleNew;
  1176. ASSERT(!(changeFlags & (MCS_MULTISELECT|MCS_DAYSTATE|MCS_INVALIDBITS)));
  1177. pmc->ci.style = pinfo->styleNew;
  1178. if (changeFlags & MCS_WEEKNUMBERS)
  1179. {
  1180. MCCalcSizes(pmc);
  1181. MCUpdateRcDayCur(pmc, &pmc->st);
  1182. //MCUpdateToday(pmc);
  1183. }
  1184. // save a touch of code and share the MCUpdateToday
  1185. // call with MCS_WEEKNUMBERS above
  1186. if (changeFlags & (MCS_NOTODAY|MCS_NOTODAYCIRCLE|MCS_WEEKNUMBERS))
  1187. {
  1188. MCUpdateToday(pmc);
  1189. }
  1190. if (changeFlags & (WS_BORDER | WS_CAPTION | WS_THICKFRAME)) {
  1191. // the changing of these bits affect the size of the window
  1192. // but not until after this message is handled
  1193. // so post ourself a message.
  1194. PostMessage(pmc->ci.hwnd, MCMP_WINDOWPOSCHANGED, 0, 0);
  1195. }
  1196. if (changeFlags)
  1197. InvalidateRect(pmc->ci.hwnd, NULL, TRUE);
  1198. }
  1199. else if (gwl == GWL_EXSTYLE)
  1200. {
  1201. if ((pinfo->styleOld ^ pinfo->styleNew) & RTL_MIRRORED_WINDOW)
  1202. {
  1203. MCUpdateMonthNamePos(pmc);
  1204. }
  1205. }
  1206. return(0);
  1207. }
  1208. void MCCalcSizes(MONTHCAL *pmc)
  1209. {
  1210. HDC hdc;
  1211. HFONT hfontOrig;
  1212. int i, dxMax, dyMax, dxExtra;
  1213. RECT rect;
  1214. TCHAR szBuf[128];
  1215. TCHAR szDateFmt[64];
  1216. // get sizing info for bold font...
  1217. hdc = GetDC(pmc->ci.hwnd);
  1218. hfontOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
  1219. MGetTextExtent(hdc, g_szTextExtentDef, 2, &dxMax, &dyMax);
  1220. MGetTextExtent(hdc, g_szTextExtentDef, 4, &pmc->dxYearMax, NULL);
  1221. GetDateFormat(pmc->ct.lcid, DATE_SHORTDATE, &pmc->stToday,
  1222. NULL, szDateFmt, ARRAYSIZE(szDateFmt));
  1223. wsprintf(szBuf,TEXT("%s %s"),pmc->li.szToday,szDateFmt);
  1224. MGetTextExtent(hdc, szBuf, -1, &pmc->dxToday, &pmc->dyToday);
  1225. // REARCHITECT raymondc - hard-coded numbers are accessibility-incompatible
  1226. pmc->dyToday += 4;
  1227. //
  1228. // Cache these values so we don't go wacko if the app fails to
  1229. // forward WM_WININCHANGE messages into us and the user changes
  1230. // scrollbar widths. We'll draw with the wrong width, but at
  1231. // least they will be consistently wrong.
  1232. //
  1233. pmc->dxArrowMargin = DX_ARROWMARGIN;
  1234. pmc->dxCalArrow = DX_CALARROW;
  1235. pmc->dyCalArrow = DY_CALARROW;
  1236. //
  1237. // The banner bar consists of
  1238. //
  1239. // margin + scrollbutton + spacer +
  1240. // MonthName yyyy +
  1241. // + spacer + scrollbutton + margin
  1242. //
  1243. // Margin is dxArrowMargin
  1244. //
  1245. // Scrollbutton = dxCalArrow
  1246. //
  1247. // Spacer = border + CXVSCROLL + border
  1248. //
  1249. // The spacer needs to be large enough for us to insert an updown
  1250. // control when it comes time to spin the year. We don't need to
  1251. // cache the spacer anywhere - its value is implicit from the others.
  1252. //
  1253. // The actual width is divided by the number of columns we need
  1254. // (typically 7, but perhaps 8 if we are also displaying week numbers).
  1255. //
  1256. // We round the division down - later, we'll add some random futz
  1257. // to compensate.
  1258. //
  1259. dxExtra = pmc->dxArrowMargin + pmc->dxCalArrow +
  1260. (g_cxBorder + g_cxVScroll + g_cxBorder);
  1261. dxExtra = dxExtra + dxExtra; // left + right
  1262. for (i = 0; i < 12; i++)
  1263. {
  1264. int dxTemp;
  1265. // REARCHITECT raymondc - not localization safe for languages which change
  1266. // month forms based on context
  1267. wsprintf(szBuf,TEXT("%s %s"),pmc->li.rgszMonth[i],g_szTextExtentDef);
  1268. MGetTextExtent(hdc, szBuf, -1, &dxTemp, NULL);
  1269. dxTemp += dxExtra;
  1270. dxTemp = dxTemp / (CALCOLMAX + (MonthCal_ShowWeekNumbers(pmc) ? 1:0));
  1271. if (dxTemp > dxMax)
  1272. dxMax = dxTemp;
  1273. }
  1274. SelectObject(hdc, (HGDIOBJ)pmc->hfont);
  1275. for (i = 0; i < 7; i++)
  1276. {
  1277. SIZE size;
  1278. MGetTextExtent(hdc, pmc->li.rgszDay[i], -1, (LPINT)&size.cx, (LPINT)&size.cy);
  1279. if (size.cx > dxMax)
  1280. dxMax = size.cx;
  1281. if (size.cy > dyMax)
  1282. dyMax = size.cy;
  1283. }
  1284. if (dyMax < pmc->dyCalArrow / 2)
  1285. dyMax = pmc->dyCalArrow / 2;
  1286. SelectObject(hdc, (HGDIOBJ)hfontOrig);
  1287. ReleaseDC(pmc->ci.hwnd, hdc);
  1288. pmc->dxCol = dxMax + 2;
  1289. pmc->dyRow = dyMax + 2;
  1290. pmc->dxMonth = pmc->dxCol * (CALCOLMAX + (MonthCal_ShowWeekNumbers(pmc) ? 1:0)) + 1;
  1291. pmc->dyMonth = pmc->dyRow * (CALROWMAX + 3) + 1; // we add 2 for the month name and day names
  1292. pmc->dxToday += pmc->dxCol+6+CALBORDER; // +2 for -1 at ends and 4 for shift of circle
  1293. if (pmc->dxMonth > pmc->dxToday)
  1294. pmc->dxToday = pmc->dxMonth;
  1295. // Space for month name (tile bar area of each month)
  1296. pmc->rcMonthName.left = 0;
  1297. pmc->rcMonthName.top = 0;
  1298. pmc->rcMonthName.right = pmc->dxMonth;
  1299. pmc->rcMonthName.bottom = pmc->rcMonthName.top + (pmc->dyRow * 2);
  1300. // Space for day-of-week
  1301. pmc->rcDow.left = 0;
  1302. pmc->rcDow.top = pmc->rcMonthName.bottom;
  1303. pmc->rcDow.right = pmc->dxMonth;
  1304. pmc->rcDow.bottom = pmc->rcDow.top + pmc->dyRow;
  1305. // Space for week numbers
  1306. if (MonthCal_ShowWeekNumbers(pmc))
  1307. {
  1308. pmc->rcWeekNum.left = pmc->rcDow.left;
  1309. pmc->rcWeekNum.top = pmc->rcDow.bottom;
  1310. pmc->rcWeekNum.right = pmc->rcWeekNum.left + pmc->dxCol;
  1311. pmc->rcWeekNum.bottom = pmc->dyMonth;
  1312. pmc->rcDow.left += pmc->dxCol; // shift days of week
  1313. }
  1314. // Space for the day numbers
  1315. pmc->rcDayNum.left = pmc->rcDow.left;
  1316. pmc->rcDayNum.top = pmc->rcDow.bottom;
  1317. pmc->rcDayNum.right = pmc->rcDayNum.left + (CALCOLMAX * pmc->dxCol);
  1318. pmc->rcDayNum.bottom = pmc->dyMonth;
  1319. GetClientRect(pmc->ci.hwnd, &rect);
  1320. MCRecomputeSizing(pmc, &rect);
  1321. }
  1322. void MCHandleSetFont(MONTHCAL *pmc, HFONT hfont, BOOL fRedraw)
  1323. {
  1324. LOGFONT lf;
  1325. HFONT hfontBold;
  1326. if (hfont == NULL)
  1327. hfont = (HFONT)GetStockObject(SYSTEM_FONT);
  1328. GetObject(hfont, sizeof(LOGFONT), (LPVOID)&lf);
  1329. // we want to make sure that the bold days are obviously different
  1330. // from the non-bold days...
  1331. CCAdjustForBold(&lf);
  1332. hfontBold = CreateFontIndirect(&lf);
  1333. if (hfontBold == NULL)
  1334. return;
  1335. if (pmc->hfontBold)
  1336. DeleteObject((HGDIOBJ)pmc->hfontBold);
  1337. pmc->hfont = hfont;
  1338. pmc->hfontBold = hfontBold;
  1339. pmc->ci.uiCodePage = GetCodePageForFont(hfont);
  1340. // calculate the new row and column sizes
  1341. MCCalcSizes(pmc);
  1342. if (fRedraw)
  1343. {
  1344. InvalidateRect(pmc->ci.hwnd, NULL, TRUE);
  1345. UpdateWindow(pmc->ci.hwnd);
  1346. }
  1347. }
  1348. #if 0 // why is this here? it's not called anywhere??
  1349. // Stolen from the windows tips help file
  1350. void DrawTransparentBitmap(HDC hdc, HBITMAP hbmp, RECT *prc, COLORREF cTransparentColor)
  1351. {
  1352. COLORREF cColor;
  1353. BITMAP bm;
  1354. HBITMAP hbmAndBack, hbmAndObject, hbmAndMem, hbmSave;
  1355. HGDIOBJ hbmBackOld, hbmObjectOld, hbmMemOld, hbmSaveOld;
  1356. HDC hdcMem, hdcBack, hdcObject, hdcTemp, hdcSave;
  1357. POINT ptSize;
  1358. int d;
  1359. hdcTemp = CreateCompatibleDC(hdc);
  1360. SelectObject(hdcTemp, (HGDIOBJ)hbmp); // Select the bitmap
  1361. GetObject(hbmp, sizeof(BITMAP), &bm);
  1362. ptSize.x = bm.bmWidth; // Get width of bitmap
  1363. ptSize.y = bm.bmHeight; // Get height of bitmap
  1364. DPtoLP(hdcTemp, &ptSize, 1); // Convert from device to logical points
  1365. d = prc->right - prc->left;
  1366. if (d < ptSize.x)
  1367. ptSize.x = d;
  1368. d = prc->bottom - prc->top;
  1369. if (d < ptSize.y)
  1370. ptSize.y = d;
  1371. // Create some DCs to hold temporary data.
  1372. hdcBack = CreateCompatibleDC(hdc);
  1373. hdcObject = CreateCompatibleDC(hdc);
  1374. hdcMem = CreateCompatibleDC(hdc);
  1375. hdcSave = CreateCompatibleDC(hdc);
  1376. // Create a bitmap for each DC. DCs are required for a number of
  1377. // GDI functions.
  1378. // Monochrome DC
  1379. hbmAndBack = CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
  1380. // Monochrome DC
  1381. hbmAndObject = CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
  1382. hbmAndMem = CreateCompatibleBitmap(hdc, ptSize.x, ptSize.y);
  1383. hbmSave = CreateCompatibleBitmap(hdc, ptSize.x, ptSize.y);
  1384. // Each DC must select a bitmap object to store pixel data.
  1385. hbmBackOld = SelectObject(hdcBack, (HGDIOBJ)hbmAndBack);
  1386. hbmObjectOld = SelectObject(hdcObject, (HGDIOBJ)hbmAndObject);
  1387. hbmMemOld = SelectObject(hdcMem, (HGDIOBJ)hbmAndMem);
  1388. hbmSaveOld = SelectObject(hdcSave, (HGDIOBJ)hbmSave);
  1389. // Set proper mapping mode.
  1390. SetMapMode(hdcTemp, GetMapMode(hdc));
  1391. // Save the bitmap sent here, because it will be overwritten.
  1392. BitBlt(hdcSave, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, SRCCOPY);
  1393. // Set the background color of the source DC to the color.
  1394. // contained in the parts of the bitmap that should be transparent
  1395. cColor = SetBkColor(hdcTemp, cTransparentColor);
  1396. // Create the object mask for the bitmap by performing a BitBlt
  1397. // from the source bitmap to a monochrome bitmap.
  1398. BitBlt(hdcObject, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, SRCCOPY);
  1399. // Set the background color of the source DC back to the original
  1400. // color.
  1401. SetBkColor(hdcTemp, cColor);
  1402. // Create the inverse of the object mask.
  1403. BitBlt(hdcBack, 0, 0, ptSize.x, ptSize.y, hdcObject, 0, 0, NOTSRCCOPY);
  1404. // Copy the background of the main DC to the destination.
  1405. BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdc, prc->left, prc->top, SRCCOPY);
  1406. // Mask out the places where the bitmap will be placed.
  1407. BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdcObject, 0, 0, SRCAND);
  1408. // Mask out the transparent colored pixels on the bitmap.
  1409. BitBlt(hdcTemp, 0, 0, ptSize.x, ptSize.y, hdcBack, 0, 0, SRCAND);
  1410. // XOR the bitmap with the background on the destination DC.
  1411. BitBlt(hdcMem, 0, 0, ptSize.x, ptSize.y, hdcTemp, 0, 0, SRCPAINT);
  1412. // Copy the destination to the screen.
  1413. BitBlt(hdc, prc->left, prc->top, ptSize.x, ptSize.y, hdcMem, 0, 0, SRCCOPY);
  1414. // Place the original bitmap back into the bitmap sent here.
  1415. BitBlt(hdcTemp, 0, 0, ptSize.x, ptSize.y, hdcSave, 0, 0, SRCCOPY);
  1416. // Delete the memory bitmaps.
  1417. SelectObject(hdcBack, hbmBackOld);
  1418. DeleteObject(hbmAndBack);
  1419. SelectObject(hdcObject, hbmObjectOld);
  1420. DeleteObject(hbmAndObject);
  1421. SelectObject(hdcMem, hbmMemOld);
  1422. DeleteObject(hbmAndMem);
  1423. SelectObject(hdcSave, hbmSaveOld);
  1424. DeleteObject(hbmSave);
  1425. // Delete the memory DCs.
  1426. DeleteDC(hdcMem);
  1427. DeleteDC(hdcBack);
  1428. DeleteDC(hdcObject);
  1429. DeleteDC(hdcSave);
  1430. DeleteDC(hdcTemp);
  1431. }
  1432. #endif // DEAD CODE
  1433. void MCDrawTodayCircle(MONTHCAL *pmc, HDC hdc, RECT *prc)
  1434. {
  1435. FrameRect(hdc, prc, pmc->hbrushToday);
  1436. }
  1437. void MCInvalidateMonthDays(MONTHCAL *pmc)
  1438. {
  1439. InvalidateRect(pmc->ci.hwnd, &pmc->rcCentered, FALSE);
  1440. }
  1441. void MCGetTodayBtnRect(MONTHCAL *pmc, RECT *prc)
  1442. {
  1443. if (pmc->dxToday > pmc->rcCentered.right - pmc->rcCentered.left)
  1444. {
  1445. prc->left = pmc->rc.left + 1;
  1446. prc->right = pmc->rc.right - 1;
  1447. }
  1448. else
  1449. {
  1450. prc->left = pmc->rcCentered.left + 1;
  1451. prc->right = pmc->rcCentered.right - 1;
  1452. }
  1453. prc->top = pmc->rcCentered.bottom - pmc->dyToday;
  1454. prc->bottom = pmc->rcCentered.bottom;
  1455. // center the today rect when we only have 1 col and it will fit in window
  1456. if ((pmc->nViewCols == 1) && (pmc->dxToday <= pmc->rc.right - pmc->rc.left))
  1457. {
  1458. int dx = ((pmc->rcCentered.right - pmc->rcCentered.left) - pmc->dxToday) / 2 - 1;
  1459. prc->left += dx;
  1460. prc->right -= dx;
  1461. }
  1462. }
  1463. void MCPaintArrowBtn(MONTHCAL *pmc, HDC hdc, BOOL fPrev, BOOL fPressed)
  1464. {
  1465. int iStateId;
  1466. LPRECT prc;
  1467. UINT dfcs;
  1468. if (fPrev)
  1469. {
  1470. iStateId = ABS_LEFTNORMAL;
  1471. dfcs = DFCS_SCROLLLEFT;
  1472. prc = &pmc->rcPrev;
  1473. }
  1474. else
  1475. {
  1476. iStateId = ABS_RIGHTNORMAL;
  1477. dfcs = DFCS_SCROLLRIGHT;
  1478. prc = &pmc->rcNext;
  1479. }
  1480. if (pmc->fEnabled)
  1481. {
  1482. if (fPressed)
  1483. {
  1484. iStateId += 2;
  1485. dfcs |= DFCS_PUSHED | DFCS_FLAT;
  1486. }
  1487. }
  1488. else
  1489. {
  1490. iStateId += 3; // Disabled
  1491. dfcs |= DFCS_INACTIVE;
  1492. }
  1493. if (pmc->hThemeScroll)
  1494. {
  1495. DrawThemeBackground(pmc->hThemeScroll, hdc, SBP_ARROWBTN, iStateId, prc, 0);
  1496. }
  1497. else
  1498. {
  1499. DrawFrameControl(hdc, prc, DFC_SCROLL, dfcs);
  1500. }
  1501. }
  1502. void MCPaint(MONTHCAL *pmc, HDC hdc)
  1503. {
  1504. RECT rc, rcT;
  1505. int irow, icol, iMonth, iYear, iIndex, dx, dy;
  1506. HBRUSH hbrSelect;
  1507. HGDIOBJ hgdiOrig, hpenOrig;
  1508. pmc->hpen = CreatePen(PS_SOLID, 0, pmc->clr[MCSC_TEXT]);
  1509. hbrSelect = CreateSolidBrush(pmc->clr[MCSC_TITLEBK]);
  1510. SetBkMode(hdc, TRANSPARENT);
  1511. SetTextColor(hdc, pmc->clr[MCSC_TEXT]);
  1512. hpenOrig = SelectObject(hdc, GetStockObject(BLACK_PEN));
  1513. rc = pmc->rcCentered;
  1514. FillRectClr(hdc, &rc, pmc->clr[MCSC_MONTHBK]);
  1515. SelectObject(hdc, (HGDIOBJ)pmc->hpen);
  1516. // get the place for top left month
  1517. rc.left = pmc->rcCentered.left;
  1518. rc.right = rc.left + pmc->dxMonth;
  1519. rc.top = pmc->rcCentered.top;
  1520. rc.bottom = rc.top + pmc->dyMonth;
  1521. iMonth = pmc->stMonthFirst.wMonth;
  1522. iYear = pmc->stMonthFirst.wYear;
  1523. dx = pmc->dxMonth + CALBORDER;
  1524. dy = pmc->dyMonth + CALBORDER;
  1525. iIndex = 0;
  1526. for (irow = 0; irow < pmc->nViewRows; irow++)
  1527. {
  1528. rcT = rc;
  1529. for (icol = 0; icol < pmc->nViewCols; icol++)
  1530. {
  1531. if (RectVisible(hdc, &rcT))
  1532. {
  1533. MCPaintMonth(pmc, hdc, &rcT, iMonth, iYear, iIndex,
  1534. iIndex == 0,
  1535. iIndex == (pmc->nMonths - 1), hbrSelect);
  1536. }
  1537. rcT.left += dx;
  1538. rcT.right += dx;
  1539. if (++iMonth > 12)
  1540. {
  1541. iMonth = 1;
  1542. iYear++;
  1543. }
  1544. iIndex++;
  1545. }
  1546. rc.top += dy;
  1547. rc.bottom += dy;
  1548. }
  1549. // draw the today stuff
  1550. if (MonthCal_ShowToday(pmc))
  1551. {
  1552. MCGetTodayBtnRect(pmc, &rc);
  1553. if (RectVisible(hdc, &rc))
  1554. {
  1555. TCHAR szDateFmt[32];
  1556. TCHAR szBuf[64];
  1557. rcT.right = rc.left + 2; // a bit extra border space
  1558. if (MonthCal_ShowTodayCircle(pmc)) // this turns on/off the red circle
  1559. {
  1560. rcT.left = rcT.right + 2;
  1561. rcT.right = rcT.left + pmc->dxCol - 2;
  1562. rcT.top = rc.top + 2;
  1563. rcT.bottom = rc.bottom - 2;
  1564. MCDrawTodayCircle(pmc, hdc, &rcT);
  1565. }
  1566. rcT.left = rcT.right + 2;
  1567. rcT.right = rc.right - 2;
  1568. rcT.top = rc.top;
  1569. rcT.bottom = rc.bottom;
  1570. hgdiOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
  1571. SetTextColor(hdc, pmc->clr[MCSC_TEXT]);
  1572. GetDateFormat(pmc->ct.lcid, DATE_SHORTDATE, &pmc->stToday,
  1573. NULL, szDateFmt, ARRAYSIZE(szDateFmt));
  1574. wsprintf(szBuf, TEXT("%s %s"), pmc->li.szToday, szDateFmt);
  1575. DrawText(hdc, szBuf, lstrlen(szBuf), &rcT,
  1576. DT_LEFT | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
  1577. SelectObject(hdc, hgdiOrig);
  1578. }
  1579. }
  1580. // Draw the spin buttons
  1581. if (RectVisible(hdc, &pmc->rcPrev))
  1582. MCPaintArrowBtn(pmc, hdc, TRUE, (pmc->idTimer && pmc->fSpinPrev));
  1583. if (RectVisible(hdc, &pmc->rcNext))
  1584. MCPaintArrowBtn(pmc, hdc, FALSE, (pmc->idTimer && !pmc->fSpinPrev));
  1585. SelectObject(hdc, hpenOrig);
  1586. DeleteObject((HGDIOBJ)hbrSelect);
  1587. DeleteObject((HGDIOBJ)pmc->hpen);
  1588. }
  1589. //
  1590. // MCGetMonthFormat gets the string to display for the month/year
  1591. // in the passed-in SYSTEMTIME. This is tricky because of eras.
  1592. // If pmm is non-NULL, it receives the metrics of the
  1593. // formatted month/year string.
  1594. //
  1595. void MCGetMonthFormat(MONTHCAL *pmc, SYSTEMTIME *pst, LPTSTR rgch, UINT cch, PMONTHMETRICS pmm)
  1596. {
  1597. // For all months, we display the name appropriate to the first
  1598. // day of the month. Note that this means that the title of the
  1599. // month in which the era changes may be confusing. If the era
  1600. // changes in the middle of a month, we name the month after the
  1601. // previous era, even if the current selection belongs to the next
  1602. // era. I hope nobody will mind.
  1603. #if 0 // code that tried to track the era based on where the selection is
  1604. // but before you can turn this on, you have to find everybody who
  1605. // changes the selection, and that's hard because the monthcal control
  1606. // doesn't have a centralizesd selection changer; people just party on
  1607. // the selection directly
  1608. if (pst->wMonth == pmc->st.wMonth &&
  1609. pst->wYear == pmc->st.wYear) {
  1610. pst->wDay = pmc->st.wDay;
  1611. } else {
  1612. pst->wDay = 1;
  1613. }
  1614. #else
  1615. pst->wDay = 1;
  1616. #endif
  1617. //
  1618. // Get the string (all marked up), then extract the markers
  1619. // to locate the month and year substrings.
  1620. //
  1621. rgch[0] = TEXT('\0'); // In case something horrible happens
  1622. GetDateFormat(pmc->ct.lcid, 0, pst,
  1623. pmc->li.szMonthYearFmt,
  1624. rgch, cch);
  1625. MCRemoveMarkers(rgch, pmm);
  1626. }
  1627. void MCPaintMonth(MONTHCAL *pmc, HDC hdc, RECT *prc, int iMonth, int iYear, int iIndex,
  1628. BOOL fDrawPrev, BOOL fDrawNext, HBRUSH hbrSelect)
  1629. {
  1630. BOOL fBold, fView, fReset;
  1631. RECT rc, rcT;
  1632. int nDay, cdy, irow, icol, crowShow, nweek, isel;
  1633. TCHAR rgch[64];
  1634. LPTSTR psz;
  1635. HGDIOBJ hfontOrig;
  1636. COLORREF clrGrayText, clrHiliteText, clrOld, clrText;
  1637. SYSTEMTIME st = {0};
  1638. int iIndexSave = iIndex;
  1639. clrText = pmc->clr[MCSC_TEXT];
  1640. clrGrayText = pmc->clr[MCSC_TRAILINGTEXT];
  1641. clrHiliteText = pmc->clr[MCSC_TITLETEXT];
  1642. hfontOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfont);
  1643. SelectObject(hdc, (HGDIOBJ)pmc->hpen);
  1644. //
  1645. // Draw the Month and Year
  1646. //
  1647. // translate the relative coords to window coords
  1648. rc = pmc->rcMonthName;
  1649. rc.left += prc->left;
  1650. rc.right += prc->left;
  1651. rc.top += prc->top;
  1652. rc.bottom += prc->top;
  1653. if (RectVisible(hdc, &rc))
  1654. {
  1655. FillRectClr(hdc, &rc, pmc->clr[MCSC_TITLEBK]);
  1656. SetTextColor(hdc, pmc->clr[MCSC_TITLETEXT]);
  1657. SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
  1658. st.wYear = (WORD) iYear;
  1659. st.wMonth = (WORD) iMonth;
  1660. MCGetMonthFormat(pmc, &st, rgch, ARRAYSIZE(rgch), NULL);
  1661. DrawText(hdc, rgch, lstrlen(rgch), &rc, DT_CENTER | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
  1662. #ifdef MARKER_DEBUG
  1663. //
  1664. // When debugging MCInsertMarker and MCRemoveMarker, draw colored
  1665. // bars where we think the markers were.
  1666. //
  1667. { RECT rcT = rc;
  1668. rcT.top = rcT.bottom - 2;
  1669. rcT.left = rc.left + pmc->rgmm[iIndex].rgi[IMM_MONTHSTART];
  1670. rcT.right = rc.left + pmc->rgmm[iIndex].rgi[IMM_MONTHEND];
  1671. FillRectClr(hdc, &rcT, RGB(0xFF, 0, 0));
  1672. rcT.left = rc.left + pmc->rgmm[iIndex].rgi[IMM_YEARSTART];
  1673. rcT.right = rc.left + pmc->rgmm[iIndex].rgi[IMM_YEAREND];
  1674. FillRectClr(hdc, &rcT, RGB(0, 0xFF, 0));
  1675. }
  1676. #endif
  1677. SelectObject(hdc, (HGDIOBJ)pmc->hfont);
  1678. }
  1679. SetTextColor(hdc, pmc->clr[MCSC_TITLEBK]);
  1680. //
  1681. // Draw the days of the month
  1682. //
  1683. // translate the relative coords to window coords
  1684. rc = pmc->rcDow;
  1685. rc.left += prc->left;
  1686. rc.right += prc->left;
  1687. rc.top += prc->top;
  1688. rc.bottom += prc->top;
  1689. if (RectVisible(hdc, &rc))
  1690. {
  1691. MoveToEx(hdc, rc.left + 4, rc.bottom - 1, NULL);
  1692. LineTo(hdc, rc.right - 4, rc.bottom - 1);
  1693. rc.right = rc.left + pmc->dxCol;
  1694. for (icol = 0; icol < CALCOLMAX; icol++)
  1695. {
  1696. psz = pmc->li.rgszDay[(icol + pmc->li.dowStartWeek) % 7];
  1697. DrawText(hdc, psz, lstrlen(psz), &rc, DT_CENTER | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
  1698. rc.left += pmc->dxCol;
  1699. rc.right += pmc->dxCol;
  1700. }
  1701. }
  1702. // Check to see how many days from the previous month exist in this months calendar
  1703. nDay = pmc->rgnDayUL[iIndex]; // last day in prev month that won't be shown in this month
  1704. cdy = pmc->rgcDay[iIndex]; // # of days in prev month
  1705. // Calculate the number of weeks to display
  1706. if (fDrawNext)
  1707. crowShow = CALROWMAX;
  1708. else
  1709. crowShow = ((cdy - nDay) + pmc->rgcDay[iIndex + 1] + 6/* round up */) / 7;
  1710. if (nDay != cdy)
  1711. {
  1712. // start at previous month
  1713. iMonth--;
  1714. if(iMonth <= 0)
  1715. {
  1716. iMonth = 12;
  1717. iYear--;
  1718. }
  1719. nDay++;
  1720. fView = FALSE;
  1721. }
  1722. else
  1723. {
  1724. // start at this month
  1725. iIndex++; // this month
  1726. nDay = 1;
  1727. cdy = pmc->rgcDay[iIndex];
  1728. fView = TRUE;
  1729. }
  1730. //
  1731. // Draw the week numbers
  1732. //
  1733. if (MonthCal_ShowWeekNumbers(pmc))
  1734. {
  1735. // translate the relative coords to window coords
  1736. rc = pmc->rcWeekNum;
  1737. rc.left += prc->left;
  1738. rc.top += prc->top;
  1739. rc.right += prc->left;
  1740. rc.bottom = rc.top + (pmc->dyRow * crowShow);
  1741. // draw the week numbers
  1742. if (RectVisible(hdc, &rc))
  1743. {
  1744. MoveToEx(hdc, rc.right - 1, rc.top + 4, NULL);
  1745. LineTo(hdc, rc.right - 1, rc.bottom - 4);
  1746. st.wYear = (WORD) iYear;
  1747. st.wMonth = (WORD) iMonth;
  1748. st.wDay = (WORD) nDay;
  1749. nweek = GetWeekNumber(&st, pmc->li.dowStartWeek, pmc->li.firstWeek);
  1750. rc.bottom = rc.top + pmc->dyRow;
  1751. for (irow = 0; irow < crowShow; irow++)
  1752. {
  1753. wsprintf(rgch, g_szNumFmt, nweek);
  1754. DrawText(hdc, rgch, (nweek > 9 ? 2 : 1), &rc,
  1755. DT_CENTER | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
  1756. rc.top += pmc->dyRow;
  1757. rc.bottom += pmc->dyRow;
  1758. IncrSystemTime(&st, &st, 1, INCRSYS_WEEK);
  1759. nweek = GetWeekNumber(&st, pmc->li.dowStartWeek, pmc->li.firstWeek);
  1760. }
  1761. }
  1762. }
  1763. if (!fView)
  1764. SetTextColor(hdc, clrGrayText);
  1765. else
  1766. SetTextColor(hdc, clrText);
  1767. rc = pmc->rcDayNum;
  1768. rc.left += prc->left;
  1769. rc.top += prc->top;
  1770. rc.right = rc.left + pmc->dxCol;
  1771. rc.bottom = rc.top + pmc->dyRow;
  1772. fReset = FALSE;
  1773. fBold = FALSE;
  1774. for (irow = 0; irow < crowShow; irow++)
  1775. {
  1776. rcT = rc;
  1777. for (icol = 0; icol < CALCOLMAX; icol++)
  1778. {
  1779. if ((fView || fDrawPrev) && RectVisible(hdc, &rcT))
  1780. {
  1781. wsprintf(rgch, g_szNumFmt, nDay);
  1782. if (MonthCal_IsDayState(pmc))
  1783. {
  1784. // if we're in a dropdown we don't display
  1785. if (MCIsBoldOffsetDay(pmc, nDay, iIndex))
  1786. {
  1787. if (!fBold)
  1788. {
  1789. SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
  1790. fBold = TRUE;
  1791. }
  1792. }
  1793. else
  1794. {
  1795. if (fBold)
  1796. {
  1797. SelectObject(hdc, (HGDIOBJ)pmc->hfont);
  1798. fBold = FALSE;
  1799. }
  1800. }
  1801. }
  1802. if (isel = MCIsSelectedDayMoYr(pmc, nDay, iMonth, iYear))
  1803. {
  1804. int x1, x2;
  1805. clrOld = SetTextColor(hdc, clrHiliteText);
  1806. fReset = TRUE;
  1807. SelectObject(hdc, GetStockObject(NULL_PEN));
  1808. x1 = 0;
  1809. x2 = 0;
  1810. if (isel & SEL_DOT)
  1811. {
  1812. FillRect(hdc, &rcT, hbrSelect);
  1813. if (isel == SEL_BEGIN)
  1814. {
  1815. x1 = rcT.left + (rcT.right - rcT.left) / 2;
  1816. x2 = rcT.right;
  1817. }
  1818. else if (isel == SEL_END)
  1819. {
  1820. x1 = rcT.left;
  1821. x2 = rcT.left + (rcT.right - rcT.left) / 2;
  1822. }
  1823. }
  1824. else
  1825. {
  1826. x1 = rcT.left;
  1827. x2 = rcT.right;
  1828. }
  1829. if (x1 && x2)
  1830. {
  1831. Rectangle(hdc, x1, rcT.top + 2, x2 + 1, rcT.bottom - 1);
  1832. }
  1833. }
  1834. DrawText(hdc, rgch, (nDay > 9 ? 2 : 1), &rcT,
  1835. DT_CENTER | DT_NOCLIP | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
  1836. if (MonthCal_ShowTodayCircle(pmc) && pmc->fToday && iIndexSave == pmc->iMonthToday &&
  1837. icol == pmc->iColToday && irow == pmc->iRowToday)
  1838. {
  1839. MCDrawTodayCircle(pmc, hdc, &rcT);
  1840. }
  1841. if (fReset)
  1842. {
  1843. SetTextColor(hdc, clrOld);
  1844. fReset = FALSE;
  1845. }
  1846. }
  1847. rcT.left += pmc->dxCol;
  1848. rcT.right += pmc->dxCol;
  1849. nDay++;
  1850. if (nDay > cdy)
  1851. {
  1852. if (!fDrawNext && iIndex > iIndexSave)
  1853. goto doneMonth;
  1854. nDay = 1;
  1855. iIndex++;
  1856. cdy = pmc->rgcDay[iIndex];
  1857. iMonth++;
  1858. if (iMonth > 12)
  1859. {
  1860. iMonth = 1;
  1861. iYear++;
  1862. }
  1863. fView = !fView;
  1864. SetTextColor(hdc, fView ? clrText : clrGrayText);
  1865. fDrawPrev = fDrawNext;
  1866. }
  1867. }
  1868. rc.top += pmc->dyRow;
  1869. rc.bottom += pmc->dyRow;
  1870. }
  1871. doneMonth:
  1872. SelectObject(hdc, hfontOrig);
  1873. return;
  1874. }
  1875. int MCIsSelectedDayMoYr(MONTHCAL *pmc, int iDay, int iMonth, int iYear)
  1876. {
  1877. SYSTEMTIME st;
  1878. int iBegin, iEnd;
  1879. int iret = 0;
  1880. st.wYear = (WORD) iYear;
  1881. st.wMonth = (WORD) iMonth;
  1882. st.wDay = (WORD) iDay;
  1883. iBegin = CmpDate(&st, &pmc->st);
  1884. if (MonthCal_IsMultiSelect(pmc))
  1885. {
  1886. iEnd = CmpDate(&st, &pmc->stEndSel);
  1887. if (iBegin > 0 && iEnd< 0)
  1888. iret = SEL_MID;
  1889. else
  1890. {
  1891. if (iBegin == 0)
  1892. iret |= SEL_BEGIN;
  1893. if (iEnd == 0)
  1894. iret |= SEL_END;
  1895. }
  1896. }
  1897. else if (iBegin == 0)
  1898. {
  1899. iret = SEL_DOT;
  1900. }
  1901. return(iret);
  1902. }
  1903. BOOL MCIsBoldOffsetDay(MONTHCAL *pmc, int nDay, int iIndex)
  1904. {
  1905. return(pmc->rgdayState && (pmc->rgdayState[iIndex] & (1L << (nDay - 1))) != 0);
  1906. }
  1907. void MCNcDestroyHandler(HWND hwnd, MONTHCAL *pmc, WPARAM wParam, LPARAM lParam)
  1908. {
  1909. if (pmc)
  1910. {
  1911. if (pmc->hThemeScroll)
  1912. CloseThemeData(pmc->hThemeScroll);
  1913. if (pmc->hbrushToday)
  1914. DeleteObject((HGDIOBJ)pmc->hbrushToday);
  1915. if (pmc->hfontBold)
  1916. DeleteObject((HGDIOBJ)pmc->hfontBold);
  1917. if (pmc->hmenuCtxt)
  1918. DestroyMenu(pmc->hmenuCtxt);
  1919. if (pmc->hmenuMonth)
  1920. DestroyMenu(pmc->hmenuMonth);
  1921. if (pmc->idTimer)
  1922. KillTimer(pmc->ci.hwnd, pmc->idTimer);
  1923. if (pmc->idTimerToday)
  1924. KillTimer(pmc->ci.hwnd, pmc->idTimerToday);
  1925. MCFreeCalendarInfo(&pmc->ct);
  1926. GlobalFreePtr(pmc);
  1927. }
  1928. // In case rogue messages float through after we have freed the pdtpick, set
  1929. // the handle in the window structure to FFFF and test for this value at
  1930. // the top of the WndProc
  1931. MonthCal_SetPtr(hwnd, NULL);
  1932. // Call DefWindowProc32 to free all little chunks of memory such as szName
  1933. // and rgwScroll.
  1934. DefWindowProc(hwnd, WM_NCDESTROY, wParam, lParam);
  1935. }
  1936. /* Computes the following:
  1937. * nViewCols
  1938. * nViewRows
  1939. * rcCentered
  1940. * rcPrev
  1941. * rcNext
  1942. */
  1943. void MCRecomputeSizing(MONTHCAL *pmc, RECT *prect)
  1944. {
  1945. RECT rc;
  1946. int dx, dy, dCal;
  1947. // Space for entire calendar
  1948. pmc->rc = *prect;
  1949. dx = prect->right - prect->left;
  1950. dy = prect->bottom - prect->top;
  1951. pmc->nViewCols = 1 + (dx - pmc->dxMonth) / (pmc->dxMonth + CALBORDER);
  1952. pmc->nViewRows = 1 + (dy - pmc->dyMonth - pmc->dyToday) / (pmc->dyMonth + CALBORDER);
  1953. // if dx < dxMonth or dy < dyMonth, these can be zero. That's bad...
  1954. if (pmc->nViewCols < 1)
  1955. pmc->nViewCols = 1;
  1956. if (pmc->nViewRows < 1)
  1957. pmc->nViewRows = 1;
  1958. // Make sure we don't display more than CALMONTHMAX months
  1959. while ((pmc->nViewRows * pmc->nViewCols) > CALMONTHMAX)
  1960. {
  1961. if (pmc->nViewRows > pmc->nViewCols)
  1962. pmc->nViewRows--;
  1963. else
  1964. pmc->nViewCols--;
  1965. }
  1966. // RC for the months, centered within the client window
  1967. dCal = pmc->nViewCols * (pmc->dxMonth + CALBORDER) - CALBORDER;
  1968. pmc->rcCentered.left = (dx - dCal) / 2;
  1969. if (pmc->rcCentered.left < 0)
  1970. pmc->rcCentered.left = 0;
  1971. pmc->rcCentered.right = pmc->rcCentered.left + dCal;
  1972. dCal = pmc->nViewRows * (pmc->dyMonth + CALBORDER) - CALBORDER + pmc->dyToday;
  1973. pmc->rcCentered.top = (dy - dCal) / 2;
  1974. if (pmc->rcCentered.top < 0)
  1975. pmc->rcCentered.top = 0;
  1976. pmc->rcCentered.bottom = pmc->rcCentered.top + dCal;
  1977. // Calculate and set RCs for the spin buttons
  1978. rc.top = pmc->rcCentered.top + (pmc->dyRow * 2 - pmc->dyCalArrow) /2;
  1979. rc.bottom = rc.top + pmc->dyCalArrow;
  1980. rc.left = pmc->rcCentered.left + pmc->dxArrowMargin;
  1981. rc.right = rc.left + pmc->dxCalArrow;
  1982. pmc->rcPrev = rc;
  1983. rc.right = pmc->rcCentered.right - pmc->dxArrowMargin;
  1984. rc.left = rc.right - pmc->dxCalArrow;
  1985. pmc->rcNext = rc;
  1986. }
  1987. LRESULT MCSizeHandler(MONTHCAL *pmc, RECT *prc)
  1988. {
  1989. int nMax;
  1990. SYSTEMTIME st;
  1991. int cmo, dmo;
  1992. MCRecomputeSizing(pmc, prc);
  1993. nMax = pmc->nViewRows * pmc->nViewCols;
  1994. // Compute new start date
  1995. CopyDate(pmc->stMonthFirst, st);
  1996. // REARCHITECT: this doesn't consider stEndSel
  1997. cmo = (pmc->stMonthLast.wYear - (int)pmc->st.wYear) * 12 +
  1998. (pmc->stMonthLast.wMonth - (int)pmc->st.wMonth);
  1999. dmo = nMax - pmc->nMonths;
  2000. if (-dmo > cmo)
  2001. {
  2002. // Selected mon/yr not in view
  2003. IncrSystemTime(&st, &st, -(cmo + dmo), INCRSYS_MONTH);
  2004. cmo = 0;
  2005. }
  2006. // If the # of months being displayed has changed, then lets try to
  2007. // start the calendar from January.
  2008. if ((dmo != 0) && (cmo + dmo >= pmc->stMonthFirst.wMonth - 1))
  2009. st.wMonth = 1;
  2010. MCUpdateStartEndDates(pmc, &st);
  2011. InvalidateRect(pmc->ci.hwnd, NULL, TRUE);
  2012. UpdateWindow(pmc->ci.hwnd);
  2013. return(0);
  2014. }
  2015. //
  2016. // For each month being displayed, compute the precise locations of all
  2017. // the gizmos we draw into the month header area.
  2018. //
  2019. void MCUpdateMonthNamePos(MONTHCAL *pmc)
  2020. {
  2021. HDC hdc;
  2022. int iCount;
  2023. SYSTEMTIME st;
  2024. TCHAR rgch[64];
  2025. SIZE size;
  2026. HGDIOBJ hfontOrig;
  2027. hdc = GetDC(pmc->ci.hwnd);
  2028. hfontOrig = SelectObject(hdc, (HGDIOBJ)pmc->hfontBold);
  2029. st = pmc->stMonthFirst;
  2030. for (iCount = 0; iCount < pmc->nMonths; iCount++)
  2031. {
  2032. PMONTHMETRICS pmm = &pmc->rgmm[iCount];
  2033. int i;
  2034. MCGetMonthFormat(pmc, &st, rgch, ARRAYSIZE(rgch), pmm);
  2035. GetTextExtentPoint32(hdc, rgch, lstrlen(rgch), &size);
  2036. pmm->rgi[IMM_START] = (pmc->dxMonth - size.cx) / 2;
  2037. //
  2038. // Now convert the indices into pixels so we can figure out where
  2039. // all the strings ended up.
  2040. //
  2041. for (i = IMM_DATEFIRST; i <= IMM_DATELAST; i++) {
  2042. SIZE sizeT;
  2043. // In case of horrible error, pretend the marker was at the
  2044. // beginning of the string.
  2045. sizeT.cx = 0;
  2046. GetTextExtentPoint32(hdc, rgch, pmm->rgi[i], &sizeT);
  2047. pmm->rgi[i] = pmm->rgi[IMM_START] + sizeT.cx;
  2048. }
  2049. //
  2050. // Now flip the coordinates for RTL.
  2051. //
  2052. if (pmc->fHeaderRTL || IS_WINDOW_RTL_MIRRORED(pmc->ci.hwnd))
  2053. {
  2054. int dxStart, dxEnd;
  2055. // Flip the month...
  2056. dxStart = pmm->rgi[IMM_MONTHSTART] - pmm->rgi[IMM_START];
  2057. dxEnd = pmm->rgi[IMM_MONTHEND ] - pmm->rgi[IMM_START];
  2058. pmm->rgi[IMM_MONTHSTART] = pmm->rgi[IMM_START] + size.cx - dxEnd;
  2059. pmm->rgi[IMM_MONTHEND ] = pmm->rgi[IMM_START] + size.cx - dxStart;
  2060. // Flip the year...
  2061. dxStart = pmm->rgi[IMM_YEARSTART] - pmm->rgi[IMM_START];
  2062. dxEnd = pmm->rgi[IMM_YEAREND ] - pmm->rgi[IMM_START];
  2063. pmm->rgi[IMM_YEARSTART] = pmm->rgi[IMM_START] + size.cx - dxEnd;
  2064. pmm->rgi[IMM_YEAREND ] = pmm->rgi[IMM_START] + size.cx - dxStart;
  2065. }
  2066. // On to the next month
  2067. if(++st.wMonth > 12)
  2068. {
  2069. st.wMonth = 1;
  2070. st.wYear++;
  2071. }
  2072. }
  2073. SelectObject(hdc, hfontOrig);
  2074. ReleaseDC(pmc->ci.hwnd, hdc);
  2075. }
  2076. /*
  2077. * Computes the following, given the number of rows & columns available:
  2078. * stMonthFirst.wMonth
  2079. * stMonthFirst.wYear
  2080. * stMonthLast.wMonth
  2081. * stMonthLast.wYear
  2082. * nMonths
  2083. *
  2084. * Trashes *pstStart
  2085. */
  2086. void MCUpdateStartEndDates(MONTHCAL *pmc, SYSTEMTIME *pstStart)
  2087. {
  2088. int iCount, iMonth, iYear;
  2089. int nMonthsToEdge;
  2090. pmc->nMonths = pmc->nViewRows * pmc->nViewCols;
  2091. // make sure pstStart to pstStart+nMonths is within range
  2092. nMonthsToEdge = ((int)pmc->stMax.wYear - (int)pstStart->wYear) * 12 +
  2093. ((int)pmc->stMax.wMonth - (int)pstStart->wMonth) + 1;
  2094. if (nMonthsToEdge < pmc->nMonths)
  2095. IncrSystemTime(pstStart, pstStart, nMonthsToEdge - pmc->nMonths, INCRSYS_MONTH);
  2096. if (CmpDate(pstStart, &pmc->stMin) < 0)
  2097. {
  2098. CopyDate(pmc->stMin, *pstStart);
  2099. }
  2100. nMonthsToEdge = ((int)pmc->stMax.wYear - (int)pstStart->wYear) * 12 +
  2101. ((int)pmc->stMax.wMonth - (int)pstStart->wMonth) + 1;
  2102. if (nMonthsToEdge < pmc->nMonths)
  2103. pmc->nMonths = nMonthsToEdge;
  2104. pmc->stMonthFirst.wYear = pstStart->wYear;
  2105. pmc->stMonthFirst.wMonth = pstStart->wMonth;
  2106. pmc->stMonthFirst.wDay = 1;
  2107. if (CmpDate(&pmc->stMonthFirst, &pmc->stMin) < 0)
  2108. {
  2109. pmc->stMonthFirst.wDay = pmc->stMin.wDay;
  2110. ASSERT(0==CmpDate(&pmc->stMonthFirst, &pmc->stMin));
  2111. }
  2112. // these ranges are CALMONTHMAX+2 and nMonths <= CALMONTHMAX, so we are safe
  2113. // index 0 corresponds to stViewFirst (DAYSTATE) info
  2114. // index 1..nMonths correspond to stMonthFirst..stMonthLast info
  2115. // index nMonths+1 corresponds to stViewLast (DAYSTATE) info
  2116. //
  2117. iYear = pmc->stMonthFirst.wYear;
  2118. iMonth = pmc->stMonthFirst.wMonth - 1;
  2119. if(iMonth == 0)
  2120. {
  2121. iMonth = 12;
  2122. iYear--;
  2123. }
  2124. for (iCount = 0; iCount <= pmc->nMonths+1; iCount++)
  2125. {
  2126. int cdy, dow, ddow;
  2127. // number of days in this month
  2128. cdy = GetDaysForMonth(iYear, iMonth);
  2129. pmc->rgcDay[iCount] = cdy;
  2130. // move to "this" month
  2131. if(++iMonth > 12)
  2132. {
  2133. iMonth = 1;
  2134. iYear++;
  2135. }
  2136. // last day of this month NOT visible when viewing NEXT month
  2137. dow = GetStartDowForMonth(iYear, iMonth);
  2138. ddow = dow - pmc->li.dowStartWeek;
  2139. if(ddow < 0)
  2140. ddow += CALCOLMAX;
  2141. pmc->rgnDayUL[iCount] = cdy - ddow;
  2142. }
  2143. // we want to always have days visible on the previous month
  2144. if (pmc->rgnDayUL[0] == pmc->rgcDay[0])
  2145. pmc->rgnDayUL[0] -= CALCOLMAX;
  2146. IncrSystemTime(&pmc->stMonthFirst, &pmc->stMonthLast, pmc->nMonths - 1, INCRSYS_MONTH);
  2147. pmc->stMonthLast.wDay = (WORD) pmc->rgcDay[pmc->nMonths];
  2148. if (pmc->fMaxYrSet && CmpDate(&pmc->stMonthLast, &pmc->stMax) > 0)
  2149. {
  2150. pmc->stMonthLast.wDay = pmc->stMax.wDay;
  2151. ASSERT(0==CmpDate(&pmc->stMonthLast, &pmc->stMax));
  2152. }
  2153. pmc->stViewFirst.wYear = pmc->stMonthFirst.wYear;
  2154. pmc->stViewFirst.wMonth = pmc->stMonthFirst.wMonth - 1;
  2155. if (pmc->stViewFirst.wMonth == 0)
  2156. {
  2157. pmc->stViewFirst.wMonth = 12;
  2158. pmc->stViewFirst.wYear--;
  2159. }
  2160. pmc->stViewFirst.wDay = pmc->rgnDayUL[0] + 1;
  2161. pmc->stViewLast.wYear = pmc->stMonthLast.wYear;
  2162. pmc->stViewLast.wMonth = pmc->stMonthLast.wMonth + 1;
  2163. if (pmc->stViewLast.wMonth == 13)
  2164. {
  2165. pmc->stViewLast.wMonth = 1;
  2166. pmc->stViewLast.wYear++;
  2167. }
  2168. // total days - (days in last month + remaining days in previous month)
  2169. pmc->stViewLast.wDay = CALROWMAX * CALCOLMAX -
  2170. (pmc->rgcDay[pmc->nMonths] +
  2171. pmc->rgcDay[pmc->nMonths-1] - pmc->rgnDayUL[pmc->nMonths-1]);
  2172. MCUpdateDayState(pmc);
  2173. MCUpdateRcDayCur(pmc, &pmc->st);
  2174. MCUpdateToday(pmc);
  2175. MCUpdateMonthNamePos(pmc);
  2176. }
  2177. void MCUpdateToday(MONTHCAL *pmc)
  2178. {
  2179. if (MonthCal_ShowTodayCircle(pmc))
  2180. {
  2181. int iMonth;
  2182. iMonth = MCGetOffsetForYrMo(pmc, pmc->stToday.wYear, pmc->stToday.wMonth);
  2183. if (iMonth < 0)
  2184. {
  2185. // today is not visible in the displayed months
  2186. pmc->fToday = FALSE;
  2187. }
  2188. else
  2189. {
  2190. int iDay;
  2191. // today is visible in the displayed months
  2192. pmc->fToday = TRUE;
  2193. iDay = pmc->rgcDay[iMonth] - pmc->rgnDayUL[iMonth] + pmc->stToday.wDay - 1;
  2194. pmc->iMonthToday = iMonth;
  2195. pmc->iRowToday = iDay / CALCOLMAX;
  2196. pmc->iColToday = iDay % CALCOLMAX;
  2197. }
  2198. }
  2199. }
  2200. BOOL FUpdateRcDayCur(MONTHCAL *pmc, POINT pt)
  2201. {
  2202. int iRow, iCol;
  2203. RECT rc;
  2204. SYSTEMTIME st;
  2205. if (!FGetDateForPt(pmc, pt, &st, NULL, &iCol, &iRow, &rc))
  2206. return FALSE;
  2207. if (CmpDate(&st, &pmc->stMin) < 0)
  2208. return FALSE;
  2209. if (CmpDate(&st, &pmc->stMax) > 0)
  2210. return FALSE;
  2211. // calculate the day rc
  2212. pmc->rcDayCur.left = rc.left + pmc->rcDayNum.left + iCol * pmc->dxCol;
  2213. pmc->rcDayCur.top = rc.top + pmc->rcDayNum.top + iRow * pmc->dyRow;
  2214. pmc->rcDayCur.right = pmc->rcDayCur.left + pmc->dxCol;
  2215. pmc->rcDayCur.bottom = pmc->rcDayCur.top + pmc->dyRow;
  2216. return(TRUE);
  2217. }
  2218. void MCUpdateDayState(MONTHCAL *pmc)
  2219. {
  2220. HWND hwndParent;
  2221. if (!MonthCal_IsDayState(pmc))
  2222. return;
  2223. hwndParent = GetParent(pmc->ci.hwnd);
  2224. if (hwndParent)
  2225. {
  2226. int i, mon, yr, cmonths;
  2227. yr = pmc->stViewFirst.wYear;
  2228. mon = pmc->stViewFirst.wMonth;
  2229. cmonths = pmc->nMonths + 2;
  2230. // don't do anything unless we need to
  2231. if (cmonths != pmc->cds || mon != pmc->dsMonth || yr != pmc->dsYear)
  2232. {
  2233. // this is a small enough to not deal with allocating it
  2234. NMDAYSTATE nmds;
  2235. MONTHDAYSTATE buffer[CALMONTHMAX+2];
  2236. ZeroMemory(&nmds, sizeof(nmds));
  2237. nmds.stStart.wYear = (WORD) yr;
  2238. nmds.stStart.wMonth = (WORD) mon;
  2239. nmds.stStart.wDay = 1;
  2240. nmds.cDayState = cmonths;
  2241. nmds.prgDayState = buffer;
  2242. CCSendNotify(&pmc->ci, MCN_GETDAYSTATE, &nmds.nmhdr);
  2243. for (i = 0; i < cmonths; i++)
  2244. pmc->rgdayState[i] = nmds.prgDayState[i];
  2245. pmc->cds = cmonths;
  2246. pmc->dsMonth = mon;
  2247. pmc->dsYear = yr;
  2248. }
  2249. }
  2250. }
  2251. void MCNotifySelChange(MONTHCAL *pmc, UINT uMsg)
  2252. {
  2253. HWND hwndParent;
  2254. if (pmc->fNoNotify)
  2255. return;
  2256. hwndParent = GetParent(pmc->ci.hwnd);
  2257. if (hwndParent)
  2258. {
  2259. NMSELCHANGE nmsc;
  2260. ZeroMemory(&nmsc, sizeof(nmsc));
  2261. CopyDate(pmc->st, nmsc.stSelStart);
  2262. if (MonthCal_IsMultiSelect(pmc))
  2263. CopyDate(pmc->stEndSel, nmsc.stSelEnd);
  2264. CCSendNotify(&pmc->ci, uMsg, &nmsc.nmhdr);
  2265. }
  2266. }
  2267. void MCUpdateRcDayCur(MONTHCAL *pmc, SYSTEMTIME *pst)
  2268. {
  2269. int iOff;
  2270. iOff = MCGetOffsetForYrMo(pmc, pst->wYear, pst->wMonth);
  2271. if (iOff >= 0)
  2272. MCGetRcForDay(pmc, iOff, pst->wDay, &pmc->rcDayCur);
  2273. }
  2274. // returns zero-based index into DISPLAYED months for month
  2275. // if month is not in DISPLAYED months, then -1 is returned...
  2276. int MCGetOffsetForYrMo(MONTHCAL *pmc, int iYear, int iMonth)
  2277. {
  2278. int iOff;
  2279. iOff = ((int)iYear - pmc->stMonthFirst.wYear) * 12 + (int)iMonth - pmc->stMonthFirst.wMonth;
  2280. if (iOff < 0 || iOff >= pmc->nMonths)
  2281. return(-1);
  2282. return(iOff);
  2283. }
  2284. // iMonth is a zero-based index relative to the DISPLAYED months.
  2285. // iDay is a 1-based index of the day of the month,
  2286. void MCGetRcForDay(MONTHCAL *pmc, int iMonth, int iDay, RECT *prc)
  2287. {
  2288. RECT rc;
  2289. int iPlace, iRow, iCol;
  2290. MCGetRcForMonth(pmc, iMonth, &rc);
  2291. iPlace = pmc->rgcDay[iMonth] - pmc->rgnDayUL[iMonth] + iDay - 1;
  2292. iRow = iPlace / CALCOLMAX;
  2293. iCol = iPlace % CALCOLMAX;
  2294. prc->left = rc.left + pmc->rcDayNum.left + (pmc->dxCol * iCol);
  2295. prc->top = rc.top + pmc->rcDayNum.top + (pmc->dyRow * iRow);
  2296. prc->right = prc->left + pmc->dxCol;
  2297. prc->bottom = prc->top + pmc->dyRow;
  2298. }
  2299. //
  2300. // This routine gets the bounding rect for the iMonth of the displayed months.
  2301. // NOTE: iMonth is a zero-based index relative to the DISPLAYED months,
  2302. // counting along the rows.
  2303. //
  2304. void MCGetRcForMonth(MONTHCAL *pmc, int iMonth, RECT *prc)
  2305. {
  2306. int iRow, iCol, d;
  2307. iRow = iMonth / pmc->nViewCols;
  2308. iCol = iMonth % pmc->nViewCols;
  2309. // intialize the rect to be the bounding rect for the month in the
  2310. // top left corner
  2311. prc->left = pmc->rcCentered.left;
  2312. prc->right = prc->left + pmc->dxMonth;
  2313. prc->top = pmc->rcCentered.top;
  2314. prc->bottom = prc->top + pmc->dyMonth;
  2315. if (iCol) // slide the rect across to the correct column
  2316. {
  2317. d = (pmc->dxMonth + CALBORDER) * iCol;
  2318. prc->left += d;
  2319. prc->right += d;
  2320. }
  2321. if (iRow) // slide the rect down to the correct row
  2322. {
  2323. d = (pmc->dyMonth + CALBORDER) * iRow;
  2324. prc->top += d;
  2325. prc->bottom += d;
  2326. }
  2327. }
  2328. // Changes starting month by nDelta
  2329. // returns number of months actually changed
  2330. int FIncrStartMonth(MONTHCAL *pmc, int nDelta, BOOL fNoCurDayChange)
  2331. {
  2332. SYSTEMTIME stStart;
  2333. int nOldStartYear = pmc->stMonthFirst.wYear;
  2334. int nOldStartMonth = pmc->stMonthFirst.wMonth;
  2335. IncrSystemTime(&pmc->stMonthFirst, &stStart, nDelta, INCRSYS_MONTH);
  2336. // MCUpdateStartEndDates takes stMin/stMax into account
  2337. MCUpdateStartEndDates(pmc, &stStart);
  2338. if (!fNoCurDayChange)
  2339. {
  2340. int cday;
  2341. // REARCHITECT: we arbitrarily set the currently selected day
  2342. // to be in the new stMonthFirst, but given the way the
  2343. // control works, I doubt we ever hit this code. what's it for??
  2344. if (MonthCal_IsMultiSelect(pmc))
  2345. cday = DaysBetweenDates(&pmc->st, &pmc->stEndSel);
  2346. // need to set date for focus here
  2347. pmc->st.wMonth = pmc->stMonthFirst.wMonth;
  2348. pmc->st.wYear = pmc->stMonthFirst.wYear;
  2349. // Check to see if the day is in range, eg, Jan 31 -> Feb 28
  2350. if (pmc->st.wDay > pmc->rgcDay[1])
  2351. pmc->st.wDay = (WORD) pmc->rgcDay[1];
  2352. if (MonthCal_IsMultiSelect(pmc))
  2353. IncrSystemTime(&pmc->st, &pmc->stEndSel, cday, INCRSYS_DAY);
  2354. MCNotifySelChange(pmc, MCN_SELCHANGE);
  2355. MCUpdateRcDayCur(pmc, &pmc->st);
  2356. }
  2357. MCInvalidateMonthDays(pmc);
  2358. return((pmc->stMonthFirst.wYear-nOldStartYear)*12 + (pmc->stMonthFirst.wMonth-nOldStartMonth));
  2359. }
  2360. // FIncrStartMonth with a beep when it doesn't change.
  2361. int MCIncrStartMonth(MONTHCAL *pmc, int nDelta, BOOL fDelayDayChange)
  2362. {
  2363. int cmoSpun;
  2364. // FIncrStartMonth takes stMin/stMax into account
  2365. cmoSpun = FIncrStartMonth(pmc, nDelta, fDelayDayChange);
  2366. if (cmoSpun==0)
  2367. MessageBeep(0);
  2368. return(cmoSpun);
  2369. }
  2370. //
  2371. // Determines in which month the given point lies. In other words, if the
  2372. // calendar control is currently sized to show six months, this routine
  2373. // determines in which which of those six months the point lies. It returns
  2374. // the zero based index of the month, counting along the rows.
  2375. //
  2376. BOOL FGetOffsetForPt(MONTHCAL *pmc, POINT pt, int *piOffset)
  2377. {
  2378. int iRow, iCol, i;
  2379. // check to see if point is within the centered months
  2380. if (!PtInRect(&pmc->rcCentered, pt))
  2381. return(FALSE);
  2382. // calculate the month row and column
  2383. // (we're really fudging a little here, since the point could
  2384. // actually be within the space between months...)
  2385. iCol = (pt.x - pmc->rcCentered.left) / (pmc->dxMonth + CALBORDER);
  2386. iRow = (pt.y - pmc->rcCentered.top) / (pmc->dyMonth + CALBORDER);
  2387. i = iRow * pmc->nViewCols + iCol;
  2388. if (i >= pmc->nMonths)
  2389. return(FALSE);
  2390. *piOffset = i;
  2391. return(TRUE);
  2392. }
  2393. //
  2394. // This routine returns the row and column of day containing the given point
  2395. //
  2396. BOOL FGetRowColForRelPt(MONTHCAL *pmc, POINT ptRel, int *piRow, int *piCol)
  2397. {
  2398. if (!PtInRect(&pmc->rcDayNum, ptRel))
  2399. return(FALSE);
  2400. ptRel.x -= pmc->rcDayNum.left;
  2401. ptRel.y -= pmc->rcDayNum.top;
  2402. *piCol = ptRel.x / pmc->dxCol;
  2403. *piRow = ptRel.y / pmc->dyRow;
  2404. return(TRUE);
  2405. }
  2406. //
  2407. // This routine returns the month and year of the iMonth in the displayed
  2408. // months. NOTE: iMonth is a zero-based index of the displayed months
  2409. //
  2410. void GetYrMoForOffset(MONTHCAL *pmc, int iMonth, int *piYear, int *piMonth)
  2411. {
  2412. SYSTEMTIME st;
  2413. st.wDay = 1;
  2414. st.wMonth = pmc->stMonthFirst.wMonth;
  2415. st.wYear = pmc->stMonthFirst.wYear;
  2416. IncrSystemTime(&st, &st, iMonth, INCRSYS_MONTH);
  2417. *piYear = st.wYear;
  2418. *piMonth = st.wMonth;
  2419. }
  2420. //
  2421. // This routine returns, the day, month, and year of day containing the
  2422. // given point. It will optionally return the day of the month, the row and
  2423. // column in the month, and the bounding rect of the month containing the point.
  2424. // NOTE: the day returned in piDay can be less than 1 (to indicate a day in the
  2425. // previous month) or greater than the number of days in the month (to indicate
  2426. // a day in the next month).
  2427. //
  2428. BOOL FGetDateForPt(MONTHCAL *pmc, POINT pt, SYSTEMTIME *pst, int *piDay,
  2429. int* piCol, int* piRow, LPRECT prcMonth)
  2430. {
  2431. int iOff, iRow, iCol, iDay, iMon, iYear;
  2432. RECT rcMonth;
  2433. if (!FGetOffsetForPt(pmc, pt, &iOff))
  2434. return(FALSE);
  2435. MCGetRcForMonth(pmc, iOff, &rcMonth);
  2436. pt.x -= rcMonth.left;
  2437. pt.y -= rcMonth.top;
  2438. if (!FGetRowColForRelPt(pmc, pt, &iRow, &iCol))
  2439. return(FALSE);
  2440. // get the day containing the point by subtracting the number of days
  2441. // that are visible from the previous month, and then add one, since
  2442. // we are zero-based and the days of the month are 1-based.
  2443. //
  2444. iDay = iRow * CALCOLMAX + iCol - (pmc->rgcDay[iOff] - pmc->rgnDayUL[iOff]) + 1;
  2445. if (piDay)
  2446. *piDay = iDay;
  2447. if (iDay <= 0)
  2448. {
  2449. if (iOff)
  2450. return(FALSE); // dont accept days in prev month unless
  2451. // this happens to be the first month
  2452. iDay += pmc->rgcDay[iOff]; // add the cnt of days in the prev month,
  2453. --iOff; // then incr the month to get day in new month
  2454. }
  2455. else if (iDay > pmc->rgcDay[iOff+1])
  2456. {
  2457. if (iOff < (pmc->nMonths - 1)) // dont accept days in next month unless
  2458. return(FALSE); // this happens to be the last month
  2459. ++iOff; // increment the month, and then sub the
  2460. iDay -= pmc->rgcDay[iOff]; // count of days to get day in new month
  2461. }
  2462. GetYrMoForOffset(pmc, iOff, &iYear, &iMon);
  2463. pst->wDay = (WORD) iDay;
  2464. pst->wMonth = (WORD) iMon;
  2465. pst->wYear = (WORD) iYear;
  2466. if (piCol)
  2467. *piCol = iCol;
  2468. if (piRow)
  2469. *piRow = iRow;
  2470. if (prcMonth)
  2471. *prcMonth = rcMonth;
  2472. return(TRUE);
  2473. }
  2474. BOOL MCSetDate(MONTHCAL *pmc, SYSTEMTIME *pst)
  2475. {
  2476. int nDelta = 0;
  2477. //
  2478. // Can't set date outside of min/max range
  2479. //
  2480. if (CmpDate(pst, &pmc->stMin) < 0)
  2481. return FALSE;
  2482. if (CmpDate(pst, &pmc->stMax) > 0)
  2483. return FALSE;
  2484. //
  2485. // Set new day
  2486. //
  2487. pmc->st = *pst;
  2488. if (MonthCal_IsMultiSelect(pmc))
  2489. pmc->stEndSel = *pst;
  2490. FScrollIntoView(pmc);
  2491. MCNotifySelChange(pmc, MCN_SELCHANGE);
  2492. MCUpdateRcDayCur(pmc, pst);
  2493. return(TRUE);
  2494. }
  2495. void MCSetToday(MONTHCAL* pmc, SYSTEMTIME* pst)
  2496. {
  2497. SYSTEMTIME st;
  2498. RECT rc;
  2499. if (!pst)
  2500. {
  2501. GetLocalTime(&st);
  2502. pmc->fTodaySet = FALSE;
  2503. }
  2504. else
  2505. {
  2506. st = *pst;
  2507. pmc->fTodaySet = TRUE;
  2508. }
  2509. if (CmpDate(&st, &pmc->stToday) != 0)
  2510. {
  2511. MCGetRcForDay(pmc, pmc->iMonthToday, pmc->stToday.wDay, &rc);
  2512. InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
  2513. pmc->stToday = st;
  2514. MCUpdateToday(pmc);
  2515. MCGetRcForDay(pmc, pmc->iMonthToday, pmc->stToday.wDay, &rc);
  2516. InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
  2517. if (MonthCal_ShowToday(pmc))
  2518. {
  2519. MCGetTodayBtnRect(pmc, &rc);
  2520. InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
  2521. }
  2522. UpdateWindow(pmc->ci.hwnd);
  2523. }
  2524. }
  2525. LRESULT MCHandleTimer(MONTHCAL *pmc, WPARAM wParam)
  2526. {
  2527. if (wParam == CAL_IDAUTOSPIN)
  2528. {
  2529. int nDelta = pmc->fMonthDelta ? pmc->nMonthDelta : pmc->nMonths;
  2530. // REARCHITECT: pass last parameter TRUE if multiselect! else you
  2531. // can't multiselect across months
  2532. MCIncrStartMonth(pmc, (pmc->fSpinPrev ? -nDelta : nDelta), FALSE);
  2533. if (pmc->idTimer == 0)
  2534. pmc->idTimer = SetTimer(pmc->ci.hwnd, CAL_IDAUTOSPIN, CAL_MSECAUTOSPIN, NULL);
  2535. pmc->rcDayOld = pmc->rcDayCur;
  2536. UpdateWindow(pmc->ci.hwnd);
  2537. }
  2538. else if (wParam == CAL_TODAYTIMER)
  2539. {
  2540. if (!pmc->fTodaySet)
  2541. MCSetToday(pmc, NULL);
  2542. }
  2543. MCNotifySelChange(pmc, MCN_SELCHANGE); // our date has changed
  2544. return((LRESULT)TRUE);
  2545. }
  2546. void MCInvalidateDates(MONTHCAL *pmc, SYSTEMTIME *pst1, SYSTEMTIME *pst2)
  2547. {
  2548. int iMonth, ioff, icol, irow;
  2549. RECT rc, rcMonth;
  2550. SYSTEMTIME st, stEnd;
  2551. if (CmpDate(pst1, &pmc->stViewLast) > 0 ||
  2552. CmpDate(pst2, &pmc->stViewFirst) < 0)
  2553. return;
  2554. if (CmpDate(pst1, &pmc->stViewFirst) < 0)
  2555. CopyDate(pmc->stViewFirst, st);
  2556. else
  2557. CopyDate(*pst1, st);
  2558. if (CmpDate(pst2, &pmc->stViewLast) > 0)
  2559. CopyDate(pmc->stViewLast, stEnd);
  2560. else
  2561. CopyDate(*pst2, stEnd);
  2562. iMonth = MCGetOffsetForYrMo(pmc, st.wYear, st.wMonth);
  2563. if (iMonth == -1)
  2564. {
  2565. if (st.wMonth == pmc->stViewFirst.wMonth)
  2566. {
  2567. iMonth = 0;
  2568. ioff = st.wDay - pmc->rgnDayUL[0] - 1;
  2569. }
  2570. else
  2571. {
  2572. iMonth = pmc->nMonths - 1;
  2573. ioff = st.wDay + pmc->rgcDay[pmc->nMonths] +
  2574. pmc->rgcDay[iMonth] - pmc->rgnDayUL[iMonth] - 1;
  2575. }
  2576. }
  2577. else
  2578. {
  2579. ioff = st.wDay + (pmc->rgcDay[iMonth] - pmc->rgnDayUL[iMonth]) - 1;
  2580. }
  2581. MCGetRcForMonth(pmc, iMonth, &rcMonth);
  2582. // TODO: make it more efficient...
  2583. while (CmpDate(&st, &stEnd) <= 0)
  2584. {
  2585. irow = ioff / CALCOLMAX;
  2586. icol = ioff % CALCOLMAX;
  2587. rc.left = rcMonth.left + pmc->rcDayNum.left + (pmc->dxCol * icol);
  2588. rc.top = rcMonth.top + pmc->rcDayNum.top + (pmc->dyRow * irow);
  2589. rc.right = rc.left + pmc->dxCol;
  2590. rc.bottom = rc.top + pmc->dyRow;
  2591. InvalidateRect(pmc->ci.hwnd, &rc, FALSE);
  2592. IncrSystemTime(&st, &st, 1, INCRSYS_DAY);
  2593. ioff++;
  2594. if (st.wDay == 1)
  2595. {
  2596. if (st.wMonth != pmc->stMonthFirst.wMonth &&
  2597. st.wMonth != pmc->stViewLast.wMonth)
  2598. {
  2599. iMonth++;
  2600. MCGetRcForMonth(pmc, iMonth, &rcMonth);
  2601. ioff = ioff % CALCOLMAX;
  2602. }
  2603. }
  2604. }
  2605. }
  2606. void MCHandleMultiSelect(MONTHCAL *pmc, SYSTEMTIME *pst)
  2607. {
  2608. int i;
  2609. DWORD cday;
  2610. SYSTEMTIME stStart, stEnd;
  2611. if (!pmc->fMultiSelecting)
  2612. {
  2613. CopyDate(*pst, stStart);
  2614. CopyDate(*pst, stEnd);
  2615. pmc->fMultiSelecting = TRUE;
  2616. pmc->fForwardSelect = TRUE;
  2617. CopyDate(pmc->st, pmc->stStartPrev);
  2618. CopyDate(pmc->stEndSel, pmc->stEndPrev);
  2619. }
  2620. else
  2621. {
  2622. if (pmc->fForwardSelect)
  2623. {
  2624. i = CmpDate(pst, &pmc->st);
  2625. if (i >= 0)
  2626. {
  2627. CopyDate(pmc->st, stStart);
  2628. CopyDate(*pst, stEnd);
  2629. }
  2630. else
  2631. {
  2632. CopyDate(*pst, stStart);
  2633. CopyDate(pmc->st, stEnd);
  2634. pmc->fForwardSelect = FALSE;
  2635. }
  2636. }
  2637. else
  2638. {
  2639. i = CmpDate(pst, &pmc->stEndSel);
  2640. if (i < 0)
  2641. {
  2642. CopyDate(*pst, stStart);
  2643. CopyDate(pmc->stEndSel, stEnd);
  2644. }
  2645. else
  2646. {
  2647. CopyDate(pmc->stEndSel, stStart);
  2648. CopyDate(*pst, stEnd);
  2649. pmc->fForwardSelect = TRUE;
  2650. }
  2651. }
  2652. }
  2653. // check to make sure not exceeding cSelMax
  2654. cday = DaysBetweenDates(&stStart, &stEnd) + 1;
  2655. if (cday > pmc->cSelMax)
  2656. {
  2657. if (pmc->fForwardSelect)
  2658. IncrSystemTime(&stStart, &stEnd, pmc->cSelMax - 1, INCRSYS_DAY);
  2659. else
  2660. IncrSystemTime(&stEnd, &stStart, 1 - pmc->cSelMax, INCRSYS_DAY);
  2661. }
  2662. if (0 == CmpDate(&stStart, &pmc->st) &&
  2663. 0 == CmpDate(&stEnd, &pmc->stEndSel))
  2664. return;
  2665. // TODO: do this more effeciently..
  2666. MCInvalidateDates(pmc, &pmc->st, &pmc->stEndSel);
  2667. MCInvalidateDates(pmc, &stStart, &stEnd);
  2668. CopyDate(stStart, pmc->st);
  2669. CopyDate(stEnd, pmc->stEndSel);
  2670. MCNotifySelChange(pmc, MCN_SELCHANGE);
  2671. UpdateWindow(pmc->ci.hwnd);
  2672. }
  2673. void MCGotoToday(MONTHCAL *pmc)
  2674. {
  2675. pmc->rcDayOld = pmc->rcDayCur;
  2676. // force old selection to get repainted
  2677. if (MonthCal_IsMultiSelect(pmc))
  2678. MCInvalidateDates(pmc, &pmc->st, &pmc->stEndSel);
  2679. else
  2680. InvalidateRect(pmc->ci.hwnd, &pmc->rcDayOld, FALSE);
  2681. MCSetDate(pmc, &pmc->stToday);
  2682. MCNotifySelChange(pmc, MCN_SELECT);
  2683. // force new selection to get repainted
  2684. InvalidateRect(pmc->ci.hwnd, &pmc->rcDayCur, FALSE);
  2685. UpdateWindow(pmc->ci.hwnd);
  2686. }
  2687. LRESULT MCContextMenu(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam)
  2688. {
  2689. POINT pt;
  2690. int click;
  2691. if (!pmc->fEnabled || !MonthCal_ShowToday(pmc))
  2692. return(0);
  2693. // ignore double click since this makes us advance twice
  2694. // since we already had a leftdown before the leftdblclk
  2695. if (!pmc->fCapture)
  2696. {
  2697. pt.x = GET_X_LPARAM(lParam);
  2698. pt.y = GET_Y_LPARAM(lParam);
  2699. //
  2700. // If the context menu was generated from the keyboard,
  2701. // then put it at the focus rectangle.
  2702. //
  2703. if (pt.x == -1 && pt.y == -1)
  2704. {
  2705. pt.x = (pmc->rcDayCur.left + pmc->rcDayCur.right ) / 2;
  2706. pt.y = (pmc->rcDayCur.top + pmc->rcDayCur.bottom) / 2;
  2707. ClientToScreen(pmc->ci.hwnd, &pt);
  2708. }
  2709. click = TrackPopupMenu(pmc->hmenuCtxt,
  2710. TPM_LEFTBUTTON | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY,
  2711. pt.x, pt.y, 0, pmc->ci.hwnd, NULL);
  2712. if (click >= 1)
  2713. MCGotoToday(pmc);
  2714. }
  2715. return(0);
  2716. }
  2717. //
  2718. // Computes the bounding rects for the month and the year in the title area of
  2719. // the month.
  2720. //
  2721. void MCGetTitleRcsForOffset(MONTHCAL* pmc, int iOffset, LPRECT prcMonth, LPRECT prcYear)
  2722. {
  2723. RECT rcT;
  2724. RECT rc;
  2725. MCGetRcForMonth(pmc, iOffset, &rc);
  2726. rcT.top = rc.top + (pmc->dyRow / 2);
  2727. rcT.bottom = rcT.top + pmc->dyRow;
  2728. rcT.left = rc.left + pmc->rcMonthName.left + pmc->rgmm[iOffset].rgi[IMM_MONTHSTART];
  2729. rcT.right = rc.left + pmc->rcMonthName.left + pmc->rgmm[iOffset].rgi[IMM_MONTHEND];
  2730. *prcMonth = rcT;
  2731. rcT.left = rc.left + pmc->rcMonthName.left + pmc->rgmm[iOffset].rgi[IMM_YEARSTART];
  2732. rcT.right = rc.left + pmc->rcMonthName.left + pmc->rgmm[iOffset].rgi[IMM_YEAREND];
  2733. *prcYear = rcT;
  2734. }
  2735. LRESULT MCLButtonDown(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam)
  2736. {
  2737. HDC hdc;
  2738. POINT pt;
  2739. SYSTEMTIME st;
  2740. RECT rc, rcCal;
  2741. BOOL fShow;
  2742. MSG msg;
  2743. int offset, imonth, iyear;
  2744. if (!pmc->fEnabled)
  2745. return(0);
  2746. pt.x = GET_X_LPARAM(lParam);
  2747. pt.y = GET_Y_LPARAM(lParam);
  2748. // treat a shift click like an LMouseDown at the prev location and
  2749. // a MouseMove to the new location
  2750. if (MonthCal_IsMultiSelect(pmc) && ((wParam & MK_SHIFT) == MK_SHIFT) && (!PtInRect(&pmc->rcDayCur, pt)))
  2751. {
  2752. SetCapture(pmc->ci.hwnd);
  2753. pmc->fCapture = TRUE;
  2754. pmc->fForwardSelect = (CmpDate(&pmc->stAnchor, &pmc->st) != 0) ? FALSE : TRUE;
  2755. pmc->fMultiSelecting = TRUE;
  2756. hdc = GetDC(pmc->ci.hwnd);
  2757. DrawFocusRect(hdc, &pmc->rcDayCur); // draw focus rect
  2758. pmc->fFocusDrawn = TRUE;
  2759. ReleaseDC(pmc->ci.hwnd, hdc);
  2760. MCMouseMove(pmc, wParam, lParam); // draw the highlight to new date
  2761. return 0;
  2762. }
  2763. // ignore double click since this makes us advance twice
  2764. // since we already had a leftdown before the leftdblclk
  2765. if (!pmc->fCapture)
  2766. {
  2767. SetCapture(pmc->ci.hwnd);
  2768. pmc->fCapture = TRUE;
  2769. // check for spin buttons
  2770. if ((pmc->fSpinPrev = (WORD) PtInRect(&pmc->rcPrev, pt)) || PtInRect(&pmc->rcNext, pt))
  2771. {
  2772. MCHandleTimer(pmc, CAL_IDAUTOSPIN);
  2773. return(0);
  2774. }
  2775. // check for valid day
  2776. pmc->rcDayOld = pmc->rcDayCur; // rcDayCur should always be valid now
  2777. if (MonthCal_IsMultiSelect(pmc))
  2778. {
  2779. // need to cache these values because these are how
  2780. // we determine if the selection has changed and we
  2781. // need to notify the parent
  2782. CopyDate(pmc->st, pmc->stStartPrev);
  2783. CopyDate(pmc->stEndSel, pmc->stEndPrev);
  2784. }
  2785. if (FUpdateRcDayCur(pmc, pt))
  2786. {
  2787. if (MonthCal_IsMultiSelect(pmc))
  2788. {
  2789. if (FGetDateForPt(pmc, pt, &st, NULL, NULL, NULL, NULL))
  2790. MCHandleMultiSelect(pmc, &st);
  2791. }
  2792. hdc = GetDC(pmc->ci.hwnd);
  2793. DrawFocusRect(hdc, &pmc->rcDayCur); // draw focus rect
  2794. pmc->fFocusDrawn = TRUE;
  2795. ReleaseDC(pmc->ci.hwnd, hdc);
  2796. CopyDate(st, pmc->stAnchor); // new Anchor point
  2797. }
  2798. else
  2799. {
  2800. RECT rcMonth, rcYear;
  2801. int delta, year, month;
  2802. // is this a click in the today area...
  2803. if (MonthCal_ShowToday(pmc))
  2804. {
  2805. MCGetTodayBtnRect(pmc, &rc);
  2806. if (PtInRect(&rc, pt))
  2807. {
  2808. CCReleaseCapture(&pmc->ci);
  2809. pmc->fCapture = FALSE;
  2810. MCGotoToday(pmc);
  2811. return(0);
  2812. }
  2813. }
  2814. // figure out if the click was in a month name or a year
  2815. if (!FGetOffsetForPt(pmc, pt, &offset))
  2816. return(0);
  2817. GetYrMoForOffset(pmc, offset, &year, &month);
  2818. // calculate where the month name and year are,
  2819. // so we can figure out if they clicked in them...
  2820. MCGetTitleRcsForOffset(pmc, offset, &rcMonth, &rcYear);
  2821. delta = 0;
  2822. if (PtInRect(&rcMonth, pt))
  2823. {
  2824. CCReleaseCapture(&pmc->ci);
  2825. pmc->fCapture = FALSE;
  2826. ClientToScreen(pmc->ci.hwnd, &pt);
  2827. imonth = TrackPopupMenu(pmc->hmenuMonth,
  2828. TPM_LEFTALIGN | TPM_TOPALIGN |
  2829. TPM_NONOTIFY | TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_RIGHTBUTTON,
  2830. pt.x, pt.y, 0, pmc->ci.hwnd, NULL);
  2831. if (imonth >= 1)
  2832. delta = imonth - month;
  2833. goto ChangeMonth;
  2834. }
  2835. if (PtInRect(&rcYear, pt))
  2836. {
  2837. HWND hwndEdit, hwndUD, hwndFocus;
  2838. int yrMin, yrMax;
  2839. DWORD dwExStyle = 0L;
  2840. CCReleaseCapture(&pmc->ci);
  2841. pmc->fCapture = FALSE;
  2842. //
  2843. // If the year is in a RTL string, then numeric control
  2844. // is to the left.
  2845. //
  2846. if (pmc->fHeaderRTL)
  2847. {
  2848. rcYear.left = (rcYear.right - (pmc->dxYearMax + 6));
  2849. }
  2850. else
  2851. {
  2852. rcYear.right = rcYear.left + pmc->dxYearMax + 6;
  2853. }
  2854. rcYear.top--;
  2855. rcYear.bottom++;
  2856. if(((pmc->fHeaderRTL) && !(IS_WINDOW_RTL_MIRRORED(pmc->ci.hwnd))) ||
  2857. (!(pmc->fHeaderRTL) && (IS_WINDOW_RTL_MIRRORED(pmc->ci.hwnd))))
  2858. {
  2859. // not mirrored force RTL, mirrored force LTR (for mirroring RTLis LTR!!)
  2860. dwExStyle|= WS_EX_RTLREADING;
  2861. }
  2862. hwndEdit = CreateWindowEx(dwExStyle, TEXT("EDIT"), NULL,
  2863. WS_CHILD | WS_VISIBLE | WS_BORDER | ES_READONLY | ES_LEFT | ES_AUTOHSCROLL,
  2864. rcYear.left, rcYear.top, rcYear.right - rcYear.left, rcYear.bottom - rcYear.top,
  2865. pmc->ci.hwnd, (HMENU)0, pmc->hinstance, NULL);
  2866. if (hwndEdit == NULL)
  2867. return(0);
  2868. pmc->hwndEdit = hwndEdit;
  2869. SendMessage(hwndEdit, WM_SETFONT, (WPARAM)pmc->hfontBold, (LPARAM)FALSE);
  2870. SendMessage(hwndEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN,
  2871. (LPARAM)MAKELONG(1, 1));
  2872. MCUpdateEditYear(pmc);
  2873. //
  2874. // Convert from Gregorian to display years.
  2875. //
  2876. year = GregorianToOther(&pmc->ct, year);
  2877. yrMin = GregorianToOther(&pmc->ct, pmc->stMin.wYear);
  2878. yrMax = 9999;
  2879. if (pmc->fMaxYrSet)
  2880. yrMax = GregorianToOther(&pmc->ct, pmc->stMax.wYear);
  2881. hwndUD = CreateUpDownControl(
  2882. WS_CHILD | WS_VISIBLE | WS_BORDER |
  2883. UDS_NOTHOUSANDS | UDS_ARROWKEYS,// | UDS_SETBUDDYINT,
  2884. pmc->fHeaderRTL ? (rcYear.left - 1 - (rcYear.bottom-rcYear.top)): (rcYear.right + 1),
  2885. rcYear.top,
  2886. rcYear.bottom - rcYear.top, rcYear.bottom - rcYear.top, pmc->ci.hwnd,
  2887. 1, pmc->hinstance, hwndEdit, yrMax, yrMin, year);
  2888. if (hwndUD == NULL)
  2889. {
  2890. DestroyWindow(hwndEdit);
  2891. return(0);
  2892. }
  2893. pmc->hwndUD = hwndUD;
  2894. hwndFocus = SetFocus(hwndEdit);
  2895. //
  2896. // Widen the area depending on the string direction.
  2897. //
  2898. if (pmc->fHeaderRTL)
  2899. rcYear.left -= (1 + rcYear.bottom - rcYear.top);
  2900. else
  2901. rcYear.right += 1 + rcYear.bottom - rcYear.top;
  2902. // Use MapWindowRect, It works in a mirrored and unmirrored windows.
  2903. MapWindowRect(pmc->ci.hwnd, NULL, (LPPOINT)&rcYear);
  2904. rcCal = pmc->rc;
  2905. MapWindowRect(pmc->ci.hwnd, NULL, (LPPOINT)&rcCal);
  2906. fShow = TRUE;
  2907. while (fShow && GetFocus() == hwndEdit)
  2908. {
  2909. if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
  2910. {
  2911. // Check for events that cause the calendar to go away
  2912. if (msg.message == WM_KILLFOCUS ||
  2913. (msg.message >= WM_SYSKEYDOWN &&
  2914. msg.message <= WM_SYSDEADCHAR))
  2915. {
  2916. fShow = FALSE;
  2917. }
  2918. else if ((msg.message == WM_LBUTTONDOWN ||
  2919. msg.message == WM_NCLBUTTONDOWN ||
  2920. msg.message == WM_RBUTTONDOWN ||
  2921. msg.message == WM_NCRBUTTONDOWN ||
  2922. msg.message == WM_MBUTTONDOWN ||
  2923. msg.message == WM_NCMBUTTONDOWN) &&
  2924. !PtInRect(&rcYear, msg.pt))
  2925. {
  2926. fShow = FALSE;
  2927. // if its a button down inside the calendar, eat it
  2928. // so the calendar doesn't do anything strange when
  2929. // the user is just trying to get rid of the year edit
  2930. if (PtInRect(&rcCal, msg.pt))
  2931. GetMessage(&msg, NULL, 0, 0);
  2932. break; // do not dispatch
  2933. }
  2934. else if (msg.message == WM_QUIT)
  2935. { // Don't dispatch a WM_QUIT; leave it in the queue
  2936. break; // do not dispatch
  2937. }
  2938. else if (msg.message == WM_CHAR)
  2939. {
  2940. if (msg.wParam == VK_ESCAPE)
  2941. {
  2942. goto NoYearChange;
  2943. }
  2944. else if (msg.wParam == VK_RETURN)
  2945. {
  2946. fShow = FALSE;
  2947. }
  2948. }
  2949. GetMessage(&msg, NULL, 0, 0);
  2950. TranslateMessage(&msg);
  2951. DispatchMessage(&msg);
  2952. }
  2953. else
  2954. WaitMessage();
  2955. }
  2956. iyear = (int) SendMessage(hwndUD, UDM_GETPOS, 0, 0);
  2957. if (HIWORD(iyear) == 0)
  2958. delta = (iyear - year) * 12;
  2959. NoYearChange:
  2960. DestroyWindow(hwndUD);
  2961. DestroyWindow(hwndEdit);
  2962. pmc->hwndUD = NULL;
  2963. pmc->hwndEdit = NULL;
  2964. UpdateWindow(pmc->ci.hwnd);
  2965. if (hwndFocus != NULL)
  2966. SetFocus(hwndFocus);
  2967. }
  2968. ChangeMonth:
  2969. if (delta != 0)
  2970. {
  2971. MCIncrStartMonth(pmc, delta, FALSE);
  2972. MCNotifySelChange(pmc,MCN_SELCHANGE);
  2973. }
  2974. }
  2975. }
  2976. return(0);
  2977. }
  2978. LRESULT MCLButtonUp(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam)
  2979. {
  2980. HDC hdc;
  2981. SYSTEMTIME st;
  2982. POINT pt;
  2983. if (pmc->fCapture)
  2984. {
  2985. CCReleaseCapture(&pmc->ci);
  2986. pmc->fCapture = FALSE;
  2987. if (pmc->idTimer)
  2988. {
  2989. KillTimer(pmc->ci.hwnd, pmc->idTimer);
  2990. pmc->idTimer = 0;
  2991. hdc = GetDC(pmc->ci.hwnd);
  2992. MCPaintArrowBtn(pmc, hdc, pmc->fSpinPrev, FALSE);
  2993. ReleaseDC(pmc->ci.hwnd, hdc);
  2994. return(0);
  2995. }
  2996. if (pmc->fFocusDrawn)
  2997. {
  2998. hdc = GetDC(pmc->ci.hwnd);
  2999. DrawFocusRect(hdc, &pmc->rcDayCur); // erase old focus rect
  3000. pmc->fFocusDrawn = FALSE;
  3001. ReleaseDC(pmc->ci.hwnd, hdc);
  3002. }
  3003. pt.x = GET_X_LPARAM(lParam);
  3004. pt.y = GET_Y_LPARAM(lParam);
  3005. if (MonthCal_IsMultiSelect(pmc))
  3006. {
  3007. FUpdateRcDayCur(pmc, pt);
  3008. if (!EqualRect(&pmc->rcDayOld, &pmc->rcDayCur))
  3009. {
  3010. if (FGetDateForPt(pmc, pt, &st, NULL, NULL, NULL, NULL))
  3011. MCHandleMultiSelect(pmc, &st);
  3012. }
  3013. pmc->fMultiSelecting = FALSE;
  3014. if (0 != CmpDate(&pmc->stStartPrev, &pmc->st) ||
  3015. 0 != CmpDate(&pmc->stEndPrev, &pmc->stEndSel))
  3016. {
  3017. FScrollIntoView(pmc);
  3018. }
  3019. MCNotifySelChange(pmc, MCN_SELECT);
  3020. }
  3021. else
  3022. {
  3023. if (FUpdateRcDayCur(pmc, pt))
  3024. {
  3025. if (!EqualRect(&pmc->rcDayOld, &pmc->rcDayCur) && (FGetDateForPt(pmc, pt, &st, NULL, NULL, NULL, NULL)))
  3026. {
  3027. InvalidateRect(pmc->ci.hwnd, &pmc->rcDayOld, FALSE);
  3028. InvalidateRect(pmc->ci.hwnd, &pmc->rcDayCur, FALSE);
  3029. MCSetDate(pmc, &st);
  3030. }
  3031. MCNotifySelChange(pmc, MCN_SELECT);
  3032. }
  3033. }
  3034. }
  3035. return(0);
  3036. }
  3037. LRESULT MCMouseMove(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam)
  3038. {
  3039. BOOL fPrev;
  3040. HDC hdc;
  3041. POINT pt;
  3042. SYSTEMTIME st;
  3043. if (pmc->fCapture)
  3044. {
  3045. pt.x = GET_X_LPARAM(lParam);
  3046. pt.y = GET_Y_LPARAM(lParam);
  3047. // check spin buttons
  3048. if ((fPrev = PtInRect(&pmc->rcPrev, pt)) || PtInRect(&pmc->rcNext, pt))
  3049. {
  3050. if (pmc->idTimer == 0)
  3051. {
  3052. pmc->fSpinPrev = (WORD) fPrev;
  3053. MCHandleTimer(pmc, CAL_IDAUTOSPIN);
  3054. }
  3055. return(0);
  3056. }
  3057. else
  3058. {
  3059. hdc = GetDC(pmc->ci.hwnd);
  3060. if (pmc->idTimer)
  3061. {
  3062. KillTimer(pmc->ci.hwnd, pmc->idTimer);
  3063. pmc->idTimer = 0;
  3064. MCPaintArrowBtn(pmc, hdc, pmc->fSpinPrev, FALSE);
  3065. }
  3066. }
  3067. // check days
  3068. if (!PtInRect(&pmc->rcDayCur, pt))
  3069. {
  3070. if (pmc->fFocusDrawn)
  3071. DrawFocusRect(hdc, &pmc->rcDayCur); // erase focus rect
  3072. if (pmc->fFocusDrawn = (WORD) FUpdateRcDayCur(pmc, pt))
  3073. {
  3074. // moved into a new valid day
  3075. if (pmc->fMultiSelecting)
  3076. {
  3077. if (FGetDateForPt(pmc, pt, &st, NULL, NULL, NULL, NULL))
  3078. MCHandleMultiSelect(pmc, &st);
  3079. }
  3080. DrawFocusRect(hdc, &pmc->rcDayCur);
  3081. }
  3082. else
  3083. {
  3084. // moved into an invalid position
  3085. pmc->rcDayCur = pmc->rcDayOld;
  3086. }
  3087. }
  3088. else if (!pmc->fFocusDrawn)
  3089. {
  3090. // handle case where we just moved back into rcDayCur from invalid area
  3091. DrawFocusRect(hdc, &pmc->rcDayCur);
  3092. pmc->fFocusDrawn = TRUE;
  3093. }
  3094. ReleaseDC(pmc->ci.hwnd, hdc);
  3095. }
  3096. return(0);
  3097. }
  3098. LRESULT MCHandleKeydown(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam)
  3099. {
  3100. LONG lIncrement;
  3101. int iDirection;
  3102. SYSTEMTIME st;
  3103. BOOL fRet = FALSE;
  3104. HDC hdc = NULL;
  3105. RECT rcCurFocus;
  3106. // REARCHITECT: raymondc ERA - need to invalidate month title when selection
  3107. // moves in/out/within
  3108. switch (wParam)
  3109. {
  3110. case VK_CONTROL:
  3111. pmc->fControl = TRUE; // we'll clear this on WM_KEYUP
  3112. return TRUE;
  3113. break;
  3114. case VK_SHIFT:
  3115. pmc->fShift = TRUE; // we'll clear this on WM_KEYUP
  3116. return TRUE;
  3117. break;
  3118. case VK_LEFT: // goto previous day
  3119. iDirection = -1;
  3120. lIncrement = INCRSYS_DAY;
  3121. break;
  3122. case VK_RIGHT: // goto next day
  3123. iDirection = 1;
  3124. lIncrement = INCRSYS_DAY;
  3125. break;
  3126. case VK_UP: // goto previous week
  3127. iDirection = -1;
  3128. lIncrement = INCRSYS_WEEK;
  3129. break;
  3130. case VK_DOWN: // goto next week
  3131. iDirection = 1;
  3132. lIncrement = INCRSYS_WEEK;
  3133. break;
  3134. case VK_NEXT:
  3135. iDirection = 1;
  3136. if (pmc->fControl) // goto next year
  3137. lIncrement = INCRSYS_YEAR;
  3138. else // goto next month
  3139. lIncrement = INCRSYS_MONTH;
  3140. break;
  3141. case VK_PRIOR:
  3142. iDirection = -1;
  3143. if (pmc->fControl) // goto previous year
  3144. lIncrement = INCRSYS_YEAR;
  3145. else
  3146. lIncrement = INCRSYS_MONTH; // goto next month
  3147. break;
  3148. case VK_HOME:
  3149. if (pmc->fControl) // goto first visible month
  3150. {
  3151. CopyDate(pmc->stMonthFirst, st);
  3152. }
  3153. else // goto first day of current month
  3154. {
  3155. CopyDate(pmc->st, st);
  3156. st.wDay = 1;
  3157. }
  3158. goto setDate;
  3159. break;
  3160. case VK_END:
  3161. if (pmc->fControl) // goto last visible month
  3162. {
  3163. CopyDate(pmc->stMonthLast, st);
  3164. }
  3165. else // goto last day of current month
  3166. {
  3167. CopyDate(pmc->st, st);
  3168. st.wDay = (WORD) GetDaysForMonth(st.wYear, st.wMonth);
  3169. }
  3170. goto setDate;
  3171. break;
  3172. default:
  3173. return FALSE;
  3174. }
  3175. // if we're multiselecting, we need to know which "end" of the selection
  3176. // the user is moving.
  3177. if (pmc->fMultiSelecting && pmc->fForwardSelect)
  3178. CopyDate(pmc->stEndSel, st);
  3179. else
  3180. CopyDate(pmc->st, st);
  3181. IncrSystemTime(&st, &st, iDirection, lIncrement);
  3182. setDate:
  3183. // based on the window style and the shift key state,
  3184. // we'll do a multi-select (or not)
  3185. if (MonthCal_IsMultiSelect(pmc) && pmc->fShift)
  3186. {
  3187. pmc->fForwardSelect = (CmpDate(&pmc->st, &pmc->stAnchor) >= 0) ? TRUE : FALSE;
  3188. pmc->fMultiSelecting = TRUE;
  3189. }
  3190. // otherwise, we'll end multiselect, and set the new anchor
  3191. else
  3192. {
  3193. pmc->fMultiSelecting = FALSE;
  3194. CopyDate(st, pmc->stAnchor);
  3195. }
  3196. if (pmc->fFocusDrawn) // erase the focus rect, but don't clear the bit
  3197. { // so we know to put it back
  3198. hdc = GetDC(pmc->ci.hwnd);
  3199. DrawFocusRect(hdc, &pmc->rcDayCur);
  3200. ReleaseDC(pmc->ci.hwnd, hdc);
  3201. rcCurFocus = pmc->rcDayCur;
  3202. }
  3203. else
  3204. {
  3205. pmc->rcDayOld = pmc->rcDayCur;
  3206. }
  3207. if (MonthCal_IsMultiSelect(pmc))
  3208. {
  3209. int nDelta = 0;
  3210. MCHandleMultiSelect(pmc, &st);
  3211. FScrollIntoView(pmc);
  3212. }
  3213. else if (fRet = MCSetDate(pmc, &st))
  3214. {
  3215. InvalidateRect(pmc->ci.hwnd, &pmc->rcDayOld, FALSE);
  3216. InvalidateRect(pmc->ci.hwnd, &pmc->rcDayCur, FALSE);
  3217. UpdateWindow(pmc->ci.hwnd);
  3218. }
  3219. if (pmc->fFocusDrawn) // put the focus rect back
  3220. {
  3221. pmc->rcDayOld = pmc->rcDayCur;
  3222. pmc->rcDayCur = rcCurFocus;
  3223. hdc = GetDC(pmc->ci.hwnd);
  3224. DrawFocusRect(hdc, &pmc->rcDayCur);
  3225. ReleaseDC(pmc->ci.hwnd, hdc);
  3226. }
  3227. return fRet;
  3228. }
  3229. #if 0 // coming soon
  3230. LRESULT MCHandleChar(MONTHCAL *pmc, WPARAM wParam, LPARAM lParam)
  3231. {
  3232. return 0;
  3233. }
  3234. #endif
  3235. //
  3236. // Era information is kept in a DPA of LocalAlloc'd strings.
  3237. //
  3238. int MCDPAEnumCallback(LPVOID d, LPVOID p)
  3239. {
  3240. UNREFERENCED_PARAMETER(p);
  3241. if (d)
  3242. LocalFree(d);
  3243. return TRUE;
  3244. }
  3245. void MCDPADestroy(HDPA hdpa)
  3246. {
  3247. if (hdpa)
  3248. DPA_DestroyCallback(hdpa, MCDPAEnumCallback, 0);
  3249. }
  3250. //
  3251. // Collect era information.
  3252. //
  3253. // Since EnumCalendarInfo is not thread-safe, we have to take the critical
  3254. // section.
  3255. HDPA g_hdpaCal;
  3256. #ifdef WINNT
  3257. BOOL MCEnumCalInfoProc(LPWSTR psz)
  3258. #else
  3259. BOOL MCEnumCalInfoProc(LPSTR psz)
  3260. #endif
  3261. {
  3262. #ifdef UNICODE_WIN9x
  3263. LPWSTR pwszSave = StrDup_AtoW((LPCWSTR)psz);
  3264. #else
  3265. LPWSTR pwszSave = StrDup(psz);
  3266. #endif
  3267. if (pwszSave) {
  3268. if (DPA_AppendPtr(g_hdpaCal, pwszSave) >= 0) {
  3269. return TRUE;
  3270. }
  3271. LocalFree(pwszSave);
  3272. }
  3273. //
  3274. // Out of memory. Bail.
  3275. //
  3276. MCDPADestroy(g_hdpaCal);
  3277. g_hdpaCal = NULL;
  3278. return FALSE;
  3279. }
  3280. HDPA MCGetCalInfoDPA(CALID calid, CALTYPE calType)
  3281. {
  3282. HDPA hdpa = DPA_Create(4);
  3283. ENTERCRITICAL;
  3284. ASSERT(g_hdpaCal == NULL);
  3285. g_hdpaCal = hdpa;
  3286. #ifdef WINNT
  3287. EnumCalendarInfoW(MCEnumCalInfoProc, LOCALE_USER_DEFAULT, calid, calType);
  3288. #else
  3289. EnumCalendarInfoA(MCEnumCalInfoProc, LOCALE_USER_DEFAULT, calid, calType);
  3290. #endif
  3291. hdpa = g_hdpaCal;
  3292. g_hdpaCal = NULL;
  3293. LEAVECRITICAL;
  3294. return hdpa;
  3295. }
  3296. void MCFreeCalendarInfo(PCALENDARTYPE pct)
  3297. {
  3298. MCDPADestroy(pct->hdpaYears);
  3299. MCDPADestroy(pct->hdpaEras);
  3300. pct->hdpaYears = 0;
  3301. pct->hdpaEras = 0;
  3302. }
  3303. //
  3304. // Get all the era info and validate it so we don't fault when we try to
  3305. // use them.
  3306. //
  3307. BOOL MCGetEraInfo(PCALENDARTYPE pct)
  3308. {
  3309. int i;
  3310. pct->hdpaYears = MCGetCalInfoDPA(pct->calid, CAL_IYEAROFFSETRANGE);
  3311. if (!pct->hdpaYears)
  3312. goto Bad;
  3313. pct->hdpaEras = MCGetCalInfoDPA(pct->calid, CAL_SERASTRING);
  3314. if (!pct->hdpaEras)
  3315. goto Bad;
  3316. // There must be at least one era...
  3317. if (!DPA_GetPtrCount(pct->hdpaEras))
  3318. goto Bad;
  3319. // The number of eras must be equal to the number of era names
  3320. if (DPA_GetPtrCount(pct->hdpaEras) != DPA_GetPtrCount(pct->hdpaYears))
  3321. goto Bad;
  3322. // The era dates must be in descending order.
  3323. for (i = 1; i < DPA_GetPtrCount(pct->hdpaYears); i++)
  3324. {
  3325. if (StrToInt(DPA_FastGetPtr(pct->hdpaYears, i)) >
  3326. StrToInt(DPA_FastGetPtr(pct->hdpaYears, i - 1)))
  3327. goto Bad;
  3328. }
  3329. return TRUE;
  3330. Bad:
  3331. /*
  3332. * Something went wrong, so clean up.
  3333. */
  3334. MCFreeCalendarInfo(pct);
  3335. return FALSE;
  3336. }
  3337. //
  3338. // Check to see if this calendar is not supported currently
  3339. //
  3340. // Return FALSE for Hijri, Hebrew calendars, since these are
  3341. // Lunar calndars. This is hack so that this control behaves well when the calendar
  3342. // is any of the non-supported till we add this support to this control. [samera]
  3343. //
  3344. void MCGetCalendarInfo(PCALENDARTYPE pct)
  3345. {
  3346. TCHAR tchCalendar[32];
  3347. CALTYPE defCalendar = CAL_GREGORIAN;
  3348. if (GetLocaleInfo(LOCALE_USER_DEFAULT,
  3349. LOCALE_ICALENDARTYPE,
  3350. tchCalendar,
  3351. ARRAYSIZE(tchCalendar)))
  3352. {
  3353. defCalendar = StrToInt(tchCalendar);
  3354. }
  3355. //
  3356. // Start with a clean slate. Assume we don't have to do funky
  3357. // offset stuff (dyrOFfset = 0) or era stuff (hdpaEras = NULL),
  3358. // and that we don't need to do locale munging (LOCALE_USER_DEFAULT).
  3359. //
  3360. MCFreeCalendarInfo(pct);
  3361. ZeroMemory(pct, sizeof(CALTYPE));
  3362. pct->calid = defCalendar;
  3363. pct->lcid = LOCALE_USER_DEFAULT;
  3364. switch (pct->calid) {
  3365. case CAL_GREGORIAN:
  3366. case CAL_GREGORIAN_US:
  3367. case CAL_GREGORIAN_ME_FRENCH:
  3368. case CAL_GREGORIAN_ARABIC:
  3369. case CAL_GREGORIAN_XLIT_ENGLISH:
  3370. case CAL_GREGORIAN_XLIT_FRENCH:
  3371. break; // Gregorian calendars are just fine
  3372. case CAL_JAPAN:
  3373. case CAL_TAIWAN:
  3374. //
  3375. // These are era calendars. Go get the era info. Get hdpaEras
  3376. // last so we can use it to test whether we have a supported era
  3377. // calendar.
  3378. //
  3379. // If not enough memory to support traditional calendar, then just
  3380. // force Gregorian. Hey, at least we display *something*.
  3381. //
  3382. if (!MCGetEraInfo(pct))
  3383. goto ForceGregorian;
  3384. break;
  3385. case CAL_THAI:
  3386. pct->dyrOffset = BUDDHIST_BIAS; // You Just Have To Know this number
  3387. break;
  3388. case CAL_KOREA:
  3389. pct->dyrOffset = KOREAN_BIAS; // You Just Have To Know this number
  3390. break;
  3391. default:
  3392. //
  3393. // If the calenday isn't supported, then treat it as Gregorian. [samera]
  3394. //
  3395. ForceGregorian:
  3396. pct->calid = CAL_GREGORIAN;
  3397. pct->lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
  3398. break;
  3399. }
  3400. }
  3401. //
  3402. // Check whether the date string returned accorind to the
  3403. // current user-locale and calendar setting is Right-To-Left (RTL),
  3404. // and is so the RECTs for month year (@LBUTTONDOWN and NCHITTEST)
  3405. // needs to be adjusted. [samera]
  3406. //
  3407. BOOL MCIsDateStringRTL(TCHAR tch)
  3408. {
  3409. WORD wAttrib=0;
  3410. LCID lcidUserDefault;
  3411. BOOL fRTL = FALSE;
  3412. lcidUserDefault = GetUserDefaultLCID();
  3413. if (lcidUserDefault)
  3414. {
  3415. //
  3416. // Return TRUE if the 1st character is a RTL string.
  3417. // A RTL char followed by a european number will
  3418. // display visually as "european-num RTL-string" since the
  3419. // BiDi layout algorithm of the language-pack will do
  3420. // this. [samera]
  3421. //
  3422. if(GetStringTypeEx(lcidUserDefault,
  3423. CT_CTYPE2,
  3424. &tch,
  3425. 1,
  3426. &wAttrib))
  3427. {
  3428. if(C2_RIGHTTOLEFT == wAttrib)
  3429. {
  3430. fRTL = TRUE;
  3431. }
  3432. }
  3433. }
  3434. return fRTL;
  3435. }
  3436. ////////////////////////////////////////////////////////////////////////////
  3437. //
  3438. // Date/Time Picker
  3439. //
  3440. ////////////////////////////////////////////////////////////////////////////
  3441. //
  3442. // Subedit wrapper for the various weird things we need to get from NLS.
  3443. //
  3444. // SE_YEARALT means that the year field is only two digits wide
  3445. // in the format string, so we need to perform special Y2K enhancements.
  3446. // For these fields, the field is displayed in two-digit format, but
  3447. // when you go to edit the field, it temporarily changes to four-digit
  3448. // format so you can change the century too. Then when you complete the
  3449. // edit, it returns to two-digit format.
  3450. //
  3451. // The SE_YEARLIKE macro detects either SE_YEAR or SE_YEARALT.
  3452. //
  3453. // SE_MONTHALT is just like SE_MONTH except that it is used
  3454. // when the day (dd) comes before the month (mmm). This is
  3455. // important for languages like Russian where "12 October"
  3456. // and "October 12" use different strings for the word
  3457. // "October". There is no way to get the alternate string
  3458. // directly, except by creating a bogus date format that also
  3459. // has the day before the month, then throwing away the day.
  3460. //
  3461. // For example, if the incoming date string is
  3462. //
  3463. // "MMMM dd yyyy"
  3464. //
  3465. // we break it up into
  3466. //
  3467. // "MMMM" SE_MONTH
  3468. // " " SE_STATIC
  3469. // "dd" SE_DAY
  3470. // " " SE_STATIC
  3471. // "yyyy" SE_YEAR
  3472. //
  3473. // However, if the incoming date is
  3474. //
  3475. // "dd MMMM yyyy"
  3476. //
  3477. // we break it up into
  3478. //
  3479. // "dd" SE_DAY
  3480. // " " SE_STATIC
  3481. // "ddMMMM" SE_MONTHALT
  3482. // " " SE_STATIC
  3483. // "yyyy" SE_YEAR
  3484. //
  3485. // The extra "dd" at the beginning of SE_MONTHALT is stripped out below.
  3486. //
  3487. // Y2K weirdness: If we are getting the date format for the year,
  3488. // and the year is being edited (either due to a SUBEDIT_ALL or because
  3489. // it is the active subedit), and the format year is only
  3490. // two digits, then force a four-digit year for editing purposes.
  3491. //
  3492. //
  3493. void SEGetTimeDateFormat(LPSUBEDIT pse, LPSUBEDITCONTROL psec, LPTSTR pszBuf, DWORD cchBuf)
  3494. {
  3495. int cch;
  3496. ASSERT(cchBuf >= 2); // We assume it can hold at least space and a null
  3497. pszBuf[0] = TEXT('\0'); // In case something fails
  3498. if (pse->id == SE_MONTHALT) {
  3499. TCHAR tszBuf[DTP_FORMATLENGTH + 3];
  3500. //
  3501. // When we parsed the date string and realized that we needed
  3502. // the Alternate Month format, we created a date format of
  3503. // the form "ddMMM...", where "MMM..." is the
  3504. // the original month format. Here we strip off the digits.
  3505. //
  3506. cch = GetDateFormat(psec->ct.lcid, 0, &psec->st, pse->pv, tszBuf, ARRAYSIZE(tszBuf));
  3507. if (cch >= 2) {
  3508. // [msadek] For Hebrew calander, day format "dd" is actually
  3509. // two OR THREE characters. Don't hardcode it as 2.
  3510. int cchDay = GetDateFormat(psec->ct.lcid, 0, &psec->st, TEXT("dd"), NULL, 0);
  3511. lstrcpyn(pszBuf, tszBuf + cchDay - 1, cchBuf);
  3512. }
  3513. } else if (pse->id == SE_YEARALT &&
  3514. (psec->iseCur == SUBEDIT_ALL || pse == &psec->pse[psec->iseCur])) {
  3515. GetDateFormat(psec->ct.lcid, 0, &psec->st, TEXT("yyyy"), pszBuf, cchBuf);
  3516. } else if (SE_DATELIKE(pse->id)) {
  3517. GetDateFormat(psec->ct.lcid, 0, &psec->st, pse->pv, pszBuf, cchBuf);
  3518. // Change a blank era to a space so the user can see it
  3519. if (pse->id == SE_ERA && pszBuf[0] == TEXT('\0')) {
  3520. pszBuf[0] = TEXT(' ');
  3521. pszBuf[1] = TEXT('\0');
  3522. }
  3523. } else if (pse->id != SE_APP) {
  3524. GetTimeFormat(LOCALE_USER_DEFAULT, 0, &psec->st, pse->pv, pszBuf, cchBuf);
  3525. } else {
  3526. NMDATETIMEFORMAT nmdtf = { 0 };
  3527. nmdtf.pszFormat = pse->pv;
  3528. SECGetSystemtime(psec, &nmdtf.st);
  3529. nmdtf.pszDisplay = nmdtf.szDisplay;
  3530. CCSendNotify(psec->pci, DTN_FORMAT, &nmdtf.nmhdr);
  3531. lstrcpyn(pszBuf, nmdtf.pszDisplay, cchBuf);
  3532. #ifdef UNICODE
  3533. //
  3534. // If the parent is an ANSI window, and pszDisplay
  3535. // does not equal szDisplay, the then thunk had to
  3536. // allocated memory for pszDisplay. We need to
  3537. // free it here.
  3538. //
  3539. if (!psec->pci->bUnicode && nmdtf.pszDisplay &&
  3540. nmdtf.pszDisplay != nmdtf.szDisplay) {
  3541. LocalFree ((LPSTR)nmdtf.pszDisplay);
  3542. }
  3543. #endif
  3544. }
  3545. }
  3546. //
  3547. // SUBEDIT stuff for DateTimePicker
  3548. //
  3549. // NOTE: Now that the DatePicker and TimePicker are combined,
  3550. // this could be moved back into the parent structure.
  3551. //
  3552. //
  3553. // Used in an era calendar to get the length of the longest era name.
  3554. // Also leaves a random heigth in psize->cy because that's what the
  3555. // non-era code does, too.
  3556. //
  3557. int SECGetMaxEraLength(PCALENDARTYPE pct, HDC hdc, PSIZE psize)
  3558. {
  3559. int i;
  3560. int wid = 0;
  3561. for (i = 0; i < DPA_GetPtrCount(pct->hdpaEras); i++)
  3562. {
  3563. LPCTSTR ptsz = DPA_FastGetPtr(pct->hdpaEras, i);
  3564. if (GetTextExtentPoint32(hdc, ptsz, lstrlen(ptsz), psize) &&
  3565. psize->cx > wid)
  3566. {
  3567. wid = psize->cx;
  3568. }
  3569. }
  3570. return wid;
  3571. }
  3572. // SECRecomputeSizing needs to calculate the maximum rectangle each subedit can be. Ugh.
  3573. //
  3574. // The size of the SE_YEARALT field changes depending on whether or not it is
  3575. // the current psec->iseCur. Double ugh.
  3576. void SECRecomputeSizing(LPSUBEDITCONTROL psec, LPRECT prc)
  3577. {
  3578. HDC hdc;
  3579. HGDIOBJ hfontOrig;
  3580. int i;
  3581. LPSUBEDIT pse;
  3582. int left = prc->left;
  3583. psec->rc = *prc;
  3584. hdc = GetDC(psec->pci->hwnd);
  3585. hfontOrig = SelectObject(hdc, (HGDIOBJ)psec->hfont);
  3586. for (i=0, pse=psec->pse; i < psec->cse ; i++, pse++)
  3587. {
  3588. TCHAR szTmp[DTP_FORMATLENGTH];
  3589. LPCTSTR sz;
  3590. int min, max;
  3591. SIZE size;
  3592. int wid;
  3593. min = pse->min;
  3594. max = pse->max;
  3595. if (pse->id == SE_STATIC)
  3596. {
  3597. ASSERT(pse->fReadOnly);
  3598. sz = pse->pv;
  3599. }
  3600. else
  3601. {
  3602. sz = szTmp;
  3603. // make some assumptions so we don't loop more than we have to
  3604. switch (pse->id)
  3605. {
  3606. // we only need seven for the text days of the week
  3607. case SE_DAY:
  3608. min = 10; // make them all double-digit
  3609. max = 17;
  3610. break;
  3611. // Assume we only have numeric output with all chars same width
  3612. case SE_MARK:
  3613. min = 11;
  3614. max = 12;
  3615. break;
  3616. case SE_HOUR:
  3617. case SE_YEAR:
  3618. case SE_YEARALT:
  3619. case SE_MINUTE:
  3620. case SE_SECOND:
  3621. min = max;
  3622. break;
  3623. case SE_ERA:
  3624. if (ISERACALENDAR(&psec->ct)) {
  3625. wid = SECGetMaxEraLength(&psec->ct, hdc, &size);
  3626. goto HaveWidth;
  3627. } else {
  3628. min = max = *pse->pval; // current value is good enough
  3629. }
  3630. break;
  3631. }
  3632. }
  3633. // now get max width
  3634. if (pse->id == SE_APP)
  3635. {
  3636. NMDATETIMEFORMATQUERY nmdtfq = {0};
  3637. nmdtfq.pszFormat = pse->pv;
  3638. CCSendNotify(psec->pci, DTN_FORMATQUERY, &nmdtfq.nmhdr);
  3639. size = nmdtfq.szMax;
  3640. wid = nmdtfq.szMax.cx;
  3641. }
  3642. else
  3643. {
  3644. SYSTEMTIME st = psec->st;
  3645. /*
  3646. * SUBTLE - Munge the month/day to January 1. This solves
  3647. * lots of problems, such as "Today is Feb 29 1996, and
  3648. * when we iterate through year = 1997, we get Feb 29 1997,
  3649. * which is invalid." Or "Today is Jan 31 1999, and when
  3650. * we iterate through the months, we get Sep 31 1999, which
  3651. * is invalid."
  3652. *
  3653. * We choose "January 1" because
  3654. *
  3655. * 1. Every year has a "January 1", so the year can vary.
  3656. * 2. Every month has a "first", so the month can vary.
  3657. * 3. Every day up to 31 is valid in January, so the day can vary.
  3658. */
  3659. psec->st.wMonth = psec->st.wDay = 1;
  3660. for (wid = 0 ; min <= max ; min++)
  3661. {
  3662. if (pse->id != SE_STATIC)
  3663. {
  3664. *pse->pval = (WORD) min;
  3665. SEGetTimeDateFormat(pse, psec, szTmp, ARRAYSIZE(szTmp));
  3666. if (szTmp[0] == TEXT('\0'))
  3667. {
  3668. DebugMsg(TF_ERROR, TEXT("SECRecomputeSizing: GetDate/TimeFormat([%s] y=%d m=%d d=%d h=%d m=%d s=%d) = ERROR %d"),
  3669. pse->pv, psec->st.wYear, psec->st.wMonth, psec->st.wDay, psec->st.wHour, psec->st.wMinute, psec->st.wSecond,
  3670. GetLastError());
  3671. }
  3672. }
  3673. if (!GetTextExtentPoint32(hdc, sz, lstrlen(sz), &size))
  3674. {
  3675. size.cx = 0;
  3676. DebugMsg(TF_MONTHCAL,TEXT("SECRecomputeSizing: GetTextExtentPoint32(%s) = ERROR %d"), sz, GetLastError());
  3677. }
  3678. if (size.cx > wid)
  3679. wid = size.cx;
  3680. }
  3681. psec->st = st;
  3682. }
  3683. HaveWidth:
  3684. // now set up subedit's bounding rectangle
  3685. pse->rc.top = prc->top + SECYBORDER;
  3686. pse->rc.bottom = pse->rc.top + size.cy;
  3687. pse->rc.left = left;
  3688. pse->rc.right = left + wid;
  3689. left = pse->rc.right;
  3690. }
  3691. SelectObject(hdc, hfontOrig);
  3692. ReleaseDC(psec->pci->hwnd, hdc);
  3693. }
  3694. // InitSubEditControl parses szFormat into psec, setting the time to pst.
  3695. TCHAR c_szFormats[] = TEXT("gyMdthHmsX");
  3696. BOOL SECParseFormat(DATEPICK* pdp, LPSUBEDITCONTROL psec, LPCTSTR szFormat)
  3697. {
  3698. LPCTSTR pFmt;
  3699. LPTSTR psecFmt;
  3700. int cse, cchExtra;
  3701. int nTmp;
  3702. LPSUBEDIT pse;
  3703. BOOL fDaySeen = FALSE;
  3704. BOOL fForceCentury = FALSE;
  3705. int iLen, i;
  3706. TCHAR tch;
  3707. LPTSTR pFmtTemp;
  3708. TCHAR szFormatTemp[DTP_FORMATLENGTH];
  3709. //
  3710. // We need to force the century if the format is
  3711. // DTS_SHORTDATECENTURYFORMAT.
  3712. //
  3713. if (pdp->fLocale &&
  3714. (pdp->ci.style & DTS_FORMATMASK) == DTS_SHORTDATECENTURYFORMAT)
  3715. {
  3716. fForceCentury = TRUE;
  3717. }
  3718. // [msadek]; If we need to mirror the format and the
  3719. // cleint passed a read only buffer, we will AV (W2k bug# 354533)
  3720. // Let's copy it first
  3721. if (psec->fMirrorSEC)
  3722. {
  3723. lstrcpyn(szFormatTemp, szFormat, ARRAYSIZE(szFormatTemp));
  3724. szFormat = szFormatTemp;
  3725. }
  3726. // count szFormat sections so we know what to allocate
  3727. pFmt = szFormat;
  3728. cse = 0;
  3729. cchExtra = 0;
  3730. while (*pFmt)
  3731. {
  3732. if (StrChr(c_szFormats, *pFmt)) // format string
  3733. {
  3734. TCHAR c = *pFmt;
  3735. while (c == *pFmt)
  3736. pFmt++;
  3737. cse++;
  3738. // If it was a string Month format, reserve 2 more chars for
  3739. // the possible "dd" leader, in case we need SE_MONTHALT.
  3740. if (c == TEXT('M'))
  3741. cchExtra += 2;
  3742. }
  3743. else if (*pFmt == TEXT('\'')) // quoted static string
  3744. {
  3745. KeepSearching:
  3746. pFmt++;
  3747. while (*pFmt && *pFmt != TEXT('\''))
  3748. pFmt++;
  3749. if (*pFmt) // handle poorly quoted strings
  3750. {
  3751. pFmt++;
  3752. if (*pFmt == TEXT('\'')) // quoted quote, not end of quote
  3753. goto KeepSearching;
  3754. }
  3755. cse++;
  3756. }
  3757. else // static string probably a delimiter
  3758. {
  3759. while (*pFmt && *pFmt!=TEXT('\'') && !StrChr(c_szFormats, *pFmt))
  3760. pFmt++;
  3761. cse++;
  3762. }
  3763. }
  3764. // Allocate space
  3765. nTmp = cse + lstrlen(szFormat) + cchExtra + 1; // number of chars
  3766. nTmp = nTmp * sizeof(TCHAR); // size in BYTES
  3767. nTmp = ROUND_TO_POINTER(nTmp); // round up to POINTER boundary
  3768. psecFmt = (LPTSTR)LocalAlloc(LPTR, nTmp + cse * sizeof(SUBEDIT));
  3769. if (!psecFmt)
  3770. {
  3771. DebugMsg(TF_MONTHCAL, TEXT("SECParseFormat failed to allocate memory"));
  3772. return FALSE; // use whatever we already have
  3773. }
  3774. if (psec->szFormat)
  3775. LocalFree(psec->szFormat);
  3776. psec->szFormat = psecFmt;
  3777. psec->cDelimeter = '\0';
  3778. psec->pse = (LPSUBEDIT)((LPBYTE)psecFmt + nTmp);
  3779. // Fill psec
  3780. psec->iseCur = SUBEDIT_NONE;
  3781. psec->cse = cse;
  3782. pse = psec->pse;
  3783. ZeroMemory(pse, cse * sizeof(SUBEDIT));
  3784. pFmt = szFormat;
  3785. pdp->fHasMark = FALSE;
  3786. //
  3787. // Before start parsing the format string, let's mirror it if requested.
  3788. //
  3789. if (psec->fMirrorSEC)
  3790. {
  3791. pFmtTemp = (LPTSTR)pFmt;
  3792. iLen = lstrlen(pFmtTemp);
  3793. for( i=0 ; i<iLen/2 ; i++ )
  3794. {
  3795. tch = pFmtTemp[i];
  3796. pFmtTemp[i] = pFmtTemp[iLen-i-1];
  3797. pFmtTemp[iLen-i-1] = tch;
  3798. }
  3799. }
  3800. while (*pFmt)
  3801. {
  3802. pse->flDrawText = DT_CENTER;
  3803. if (*pFmt == TEXT('y') || *pFmt == TEXT('g')) // y=year g=era
  3804. {
  3805. TCHAR ch = *pFmt;
  3806. // If the calendar doesn't use eras, then the era field is just
  3807. // for show and can't be changed.
  3808. if (ch == TEXT('g') && !ISERACALENDAR(&psec->ct)) {
  3809. pse->fReadOnly = TRUE;
  3810. }
  3811. pse->id = ch == TEXT('y') ? SE_YEAR : SE_ERA;
  3812. pse->pval = &psec->st.wYear;
  3813. pse->min = c_stEpoch.wYear;
  3814. pse->max = c_stArmageddon.wYear;
  3815. pse->cchMax = 0;
  3816. pse->pv = psecFmt;
  3817. while (*pFmt == ch) {
  3818. pse->cchMax++;
  3819. *psecFmt++ = *pFmt++;
  3820. }
  3821. if (pse->id == SE_YEAR)
  3822. {
  3823. pse->flDrawText = DT_RIGHT;
  3824. if (fForceCentury)
  3825. {
  3826. pse->pv = TEXT("yyyy");
  3827. pse->cchMax = 4;
  3828. }
  3829. else
  3830. {
  3831. switch (pse->cchMax)
  3832. {
  3833. case 1: // "y" is a SE_YEARALT
  3834. case 2: // "yy" is a SE_YEARALT
  3835. pse->id = SE_YEARALT;
  3836. pse->cchMax = 4; // Force four-digit editing
  3837. break;
  3838. case 3: // "yyy" is an alias for "yyyy".
  3839. pse->cchMax = 4;
  3840. break;
  3841. }
  3842. }
  3843. }
  3844. *psecFmt++ = TEXT('\0');
  3845. }
  3846. else if (*pFmt == TEXT('M')) // month
  3847. {
  3848. pse->pv = psecFmt;
  3849. // If the day has been seen, then we need to use the alternate
  3850. // month format, so set up the gratuitous "dd" prefix.
  3851. // See SEGetTimeDateFormat.
  3852. //
  3853. if (fDaySeen) {
  3854. pse->id = SE_MONTHALT;
  3855. *psecFmt++ = TEXT('d');
  3856. *psecFmt++ = TEXT('d');
  3857. } else {
  3858. pse->id = SE_MONTH;
  3859. }
  3860. pse->pval = &psec->st.wMonth;
  3861. pse->min = 1;
  3862. pse->max = 12;
  3863. pse->cchMax = 2;
  3864. while (*pFmt == TEXT('M'))
  3865. *psecFmt++ = *pFmt++;
  3866. if (psecFmt - pse->pv <= 2)
  3867. pse->flDrawText = DT_RIGHT;
  3868. *psecFmt++ = TEXT('\0');
  3869. }
  3870. #if 0 // if we ever do week-of-year format
  3871. else if (*pFmt == TEXT('w')) // week
  3872. {
  3873. // REARCHITECT: NLS date functions can modify the displayed YEAR if the displayed WEEK overflows
  3874. // into the next or prior year!
  3875. // REARCHITECT: We need to make the week increment move beyond month boundries...
  3876. // In order to do this, we need to add an "overflow" flag so day increments will
  3877. // carry into month increments etc.
  3878. pse->id = SE_DAY;
  3879. pse->pval = &psec->st.wDay;
  3880. pse->min = 1;
  3881. pse->max = GetDaysForMonth(psec->st.wYear, psec->st.wMonth);
  3882. pse->cIncrement = 7;
  3883. pse->cchMax = 2;
  3884. pse->pv = psecFmt;
  3885. while (*pFmt == TEXT('w'))
  3886. *psecFmt++ = *pFmt++;
  3887. *psecFmt++ = TEXT('\0');
  3888. }
  3889. #endif
  3890. else if (*pFmt == TEXT('d')) // day or day of week
  3891. {
  3892. fDaySeen = TRUE; // See SEGetTimeDateFormat
  3893. pse->id = SE_DAY;
  3894. pse->pval = &psec->st.wDay;
  3895. pse->min = 1;
  3896. pse->max = GetDaysForMonth(psec->st.wYear, psec->st.wMonth);
  3897. pse->cchMax = 2;
  3898. pse->pv = psecFmt;
  3899. while (*pFmt == TEXT('d'))
  3900. *psecFmt++ = *pFmt++;
  3901. if (psecFmt - pse->pv <= 2)
  3902. pse->flDrawText = DT_RIGHT; // day
  3903. else
  3904. pse->fReadOnly = TRUE; // day of week
  3905. *psecFmt++ = TEXT('\0');
  3906. }
  3907. else if (*pFmt == TEXT('t')) // marker
  3908. {
  3909. pdp->fHasMark = TRUE;
  3910. pse->id = SE_MARK;
  3911. pse->pval = &psec->st.wHour;
  3912. pse->min = 0;
  3913. pse->max = 23;
  3914. pse->cIncrement = 12;
  3915. pse->cchMax = 2;
  3916. pse->pv = psecFmt;
  3917. while (*pFmt == TEXT('t'))
  3918. *psecFmt++ = *pFmt++;
  3919. *psecFmt++ = TEXT('\0');
  3920. }
  3921. else if (*pFmt == TEXT('h')) // (12) hour
  3922. {
  3923. pse->id = SE_HOUR;
  3924. pse->pval = &psec->st.wHour;
  3925. pse->min = 0;
  3926. pse->max = 23;
  3927. pse->cchMax = 2;
  3928. pse->flDrawText = DT_RIGHT;
  3929. pse->pv = psecFmt;
  3930. while (*pFmt == TEXT('h'))
  3931. *psecFmt++ = *pFmt++;
  3932. *psecFmt++ = TEXT('\0');
  3933. }
  3934. else if (*pFmt == TEXT('H')) // (24) hour
  3935. {
  3936. pse->id = SE_HOUR;
  3937. pse->pval = &psec->st.wHour;
  3938. pse->min = 0;
  3939. pse->max = 23;
  3940. pse->cchMax = 2;
  3941. pse->flDrawText = DT_RIGHT;
  3942. pse->pv = psecFmt;
  3943. while (*pFmt == TEXT('H'))
  3944. *psecFmt++ = *pFmt++;
  3945. *psecFmt++ = TEXT('\0');
  3946. }
  3947. else if (*pFmt == TEXT('m')) // minute
  3948. {
  3949. pse->id = SE_MINUTE;
  3950. pse->pval = &psec->st.wMinute;
  3951. pse->min = 0;
  3952. pse->max = 59;
  3953. pse->cchMax = 2;
  3954. pse->flDrawText = DT_RIGHT;
  3955. pse->pv = psecFmt;
  3956. while (*pFmt == TEXT('m'))
  3957. *psecFmt++ = *pFmt++;
  3958. *psecFmt++ = TEXT('\0');
  3959. }
  3960. else if (*pFmt == TEXT('s')) // second
  3961. {
  3962. pse->id = SE_SECOND;
  3963. pse->pval = &psec->st.wSecond;
  3964. pse->min = 0;
  3965. pse->max = 59;
  3966. pse->cchMax = 2;
  3967. pse->flDrawText = DT_RIGHT;
  3968. pse->pv = psecFmt;
  3969. while (*pFmt == TEXT('s'))
  3970. *psecFmt++ = *pFmt++;
  3971. *psecFmt++ = TEXT('\0');
  3972. }
  3973. else if (*pFmt == TEXT('X')) // app specified field
  3974. {
  3975. pse->id = SE_APP;
  3976. pse->pv = psecFmt;
  3977. while (*pFmt == TEXT('X'))
  3978. *psecFmt++ = *pFmt++;
  3979. *psecFmt++ = TEXT('\0');
  3980. }
  3981. else if (*pFmt == TEXT('\'')) // quoted static string
  3982. {
  3983. pse->id = SE_STATIC;
  3984. pse->fReadOnly = TRUE;
  3985. pse->pv = psecFmt;
  3986. SearchSomeMore:
  3987. pFmt++;
  3988. while (*pFmt && *pFmt != TEXT('\''))
  3989. *psecFmt++ = *pFmt++;
  3990. if (*pFmt) // handle poorly quoted strings
  3991. {
  3992. pFmt++;
  3993. if (*pFmt == TEXT('\'')) // quoted quote, not end of quote
  3994. {
  3995. *psecFmt++ = *pFmt;
  3996. goto SearchSomeMore;
  3997. }
  3998. }
  3999. *psecFmt++ = TEXT('\0');
  4000. }
  4001. else // unknown non-editable stuff (most likely a delimeter)
  4002. {
  4003. // FEATURE: even though it's unknown, we should probably pass
  4004. // it off to GetDateFormat so we will be forward compatible
  4005. // with future date formats...
  4006. //
  4007. pse->id = SE_STATIC;
  4008. pse->fReadOnly = TRUE;
  4009. if (!psec->cDelimeter)
  4010. psec->cDelimeter = *pFmt;
  4011. pse->pv = psecFmt;
  4012. while (*pFmt && *pFmt!=TEXT('\'') && !StrChr(c_szFormats, *pFmt))
  4013. *psecFmt++ = *pFmt++;
  4014. *psecFmt++ = TEXT('\0');
  4015. // we'll assume that the first not formatting char is the
  4016. // delimeter...maybe not a great assumption, but it will work
  4017. // most of the time.
  4018. //
  4019. }
  4020. pse++;
  4021. }
  4022. #ifdef DEBUG
  4023. {
  4024. TCHAR sz[200];
  4025. LPTSTR psz;
  4026. psz = sz;
  4027. sz[0]=TEXT('\0');
  4028. pse = psec->pse;
  4029. cse = psec->cse;
  4030. while (cse > 0)
  4031. {
  4032. wsprintf(psz, TEXT("[%s] "), pse->pv);
  4033. psz = psz + lstrlen(psz);
  4034. cse--;
  4035. pse++;
  4036. }
  4037. DebugMsg(TF_MONTHCAL, TEXT("SECParseFormat: %s"), sz);
  4038. }
  4039. #endif
  4040. //
  4041. // Let restore the original format
  4042. //
  4043. if (psec->fMirrorSEC)
  4044. {
  4045. pFmtTemp = (LPTSTR)szFormat;
  4046. for( i=0 ; i<iLen/2 ; i++ )
  4047. {
  4048. tch = pFmtTemp[i];
  4049. pFmtTemp[i] = pFmtTemp[iLen-i-1];
  4050. pFmtTemp[iLen-i-1] = tch;
  4051. }
  4052. }
  4053. //
  4054. // If this is a time-only DTP control and we need to swap the AM/PM symbol
  4055. // to the other side, then let's do it.
  4056. //
  4057. if (psec->fSwapTimeMarker)
  4058. {
  4059. SUBEDIT se;
  4060. pse = psec->pse;
  4061. cse = psec->cse;
  4062. if ((cse > 1) && (psec->pse[0].id == SE_MARK))
  4063. {
  4064. se = psec->pse[0];
  4065. i = 0;
  4066. while( i < (cse-1) )
  4067. {
  4068. pse[i] = pse[i+1];
  4069. i++;
  4070. }
  4071. pse[psec->cse-1] = se;
  4072. }
  4073. }
  4074. // The subedits have changed, recompute sizes
  4075. SECRecomputeSizing(psec, &psec->rc);
  4076. // We're going to need to redraw this
  4077. InvalidateRect(psec->pci->hwnd, NULL, TRUE);
  4078. // Changing the format also changes the window text.
  4079. NotifyWinEvent(EVENT_OBJECT_VALUECHANGE, pdp->ci.hwnd, OBJID_CLIENT, INDEXID_CONTAINER);
  4080. return TRUE;
  4081. }
  4082. void SECDestroy(LPSUBEDITCONTROL psec)
  4083. {
  4084. if (psec->szFormat)
  4085. {
  4086. LocalFree(psec->szFormat);
  4087. psec->szFormat = NULL;
  4088. }
  4089. }
  4090. void SECSetFont(LPSUBEDITCONTROL psec, HFONT hfont)
  4091. {
  4092. if (hfont == NULL)
  4093. hfont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
  4094. psec->hfont = hfont;
  4095. }
  4096. void InvalidateScrollRect(HWND hwnd, RECT *prc, int xScroll)
  4097. {
  4098. RECT rc;
  4099. if (xScroll)
  4100. {
  4101. rc = *prc;
  4102. OffsetRect(&rc, -xScroll, 0);
  4103. prc = &rc;
  4104. }
  4105. InvalidateRect(hwnd, prc, TRUE);
  4106. }
  4107. void SECSaveResetSubeditEdit(DATEPICK *pdp, BOOL fReset);
  4108. #define SECSaveSubeditEdit(pdp) SECSaveResetSubeditEdit(pdp, FALSE)
  4109. #define SECResetSubeditEdit(pdp) SECSaveResetSubeditEdit(pdp, TRUE)
  4110. // Set the current subedit, scrolling things into view as needed
  4111. void SECSetCurSubed(DATEPICK *pdp, int isubed)
  4112. {
  4113. LPSUBEDITCONTROL psec = &pdp->sec;
  4114. // validate the arguments
  4115. ASSERT(isubed < psec->cse);
  4116. // if the subedit is changing, we need to invalidate stuff
  4117. if (isubed != psec->iseCur)
  4118. {
  4119. int isePre;
  4120. if (psec->iseCur >= 0)
  4121. {
  4122. SECResetSubeditEdit(pdp);
  4123. InvalidateScrollRect(psec->pci->hwnd, &psec->pse[psec->iseCur].rc, psec->xScroll);
  4124. }
  4125. isePre = psec->iseCur;
  4126. psec->iseCur = isubed;
  4127. // For perf reasons, do a full recompute only if SE_YEARALT or
  4128. // SUBEDIT_ALL was involved, since those are the only cases
  4129. // where SE_YEARALT fields change size.
  4130. #define YearAffected(psec, ise) \
  4131. (ise == SUBEDIT_ALL || \
  4132. (ise >= 0 && psec->pse[ise].id == SE_YEARALT))
  4133. if (YearAffected(psec, isePre) || YearAffected(psec, isubed))
  4134. {
  4135. SECRecomputeSizing(psec, &psec->rc);
  4136. InvalidateRect(psec->pci->hwnd, NULL, TRUE);
  4137. }
  4138. #undef YearAffected
  4139. if (psec->iseCur >= 0)
  4140. {
  4141. RECT rc = psec->pse[psec->iseCur].rc;
  4142. OffsetRect(&rc, -psec->xScroll, 0);
  4143. if (rc.left < psec->rc.left)
  4144. {
  4145. psec->xScroll += rc.left - psec->rc.left;
  4146. InvalidateRect(psec->pci->hwnd, NULL, TRUE);
  4147. }
  4148. else if (rc.right > psec->rc.right)
  4149. {
  4150. psec->xScroll += rc.right - psec->rc.right;
  4151. InvalidateRect(psec->pci->hwnd, NULL, TRUE);
  4152. }
  4153. else
  4154. {
  4155. InvalidateRect(psec->pci->hwnd, &rc, TRUE);
  4156. }
  4157. }
  4158. }
  4159. }
  4160. int SECIncrFocus(DATEPICK *pdp, int delta)
  4161. {
  4162. int ise, loop;
  4163. LPSUBEDITCONTROL psec = &pdp->sec;
  4164. ASSERT(-1 == delta || 1 == delta);
  4165. ise = psec->iseCur;
  4166. if (ise < 0 && delta < 0)
  4167. ise = psec->cse;
  4168. for (loop = 0 ; loop < psec->cse ; loop++)
  4169. {
  4170. int oldise = ise;
  4171. ise = (ise + delta + psec->cse) % psec->cse;
  4172. if (ise != oldise+delta && psec->fNone)
  4173. {
  4174. // we wrapped and we allow scrolling into SUBEDIT_NONE state
  4175. break;
  4176. }
  4177. if (!psec->pse[ise].fReadOnly)
  4178. {
  4179. goto Found;
  4180. }
  4181. }
  4182. ise = SUBEDIT_NONE;
  4183. Found:
  4184. SECSetCurSubed(pdp, ise);
  4185. return ise;
  4186. }
  4187. void SECInvalidate(LPSUBEDITCONTROL psec, int id);
  4188. //
  4189. // Given a Gregorian year, get the local name for that year.
  4190. //
  4191. UINT SECGetYearValue(DATEPICK *pdp, UINT uYear)
  4192. {
  4193. UINT uiRc = 0;
  4194. TCHAR rgch[64];
  4195. if (EVAL(MCGetDateFormatWithTempYear(&pdp->sec.ct, &pdp->sec.st, TEXT("yyyy"), uYear, rgch, ARRAYSIZE(rgch)))) {
  4196. uiRc = StrToInt(rgch);
  4197. }
  4198. return uiRc;
  4199. }
  4200. //
  4201. // SECAdjustByEra
  4202. //
  4203. // pct - PCALENDARTYPE structure to use for conversion
  4204. //
  4205. // uInput - the value the user typed (to be interpreted as local calendar)
  4206. //
  4207. // The basic idea is that if you type a year, it is interpreted relative
  4208. // to the era you were in previously. If the number you typed isn't valid
  4209. // for that era, then reject it (by returning the original year unchanged).
  4210. //
  4211. UINT SECAdjustByEra(DATEPICK *pdp, UINT uInput)
  4212. {
  4213. UINT uResult = pdp->sec.st.wYear;
  4214. //
  4215. // Find the delta between the current local year and the current
  4216. // Gregorian year. We don't use any of the era transition dates
  4217. // since they aren't reliable at the boundaries. Just convert it
  4218. // to a display name and re-parse it back.
  4219. //
  4220. UINT uDelta = pdp->sec.st.wYear - SECGetYearValue(pdp, pdp->sec.st.wYear);
  4221. //
  4222. // Apply that delta to the year the user typed in. This converts the
  4223. // local year into a Gregorian year.
  4224. //
  4225. UINT uNewVal = uInput + uDelta;
  4226. // uNewVal is the value we want to change it to. If it's valid for that
  4227. // era, then use it. We detect that it's okay for the era by converting
  4228. // it to a display name and seeing if it matches. It can fail for being
  4229. // too large (past the end of the era) or too small (trying to change to
  4230. // January 1 local year 1 when the era didn't change until March).
  4231. if (SECGetYearValue(pdp, uNewVal) == uInput) {
  4232. uResult = uNewVal;
  4233. }
  4234. return uResult;
  4235. }
  4236. //
  4237. // SECAdjustByType
  4238. //
  4239. // Some field types are special.
  4240. //
  4241. // SE_YEAR or SE_YEARALT if user typed only two digits
  4242. //
  4243. // Use the "implied century for two-digit years" logic
  4244. // as described in GetCalendarInfoA.
  4245. //
  4246. // SE_YEAR or SE_YEARALT if user typed more than two digits
  4247. //
  4248. // Use that number.
  4249. //
  4250. // BONUS FEATURE!
  4251. //
  4252. // Some calendars run parallel to the Gregorian year,
  4253. // but with different years. Use GregorianToOther and
  4254. // OtherToGregorian to convert.
  4255. //
  4256. // The input value is the local year (Gregorian, Buddhist, whatever)
  4257. // but the return value is always the Gregorian year, since that's
  4258. // what SYSTEMTIME uses.
  4259. //
  4260. // SE_HOUR
  4261. //
  4262. // If the clock is in 12-hour format, then preserve AM/PM ness of
  4263. // the hour. For example, if it was 3pm and somebody is changing
  4264. // the hour to 4, use 4pm instead of 4am.
  4265. //
  4266. UINT SECAdjustByType(DATEPICK *pdp, LPSUBEDIT psubed, UINT uNewValue)
  4267. {
  4268. if (SE_YEARLIKE(psubed->id))
  4269. {
  4270. if (uNewValue < 100)
  4271. {
  4272. // Get the preferred century of the preferred calendar
  4273. // (in the localized year, not Gregorian.)
  4274. DWORD dwMax2DigitYear;
  4275. if (!GetCalendarInfoA(pdp->sec.ct.lcid, pdp->sec.ct.calid, CAL_RETURN_NUMBER + CAL_ITWODIGITYEARMAX,
  4276. NULL, 0, &dwMax2DigitYear))
  4277. {
  4278. // default in the absence of all information
  4279. dwMax2DigitYear = GregorianToOther(&pdp->sec.ct, 2029);
  4280. // if the current year in this era is less than 100, then the 2 digits typed
  4281. // may be the real date, so set the max to 99 (i.e., no conversion)
  4282. //
  4283. if (dwMax2DigitYear < 99)
  4284. dwMax2DigitYear = 99;
  4285. }
  4286. //
  4287. // Copy the century of dwMax2DigitYear into uNewValue.
  4288. //
  4289. uNewValue += (dwMax2DigitYear - dwMax2DigitYear % 100);
  4290. //
  4291. // If it exceeds the max, then drop to previous century.
  4292. //
  4293. if (uNewValue > dwMax2DigitYear)
  4294. uNewValue -= 100;
  4295. }
  4296. //
  4297. // Finally, convert back to Gregorian as necessary.
  4298. //
  4299. uNewValue = OtherToGregorian(&pdp->sec.ct, uNewValue);
  4300. //
  4301. // If we are in an Era calendar, then we need to adjust the
  4302. // year relative to the ambient era.
  4303. //
  4304. if (ISERACALENDAR(&pdp->sec.ct)) {
  4305. uNewValue = SECAdjustByEra(pdp, uNewValue);
  4306. }
  4307. } else if (psubed->id == SE_HOUR && psubed->pv[0] == TEXT('h')) {
  4308. if (*psubed->pval >= 12 && uNewValue < 12)
  4309. uNewValue += 12;
  4310. }
  4311. return uNewValue;
  4312. }
  4313. void SECSetSubeditValue(DATEPICK *pdp, LPSUBEDIT psubed, UINT uNewValue, BOOL fForce)
  4314. {
  4315. LPSUBEDITCONTROL psec = &pdp->sec;
  4316. UINT uOldValue;
  4317. uNewValue = SECAdjustByType(pdp, psubed, uNewValue);
  4318. //
  4319. // Must do a full-on range check in addition to the simple psubed->min
  4320. // psubed->max range check because the new value might be valid
  4321. // for our range but not in the global scheme of things. For example,
  4322. // the minimum date is Sep 14 1752, but if today is Jan 1 1995 and
  4323. // the user types "1752", that will pass the simple min/max year test,
  4324. // but it's not a valid date since Jan 1 1752 is out of range.
  4325. //
  4326. uOldValue = *psubed->pval;
  4327. *psubed->pval = (WORD)uNewValue;
  4328. if (uNewValue >= psubed->min && uNewValue <= psubed->max &&
  4329. CmpSystemtime(&pdp->sec.st, &pdp->stMin) >= 0 &&
  4330. CmpSystemtime(&pdp->sec.st, &pdp->stMax) <= 0)
  4331. {
  4332. if (fForce || uNewValue != uOldValue)
  4333. {
  4334. SECInvalidate(psec, SE_APP);
  4335. InvalidateScrollRect(psec->pci->hwnd, &psubed->rc, psec->xScroll);
  4336. DPNotifyDateChange(pdp);
  4337. }
  4338. }
  4339. else
  4340. {
  4341. // Oops, not valid, put the old value back
  4342. *psubed->pval = (WORD)uOldValue;
  4343. }
  4344. }
  4345. // This saves the current pending value and also resets the edit state
  4346. // if fReset is TRUE
  4347. void SECSaveResetSubeditEdit(DATEPICK *pdp, BOOL fReset)
  4348. {
  4349. LPSUBEDITCONTROL psec = &pdp->sec;
  4350. if (psec->iseCur >= 0)
  4351. {
  4352. LPSUBEDIT psubed = &psec->pse[psec->iseCur];
  4353. if (psubed->cchEdit)
  4354. {
  4355. SECSetSubeditValue(pdp, psubed, psubed->valEdit, FALSE);
  4356. }
  4357. if (fReset)
  4358. psubed->cchEdit = 0;
  4359. }
  4360. }
  4361. // SECInvalidate invalidates the display for each subedit affected by a change
  4362. // to ID. NOTE: as a side affect, it recalculates MAX fields for all subedits
  4363. // affected by a change to ID.
  4364. //
  4365. // SE_APP invalidates everything, anything invalidates SE_APP
  4366. // SE_MARK (am/pm) invalidate SE_HOUR, SE_HOUR invalides SE_MARK
  4367. //
  4368. void SECInvalidate(LPSUBEDITCONTROL psec, int id)
  4369. {
  4370. BOOL fAdjustDayMax = (id == SE_MONTH || id == SE_MONTHALT || id == SE_YEAR || id == SE_YEARALT || id == SE_APP || id == SE_ERA);
  4371. LPSUBEDIT pse;
  4372. int i;
  4373. // If we changed any date field and we are in a era-like calendar,
  4374. // then invalidate all, since changing the month, day or year may
  4375. // change the era or vice versa.
  4376. if (ISERACALENDAR(&psec->ct) && SE_DATELIKE(id))
  4377. {
  4378. id = SE_APP;
  4379. }
  4380. for (pse=psec->pse, i=0 ; i < psec->cse ; pse++, i++)
  4381. {
  4382. // we need to invalidate all fields that changed
  4383. if (id == pse->id || pse->id == SE_APP || id == SE_APP || (id == SE_MARK && pse->id == SE_HOUR) || (id == SE_HOUR && pse->id == SE_MARK))
  4384. {
  4385. InvalidateScrollRect(psec->pci->hwnd, &pse->rc, psec->xScroll);
  4386. }
  4387. // the month or year changed, fix max field for SE_DAY
  4388. if (fAdjustDayMax && pse->id == SE_DAY)
  4389. {
  4390. pse->max = GetDaysForMonth(psec->st.wYear, psec->st.wMonth);
  4391. if (*pse->pval > pse->max)
  4392. {
  4393. *pse->pval = (WORD) pse->max;
  4394. }
  4395. SECInvalidate(psec, SE_DAY);
  4396. }
  4397. }
  4398. }
  4399. __inline
  4400. BOOL
  4401. SECGetEraName(LPSUBEDITCONTROL psec, LPSUBEDIT pse, UINT uYear, LPTSTR ptszBuf, UINT cchBuf)
  4402. {
  4403. return MCGetDateFormatWithTempYear(&psec->ct, &psec->st, pse->pv, uYear, ptszBuf, cchBuf);
  4404. }
  4405. //
  4406. // SECIncrementEra increments/decrements the era field. ERAs are strange
  4407. // since they aren't a field unto themselves but are rather an artifact
  4408. // of the other fields. Returns the new year to use.
  4409. //
  4410. UINT SECIncrementEra(LPSUBEDITCONTROL psec, LPSUBEDIT pse, int delta)
  4411. {
  4412. TCHAR rgch[64];
  4413. TCHAR rgch2[64];
  4414. int i;
  4415. int cEras = DPA_GetPtrCount(psec->ct.hdpaEras);
  4416. UINT uNewYear;
  4417. ASSERT(pse->pval == &psec->st.wYear);
  4418. uNewYear = psec->st.wYear;
  4419. //
  4420. // First find the era that encloses the current year.
  4421. // Do this by comparing the era string, because it's possible
  4422. // for the era to change twice within the same calendar year
  4423. // (if an emperor ascends to the throne and then dies the next week)
  4424. // so comparing against hdpaYear won't help.
  4425. //
  4426. SECGetEraName(psec, pse, uNewYear, rgch, ARRAYSIZE(rgch));
  4427. //
  4428. // If the era string is blank, it means we're in the "before the
  4429. // first era" scenario, so we use the "virtual" last element that
  4430. // represents "minus infinity".
  4431. //
  4432. if (rgch[0] == TEXT('\0'))
  4433. {
  4434. i = cEras;
  4435. goto FoundEra;
  4436. }
  4437. for (i = 0; i < cEras; i++)
  4438. {
  4439. if (lstrcmp(rgch, DPA_FastGetPtr(psec->ct.hdpaEras, i)) == 0)
  4440. goto FoundEra;
  4441. }
  4442. //
  4443. // Eek! Couldn't find the era! Just increment/decrement the
  4444. // year instead.
  4445. //
  4446. uNewYear += delta;
  4447. goto Finish;
  4448. FoundEra:
  4449. //
  4450. // The era list is stored backwards, so incrementing the era means
  4451. // decrementing the index (i).
  4452. //
  4453. if (delta > 0) // Incrementing
  4454. {
  4455. //
  4456. // Don't go off the end of the list. Note that if we were in
  4457. // the "virtual era" at minus infinity, this decrement will move
  4458. // us into the first "real" era.
  4459. //
  4460. if (--i < 0)
  4461. goto Finish;
  4462. // Increment to first year of the next era.
  4463. uNewYear = StrToInt(DPA_FastGetPtr(psec->ct.hdpaYears, i));
  4464. }
  4465. else
  4466. {
  4467. //
  4468. // Don't go off the end of the list. Note that this also
  4469. // catches the "virtual era" at minus infinity.
  4470. //
  4471. if (i >= cEras)
  4472. goto Finish;
  4473. //
  4474. // Move to the last year of the previous era. Do this by
  4475. // starting with the first year of the current era and
  4476. // decrementing it if necessary.
  4477. //
  4478. uNewYear = StrToInt(DPA_FastGetPtr(psec->ct.hdpaYears, i));
  4479. }
  4480. //
  4481. // We have a year that might be in the next/prev era. Try it.
  4482. // If we're still in the original era, then inc/dec one more time
  4483. // to get there for good.
  4484. //
  4485. SECGetEraName(psec, pse, uNewYear, rgch2, ARRAYSIZE(rgch2));
  4486. if (lstrcmp(rgch, rgch2) == 0)
  4487. uNewYear += delta;
  4488. Finish:
  4489. if (uNewYear < pse->min)
  4490. uNewYear = pse->min;
  4491. if (uNewYear > pse->max)
  4492. uNewYear = pse->max;
  4493. return uNewYear;
  4494. }
  4495. // SECIncrementSubedit increments currently selected subedit by delta
  4496. // Returns TRUE iff the value changed
  4497. BOOL SECIncrementSubedit(LPSUBEDITCONTROL psec, int delta)
  4498. {
  4499. LPSUBEDIT psubed;
  4500. UINT val;
  4501. if (psec->iseCur < 0)
  4502. return(FALSE);
  4503. psubed = &psec->pse[psec->iseCur];
  4504. if (psubed->id == SE_APP)
  4505. return(FALSE);
  4506. //
  4507. // Only numeric fields should accelerate. Text fields should always
  4508. // increment/decrement by exactly one position.
  4509. //
  4510. if (psubed->flDrawText & DT_CENTER) {
  4511. if (delta < 0) delta = -1;
  4512. if (delta > 0) delta = +1;
  4513. }
  4514. //
  4515. // Incrementing/decrementing ERAs is strange.
  4516. //
  4517. if (psubed->id == SE_ERA)
  4518. {
  4519. val = SECIncrementEra(psec, psubed, delta);
  4520. }
  4521. else
  4522. {
  4523. // delta isn't a REAL delta -- it's a directional thing. Here's the REAL delta:
  4524. if (psubed->cIncrement > 0)
  4525. delta = delta * psubed->cIncrement;
  4526. if(!psubed->pval)
  4527. return (FALSE);
  4528. val = *psubed->pval + delta;
  4529. while (1) {
  4530. if ((int)val < (int)psubed->min)
  4531. {
  4532. // don't wrap years
  4533. if (SE_YEARLIKE(psubed->id)) {
  4534. val = psubed->min;
  4535. break;
  4536. }
  4537. val = psubed->min - val - 1;
  4538. val = psubed->max - val;
  4539. }
  4540. else if (val > psubed->max)
  4541. {
  4542. // don't wrap years
  4543. if (SE_YEARLIKE(psubed->id)) {
  4544. val = psubed->max;
  4545. break;
  4546. }
  4547. val = val - psubed->max - 1;
  4548. val = psubed->min + val;
  4549. } else
  4550. break;
  4551. }
  4552. }
  4553. if (*psubed->pval != val)
  4554. {
  4555. *psubed->pval = (WORD) val;
  4556. SECInvalidate(psec, psubed->id);
  4557. return(TRUE);
  4558. }
  4559. return(FALSE);
  4560. }
  4561. // returns TRUE if a value has changed, FALSE otherwise
  4562. BOOL SECHandleKeydown(DATEPICK *pdp, WPARAM wParam, LPARAM lParam)
  4563. {
  4564. int delta = 1;
  4565. LPSUBEDITCONTROL psec = &pdp->sec;
  4566. switch (wParam)
  4567. {
  4568. case VK_LEFT:
  4569. delta = -1;
  4570. // fall through...
  4571. case VK_RIGHT:
  4572. SECResetSubeditEdit(pdp);
  4573. SECIncrFocus(pdp, delta);
  4574. return(FALSE);
  4575. }
  4576. if (psec->iseCur >= 0 &&
  4577. psec->pse[psec->iseCur].id == SE_APP)
  4578. {
  4579. NMDATETIMEWMKEYDOWN nmdtkd = {0};
  4580. nmdtkd.nVirtKey = (int) wParam;
  4581. nmdtkd.pszFormat = psec->pse[psec->iseCur].pv;
  4582. SECGetSystemtime(psec,&nmdtkd.st);
  4583. CCSendNotify(psec->pci, DTN_WMKEYDOWN, &nmdtkd.nmhdr);
  4584. if (psec->st.wYear != nmdtkd.st.wYear ||
  4585. psec->st.wMonth != nmdtkd.st.wMonth ||
  4586. psec->st.wDay != nmdtkd.st.wDay ||
  4587. psec->st.wHour != nmdtkd.st.wHour ||
  4588. psec->st.wMinute != nmdtkd.st.wMinute ||
  4589. psec->st.wSecond != nmdtkd.st.wSecond) // skip wDayOfWeek and wMilliseconds
  4590. {
  4591. psec->st = nmdtkd.st;
  4592. SECInvalidate(psec, SE_APP);
  4593. return(TRUE);
  4594. }
  4595. }
  4596. else
  4597. {
  4598. MSG msg;
  4599. switch (wParam)
  4600. {
  4601. case VK_DOWN:
  4602. case VK_SUBTRACT:
  4603. delta = -1;
  4604. // fall through...
  4605. case VK_UP:
  4606. case VK_ADD:
  4607. PeekMessage(&msg, NULL, WM_CHAR, WM_CHAR, PM_REMOVE); // eat this message
  4608. SECResetSubeditEdit(pdp);
  4609. return(SECIncrementSubedit(psec, delta));
  4610. break;
  4611. case VK_HOME:
  4612. case VK_END:
  4613. if (psec->iseCur >= 0)
  4614. {
  4615. LPSUBEDIT psubed;
  4616. int valT;
  4617. SECResetSubeditEdit(pdp);
  4618. psubed = &psec->pse[psec->iseCur];
  4619. valT = *psubed->pval;
  4620. *psubed->pval = (wParam == VK_HOME ? psubed->min : psubed->max);
  4621. delta = *psubed->pval - valT;
  4622. if (delta != 0)
  4623. {
  4624. SECInvalidate(psec, psubed->id);
  4625. return(TRUE);
  4626. }
  4627. }
  4628. break;
  4629. }
  4630. }
  4631. return(FALSE);
  4632. }
  4633. // returns TRUE if a value has changed, FALSE otherwise
  4634. // This function performs a DPNotifyDateChange() if applicable.
  4635. BOOL SECHandleChar(DATEPICK *pdp, TCHAR ch)
  4636. {
  4637. LPSUBEDIT psubed;
  4638. UINT uCurDigit; // current digit hit
  4639. UINT uCurSubValue; // current displayed subvalue in edit field
  4640. UINT uCurValue; // current value of the subedit
  4641. LPSUBEDITCONTROL psec = &pdp->sec;
  4642. // NOTE: In almost all cases, uCurSubValue will be the same as uCurValue
  4643. // since most fields don't have shortened displays. However, for years
  4644. // we can display two digits of a 4 digit number, which makes for
  4645. // complications.
  4646. if (psec->iseCur < 0)
  4647. return(FALSE);
  4648. psubed = &psec->pse[psec->iseCur];
  4649. if (psubed->cchMax == 0)
  4650. return(FALSE);
  4651. if (ch == psec->cDelimeter || StrChr(psec->szDelimeters, ch))
  4652. {
  4653. SECResetSubeditEdit(pdp);
  4654. SECIncrFocus(pdp, 1);
  4655. return(FALSE);
  4656. }
  4657. // allow 'a' and 'p' to set the AM/PM fields. we need to do some
  4658. // funky stuff to get this to work right, so here it is.
  4659. else if (psubed->id == SE_MARK)
  4660. {
  4661. if ((ch == TEXT('p') || ch == TEXT('P')) && (*psubed->pval < 12))
  4662. {
  4663. int valNew = *psubed->pval+12;
  4664. ch = (valNew) % 10 + TEXT('0');
  4665. psubed->valEdit = (valNew) / 10;
  4666. psubed->cchEdit = 1;
  4667. }
  4668. else if ((ch == TEXT('a') || ch == TEXT('A')) && (*psubed->pval >= 12))
  4669. {
  4670. int valNew = *psubed->pval-12;
  4671. ch = (valNew) % 10 + TEXT('0');
  4672. psubed->valEdit = (valNew) / 10;
  4673. psubed->cchEdit = 1;
  4674. }
  4675. else
  4676. {
  4677. return(FALSE);
  4678. }
  4679. }
  4680. else if (ch < TEXT('0') || ch > TEXT('9'))
  4681. {
  4682. MessageBeep(MB_ICONHAND);
  4683. return(FALSE);
  4684. }
  4685. else if (psubed->id == SE_ERA)
  4686. {
  4687. // I don't know what to do with this field, so bail out
  4688. return(FALSE);
  4689. }
  4690. uCurDigit = ch - TEXT('0');
  4691. if (psubed->cchEdit)
  4692. uCurSubValue = psubed->valEdit * 10 + uCurDigit;
  4693. else
  4694. uCurSubValue = uCurDigit;
  4695. uCurValue = SECAdjustByType(pdp, psubed, uCurSubValue);
  4696. // Allow bogus values for years since you might need to type
  4697. // in a bogus value on the way to a valid four-digit value.
  4698. if (uCurValue > psubed->max && !SE_YEARLIKE(psubed->id))
  4699. {
  4700. // the number has exceeded the max, so no point in continuing
  4701. psubed->cchEdit = 0;
  4702. // If we're going to exceed the max, then reset the edit
  4703. // and make this the first number instead of beeping
  4704. uCurValue = uCurValue - uCurSubValue + uCurDigit;
  4705. uCurSubValue = uCurDigit;
  4706. }
  4707. // Allow 0 to be valEdit for subedits, even though it may be
  4708. // illegal for that field (e.g., month).
  4709. // This lets people type "09" and get the "expected" result.
  4710. SECInvalidate(psec, psubed->id);
  4711. psubed->valEdit = uCurSubValue;
  4712. psubed->cchEdit++;
  4713. if (psubed->cchEdit == psubed->cchMax)
  4714. psubed->cchEdit = 0;
  4715. if (psubed->cchEdit == 0)
  4716. {
  4717. // SECSetSubeditValue will do the validation
  4718. SECSetSubeditValue(pdp, psubed, uCurSubValue, TRUE);
  4719. return(TRUE);
  4720. }
  4721. if(psubed->valEdit != *psubed->pval)
  4722. InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
  4723. return(FALSE);
  4724. }
  4725. // SECFormatSubed returns pointer to correct string
  4726. LPTSTR SECFormatSubed(LPSUBEDITCONTROL psec, LPSUBEDIT psubed, LPTSTR szTmp, UINT cch)
  4727. {
  4728. LPTSTR sz;
  4729. if (psubed->id == SE_STATIC)
  4730. {
  4731. sz = (LPTSTR)psubed->pv;
  4732. }
  4733. else
  4734. {
  4735. sz = szTmp;
  4736. SEGetTimeDateFormat(psubed, psec, szTmp, cch);
  4737. }
  4738. return sz;
  4739. }
  4740. // Returns TRUE if this subedit displays as digits (rather than text).
  4741. BOOL SECIsNumeric(LPSUBEDIT psubed)
  4742. {
  4743. switch (psubed->id)
  4744. {
  4745. case SE_ERA: return FALSE; // g never
  4746. case SE_YEAR: return TRUE; // yyyy always digits
  4747. case SE_YEARALT: return TRUE; // yy always digits
  4748. case SE_MONTH: return lstrlen(psubed->pv) <= 2; // MM yes, but not MMM
  4749. case SE_MONTHALT: return lstrlen(psubed->pv) <= 4; // ddMM yes, but not ddMMM
  4750. case SE_DAY: return TRUE; // dd always digits
  4751. case SE_MARK: return FALSE; // tt never
  4752. case SE_HOUR: return TRUE; // hh always digits
  4753. case SE_MINUTE: return TRUE; // mm always digits
  4754. case SE_SECOND: return TRUE; // ss always digits
  4755. case SE_STATIC: return FALSE; // static text
  4756. case SE_APP: return FALSE; // app's job to format this
  4757. }
  4758. return FALSE;
  4759. }
  4760. // SECDrawSubedits draws subedits and updates their bounding rectangles
  4761. void SECDrawSubedits(HDC hdc, LPSUBEDITCONTROL psec, BOOL fFocus, BOOL fEnabled)
  4762. {
  4763. HGDIOBJ hfontOrig;
  4764. int i, iseCur;
  4765. LPTSTR sz;
  4766. TCHAR szTmp[DTP_FORMATLENGTH];
  4767. LPSUBEDIT psubed;
  4768. hfontOrig = SelectObject(hdc, (HGDIOBJ)psec->hfont);
  4769. // Do this cuz the xScroll stuff can send text into visible area that it shouldn't be in
  4770. IntersectClipRect(hdc, psec->rc.left, psec->rc.top, psec->rc.right, psec->rc.bottom);
  4771. SetBkColor(hdc, g_clrHighlight);
  4772. iseCur = psec->iseCur;
  4773. if (!fFocus)
  4774. iseCur = SUBEDIT_NONE;
  4775. for (i = 0, psubed = psec->pse; i < psec->cse; i++, psubed++)
  4776. {
  4777. RECT rc = psubed->rc;
  4778. if (psec->xScroll)
  4779. OffsetRect(&rc, -psec->xScroll, 0);
  4780. if (!fEnabled)
  4781. {
  4782. SetBkMode(hdc, TRANSPARENT);
  4783. SetTextColor(hdc, g_clrGrayText);
  4784. }
  4785. else if (iseCur == i)
  4786. {
  4787. SetBkMode(hdc, OPAQUE);
  4788. SetTextColor(hdc, g_clrHighlightText);
  4789. }
  4790. else
  4791. {
  4792. SetBkMode(hdc, TRANSPARENT);
  4793. SetTextColor(hdc, g_clrWindowText);
  4794. }
  4795. //HACK
  4796. //if subedit control is being edited then we display the
  4797. //value in psubed->valEdit because it is not being updated
  4798. //until psubed->cchMax is reached or SECSave/ResetSubeditEdit is
  4799. //called
  4800. if(i == psec->iseCur && psubed->cchEdit != 0)
  4801. {
  4802. //
  4803. // If the field is numeric, then display it raw including the
  4804. // leading zero. People really want to see that leading zero,
  4805. // so give the public what it wants. (And even if they didn't,
  4806. // we need this special case anyway because the value might not
  4807. // yet be a valid value because the user is still typing it.
  4808. // This is particular true for SE_YEARLIKE fields.)
  4809. //
  4810. if (SECIsNumeric(psubed))
  4811. {
  4812. TCHAR szFormat[10];
  4813. wsprintf(szFormat, TEXT("%%0%dd"), psubed->cchEdit);
  4814. wsprintf(szTmp, szFormat, psubed->valEdit);
  4815. sz = szTmp;
  4816. }
  4817. else
  4818. {
  4819. // The day-of-month might not be valid for the temporary month
  4820. // or year in psubed->valEdit, so force the day-of-month to 1
  4821. // so the month will always come out okay.
  4822. //
  4823. // This is tricky, because if the item being edited is the
  4824. // day-of-month itself, we want to display valEdit, not 1!
  4825. // So we force it to 1, then slam in the valEdit, then do
  4826. // our SECFormatSubed, then restore the original values.
  4827. //
  4828. UINT uTmp = *psubed->pval; //save the original value
  4829. WORD wOldDay = psec->st.wDay;
  4830. psec->st.wDay = 1;
  4831. // Don't change to zero in case user is typing a leading zero
  4832. // into an alphabetic field. (Stranger things have happened.)
  4833. if (psubed->valEdit)
  4834. *psubed->pval = (WORD) psubed->valEdit;
  4835. sz = SECFormatSubed(psec, psubed, szTmp, ARRAYSIZE(szTmp));
  4836. psec->st.wDay = wOldDay;
  4837. *psubed->pval = (WORD) uTmp; //restore the original value
  4838. }
  4839. }
  4840. else
  4841. sz = SECFormatSubed(psec, psubed, szTmp, ARRAYSIZE(szTmp));
  4842. DrawText(hdc, sz, -1, &rc,
  4843. psubed->flDrawText | DT_TOP | DT_NOPREFIX | DT_SINGLELINE);
  4844. }
  4845. // we know no clip region was selected before this function
  4846. SelectClipRgn(hdc, NULL);
  4847. SelectObject(hdc, hfontOrig);
  4848. }
  4849. // DON'T need to worry about xScroll here because pt is offset
  4850. int SECSubeditFromPt(LPSUBEDITCONTROL psec, POINT pt)
  4851. {
  4852. int isubed;
  4853. for (isubed = psec->cse - 1; isubed >= 0; isubed--)
  4854. {
  4855. if (!psec->pse[isubed].fReadOnly &&
  4856. pt.x >= psec->pse[isubed].rc.left)
  4857. {
  4858. break;
  4859. }
  4860. }
  4861. return(isubed);
  4862. }
  4863. void SECGetSystemtime(LPSUBEDITCONTROL psec, LPSYSTEMTIME pst)
  4864. {
  4865. *pst = psec->st;
  4866. // we don't keep doy up to date, set it now (0==sun, 6==sat)
  4867. pst->wDayOfWeek = (DowFromDate(pst)+1) % 7; // this returns 0==sun
  4868. }
  4869. BOOL SECSetSystemtime(DATEPICK *pdp, LPSYSTEMTIME pst)
  4870. {
  4871. pdp->sec.st = *pst;
  4872. return TRUE; // assume something changed
  4873. }
  4874. // SECEdit: Start a free-format edit return result in szOutput.
  4875. BOOL SECEdit(DATEPICK *pdp, LPTSTR szOutput, int cchOutput)
  4876. {
  4877. HWND hwndEdit;
  4878. TCHAR szBuf[DTP_FORMATLENGTH];
  4879. LPTSTR pszBuf;
  4880. int cchBuf;
  4881. int i;
  4882. int isePrev;
  4883. LPSUBEDIT pse;
  4884. BOOL fRet = FALSE;
  4885. LPSUBEDITCONTROL psec = &pdp->sec;
  4886. // Build the string that we hand to the app.
  4887. // For the duration of the string build, set the current subedit
  4888. // to SUBEDIT_ALL so that
  4889. // 1. partial edits are applied before building the string, and
  4890. // 2. SE_YEARALT can format appropriately.
  4891. isePrev = psec->iseCur;
  4892. SECSetCurSubed(pdp, SUBEDIT_ALL);
  4893. pszBuf = szBuf;
  4894. cchBuf = ARRAYSIZE(szBuf);
  4895. //
  4896. // Need to mirror the format since the Edit control will take
  4897. // of the origianl format with RTL mirroring.
  4898. //
  4899. if (psec->fMirrorSEC)
  4900. pse = (psec->pse + (psec->cse - 1));
  4901. else
  4902. pse = psec->pse;
  4903. for (i = 0 ; i < psec->cse ; i++)
  4904. {
  4905. int nTmp;
  4906. if (pse->id == SE_STATIC)
  4907. {
  4908. lstrcpyn(pszBuf, pse->pv, cchBuf);
  4909. }
  4910. else
  4911. {
  4912. SEGetTimeDateFormat(pse, psec, pszBuf, cchBuf);
  4913. }
  4914. nTmp = lstrlen(pszBuf);
  4915. cchBuf -= nTmp;
  4916. pszBuf += nTmp;
  4917. //
  4918. // If this control is mirrored, then read contents backward.
  4919. //
  4920. if (psec->fMirrorSEC)
  4921. pse--;
  4922. else
  4923. pse++;
  4924. }
  4925. SECSetCurSubed(pdp, isePrev);
  4926. hwndEdit = CreateWindowEx(0, TEXT("EDIT"), szBuf, WS_CHILD | ES_AUTOHSCROLL,
  4927. psec->rc.left + 2, psec->rc.top + 2,
  4928. psec->rc.right - psec->rc.left,
  4929. psec->rc.bottom - psec->rc.top,
  4930. psec->pci->hwnd, NULL, HINST_THISDLL, NULL);
  4931. if (hwndEdit)
  4932. {
  4933. RECT rcEdit = psec->rc;
  4934. MapWindowRect(psec->pci->hwnd, NULL, &rcEdit); // ClientToScreen
  4935. pdp->fFreeEditing = TRUE;
  4936. InvalidateRect(psec->pci->hwnd, NULL, TRUE);
  4937. Edit_LimitText(hwndEdit, ARRAYSIZE(szBuf) - 1);
  4938. FORWARD_WM_SETFONT(hwndEdit, psec->hfont, FALSE, SendMessage);
  4939. SetFocus(hwndEdit);
  4940. RescrollEditWindow(hwndEdit);
  4941. ShowWindow(hwndEdit, SW_SHOWNORMAL);
  4942. //
  4943. // The basic idea:
  4944. //
  4945. // Process messages until we receive a cancel message,
  4946. // or an accept message, or some implicit accept-like
  4947. // thing happens (namely, a sent WM_KILLFOCUS).
  4948. //
  4949. // If the accept or cancel was implicit, then leave the
  4950. // cancelling message in the queue for somebody else
  4951. // to process. Otherwise, if the accept/cancel was
  4952. // explicit, eat the message so nobody else gets
  4953. // confused by it.
  4954. //
  4955. for (;;)
  4956. {
  4957. MSG msg;
  4958. BOOL fPeek;
  4959. fPeek = PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
  4960. // That PeekMessage may have dispatched a sent WM_KILLFOCUS,
  4961. // in which case the change is considered to have been Accepted.
  4962. // Leave the message we peeked in the queue because the accept
  4963. // was implicit.
  4964. if (GetFocus() != hwndEdit)
  4965. {
  4966. DebugMsg(TF_MONTHCAL, TEXT("SECEdit accept (killfocus)"));
  4967. fRet = TRUE;
  4968. break;
  4969. }
  4970. if (fPeek) {
  4971. //
  4972. // Messages that cause us to cancel implicitly.
  4973. // These messages stay in the queue.
  4974. //
  4975. if (msg.message == WM_SYSCOMMAND ||
  4976. msg.message == WM_SYSCHAR ||
  4977. msg.message == WM_SYSDEADCHAR ||
  4978. msg.message == WM_DEADCHAR ||
  4979. msg.message == WM_SYSKEYDOWN ||
  4980. msg.message == WM_QUIT) {
  4981. DebugMsg(TF_MONTHCAL, TEXT("SECEdit got a message to terminate (%d)"), msg.message);
  4982. fRet = FALSE;
  4983. break;
  4984. }
  4985. //
  4986. // Messages that cause us to accept implicitly.
  4987. // These messages stay in the queue.
  4988. //
  4989. if ((msg.message == WM_LBUTTONDOWN ||
  4990. msg.message == WM_NCLBUTTONDOWN ||
  4991. msg.message == WM_RBUTTONDOWN ||
  4992. msg.message == WM_NCRBUTTONDOWN ||
  4993. msg.message == WM_LBUTTONDBLCLK) &&
  4994. !PtInRect(&rcEdit, msg.pt))
  4995. {
  4996. DebugMsg(TF_MONTHCAL, TEXT("SECEdit got a message to accept (%d)"), msg.message);
  4997. fRet = TRUE;
  4998. break;
  4999. }
  5000. // We are now committed to eating or processing the message
  5001. GetMessage(&msg, NULL, 0, 0);
  5002. //
  5003. // Messages that cause us to cancel explicitly.
  5004. //
  5005. if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
  5006. {
  5007. DebugMsg(TF_MONTHCAL, TEXT("SECEdit explicit cancel (%d)"), msg.message);
  5008. fRet = FALSE;
  5009. break;
  5010. }
  5011. //
  5012. // Messages that cause us to accept explicitly.
  5013. //
  5014. if (msg.message == WM_KEYDOWN && msg.wParam == VK_RETURN)
  5015. {
  5016. DebugMsg(TF_MONTHCAL, TEXT("SECEdit explicit accept (%d)"), msg.message);
  5017. fRet = TRUE;
  5018. break;
  5019. }
  5020. //
  5021. // All other messages just get dispatched.
  5022. //
  5023. TranslateMessage(&msg);
  5024. DispatchMessage(&msg);
  5025. } else {
  5026. WaitMessage();
  5027. }
  5028. } // for (;;)
  5029. if (fRet)
  5030. {
  5031. Edit_GetText(hwndEdit, szOutput, cchOutput);
  5032. }
  5033. DestroyWindow(hwndEdit);
  5034. pdp->fFreeEditing = FALSE;
  5035. InvalidateRect(psec->pci->hwnd, NULL, TRUE);
  5036. }
  5037. return(fRet);
  5038. }
  5039. //
  5040. // returns true if months were scrolled, false otherwise
  5041. //
  5042. BOOL FScrollIntoView(MONTHCAL *pmc)
  5043. {
  5044. int nDelta = 0;
  5045. SYSTEMTIME stEnd;
  5046. if (MonthCal_IsMultiSelect(pmc))
  5047. CopyDate(pmc->stEndSel, stEnd);
  5048. else
  5049. CopyDate(pmc->st, stEnd);
  5050. //
  5051. // If the month/yr for the new date is not in view, bring it
  5052. // into view
  5053. //
  5054. if ((stEnd.wYear < pmc->stMonthFirst.wYear) ||
  5055. ((stEnd.wYear == pmc->stMonthFirst.wYear) && (stEnd.wMonth < pmc->stMonthFirst.wMonth)))
  5056. {
  5057. nDelta = - (pmc->stMonthFirst.wYear - (int)stEnd.wYear) * 12 - (pmc->stMonthFirst.wMonth - (int)stEnd.wMonth);
  5058. }
  5059. else if ((pmc->st.wYear > pmc->stMonthLast.wYear) ||
  5060. ((pmc->st.wYear == pmc->stMonthLast.wYear) && (pmc->st.wMonth > pmc->stMonthLast.wMonth)))
  5061. {
  5062. nDelta = ((int)pmc->st.wYear - pmc->stMonthLast.wYear) * 12 + ((int)pmc->st.wMonth - pmc->stMonthLast.wMonth);
  5063. }
  5064. if (nDelta)
  5065. return FIncrStartMonth(pmc, nDelta, TRUE /* dont change day */);
  5066. else
  5067. return FALSE;
  5068. }
  5069. //
  5070. // Validates the isubed to make sure we aren't setting it to something
  5071. // bogus. If necessary, we pick a field at random.
  5072. //
  5073. void SECSafeSetCurSubed(DATEPICK *pdp, int ise)
  5074. {
  5075. if (ise >= pdp->sec.cse ||
  5076. (ise >= 0 && pdp->sec.pse[ise].fReadOnly))
  5077. {
  5078. SECSetCurSubed(pdp, SUBEDIT_NONE);
  5079. SECIncrFocus(pdp, 1);
  5080. }
  5081. else
  5082. SECSetCurSubed(pdp, ise);
  5083. }
  5084. LRESULT DTM_OnSetFormat(DATEPICK *pdp, LPCTSTR szFormat)
  5085. {
  5086. // remember the field that has focus so we can restore it later
  5087. //
  5088. int iseCur = pdp->sec.iseCur;
  5089. if (!szFormat || !*szFormat)
  5090. {
  5091. pdp->fLocale = TRUE;
  5092. DPHandleLocaleChange(pdp);
  5093. }
  5094. else
  5095. {
  5096. pdp->fLocale = FALSE;
  5097. SECParseFormat(pdp, &pdp->sec, szFormat);
  5098. }
  5099. // restore focus. it might be cool to do extra validation
  5100. // to see if iseCur is the same type that it used to be,
  5101. // maybe even validating that cse is constant. the case we're
  5102. // really trying to fix is changing "1st" to "2nd" to "3rd",
  5103. // so only a text portion is really changing...
  5104. //
  5105. SECSafeSetCurSubed(pdp, iseCur);
  5106. return((LRESULT)TRUE);
  5107. }
  5108. //
  5109. // DATEPICKER stuff
  5110. //
  5111. LRESULT CALLBACK DatePickWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  5112. {
  5113. DATEPICK *pdp;
  5114. NMHDR nmhdr;
  5115. LRESULT lres = 0;
  5116. if (message == WM_NCCREATE)
  5117. return(DPNcCreateHandler(hwnd));
  5118. pdp = DatePick_GetPtr(hwnd);
  5119. if (pdp == NULL)
  5120. return(DefWindowProc(hwnd, message, wParam, lParam));
  5121. // Dispatch the various messages we can receive
  5122. switch (message)
  5123. {
  5124. case WM_CREATE:
  5125. lres = DPCreateHandler(pdp, hwnd, (LPCREATESTRUCT)lParam);
  5126. break;
  5127. case WM_ERASEBKGND:
  5128. if (!pdp->fEnabled)
  5129. {
  5130. RECT rc;
  5131. HDC hdc = (HDC)wParam;
  5132. GetClipBox(hdc, &rc);
  5133. FillRectClr(hdc, &rc, g_clrBtnFace);
  5134. } else
  5135. goto DoDefault;
  5136. break;
  5137. case WM_NCPAINT:
  5138. if (pdp->hThemeCombo && GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_CLIENTEDGE)
  5139. {
  5140. HRGN hrgn = (wParam != 1) ? (HRGN)wParam : NULL;
  5141. HBRUSH hbr = (HBRUSH)GetClassLongPtr(hwnd, GCLP_HBRBACKGROUND);
  5142. if (CCDrawNonClientTheme(pdp->hThemeCombo, hwnd, hrgn, hbr, 0, CBXS_NORMAL))
  5143. {
  5144. break;
  5145. }
  5146. }
  5147. goto DoDefault;
  5148. case WM_PRINTCLIENT:
  5149. case WM_PAINT:
  5150. {
  5151. PAINTSTRUCT ps;
  5152. HDC hdc;
  5153. hdc = (HDC)wParam;
  5154. if (hdc) {
  5155. DPPaint(pdp, hdc);
  5156. } else {
  5157. hwnd = pdp->ci.hwnd;
  5158. hdc = BeginPaint(hwnd, &ps);
  5159. DPPaint(pdp, hdc);
  5160. EndPaint(hwnd, &ps);
  5161. }
  5162. break;
  5163. }
  5164. case WM_LBUTTONDOWN:
  5165. DPLButtonDown(pdp, wParam, lParam);
  5166. break;
  5167. case WM_NOTIFY:
  5168. switch (((LPNMHDR)lParam)->code)
  5169. {
  5170. case MCN_SELCHANGE:
  5171. case MCN_SELECT:
  5172. {
  5173. LPNMSELECT pnms = (LPNMSELECT)lParam;
  5174. DebugMsg(TF_MONTHCAL,TEXT("MonthCal notified DateTimePick of SELECT"));
  5175. if (!DPSetDate(pdp, &pnms->stSelStart, TRUE))
  5176. {
  5177. DebugMsg(DM_WARNING,TEXT("MonthCal cannot set selected date!"));
  5178. MessageBeep(MB_ICONHAND);
  5179. }
  5180. pdp->fShow = (((LPNMHDR)lParam)->code == MCN_SELCHANGE);
  5181. break;
  5182. }
  5183. case UDN_DELTAPOS:
  5184. if ((int)wParam == DATEPICK_UPDOWN)
  5185. {
  5186. LPNM_UPDOWN pnmdp = (LPNM_UPDOWN)lParam;
  5187. if (!pdp->fFocus)
  5188. SetFocus(pdp->ci.hwnd);
  5189. SECResetSubeditEdit(pdp);
  5190. if (SECIncrementSubedit(&pdp->sec, -pnmdp->iDelta))
  5191. DPNotifyDateChange(pdp);
  5192. }
  5193. break;
  5194. } // WM_NOTIFY switch
  5195. break;
  5196. case WM_GETFONT:
  5197. lres = (LRESULT)pdp->sec.hfont;
  5198. break;
  5199. case WM_SETFONT:
  5200. DPHandleSetFont(pdp, (HFONT)wParam, (BOOL)LOWORD(lParam));
  5201. break;
  5202. case WM_DESTROY:
  5203. DPDestroyHandler(hwnd, pdp, wParam, lParam);
  5204. break;
  5205. case WM_KILLFOCUS:
  5206. case WM_SETFOCUS:
  5207. {
  5208. BOOL fGotFocus = (message == WM_SETFOCUS);
  5209. if (BOOLIFY(fGotFocus) != BOOLIFY(pdp->fFocus))
  5210. {
  5211. pdp->fFocus = (WORD) fGotFocus;
  5212. if (pdp->sec.iseCur >= 0)
  5213. {
  5214. InvalidateScrollRect(pdp->ci.hwnd, &pdp->sec.pse[pdp->sec.iseCur].rc, pdp->sec.xScroll);
  5215. }
  5216. else if (DatePick_ShowCheck(pdp))
  5217. {
  5218. pdp->fCheckFocus = (WORD) fGotFocus;
  5219. InvalidateRect(pdp->ci.hwnd, &pdp->rcCheck, TRUE);
  5220. }
  5221. else if (fGotFocus) // nothing has focus, bring it to something
  5222. {
  5223. SECIncrFocus(pdp, 1);
  5224. }
  5225. CCSendNotify(&pdp->ci, (fGotFocus ? NM_SETFOCUS : NM_KILLFOCUS), &nmhdr);
  5226. }
  5227. if (fGotFocus)
  5228. {
  5229. // Revalidate iseLastActive because the app might've changed
  5230. // the format while we were nonfocus
  5231. SECSafeSetCurSubed(pdp, pdp->iseLastActive);
  5232. }
  5233. else
  5234. {
  5235. pdp->iseLastActive = pdp->sec.iseCur;
  5236. SECSetCurSubed(pdp, SUBEDIT_NONE);
  5237. }
  5238. break;
  5239. }
  5240. case WM_ENABLE:
  5241. {
  5242. BOOL fEnabled = wParam ? TRUE:FALSE;
  5243. if (BOOLIFY(pdp->fEnabled) != fEnabled)
  5244. {
  5245. pdp->fEnabled = (WORD) fEnabled;
  5246. if (pdp->hwndUD)
  5247. EnableWindow(pdp->hwndUD, fEnabled);
  5248. InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
  5249. }
  5250. break;
  5251. }
  5252. case DTMP_WINDOWPOSCHANGED:
  5253. case WM_SIZE:
  5254. {
  5255. RECT rc;
  5256. if (message == DTMP_WINDOWPOSCHANGED)
  5257. {
  5258. GetClientRect(pdp->ci.hwnd, &rc);
  5259. }
  5260. else
  5261. {
  5262. rc.left = 0;
  5263. rc.top = 0;
  5264. rc.right = GET_X_LPARAM(lParam);
  5265. rc.bottom = GET_Y_LPARAM(lParam);
  5266. }
  5267. DPRecomputeSizing(pdp, &rc);
  5268. InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
  5269. UpdateWindow(pdp->ci.hwnd);
  5270. break;
  5271. }
  5272. case WM_GETDLGCODE:
  5273. lres = DLGC_WANTARROWS | DLGC_WANTCHARS;
  5274. break;
  5275. case WM_KEYDOWN:
  5276. if (pdp->fShow)
  5277. {
  5278. SendMessage(pdp->hwndMC, WM_KEYDOWN, wParam, lParam);
  5279. return 0;
  5280. }
  5281. else
  5282. {
  5283. lres = DPHandleKeydown(pdp, wParam, lParam);
  5284. }
  5285. break;
  5286. case WM_KEYUP:
  5287. if (pdp->fShow)
  5288. SendMessage(pdp->hwndMC, WM_KEYUP, wParam, lParam);
  5289. break;
  5290. case WM_SYSKEYDOWN:
  5291. if (wParam == VK_DOWN && !pdp->fUseUpDown)
  5292. {
  5293. DPLBD_MonthCal(pdp, FALSE);
  5294. }
  5295. else
  5296. goto DoDefault;
  5297. break;
  5298. case WM_CHAR:
  5299. lres = DPHandleChar(pdp, wParam, lParam);
  5300. break;
  5301. case WM_SYSCOLORCHANGE:
  5302. InitGlobalColors();
  5303. // Don't need to propagate to pdp->hwndMC because it is its own
  5304. // top-level window.
  5305. break;
  5306. case WM_WININICHANGE:
  5307. if (lParam == 0 || !lstrcmpi((LPTSTR)lParam, TEXT("Intl")))
  5308. {
  5309. DPHandleLocaleChange(pdp);
  5310. }
  5311. // Don't need to propagate to pdp->hwndMC because it is its own
  5312. // top-level window.
  5313. break;
  5314. case WM_THEMECHANGED:
  5315. if (pdp->hThemeCombo)
  5316. {
  5317. CloseThemeData(pdp->hThemeCombo);
  5318. }
  5319. if (pdp->hThemeCheck)
  5320. {
  5321. CloseThemeData(pdp->hThemeCheck);
  5322. }
  5323. pdp->hThemeCombo = OpenThemeData(hwnd, L"Combobox"); // This one kinda looks like a combobox
  5324. pdp->hThemeCheck = OpenThemeData(hwnd, L"Button");
  5325. InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
  5326. break;
  5327. case WM_NOTIFYFORMAT:
  5328. return CIHandleNotifyFormat(&pdp->ci, lParam);
  5329. break;
  5330. // Cannot use WM_SETTEXT to change the text of a DTP
  5331. case WM_SETTEXT:
  5332. return -1;
  5333. case WM_GETTEXT:
  5334. if (!lParam || !wParam) {
  5335. // previously this just failed and returned 0
  5336. // in bogus input. should be safe to convert to
  5337. // gettextlength
  5338. message = WM_GETTEXTLENGTH;
  5339. } else
  5340. (*(LPTSTR)lParam) = 0;
  5341. // fall through
  5342. case WM_GETTEXTLENGTH:
  5343. {
  5344. TCHAR szTmp[DTP_FORMATLENGTH];
  5345. LPSUBEDIT psubed;
  5346. int i;
  5347. #ifdef UNICODE_WIN9x
  5348. char *pszText = (char *)lParam;
  5349. #else
  5350. TCHAR *pszText = (TCHAR *)lParam;
  5351. #endif
  5352. UINT nTextLen = 0;
  5353. for (i = 0, psubed = pdp->sec.pse; i < pdp->sec.cse; i++, psubed++)
  5354. {
  5355. LPTSTR sz;
  5356. UINT nLen;
  5357. sz = SECFormatSubed(&pdp->sec, psubed, szTmp, ARRAYSIZE(szTmp));
  5358. nLen = lstrlen(sz);
  5359. if (message == WM_GETTEXT) {
  5360. if (nTextLen + nLen >= wParam)
  5361. break;
  5362. #ifdef UNICODE_WIN9x
  5363. // safe to pass wparam because we calculated above that it was ok
  5364. ConvertWToAN(CP_ACP, pszText, wParam, sz, -1);
  5365. #else
  5366. lstrcpy(pszText, sz);
  5367. #endif
  5368. pszText += nLen;
  5369. }
  5370. nTextLen += nLen;
  5371. }
  5372. lres = nTextLen;
  5373. }
  5374. break;
  5375. case WM_STYLECHANGING:
  5376. lres = DPOnStyleChanging(pdp, (UINT) wParam, (LPSTYLESTRUCT)lParam);
  5377. break;
  5378. case WM_STYLECHANGED:
  5379. lres = DPOnStyleChanged(pdp, (UINT) wParam, (LPSTYLESTRUCT)lParam);
  5380. break;
  5381. case WM_CONTEXTMENU:
  5382. if (pdp->hwndMC)
  5383. lres = SendMessage(pdp->hwndMC, message, wParam, lParam);
  5384. else
  5385. goto DoDefault;
  5386. break;
  5387. //
  5388. // DATETIMEPICK specific messages
  5389. //
  5390. // DTM_GETSYSTEMTIME wParam=void lParam=LPSYSTEMTIME
  5391. // returns GDT_NONE if no date selected (DTS_SHOWNONE only)
  5392. // returns GDT_VALID and modifies *lParam to be the selected date
  5393. case DTM_GETSYSTEMTIME:
  5394. if (!pdp->fCheck)
  5395. {
  5396. lres = GDT_NONE;
  5397. }
  5398. else
  5399. {
  5400. // If there is an edit pending, save it so the app sees
  5401. // the absolute latest values. This is important for app
  5402. // compat, because IE4 wasn't Y2K compliant and people got
  5403. // away with typing just two digits of the year and hitting
  5404. // ENTER. The "Find Files" dialog would then ask us for the
  5405. // year, and in the Y2K case, we are still waiting for the
  5406. // other two digits (for a four-digit year) and return the
  5407. // wrong year.
  5408. SECSaveSubeditEdit(pdp);
  5409. SECGetSystemtime(&pdp->sec, (SYSTEMTIME *)lParam);
  5410. lres = GDT_VALID;
  5411. }
  5412. break;
  5413. // DTM_SETSYSTEMTIME wParam=GDT_flag lParam=LPSYSTEMTIME
  5414. // if wParam==GDT_NONE, sets datepick to None (DTS_SHOWNONE only)
  5415. // if wParam==GDT_VALID, sets datepick to *lParam
  5416. // returns TRUE on success, FALSE on error (such as bad params)
  5417. case DTM_SETSYSTEMTIME:
  5418. {
  5419. LPSYSTEMTIME pst = ((LPSYSTEMTIME)lParam);
  5420. if ((wParam != GDT_NONE && wParam != GDT_VALID) ||
  5421. (wParam == GDT_NONE && !DatePick_ShowCheck(pdp)) ||
  5422. (wParam == GDT_VALID && !IsValidSystemtime(pst)))
  5423. {
  5424. break;
  5425. }
  5426. // reset subed in place edit
  5427. SECResetSubeditEdit(pdp);
  5428. pdp->fNoNotify = TRUE;
  5429. if (DatePick_ShowCheck(pdp))
  5430. {
  5431. if ((wParam == GDT_NONE) || (pdp->fCheck))
  5432. {
  5433. // let checkbox have focus
  5434. SECSetCurSubed(pdp, SUBEDIT_NONE);
  5435. pdp->fCheckFocus = 1;
  5436. }
  5437. pdp->fCheck = (wParam == GDT_NONE ? 0 : 1);
  5438. InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
  5439. }
  5440. if (wParam == GDT_VALID)
  5441. {
  5442. pdp->fNoNotify = TRUE;
  5443. DPSetDate(pdp, pst, FALSE);
  5444. pdp->fNoNotify = FALSE;
  5445. }
  5446. lres = TRUE;
  5447. pdp->fNoNotify = FALSE;
  5448. break;
  5449. }
  5450. // DTM_GETRANGE wParam=void lParam=LPSYSTEMTIME[2]
  5451. // modifies *lParam to be the minimum ALLOWABLE systemtime (or 0 if no minimum)
  5452. // modifies *(lParam+1) to be the maximum ALLOWABLE systemtime (or 0 if no maximum)
  5453. // returns GDTR_MIN|GDTR_MAX if there is a minimum|maximum limit
  5454. case DTM_GETRANGE:
  5455. {
  5456. LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
  5457. ZeroMemory(pst, 2 * sizeof(SYSTEMTIME));
  5458. lres = pdp->gdtr;
  5459. if (lres & GDTR_MIN)
  5460. pst[0] = pdp->stMin;
  5461. if (lres & GDTR_MAX)
  5462. pst[1] = pdp->stMax;
  5463. break;
  5464. }
  5465. // DTM_SETRANGE wParam=GDR_flags lParam=LPSYSTEMTIME[2]
  5466. // if GDTR_MIN, sets the minimum ALLOWABLE systemtime to *lParam, otherwise removes minimum
  5467. // if GDTR_MAX, sets the maximum ALLOWABLE systemtime to *(lParam+1), otherwise removes maximum
  5468. // returns TRUE on success, FALSE on error (such as invalid parameters)
  5469. case DTM_SETRANGE:
  5470. {
  5471. LPSYSTEMTIME pst = (LPSYSTEMTIME)lParam;
  5472. const SYSTEMTIME *pstMin = (wParam & GDTR_MIN) ? pst+0 : &c_stEpoch;
  5473. const SYSTEMTIME *pstMax = (wParam & GDTR_MAX) ? pst+1 : &c_stArmageddon;
  5474. if (!IsValidDate(pstMin) || !IsValidDate(pstMax))
  5475. {
  5476. break;
  5477. }
  5478. // Save the flags so we can tell the app if it asks.
  5479. // We personally don't care.
  5480. pdp->gdtr = (UINT)wParam & (GDTR_MIN | GDTR_MAX);
  5481. if (CmpDate(&pdp->stMin, &pdp->stMax) <= 0)
  5482. {
  5483. pdp->stMin = *pstMin;
  5484. pdp->stMax = *pstMax;
  5485. }
  5486. else
  5487. {
  5488. pdp->stMin = *pstMax;
  5489. pdp->stMax = *pstMin;
  5490. }
  5491. // we might now have an invalid date, if so, try to set the current
  5492. // date and munge it to a max or min value if out of range.
  5493. pdp->fNoNotify = TRUE;
  5494. DPSetDate(pdp, &pdp->sec.st, TRUE);
  5495. pdp->fNoNotify = FALSE;
  5496. lres = TRUE;
  5497. break;
  5498. }
  5499. #ifdef UNICODE
  5500. // DTM_SETFORMAT wParam=void lParam=LPCTSTR
  5501. // Sets the formatting string to a copy of lParam.
  5502. case DTM_SETFORMATA:
  5503. {
  5504. LPCSTR pszFormat = (LPCSTR)lParam;
  5505. LPWSTR pwszFormat = NULL;
  5506. if (pszFormat && *pszFormat)
  5507. {
  5508. pwszFormat = ProduceWFromA(pdp->ci.uiCodePage, pszFormat);
  5509. }
  5510. lres = DTM_OnSetFormat(pdp, pwszFormat);
  5511. if (pwszFormat)
  5512. {
  5513. FreeProducedString(pwszFormat);
  5514. }
  5515. break;
  5516. }
  5517. #endif
  5518. // DTM_SETFORMAT wParam=void lParam=LPCTSTR
  5519. // Sets the formatting string to a copy of lParam.
  5520. case DTM_SETFORMAT:
  5521. {
  5522. lres = DTM_OnSetFormat(pdp, (LPCTSTR)lParam);
  5523. break;
  5524. }
  5525. case DTM_SETMCCOLOR:
  5526. if (wParam < MCSC_COLORCOUNT)
  5527. {
  5528. COLORREF clr = pdp->clr[wParam];
  5529. pdp->clr[wParam] = (COLORREF)lParam;
  5530. if (pdp->hwndMC)
  5531. SendMessage(pdp->hwndMC, MCM_SETCOLOR, wParam, lParam);
  5532. return clr;
  5533. }
  5534. return -1;
  5535. case DTM_GETMCCOLOR:
  5536. if (wParam < MCSC_COLORCOUNT)
  5537. return pdp->clr[wParam];
  5538. return -1;
  5539. case DTM_GETMONTHCAL:
  5540. return (LRESULT)(UINT_PTR)pdp->hwndMC;
  5541. // wParam -- HFONT, LOWORD(lParam) -- fRedraw
  5542. case DTM_SETMCFONT:
  5543. pdp->hfontMC = (HFONT)wParam;
  5544. if (pdp->hwndMC)
  5545. SendMessage(pdp->hwndMC, WM_SETFONT, wParam, lParam);
  5546. break;
  5547. // returns the font
  5548. case DTM_GETMCFONT:
  5549. return (LRESULT)pdp->hfontMC;
  5550. break;
  5551. default:
  5552. if (CCWndProc(&pdp->ci, message, wParam, lParam, &lres))
  5553. return lres;
  5554. DoDefault:
  5555. lres = DefWindowProc(hwnd, message, wParam, lParam);
  5556. break;
  5557. } /* switch (message) */
  5558. return(lres);
  5559. }
  5560. LRESULT DPNcCreateHandler(HWND hwnd)
  5561. {
  5562. DATEPICK *pdp;
  5563. // Sink the datepick -- we may only want to do this if WS_BORDER is set
  5564. SetWindowBits(hwnd, GWL_EXSTYLE, WS_EX_CLIENTEDGE, WS_EX_CLIENTEDGE);
  5565. // Allocate storage for the dtpick structure
  5566. pdp = (DATEPICK *)LocalAlloc(LPTR, sizeof(DATEPICK));
  5567. if (pdp)
  5568. {
  5569. DatePick_SetPtr(hwnd, pdp);
  5570. pdp->hThemeCombo = OpenThemeData(hwnd, L"Combobox"); // This one kinda looks like a combobox
  5571. pdp->hThemeCheck = OpenThemeData(hwnd, L"Button");
  5572. }
  5573. return((LRESULT)pdp);
  5574. }
  5575. void DPDestroyHandler(HWND hwnd, DATEPICK *pdp, WPARAM wParam, LPARAM lParam)
  5576. {
  5577. if (pdp)
  5578. {
  5579. if (pdp->hThemeCombo)
  5580. {
  5581. CloseThemeData(pdp->hThemeCombo);
  5582. }
  5583. if (pdp->hThemeCheck)
  5584. {
  5585. CloseThemeData(pdp->hThemeCheck);
  5586. }
  5587. SECDestroy(&pdp->sec);
  5588. MCFreeCalendarInfo(&pdp->sec.ct);
  5589. LocalFree(pdp);
  5590. }
  5591. DatePick_SetPtr(hwnd, NULL);
  5592. }
  5593. // set any locale-dependent values
  5594. #define DTS_TIMEFORMATONLY (DTS_TIMEFORMAT & ~DTS_UPDOWN) // remove the UPDOWN bit for testing
  5595. LRESULT DPCreateHandler(DATEPICK *pdp, HWND hwnd, LPCREATESTRUCT lpcs)
  5596. {
  5597. HFONT hfont;
  5598. SYSTEMTIME st;
  5599. LCID lcid;
  5600. // Initialize our data.
  5601. CIInitialize(&pdp->ci, hwnd, lpcs);
  5602. if (pdp->ci.style & DTS_INVALIDBITS)
  5603. return(-1);
  5604. if (pdp->ci.style & DTS_UPDOWN)
  5605. {
  5606. pdp->fUseUpDown = TRUE;
  5607. pdp->hwndUD = CreateWindow(UPDOWN_CLASS, NULL,
  5608. WS_CHILD | WS_VISIBLE | (pdp->ci.style & WS_DISABLED),
  5609. CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwnd,
  5610. (HMENU)DATEPICK_UPDOWN, HINST_THISDLL, NULL);
  5611. }
  5612. if (DatePick_ShowCheck(pdp))
  5613. {
  5614. pdp->sec.fNone = TRUE; // ugly: this SEC stuff should be merged back into DATEPICK
  5615. pdp->iseLastActive = SUBEDIT_NONE;
  5616. }
  5617. pdp->fEnabled = !(pdp->ci.style & WS_DISABLED);
  5618. pdp->fCheck = TRUE; // start checked
  5619. // Default minimum date is the epoch
  5620. pdp->stMin = c_stEpoch;
  5621. // Default maximum date is armageddon
  5622. pdp->stMax = c_stArmageddon;
  5623. pdp->gdtr = GDTR_MIN; // We marked MIN as set in IE4, go figure
  5624. //
  5625. // See if the date/time picker supports this calendar. [samera]
  5626. //
  5627. MCGetCalendarInfo(&pdp->sec.ct);
  5628. //
  5629. // If the DTP is RTL mirrored and it's a Time-Only field, then
  5630. // we need to mirror format string so that it's displayed correctly
  5631. // on a RTL mirrored window. In case of Arabic, we need to swap the
  5632. // Time-Marker to the other side (visual left) so that it looks ok.
  5633. // For the hebrew, we need to swap the field (whether it's date or time)
  5634. // bacause unlike Arabic, it doesn't have its own digit so it reads
  5635. // from LeftToRight. [samera]
  5636. //
  5637. lcid = GetUserDefaultLCID();
  5638. pdp->sec.fMirrorSEC = pdp->sec.fSwapTimeMarker = FALSE;
  5639. if (IS_WINDOW_RTL_MIRRORED(hwnd))
  5640. {
  5641. if (pdp->ci.style & DTS_TIMEFORMATONLY)
  5642. {
  5643. pdp->sec.fMirrorSEC = TRUE;
  5644. if ((PRIMARYLANGID(LANGIDFROMLCID(lcid))) == LANG_ARABIC)
  5645. pdp->sec.fSwapTimeMarker = TRUE;
  5646. }
  5647. else if((PRIMARYLANGID(LANGIDFROMLCID(lcid))) == LANG_HEBREW)
  5648. {
  5649. pdp->sec.fMirrorSEC = TRUE;
  5650. }
  5651. }
  5652. // initialize SUBEDITCONTROL
  5653. pdp->sec.pci = &pdp->ci;
  5654. GetLocalTime(&st);
  5655. SECSetSystemtime(pdp, &st);
  5656. SECSetFont(&pdp->sec, NULL);
  5657. pdp->fLocale = TRUE;
  5658. DPHandleLocaleChange(pdp);
  5659. MCLoadString(IDS_DELIMETERS, pdp->sec.szDelimeters, ARRAYSIZE(pdp->sec.szDelimeters));
  5660. hfont = NULL;
  5661. if (lpcs->hwndParent)
  5662. hfont = (HFONT)SendMessage(lpcs->hwndParent, WM_GETFONT, 0, 0);
  5663. DPHandleSetFont(pdp, hfont, FALSE);
  5664. // initialize the colors
  5665. MCInitColorArray(pdp->clr);
  5666. return(0);
  5667. }
  5668. LRESULT DPOnStyleChanging(DATEPICK *pdp, UINT gwl, LPSTYLESTRUCT pinfo)
  5669. {
  5670. if (gwl == GWL_STYLE)
  5671. {
  5672. DWORD changeFlags = pdp->ci.style ^ pinfo->styleNew;
  5673. // Don't allow these bits to change
  5674. changeFlags &= DTS_UPDOWN | DTS_SHOWNONE | DTS_INVALIDBITS;
  5675. pinfo->styleNew ^= changeFlags;
  5676. }
  5677. return(0);
  5678. }
  5679. LRESULT DPOnStyleChanged(DATEPICK *pdp, UINT gwl, LPSTYLESTRUCT pinfo)
  5680. {
  5681. if (gwl == GWL_STYLE)
  5682. {
  5683. DWORD changeFlags = pdp->ci.style ^ pinfo->styleNew;
  5684. ASSERT(!(changeFlags & (DTS_UPDOWN|DTS_SHOWNONE)));
  5685. pdp->ci.style = pinfo->styleNew;
  5686. if (changeFlags & (DTS_SHORTDATEFORMAT|DTS_LONGDATEFORMAT|DTS_TIMEFORMAT|DTS_INVALIDBITS))
  5687. {
  5688. DPHandleLocaleChange(pdp);
  5689. }
  5690. if (changeFlags & (WS_BORDER | WS_CAPTION | WS_THICKFRAME)) {
  5691. // the changing of these bits affect the size of the window
  5692. // but not until after this message is handled
  5693. // so post ourself a message.
  5694. PostMessage(pdp->ci.hwnd, DTMP_WINDOWPOSCHANGED, 0, 0);
  5695. }
  5696. }
  5697. return(0);
  5698. }
  5699. void DPHandleLocaleChange(DATEPICK *pdp)
  5700. {
  5701. //
  5702. // See if the date/time picker supports this new calendar, and refresh
  5703. // era names as appropriate.
  5704. //
  5705. MCGetCalendarInfo(&pdp->sec.ct);
  5706. if (pdp->fLocale)
  5707. {
  5708. TCHAR szFormat[DTP_FORMATLENGTH];
  5709. switch (pdp->ci.style & DTS_FORMATMASK)
  5710. {
  5711. case DTS_TIMEFORMATONLY:
  5712. GetLocaleInfo(pdp->sec.ct.lcid, LOCALE_STIMEFORMAT, szFormat, ARRAYSIZE(szFormat));
  5713. break;
  5714. case DTS_LONGDATEFORMAT:
  5715. GetLocaleInfo(pdp->sec.ct.lcid, LOCALE_SLONGDATE, szFormat, ARRAYSIZE(szFormat));
  5716. break;
  5717. case DTS_SHORTDATEFORMAT:
  5718. case DTS_SHORTDATECENTURYFORMAT:
  5719. GetLocaleInfo(pdp->sec.ct.lcid, LOCALE_SSHORTDATE, szFormat, ARRAYSIZE(szFormat));
  5720. break;
  5721. }
  5722. SECParseFormat(pdp, &pdp->sec, szFormat);
  5723. }
  5724. }
  5725. void DPHandleSetFont(DATEPICK *pdp, HFONT hfont, BOOL fRedraw)
  5726. {
  5727. SECSetFont(&pdp->sec, hfont);
  5728. SECRecomputeSizing(&pdp->sec, &pdp->rc);
  5729. pdp->ci.uiCodePage = GetCodePageForFont(hfont);
  5730. if (fRedraw)
  5731. {
  5732. InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
  5733. UpdateWindow(pdp->ci.hwnd);
  5734. }
  5735. }
  5736. void DPPaint(DATEPICK *pdp, HDC hdc)
  5737. {
  5738. if (DatePick_ShowCheck(pdp))
  5739. {
  5740. if (RectVisible(hdc, &pdp->rcCheck))
  5741. {
  5742. RECT rc = pdp->rcCheck;
  5743. if (pdp->hThemeCheck)
  5744. {
  5745. int iStateId = CBS_UNCHECKEDNORMAL;
  5746. if (pdp->fCheck)
  5747. iStateId = CBS_CHECKEDNORMAL;
  5748. if (pdp->fCheckFocus)
  5749. iStateId += 1; // Hot == Check focus...
  5750. if (!pdp->fEnabled)
  5751. iStateId += 3; // Disabled
  5752. DrawThemeBackground(pdp->hThemeCheck, hdc, BP_CHECKBOX, iStateId, &rc, 0);
  5753. }
  5754. else
  5755. {
  5756. UINT dfcs = DFCS_BUTTONCHECK;
  5757. if (pdp->fCheck)
  5758. dfcs |= DFCS_CHECKED;
  5759. if (!pdp->fEnabled)
  5760. dfcs |= DFCS_INACTIVE;
  5761. InflateRect(&rc, -1 , -1);
  5762. if (pdp->fCheckFocus)
  5763. DrawFocusRect(hdc, &rc);
  5764. DrawFrameControl(hdc, &rc, DFC_BUTTON, dfcs);
  5765. }
  5766. }
  5767. }
  5768. if (!pdp->fFreeEditing)
  5769. SECDrawSubedits(hdc, &pdp->sec, pdp->fFocus, pdp->fCheck ? pdp->fEnabled : FALSE);
  5770. if (!pdp->fUseUpDown && RectVisible(hdc, &pdp->rcBtn))
  5771. DPDrawDropdownButton(pdp, hdc, FALSE);
  5772. }
  5773. void _RecomputeMonthCalRect(DATEPICK *pdp, LPRECT prcCal, LPRECT prcCalT )
  5774. {
  5775. RECT rcCal = *prcCal;
  5776. RECT rcCalT = *prcCalT;
  5777. RECT rcWorkArea;
  5778. MONITORINFO mi = {0};
  5779. HMONITOR hMonitor;
  5780. if (DatePick_RightAlign(pdp))
  5781. {
  5782. rcCal.left = rcCal.right - (rcCalT.right - rcCalT.left);
  5783. }
  5784. else
  5785. {
  5786. rcCal.right = rcCal.left + (rcCalT.right - rcCalT.left);
  5787. }
  5788. rcCal.bottom = rcCal.top + (rcCalT.bottom - rcCalT.top);
  5789. // Get the information about the most appropriate monitor.
  5790. // (This includes both the work area and the monitor size.
  5791. hMonitor = MonitorFromRect(&rcCal, MONITOR_DEFAULTTONEAREST);
  5792. mi.cbSize = sizeof(mi);
  5793. GetMonitorInfo(hMonitor, &mi);
  5794. // we need to know where to fit this rectangle into
  5795. if (GetWindowLong(pdp->ci.hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST)
  5796. {
  5797. // if we're topmost, our limits are the screen limits (not the working area)
  5798. rcWorkArea = mi.rcMonitor;
  5799. }
  5800. else
  5801. {
  5802. // otherwise it's the limits of the workarea
  5803. rcWorkArea = mi.rcWork;
  5804. }
  5805. // slide left if off the right side of area
  5806. if (rcCal.right > rcWorkArea.right)
  5807. {
  5808. int nTmp = rcCal.right - rcWorkArea.right;
  5809. rcCal.left -= nTmp;
  5810. rcCal.right -= nTmp;
  5811. }
  5812. // slide right if off the left side of area
  5813. if (rcCal.left < rcWorkArea.left)
  5814. {
  5815. int nTmp = rcWorkArea.left - rcCal.left;
  5816. rcCal.left += nTmp;
  5817. rcCal.right += nTmp;
  5818. }
  5819. // move to top of control if off the bottom side of area
  5820. if (rcCal.bottom > rcWorkArea.bottom)
  5821. {
  5822. RECT rcT = pdp->rc;
  5823. int nTmp = rcCal.bottom - rcCal.top;
  5824. MapWindowRect(pdp->ci.hwnd, NULL, (LPPOINT)&rcT); // 2 ClientToScreen
  5825. rcCal.bottom = rcT.top;
  5826. rcCal.top = rcCal.bottom - nTmp;
  5827. }
  5828. *prcCal = rcCal;
  5829. }
  5830. void DPLBD_MonthCal(DATEPICK *pdp, BOOL fLButtonDown)
  5831. {
  5832. HDC hdc;
  5833. HWND hwndMC;
  5834. RECT rcT, rcCalT;
  5835. RECT rcBtn, rcCal;
  5836. BOOL fBtnDown; // Is the button drawn DOWN or UP
  5837. BOOL fBtnActive; // Is the button still active
  5838. SYSTEMTIME st;
  5839. SYSTEMTIME stOld;
  5840. DWORD dwWidth;
  5841. hdc = GetDC(pdp->ci.hwnd);
  5842. // turn datetimepick on but remove all focus -- the MonthCal will have focus
  5843. if (!pdp->fCheck)
  5844. {
  5845. pdp->fCheck = TRUE;
  5846. InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
  5847. DPNotifyDateChange(pdp);
  5848. }
  5849. if (pdp->fCheckFocus)
  5850. {
  5851. pdp->fCheckFocus = FALSE;
  5852. InvalidateRect(pdp->ci.hwnd, &pdp->rcCheck, TRUE);
  5853. }
  5854. SECSetCurSubed(pdp, SUBEDIT_NONE);
  5855. if (fLButtonDown)
  5856. DPDrawDropdownButton(pdp, hdc, TRUE);
  5857. rcT = pdp->rc;
  5858. MapWindowRect(pdp->ci.hwnd, NULL, &rcT); //2 ClientToScreen
  5859. rcBtn = pdp->rcBtn;
  5860. MapWindowRect(pdp->ci.hwnd, NULL, &rcBtn); //ClientToScreen
  5861. rcCal = rcT; // this size is only temp until
  5862. rcCal.top = rcCal.bottom + 1; // we ask the monthcal how big it
  5863. rcCal.bottom = rcCal.top + 1; // wants to be
  5864. hwndMC = CreateWindow(g_rgchMCName, NULL, WS_POPUP | WS_BORDER,
  5865. rcCal.left, rcCal.top,
  5866. rcCal.right - rcCal.left, rcCal.bottom - rcCal.top,
  5867. pdp->ci.hwnd, NULL, HINST_THISDLL, NULL);
  5868. if (hwndMC == NULL)
  5869. {
  5870. DebugMsg(DM_WARNING, TEXT("DPLBD_MonthCal could not create MONTHCAL"));
  5871. return;
  5872. }
  5873. pdp->hwndMC = hwndMC;
  5874. // set all the colors:
  5875. {
  5876. int i;
  5877. for (i = 0; i < MCSC_COLORCOUNT; i++)
  5878. {
  5879. SendMessage(hwndMC, MCM_SETCOLOR, i, pdp->clr[i]);
  5880. }
  5881. }
  5882. if (pdp->hfontMC)
  5883. SendMessage(hwndMC, WM_SETFONT, (WPARAM)pdp->hfontMC, (LPARAM)FALSE);
  5884. // set min/max dates
  5885. // Relies on HACK! that stMin and stMax are adjacent
  5886. MonthCal_SetRange(hwndMC, GDTR_MIN | GDTR_MAX, &pdp->stMin);
  5887. SendMessage(hwndMC, MCM_GETMINREQRECT, 0, (LPARAM)&rcCalT);
  5888. ASSERT(rcCalT.left == 0 && rcCalT.top == 0);
  5889. dwWidth = (DWORD)SendMessage(hwndMC, MCM_GETMAXTODAYWIDTH, 0, 0);
  5890. if (dwWidth > (DWORD)rcCalT.right)
  5891. rcCalT.right = dwWidth;
  5892. SECGetSystemtime(&pdp->sec, &st);
  5893. SendMessage(hwndMC, MCM_SETCURSEL, 0, (LPARAM)&st);
  5894. _RecomputeMonthCalRect(pdp, &rcCal, &rcCalT);
  5895. MoveWindow(hwndMC, rcCal.left, rcCal.top,
  5896. rcCal.right - rcCal.left, rcCal.bottom - rcCal.top, FALSE);
  5897. CCSendNotify(&pdp->ci, DTN_DROPDOWN, NULL);
  5898. //
  5899. // HACK-- App may have resized the window during DTN_DROPDOWN,
  5900. // so we need to get the new rcCal rect
  5901. //
  5902. {
  5903. MONTHCAL *pmc = MonthCal_GetPtr(hwndMC);
  5904. _RecomputeMonthCalRect(pdp, &rcCal, &pmc->rc);
  5905. MoveWindow(hwndMC, rcCal.left, rcCal.top,
  5906. rcCal.right - rcCal.left, rcCal.bottom - rcCal.top, FALSE);
  5907. #ifdef DEBUG
  5908. if (GetAsyncKeyState(VK_CONTROL) < 0)
  5909. (pmc)->ci.style |= MCS_MULTISELECT;
  5910. #endif
  5911. }
  5912. ShowWindow(hwndMC, SW_SHOWNA);
  5913. pdp->fShow = TRUE;
  5914. fBtnDown = fLButtonDown;
  5915. fBtnActive = fLButtonDown;
  5916. stOld = pdp->sec.st;
  5917. while (pdp->fShow)
  5918. {
  5919. MSG msg;
  5920. pdp->fShow = (WORD) GetMessage(&msg, NULL, 0, 0);
  5921. // Here's how button controls work as far as I can tell:
  5922. // Until the "final button draw up", the button draws down when the
  5923. // mouse is over it and it draws up when the mouse is not over it. This
  5924. // entire time, the control is active.
  5925. //
  5926. // The "final button draw up" occurs at the first opportunity of:
  5927. // the user releases the mouse button OR the user moves into the rect
  5928. // of the control. The control does it's action on a "mouse up".
  5929. if (fBtnActive)
  5930. {
  5931. switch (msg.message) {
  5932. case WM_MOUSEMOVE:
  5933. if (PtInRect(&rcBtn, msg.pt))
  5934. {
  5935. if (!fBtnDown)
  5936. {
  5937. DPDrawDropdownButton(pdp, hdc, TRUE);
  5938. fBtnDown = TRUE;
  5939. }
  5940. }
  5941. else
  5942. {
  5943. if (fBtnDown)
  5944. {
  5945. DPDrawDropdownButton(pdp, hdc, FALSE);
  5946. fBtnDown = FALSE;
  5947. }
  5948. if (PtInRect(&rcCal, msg.pt))
  5949. {
  5950. fBtnActive = FALSE;
  5951. // let MonthCal think it got a button down
  5952. // IEUNIX: dumb compiler doesn't do well with comments in macros.
  5953. FORWARD_WM_LBUTTONDOWN(hwndMC, FALSE,
  5954. rcCal.left/2 + rcCal.right/2,
  5955. rcCal.top/2 + rcCal.bottom/2,
  5956. 0, SendMessage);
  5957. }
  5958. }
  5959. continue; // the MonthCal doesn't need this message
  5960. case WM_LBUTTONUP:
  5961. if (fBtnDown)
  5962. {
  5963. DPDrawDropdownButton(pdp, hdc, FALSE);
  5964. fBtnDown = FALSE;
  5965. }
  5966. fBtnActive = FALSE;
  5967. continue; // the MonthCal doesn't need this message
  5968. }
  5969. } // if (fBtnActive)
  5970. // Check for events that cause the calendar to go away
  5971. //
  5972. // These events mean "I like it". We allow Alt+Up or Enter
  5973. // to accept the changes. (Alt+Up for compat with combo boxes.)
  5974. //
  5975. if (((msg.message == WM_LBUTTONDOWN ||
  5976. msg.message == WM_NCLBUTTONDOWN ||
  5977. msg.message == WM_LBUTTONDBLCLK) && !PtInRect(&rcCal, msg.pt)) ||
  5978. msg.message == WM_SYSCOMMAND ||
  5979. msg.message == WM_COMMAND ||
  5980. (msg.message == WM_SYSKEYDOWN && msg.wParam == VK_UP) ||
  5981. (msg.message == WM_KEYDOWN && msg.wParam == VK_RETURN) ||
  5982. msg.message == WM_KILLFOCUS)
  5983. {
  5984. DebugMsg(TF_MONTHCAL,TEXT("DPLBD_MonthCal got a message to accept (%d)"), msg.message);
  5985. pdp->fShow = FALSE;
  5986. continue;
  5987. }
  5988. //
  5989. // These events mean "I don't like it".
  5990. //
  5991. else if (((msg.message == WM_RBUTTONDOWN ||
  5992. msg.message == WM_NCRBUTTONDOWN ||
  5993. msg.message == WM_RBUTTONDBLCLK) && !PtInRect(&rcCal, msg.pt)) ||
  5994. (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE))
  5995. {
  5996. DebugMsg(TF_MONTHCAL,TEXT("DPLBD_MonthCal got a message to cancel (%d)"), msg.message);
  5997. pdp->fShow = FALSE;
  5998. pdp->sec.st = stOld;
  5999. DPNotifyDateChange(pdp);
  6000. InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
  6001. continue;
  6002. }
  6003. TranslateMessage(&msg);
  6004. DispatchMessage(&msg);
  6005. } // while(fShow)
  6006. CCSendNotify(&pdp->ci, DTN_CLOSEUP, NULL);
  6007. pdp->hwndMC = NULL;
  6008. DestroyWindow(hwndMC);
  6009. ReleaseDC(pdp->ci.hwnd, hdc);
  6010. }
  6011. void DPHandleSECEdit(DATEPICK *pdp)
  6012. {
  6013. TCHAR szBuf[DTP_FORMATLENGTH];
  6014. if (SECEdit(pdp, szBuf, ARRAYSIZE(szBuf)))
  6015. {
  6016. NMDATETIMESTRING nmdts = {0};
  6017. nmdts.pszUserString = szBuf;
  6018. // just in case the app doesn't parse the string
  6019. nmdts.st = pdp->sec.st;
  6020. nmdts.dwFlags = (pdp->fCheck==1) ? GDT_VALID : GDT_NONE;
  6021. CCSendNotify(&pdp->ci, DTN_USERSTRING, &nmdts.nmhdr);
  6022. // If the app gives us an invalid date, go back to the old date
  6023. if (nmdts.dwFlags == GDT_VALID &&
  6024. !IsValidSystemtime(&nmdts.st))
  6025. {
  6026. nmdts.st = pdp->sec.st;
  6027. }
  6028. if (nmdts.dwFlags == GDT_NONE)
  6029. {
  6030. if (DatePick_ShowCheck(pdp))
  6031. {
  6032. pdp->fCheck = FALSE;
  6033. pdp->fCheckFocus = TRUE;
  6034. SECSetCurSubed(pdp, SUBEDIT_NONE);
  6035. InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
  6036. DPNotifyDateChange(pdp);
  6037. }
  6038. }
  6039. else if (nmdts.dwFlags == GDT_VALID)
  6040. {
  6041. DPSetDate(pdp, &nmdts.st, FALSE);
  6042. }
  6043. }
  6044. }
  6045. LRESULT DPLButtonDown(DATEPICK *pdp, WPARAM wParam, LPARAM lParam)
  6046. {
  6047. POINT pt;
  6048. BOOL fFocus;
  6049. if (!pdp->fEnabled)
  6050. return(0);
  6051. pt.x = GET_X_LPARAM(lParam);
  6052. pt.y = GET_Y_LPARAM(lParam);
  6053. // reset subed char count
  6054. SECResetSubeditEdit(pdp);
  6055. fFocus = pdp->fFocus;
  6056. if (!fFocus)
  6057. SetFocus(pdp->ci.hwnd);
  6058. // display MONTHCAL iif we're not DTS_UPDOWN
  6059. if (!pdp->fUseUpDown && PtInRect(&pdp->rcBtn, pt) && IsWindowVisible(pdp->ci.hwnd))
  6060. {
  6061. DPLBD_MonthCal(pdp, TRUE);
  6062. }
  6063. else if (!pdp->fCapture)
  6064. {
  6065. // Un/check checkbox
  6066. if (DatePick_ShowCheck(pdp) && PtInRect(&pdp->rcCheck, pt))
  6067. {
  6068. pdp->fCheck = !pdp->fCheck;
  6069. pdp->fCheckFocus = 1;
  6070. SECSetCurSubed(pdp, SUBEDIT_NONE);
  6071. InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
  6072. DPNotifyDateChange(pdp);
  6073. }
  6074. // Select a subedit
  6075. else if (pdp->fCheck)
  6076. {
  6077. if (DatePick_AppCanParse(pdp) && fFocus)
  6078. {
  6079. // First click brings focus to a subedit, second click starts editing
  6080. DPHandleSECEdit(pdp);
  6081. }
  6082. else
  6083. {
  6084. int isubed;
  6085. pt.x += pdp->sec.xScroll;
  6086. isubed = SECSubeditFromPt(&pdp->sec, pt);
  6087. if (isubed >= 0)
  6088. {
  6089. SECSetCurSubed(pdp, isubed);
  6090. if (DatePick_ShowCheck(pdp))
  6091. {
  6092. pdp->fCheckFocus = 0;
  6093. InvalidateRect(pdp->ci.hwnd, &pdp->rcCheck, TRUE);
  6094. }
  6095. }
  6096. }
  6097. }
  6098. }
  6099. return(0);
  6100. }
  6101. void DPRecomputeSizing(DATEPICK *pdp, RECT *prect)
  6102. {
  6103. RECT rcTmp;
  6104. if (DatePick_ShowCheck(pdp))
  6105. {
  6106. pdp->rcCheck.top = prect->top + 1;
  6107. pdp->rcCheck.bottom = prect->bottom - 1;
  6108. pdp->rcCheck.left = prect->left + 1;
  6109. pdp->rcCheck.right = prect->left + (pdp->rcCheck.bottom - pdp->rcCheck.top);
  6110. // occupy at most half the width of the window
  6111. if (pdp->rcCheck.right > prect->left + (prect->right - prect->left)/2)
  6112. {
  6113. pdp->rcCheck.right = prect->left + (prect->right - prect->left)/2;
  6114. }
  6115. }
  6116. else
  6117. {
  6118. pdp->rcCheck.top = prect->top;
  6119. pdp->rcCheck.bottom = prect->top;
  6120. pdp->rcCheck.left = prect->left;
  6121. pdp->rcCheck.right = prect->left + DPXBUFFER - 1;
  6122. }
  6123. pdp->rcBtn = *prect;
  6124. pdp->rcBtn.left = pdp->rcBtn.right - GetSystemMetrics(SM_CXVSCROLL);
  6125. if (pdp->rcBtn.left < pdp->rcCheck.right)
  6126. pdp->rcBtn.left = pdp->rcCheck.right;
  6127. if (pdp->hwndUD)
  6128. MoveWindow(pdp->hwndUD, pdp->rcBtn.left, pdp->rcBtn.top, pdp->rcBtn.right - pdp->rcBtn.left + 1, pdp->rcBtn.bottom - pdp->rcBtn.top + 1, FALSE);
  6129. rcTmp = pdp->rc;
  6130. pdp->rc.top = prect->top;
  6131. pdp->rc.bottom = prect->bottom;
  6132. pdp->rc.left = pdp->rcCheck.right + 1;
  6133. pdp->rc.right = pdp->rcBtn.left - 1;
  6134. SECRecomputeSizing(&pdp->sec, &pdp->rc);
  6135. }
  6136. // deal with control codes
  6137. LRESULT DPHandleKeydown(DATEPICK *pdp, WPARAM wParam, LPARAM lParam)
  6138. {
  6139. int delta = 1;
  6140. if (wParam == VK_F4 && !pdp->fUseUpDown)
  6141. {
  6142. DPLBD_MonthCal(pdp, FALSE);
  6143. }
  6144. else if (DatePick_AppCanParse(pdp) && wParam == VK_F2)
  6145. {
  6146. DPHandleSECEdit(pdp);
  6147. }
  6148. else if (pdp->fCheckFocus)
  6149. {
  6150. switch (wParam)
  6151. {
  6152. case VK_LEFT:
  6153. delta = -1;
  6154. // fall through...
  6155. case VK_RIGHT:
  6156. if (pdp->fCheck)
  6157. {
  6158. if (SUBEDIT_NONE != SECIncrFocus(pdp, delta))
  6159. {
  6160. pdp->fCheckFocus = FALSE;
  6161. InvalidateRect(pdp->ci.hwnd, &pdp->rcCheck, TRUE);
  6162. }
  6163. }
  6164. break;
  6165. }
  6166. }
  6167. else
  6168. {
  6169. switch (wParam)
  6170. {
  6171. case VK_HOME:
  6172. if (GetKeyState(VK_CONTROL) < 0)
  6173. {
  6174. SYSTEMTIME st;
  6175. GetLocalTime(&st);
  6176. DPSetDate(pdp, &st, TRUE);
  6177. break;
  6178. }
  6179. // fall through...
  6180. default:
  6181. if (SECHandleKeydown(pdp, wParam, lParam))
  6182. {
  6183. DPNotifyDateChange(pdp);
  6184. }
  6185. else if (DatePick_ShowCheck(pdp))
  6186. {
  6187. if (pdp->sec.iseCur < 0)
  6188. {
  6189. pdp->fCheckFocus = TRUE;
  6190. InvalidateRect(pdp->ci.hwnd, &pdp->rcCheck, TRUE);
  6191. }
  6192. }
  6193. break;
  6194. }
  6195. }
  6196. return(0);
  6197. }
  6198. // deal with characters
  6199. LRESULT DPHandleChar(DATEPICK *pdp, WPARAM wParam, LPARAM lParam)
  6200. {
  6201. TCHAR ch = (TCHAR)wParam;
  6202. if (pdp->fCheckFocus)
  6203. {
  6204. // this is the only character we care about in this case
  6205. if (ch == TEXT(' '))
  6206. {
  6207. pdp->fCheck = 1-pdp->fCheck;
  6208. InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
  6209. DPNotifyDateChange(pdp);
  6210. }
  6211. else
  6212. {
  6213. MessageBeep(MB_ICONHAND);
  6214. }
  6215. }
  6216. else
  6217. {
  6218. // let the subedit handle this -- a value can change
  6219. SECHandleChar(pdp, ch);
  6220. }
  6221. return(0);
  6222. }
  6223. void DPNotifyDateChange(DATEPICK *pdp)
  6224. {
  6225. NMDATETIMECHANGE nmdc = {0};
  6226. BOOL fChanged;
  6227. if (pdp->fNoNotify)
  6228. return;
  6229. if (pdp->fCheck == 0)
  6230. {
  6231. nmdc.dwFlags = GDT_NONE;
  6232. }
  6233. else
  6234. {
  6235. // validate date - do it here in only one place
  6236. if (CmpSystemtime(&pdp->sec.st, &pdp->stMin) < 0)
  6237. {
  6238. pdp->sec.st = pdp->stMin;
  6239. InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
  6240. SECInvalidate(&pdp->sec, SE_APP);
  6241. }
  6242. else if (CmpSystemtime(&pdp->sec.st, &pdp->stMax) > 0)
  6243. {
  6244. pdp->sec.st = pdp->stMax;
  6245. InvalidateRect(pdp->ci.hwnd, NULL, TRUE);
  6246. SECInvalidate(&pdp->sec, SE_APP);
  6247. }
  6248. nmdc.dwFlags = GDT_VALID;
  6249. SECGetSystemtime(&pdp->sec, &nmdc.st);
  6250. }
  6251. fChanged = CmpSystemtime(&pdp->stPrev, &nmdc.st);
  6252. if (fChanged)
  6253. {
  6254. NotifyWinEvent(EVENT_OBJECT_VALUECHANGE, pdp->ci.hwnd, OBJID_CLIENT, INDEXID_CONTAINER);
  6255. }
  6256. //
  6257. // APP COMPAT: IE4 always notified even if the date didn't change.
  6258. // I don't know of any apps that rely on this
  6259. // but I'm not gonna risk it.
  6260. //
  6261. if (fChanged || pdp->ci.iVersion < 5)
  6262. {
  6263. pdp->stPrev = nmdc.st;
  6264. CCSendNotify(&pdp->ci, DTN_DATETIMECHANGE, &nmdc.nmhdr);
  6265. }
  6266. }
  6267. BOOL DPSetDate(DATEPICK *pdp, SYSTEMTIME *pst, BOOL fMungeDate)
  6268. {
  6269. BOOL fChanged = FALSE;
  6270. // make sure that the new date is within the valid range
  6271. if (CmpSystemtime(pst, &pdp->stMin) < 0)
  6272. {
  6273. if (!fMungeDate)
  6274. return(FALSE);
  6275. pst = &pdp->stMin;
  6276. }
  6277. if (CmpSystemtime(&pdp->stMax, pst) < 0)
  6278. {
  6279. if (!fMungeDate)
  6280. return(FALSE);
  6281. pst = &pdp->stMax;
  6282. }
  6283. if (fMungeDate)
  6284. {
  6285. // only copy the date portion
  6286. CopyDate(*pst, pdp->sec.st);
  6287. fChanged = TRUE;
  6288. }
  6289. else
  6290. {
  6291. fChanged = SECSetSystemtime(pdp, pst);
  6292. }
  6293. if (fChanged)
  6294. {
  6295. SECInvalidate(&pdp->sec, SE_APP); // SE_APP invalidates everything
  6296. DPNotifyDateChange(pdp);
  6297. }
  6298. return(TRUE);
  6299. }
  6300. void DPDrawDropdownButton(DATEPICK *pdp, HDC hdc, BOOL fPressed)
  6301. {
  6302. if (pdp->hThemeCombo)
  6303. {
  6304. int iStateId = CBXS_NORMAL;
  6305. if (fPressed)
  6306. iStateId = CBXS_PRESSED;
  6307. if (!pdp->fEnabled)
  6308. iStateId = CBXS_DISABLED;
  6309. DrawThemeBackground(pdp->hThemeCombo, hdc, CP_DROPDOWNBUTTON, iStateId, &pdp->rcBtn, 0);
  6310. }
  6311. else
  6312. {
  6313. UINT dfcs;
  6314. dfcs = DFCS_SCROLLDOWN;
  6315. if (fPressed)
  6316. dfcs |= DFCS_PUSHED | DFCS_FLAT;
  6317. if (!pdp->fEnabled)
  6318. dfcs |= DFCS_INACTIVE;
  6319. DrawFrameControl(hdc, &pdp->rcBtn, DFC_SCROLL, dfcs);
  6320. }
  6321. }