Leaked source code of windows server 2003
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.

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