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

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