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.

1288 lines
38 KiB

  1. /**********************************************************************/
  2. /** Microsoft Windows/NT **/
  3. /** Copyright(c) Microsoft Corp., 1991-2001 **/
  4. /**********************************************************************/
  5. /*
  6. CHKLIST.CPP
  7. This file contains the implementation of the CheckList control.
  8. */
  9. #include "stdafx.h"
  10. #include <windowsx.h>
  11. #include "chklist.h"
  12. #include "debug.h"
  13. #include "util.h"
  14. //
  15. // Text and Background colors
  16. //
  17. #define TEXT_COLOR COLOR_WINDOWTEXT
  18. #define BK_COLOR COLOR_WINDOW
  19. //
  20. // Default dimensions for child controls. All are in dialog units.
  21. // Currently only the column width is user-adjustable (via the
  22. // CLM_SETCOLUMNWIDTH message).
  23. //
  24. #define DEFAULT_COLUMN_WIDTH 32
  25. #define DEFAULT_CHECK_WIDTH 9
  26. #define DEFAULT_HORZ_SPACE 7
  27. #define DEFAULT_VERTICAL_SPACE 3
  28. #define DEFAULT_ITEM_HEIGHT 8
  29. //
  30. // 16 bits are used for the control ID's, divided into n bits for
  31. // the subitem (least significant) and 16-n bits for the item index.
  32. //
  33. // ID_SUBITEM_BITS can be adjusted to control the maximum number of
  34. // items and subitems. For example, to allow up to 7 subitems and 8k
  35. // items, set ID_SUBITEM_BITS to 3.
  36. //
  37. // Use the low 2 bits for the subitem index, the rest for the item index.
  38. // (4 subitems max, 16k items max)
  39. #define ID_SUBITEM_BITS 2
  40. #define ID_SUBITEM_MASK ((1 << ID_SUBITEM_BITS) - 1)
  41. #define GET_ITEM(id) ((id) >> ID_SUBITEM_BITS)
  42. #define GET_SUBITEM(id) ((id) & ID_SUBITEM_MASK)
  43. #define MAKE_CTRL_ID(i, s) (0xffff & (((i) << ID_SUBITEM_BITS) | ((s) & ID_SUBITEM_MASK)))
  44. #define MAKE_LABEL_ID(i) MAKE_CTRL_ID(i, 0)
  45. // Note that the subitem (column) index is one-based for the checkboxes
  46. // (the zero column is the label). The item (row) index is zero-based.
  47. #define MAX_CHECK_COLUMNS ID_SUBITEM_MASK
  48. typedef struct _USERDATA_STRUCT_LABEL
  49. {
  50. LPARAM lParam;
  51. int nLabelHeight;
  52. int itemIndex;
  53. } USERDATA_STRUCT_LABEL, *LPUSERDATA_STRUCT_LABEL;
  54. class CCheckList
  55. {
  56. private:
  57. LONG m_cItems;
  58. LONG m_cSubItems;
  59. RECT m_rcItemLabel;
  60. LONG m_nCheckPos[MAX_CHECK_COLUMNS];
  61. LONG m_cxCheckBox;
  62. LONG m_cxCheckColumn;
  63. int m_nDefaultVerticalSpace;
  64. int m_nDefaultItemHeight;
  65. int m_nNewItemYPos;
  66. HWND m_hwndCheckFocus;
  67. BOOL m_fInMessageEnable;
  68. int m_cWheelDelta;
  69. static UINT g_ucScrollLines;
  70. private:
  71. CCheckList(HWND hWnd, LPCREATESTRUCT lpcs);
  72. LRESULT MsgCommand(HWND hWnd, WORD idCmd, WORD wNotify, HWND hwndCtrl);
  73. void MsgPaint(HWND hWnd, HDC hdc);
  74. void MsgVScroll(HWND hWnd, int nCode, int nPos);
  75. void MsgMouseWheel(HWND hWnd, WORD fwFlags, int zDelta);
  76. void MsgButtonDown(HWND hWnd, WPARAM fwFlags, int xPos, int yPos);
  77. void MsgEnable(HWND hWnd, BOOL fEnabled);
  78. void MsgSize(HWND hWnd, DWORD dwSizeType, LONG nWidth, LONG nHeight);
  79. LONG AddItem(HWND hWnd, LPCTSTR pszLabel, LPARAM lParam);
  80. void SetState(HWND hWnd, WORD iItem, WORD iSubItem, LONG lState);
  81. LONG GetState(HWND hWnd, WORD iItem, WORD iSubItem);
  82. void SetColumnWidth(HWND hWnd, LONG cxDialog, LONG cxColumn);
  83. void ResetContent(HWND hWnd);
  84. LONG GetVisibleCount(HWND hWnd);
  85. LONG GetTopIndex(HWND hWnd, LONG *pnAmountObscured = NULL);
  86. void SetTopIndex(HWND hWnd, LONG nIndex);
  87. void EnsureVisible(HWND hWnd, LONG nIndex);
  88. void DrawCheckFocusRect(HWND hWnd, HWND hwndCheck, BOOL fDraw);
  89. public:
  90. HWND m_hWnd;
  91. static LRESULT CALLBACK WindowProc(HWND hWnd,
  92. UINT uMsg,
  93. WPARAM wParam,
  94. LPARAM lParam);
  95. };
  96. BOOL RegisterCheckListWndClass(void)
  97. {
  98. WNDCLASS wc;
  99. wc.style = 0;
  100. wc.lpfnWndProc = CCheckList::WindowProc;
  101. wc.cbClsExtra = 0;
  102. wc.cbWndExtra = 0;
  103. AFX_MANAGE_STATE(AfxGetStaticModuleState()); // Required for AfxGetInstanceHandle()
  104. wc.hInstance = AfxGetInstanceHandle(); //hModule;
  105. wc.hIcon = NULL;
  106. wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  107. wc.hbrBackground = (HBRUSH)(BK_COLOR+1);
  108. wc.lpszMenuName = NULL;
  109. wc.lpszClassName = TEXT(WC_CHECKLIST);
  110. return (BOOL)RegisterClass(&wc);
  111. }
  112. UINT CCheckList::g_ucScrollLines = (UINT)-1;
  113. CCheckList::CCheckList(HWND hWnd, LPCREATESTRUCT lpcs)
  114. : m_cItems(0), m_hwndCheckFocus(NULL), m_fInMessageEnable(FALSE), m_cWheelDelta(0)
  115. {
  116. TraceEnter(TRACE_CHECKLIST, "CCheckList::CCheckList");
  117. TraceAssert(hWnd != NULL);
  118. TraceAssert(lpcs != NULL);
  119. m_hWnd = hWnd;
  120. //
  121. // Get number of check columns
  122. //
  123. m_cSubItems = lpcs->style & CLS_CHECKMASK;
  124. // for wsecedit only
  125. if ( m_cSubItems > 3 ) {
  126. m_cSubItems = 3;
  127. }
  128. //
  129. // Convert default coordinates from dialog units to pixels
  130. //
  131. RECT rc;
  132. rc.left = DEFAULT_CHECK_WIDTH;
  133. rc.right = DEFAULT_COLUMN_WIDTH;
  134. rc.top = rc.bottom = 0;
  135. MapDialogRect(lpcs->hwndParent, &rc);
  136. // Save the converted values
  137. m_cxCheckBox = rc.left;
  138. m_cxCheckColumn = rc.right;
  139. rc.left = DEFAULT_HORZ_SPACE;
  140. rc.top = DEFAULT_VERTICAL_SPACE;
  141. rc.right = 10; // bogus (unused)
  142. rc.bottom = DEFAULT_VERTICAL_SPACE + DEFAULT_ITEM_HEIGHT;
  143. MapDialogRect(lpcs->hwndParent, &rc);
  144. // Save the converted values
  145. m_rcItemLabel = rc;
  146. m_nDefaultVerticalSpace = rc.top;
  147. m_nDefaultItemHeight = rc.bottom - rc.top;
  148. m_nNewItemYPos = rc.top;
  149. //
  150. // Get info for mouse wheel scrolling
  151. //
  152. if ((UINT)-1 == g_ucScrollLines)
  153. {
  154. g_ucScrollLines = 3; // default
  155. SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &g_ucScrollLines, 0);
  156. }
  157. TraceLeaveVoid();
  158. }
  159. LRESULT
  160. CCheckList::MsgCommand(HWND hWnd, WORD idCmd, WORD wNotify, HWND hwndCtrl)
  161. {
  162. TraceEnter(TRACE_CHECKLIST, "CCheckList::MsgCommand");
  163. // Should only get notifications from visible, enabled, check boxes
  164. TraceAssert(GET_ITEM(idCmd) < m_cItems);
  165. TraceAssert(0 < GET_SUBITEM(idCmd) && GET_SUBITEM(idCmd) <= m_cSubItems);
  166. TraceAssert(hwndCtrl && IsWindowEnabled(hwndCtrl));
  167. switch (wNotify)
  168. {
  169. case EN_SETFOCUS:
  170. {
  171. // Make the focus go to one of the checkboxes
  172. POINT pt;
  173. DWORD dwPos = GetMessagePos();
  174. pt.x = GET_X_LPARAM(dwPos);
  175. pt.y = GET_Y_LPARAM(dwPos);
  176. MapWindowPoints(NULL, hWnd, &pt, 1);
  177. MsgButtonDown(hWnd, 0, pt.x, pt.y);
  178. }
  179. break;
  180. case BN_CLICKED:
  181. {
  182. LPUSERDATA_STRUCT_LABEL lpUserData;
  183. NM_CHECKLIST nmc;
  184. nmc.hdr.hwndFrom = hWnd;
  185. nmc.hdr.idFrom = GetDlgCtrlID(hWnd);
  186. nmc.hdr.code = CLN_CLICK;
  187. nmc.iItem = GET_ITEM(idCmd);
  188. nmc.iSubItem = GET_SUBITEM(idCmd);
  189. nmc.dwState = (DWORD)SendMessage(hwndCtrl, BM_GETCHECK, 0, 0);
  190. if (!IsWindowEnabled(hwndCtrl))
  191. nmc.dwState |= CLST_DISABLED;
  192. lpUserData = (LPUSERDATA_STRUCT_LABEL)
  193. GetWindowLongPtr( GetDlgItem(hWnd, MAKE_LABEL_ID(nmc.iItem)),
  194. GWLP_USERDATA);
  195. nmc.dwItemData = lpUserData->lParam;
  196. SendMessage(GetParent(hWnd),
  197. WM_NOTIFY,
  198. nmc.hdr.idFrom,
  199. (LPARAM)&nmc);
  200. }
  201. break;
  202. case BN_SETFOCUS:
  203. if (GetFocus() != hwndCtrl)
  204. {
  205. // This causes another BN_SETFOCUS
  206. SetFocus(hwndCtrl);
  207. }
  208. else
  209. {
  210. if (m_hwndCheckFocus != hwndCtrl) // Has the focus moved?
  211. {
  212. // Remember where the focus is
  213. m_hwndCheckFocus = hwndCtrl;
  214. // Make sure the row is scrolled into view
  215. EnsureVisible(hWnd, GET_ITEM(idCmd));
  216. }
  217. // Always draw the focus rect
  218. DrawCheckFocusRect(hWnd, hwndCtrl, TRUE);
  219. }
  220. break;
  221. case BN_KILLFOCUS:
  222. // Remove the focus rect
  223. m_hwndCheckFocus = NULL;
  224. DrawCheckFocusRect(hWnd, hwndCtrl, FALSE);
  225. break;
  226. }
  227. TraceLeaveValue(0);
  228. }
  229. void
  230. CCheckList::MsgPaint(HWND hWnd, HDC hdc)
  231. {
  232. if (hdc == NULL && m_hwndCheckFocus != NULL)
  233. {
  234. // This will cause a focus rect to be drawn after the window and
  235. // all checkboxes have been painted.
  236. PostMessage(hWnd,
  237. WM_COMMAND,
  238. GET_WM_COMMAND_MPS(GetDlgCtrlID(m_hwndCheckFocus), m_hwndCheckFocus, BN_SETFOCUS));
  239. }
  240. // Default paint
  241. DefWindowProc(hWnd, WM_PAINT, (WPARAM)hdc, 0);
  242. }
  243. void
  244. CCheckList::MsgVScroll(HWND hWnd, int nCode, int nPos)
  245. {
  246. UINT cScrollUnitsPerLine;
  247. SCROLLINFO si;
  248. si.cbSize = sizeof(si);
  249. si.fMask = SIF_ALL;
  250. if (!GetScrollInfo(hWnd, SB_VERT, &si))
  251. return;
  252. cScrollUnitsPerLine = m_rcItemLabel.bottom;
  253. // One page is always visible, so adjust the range to a more useful value
  254. si.nMax -= si.nPage - 1;
  255. switch (nCode)
  256. {
  257. case SB_LINEUP:
  258. // "line" is the height of one item (includes the space in between)
  259. nPos = si.nPos - cScrollUnitsPerLine;
  260. break;
  261. case SB_LINEDOWN:
  262. nPos = si.nPos + cScrollUnitsPerLine;
  263. break;
  264. case SB_PAGEUP:
  265. nPos = si.nPos - si.nPage;
  266. break;
  267. case SB_PAGEDOWN:
  268. nPos = si.nPos + si.nPage;
  269. break;
  270. case SB_TOP:
  271. nPos = si.nMin;
  272. break;
  273. case SB_BOTTOM:
  274. nPos = si.nMax;
  275. break;
  276. case SB_ENDSCROLL:
  277. nPos = si.nPos; // don't go anywhere
  278. break;
  279. case SB_THUMBTRACK:
  280. // Do nothing here to allow tracking
  281. // nPos = si.nPos; // Do this to prevent tracking
  282. case SB_THUMBPOSITION:
  283. // nothing to do here... nPos is passed in
  284. break;
  285. }
  286. // Make sure the new position is within the range
  287. if (nPos < si.nMin)
  288. nPos = si.nMin;
  289. else if (nPos > si.nMax)
  290. nPos = si.nMax;
  291. if (nPos != si.nPos) // are we moving?
  292. {
  293. SetScrollPos(hWnd, SB_VERT, nPos, TRUE);
  294. ScrollWindow(hWnd, 0, si.nPos - nPos, NULL, NULL);
  295. }
  296. }
  297. void
  298. CCheckList::MsgMouseWheel(HWND hWnd, WORD fwFlags, int iWheelDelta)
  299. {
  300. int cDetants;
  301. if ((fwFlags & (MK_SHIFT | MK_CONTROL)) || 0 == g_ucScrollLines)
  302. return;
  303. TraceEnter(TRACE_CHECKLIST, "CCheckList::MsgMouseWheel");
  304. // Update count of scroll amount
  305. m_cWheelDelta -= iWheelDelta;
  306. cDetants = m_cWheelDelta / WHEEL_DELTA;
  307. if (0 == cDetants)
  308. TraceLeaveVoid();
  309. m_cWheelDelta %= WHEEL_DELTA;
  310. if (WS_VSCROLL & GetWindowLong(hWnd, GWL_STYLE))
  311. {
  312. SCROLLINFO si;
  313. UINT cScrollUnitsPerLine;
  314. UINT cLinesPerPage;
  315. UINT cLinesPerDetant;
  316. // Get the scroll amount of one line
  317. cScrollUnitsPerLine = m_rcItemLabel.bottom;
  318. TraceAssert(cScrollUnitsPerLine > 0);
  319. si.cbSize = sizeof(SCROLLINFO);
  320. si.fMask = SIF_PAGE | SIF_POS;
  321. if (!GetScrollInfo(hWnd, SB_VERT, &si))
  322. TraceLeaveVoid();
  323. // The size of a page is at least one line, and
  324. // leaves one line of overlap
  325. cLinesPerPage = (si.nPage - cScrollUnitsPerLine) / cScrollUnitsPerLine;
  326. cLinesPerPage = max(1, cLinesPerPage);
  327. // Don't scroll more than one page per detant
  328. cLinesPerDetant = min(cLinesPerPage, g_ucScrollLines);
  329. si.nPos += cDetants * cLinesPerDetant * cScrollUnitsPerLine;
  330. MsgVScroll(hWnd, SB_THUMBTRACK, si.nPos);
  331. }
  332. TraceLeaveVoid();
  333. }
  334. void
  335. CCheckList::MsgButtonDown(HWND hWnd, WPARAM /*fwFlags*/, int xPos, int yPos)
  336. {
  337. LONG nItemIndex;
  338. HWND hwndCheck;
  339. RECT rc;
  340. // Get position of the top visible item in client coords
  341. nItemIndex = GetTopIndex(hWnd);
  342. if (nItemIndex == -1)
  343. {
  344. return;
  345. }
  346. hwndCheck = GetDlgItem(hWnd, MAKE_CTRL_ID(nItemIndex, 0));
  347. GetWindowRect(hwndCheck, &rc);
  348. MapWindowPoints(NULL, hWnd, (LPPOINT)&rc, 2);
  349. // Find nearest item
  350. if( hWnd == m_hWnd ) //Raid #387542, 5/9/2001
  351. {
  352. POINT pos = {xPos,yPos};
  353. HWND ChildhWnd = ::ChildWindowFromPointEx(hWnd, pos, CWP_SKIPINVISIBLE|CWP_SKIPDISABLED);
  354. if( ChildhWnd )
  355. {
  356. LPUSERDATA_STRUCT_LABEL pUserData = (LPUSERDATA_STRUCT_LABEL)GetWindowLongPtr(ChildhWnd, GWLP_USERDATA);
  357. if( pUserData )
  358. {
  359. nItemIndex = pUserData->itemIndex;
  360. }
  361. else
  362. {
  363. return;
  364. }
  365. }
  366. }
  367. // Set focus to first subitem that is enabled
  368. for (LONG j = 1; j <= m_cSubItems; j++)
  369. {
  370. int id = MAKE_CTRL_ID(nItemIndex, j);
  371. HWND hwndCheck = GetDlgItem(hWnd, id);
  372. if (IsWindowEnabled(hwndCheck))
  373. {
  374. // Don't just SetFocus here. We sometimes call this during
  375. // EN_SETFOCUS, and USER doesn't like it when you mess with
  376. // focus during a focus change.
  377. //
  378. //SetFocus(hwndCheck);
  379. PostMessage(hWnd,
  380. WM_COMMAND,
  381. GET_WM_COMMAND_MPS(id, hwndCheck, BN_SETFOCUS));
  382. break;
  383. }
  384. }
  385. }
  386. void
  387. CCheckList::MsgEnable(HWND hWnd, BOOL fEnabled)
  388. {
  389. HWND hwndCurrentCheck;
  390. BOOL fCheckEnabled;
  391. if (!m_fInMessageEnable)
  392. {
  393. m_fInMessageEnable = TRUE;
  394. for (LONG i = 0; i < m_cItems; i++)
  395. {
  396. for (LONG j = 1; j <= m_cSubItems; j++)
  397. {
  398. hwndCurrentCheck = GetDlgItem(hWnd, MAKE_CTRL_ID(i, j));
  399. fCheckEnabled = (BOOL) GetWindowLongPtr(hwndCurrentCheck, GWLP_USERDATA);
  400. //
  401. // If the user of the checklist control is disabling the control
  402. // altogether, or the current checkbox has been disabled singularly
  403. // then disable the checkbox
  404. //
  405. if (!fEnabled || !fCheckEnabled)
  406. {
  407. EnableWindow(hwndCurrentCheck, FALSE);
  408. }
  409. else
  410. {
  411. EnableWindow(hwndCurrentCheck, TRUE);
  412. }
  413. }
  414. }
  415. // Note that the main chklist window must remain enabled
  416. // for scrolling to work while "disabled".
  417. if (!fEnabled)
  418. EnableWindow(hWnd, TRUE);
  419. m_fInMessageEnable = FALSE;
  420. }
  421. }
  422. void
  423. CCheckList::MsgSize(HWND hWnd, DWORD dwSizeType, LONG nWidth, LONG nHeight)
  424. {
  425. TraceEnter(TRACE_CHECKLIST, "CCheckList::MsgSize");
  426. TraceAssert(hWnd != NULL);
  427. if (dwSizeType == SIZE_RESTORED)
  428. {
  429. RECT rc;
  430. SCROLLINFO si;
  431. si.cbSize = sizeof(si);
  432. si.fMask = SIF_RANGE | SIF_PAGE;
  433. si.nMin = 0;
  434. si.nMax = m_nNewItemYPos - 1;
  435. si.nPage = nHeight;
  436. SetScrollInfo(hWnd, SB_VERT, &si, FALSE);
  437. // Don't trust the width value passed in, since SetScrollInfo may
  438. // affect it if the scroll bar is turning on or off.
  439. GetClientRect(hWnd, &rc);
  440. nWidth = rc.right;
  441. // If the scrollbar is turned on, artificially bump up the width
  442. // by the width of the scrollbar, so the boxes don't jump to the left
  443. // when we have a scrollbar.
  444. if ((UINT)si.nMax >= si.nPage)
  445. nWidth += GetSystemMetrics(SM_CYHSCROLL);
  446. SetColumnWidth(hWnd, nWidth, m_cxCheckColumn);
  447. }
  448. TraceLeaveVoid();
  449. }
  450. LONG CCheckList::AddItem(HWND hWnd, LPCTSTR pszLabel, LPARAM lParam)
  451. {
  452. HWND hwndPrev = 0;
  453. LPUSERDATA_STRUCT_LABEL lpUserData = 0;
  454. TraceEnter(TRACE_CHECKLIST, "CCheckList::AddItem");
  455. TraceAssert(hWnd != NULL);
  456. TraceAssert(pszLabel != NULL && !IsBadStringPtr(pszLabel, MAX_PATH));
  457. if ( !hWnd || !pszLabel || IsBadStringPtr(pszLabel, MAX_PATH) )
  458. return -1;
  459. lpUserData = new (USERDATA_STRUCT_LABEL);
  460. if ( lpUserData )
  461. {
  462. SCROLLINFO si;
  463. si.cbSize = sizeof(si);
  464. si.fMask = SIF_POS;
  465. si.nPos = 0;
  466. GetScrollInfo(hWnd, SB_VERT, &si);
  467. // Set the initial label height extra big so the control can wrap the text,
  468. // then reset it after creating the control.
  469. RECT rc;
  470. GetClientRect(hWnd, &rc);
  471. LONG nLabelHeight = rc.bottom;
  472. AFX_MANAGE_STATE(AfxGetStaticModuleState()); // Required for AfxGetInstanceHandle()
  473. HMODULE hModule = AfxGetInstanceHandle();
  474. // Create a new label control
  475. HWND hwndNew = CreateWindowEx(WS_EX_NOPARENTNOTIFY,
  476. TEXT("edit"),
  477. pszLabel,
  478. WS_CHILD | WS_VISIBLE | WS_GROUP | ES_MULTILINE | ES_READONLY | ES_LEFT,// | WS_GROUP,
  479. m_rcItemLabel.left,
  480. m_nNewItemYPos - si.nPos,
  481. m_rcItemLabel.right - m_rcItemLabel.left,
  482. nLabelHeight,
  483. hWnd,
  484. (HMENU)IntToPtr(MAKE_LABEL_ID(m_cItems)),
  485. hModule,
  486. NULL);
  487. if ( hwndNew )
  488. {
  489. HWND hwndEdit = hwndNew;
  490. //
  491. // Reset window height after word wrap has been done.
  492. //
  493. LONG nLineCount = (LONG) SendMessage(hwndNew, EM_GETLINECOUNT, 0, (LPARAM) 0);
  494. nLabelHeight = nLineCount * m_nDefaultItemHeight;
  495. SetWindowPos(hwndNew,
  496. NULL,
  497. 0,
  498. 0,
  499. m_rcItemLabel.right - m_rcItemLabel.left,
  500. nLabelHeight,
  501. SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
  502. //
  503. // Save item data
  504. //
  505. lpUserData->lParam = lParam;
  506. lpUserData->nLabelHeight = nLabelHeight;
  507. lpUserData->itemIndex = m_cItems; //Raid #387542
  508. SetLastError(0);
  509. SetWindowLongPtr(hwndNew, GWLP_USERDATA, (LPARAM) lpUserData); //Raid #286697, 4/4/2001
  510. if( 0 == GetLastError() )
  511. {
  512. // Set the font
  513. SendMessage(hwndNew,
  514. WM_SETFONT,
  515. SendMessage(GetParent(hWnd), WM_GETFONT, 0, 0),
  516. 0);
  517. // Set Z-order position just after the last checkbox. This keeps
  518. // tab order correct.
  519. if (m_cItems > 0)
  520. {
  521. hwndPrev = GetDlgItem(hWnd, MAKE_CTRL_ID(m_cItems - 1, m_cSubItems));
  522. SetWindowPos(hwndNew,
  523. hwndPrev,
  524. 0, 0, 0, 0,
  525. SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
  526. }
  527. // Create new checkboxes
  528. DWORD dwCheckStyle = WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP | BS_NOTIFY | BS_FLAT | BS_AUTOCHECKBOX;
  529. for (LONG j = 0; j < m_cSubItems; j++)
  530. {
  531. hwndPrev = hwndNew;
  532. hwndNew = CreateWindowEx(WS_EX_NOPARENTNOTIFY,
  533. TEXT("BUTTON"),
  534. NULL,
  535. dwCheckStyle,
  536. m_nCheckPos[j],
  537. m_nNewItemYPos - si.nPos,
  538. m_cxCheckBox,
  539. m_rcItemLabel.bottom - m_rcItemLabel.top,
  540. hWnd,
  541. (HMENU)IntToPtr(MAKE_CTRL_ID(m_cItems, j + 1)),
  542. hModule,
  543. NULL);
  544. if (!hwndNew)
  545. {
  546. while (j >= 0)
  547. {
  548. DestroyWindow(GetDlgItem(hWnd, MAKE_CTRL_ID(m_cItems, j)));
  549. j--;
  550. }
  551. DestroyWindow (hwndEdit);
  552. delete lpUserData;
  553. TraceLeaveValue(-1);
  554. }
  555. // Set Z-order position just after the last checkbox. This keeps
  556. // tab order correct.
  557. SetWindowPos(hwndNew,
  558. hwndPrev,
  559. 0, 0, 0, 0,
  560. SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
  561. //
  562. // Default "enabled" to TRUE
  563. //
  564. SetWindowLongPtr(hwndNew, GWLP_USERDATA, (LPARAM) TRUE);
  565. // Only want this style on the first checkbox
  566. dwCheckStyle &= ~WS_GROUP;
  567. }
  568. // We now officially have a new item
  569. m_cItems++;
  570. // calculate Y pos for next item to be inserted
  571. m_nNewItemYPos += nLabelHeight + m_nDefaultVerticalSpace;
  572. //
  573. // The last thing is to set the scroll range
  574. //
  575. GetClientRect(hWnd, &rc);
  576. si.cbSize = sizeof(si);
  577. si.fMask = SIF_PAGE | SIF_RANGE;
  578. si.nMin = 0;
  579. si.nMax = m_nNewItemYPos - 1;
  580. si.nPage = rc.bottom;
  581. SetScrollInfo(hWnd, SB_VERT, &si, FALSE);
  582. }
  583. else
  584. {
  585. delete lpUserData;
  586. DestroyWindow(hwndNew);
  587. }
  588. }
  589. else
  590. delete lpUserData;
  591. }
  592. TraceLeaveValue(m_cItems - 1); // return the index of the new item
  593. }
  594. void
  595. CCheckList::SetState(HWND hWnd, WORD iItem, WORD iSubItem, LONG lState)
  596. {
  597. HWND hwndCtrl;
  598. TraceEnter(TRACE_CHECKLIST, "CCheckList::SetState");
  599. TraceAssert(hWnd != NULL);
  600. TraceAssert(iItem < m_cItems);
  601. TraceAssert(0 < iSubItem && iSubItem <= m_cSubItems);
  602. if (iSubItem > 0)
  603. {
  604. hwndCtrl = GetDlgItem(hWnd, MAKE_CTRL_ID(iItem, iSubItem));
  605. if (hwndCtrl != NULL)
  606. {
  607. SetWindowLongPtr(hwndCtrl, GWLP_USERDATA, (LPARAM) !(lState & CLST_DISABLED));
  608. SendMessage(hwndCtrl, BM_SETCHECK, lState & CLST_CHECKED, 0);
  609. EnableWindow(hwndCtrl, !(lState & CLST_DISABLED));
  610. }
  611. }
  612. TraceLeaveVoid();
  613. }
  614. LONG
  615. CCheckList::GetState(HWND hWnd, WORD iItem, WORD iSubItem)
  616. {
  617. LONG lState = 0;
  618. TraceEnter(TRACE_CHECKLIST, "CCheckList::GetState");
  619. TraceAssert(hWnd != NULL);
  620. TraceAssert(iItem < m_cItems);
  621. TraceAssert(0 < iSubItem && iSubItem <= m_cSubItems);
  622. HWND hwndCtrl = GetDlgItem(hWnd, MAKE_CTRL_ID(iItem, iSubItem));
  623. if (hwndCtrl != NULL)
  624. {
  625. lState = (LONG)SendMessage(hwndCtrl, BM_GETCHECK, 0, 0);
  626. TraceAssert(!(lState & BST_INDETERMINATE));
  627. if (!IsWindowEnabled(hwndCtrl))
  628. lState |= CLST_DISABLED;
  629. }
  630. TraceLeaveValue(lState);
  631. }
  632. void
  633. CCheckList::SetColumnWidth(HWND hWnd, LONG cxDialog, LONG cxColumn)
  634. {
  635. LONG j;
  636. LPUSERDATA_STRUCT_LABEL pUserData;
  637. LONG nLabelHeight;
  638. TraceEnter(TRACE_CHECKLIST, "CCheckList::SetColumnWidth");
  639. TraceAssert(hWnd != NULL);
  640. TraceAssert(cxColumn > 10);
  641. m_cxCheckColumn = cxColumn;
  642. if (m_cSubItems > 0)
  643. {
  644. m_nCheckPos[m_cSubItems-1] = cxDialog // dlg width
  645. - m_rcItemLabel.left // right margin
  646. - (cxColumn + m_cxCheckBox)/2; // 1/2 col & 1/2 checkbox
  647. for (j = m_cSubItems - 1; j > 0; j--)
  648. m_nCheckPos[j-1] = m_nCheckPos[j] - cxColumn;
  649. // (leftmost check pos) - (horz margin)
  650. m_rcItemLabel.right = m_nCheckPos[0] - m_rcItemLabel.left;
  651. }
  652. else
  653. m_rcItemLabel.right = cxDialog - m_rcItemLabel.left;
  654. LONG nTop = m_rcItemLabel.top;
  655. LONG nBottom = m_rcItemLabel.bottom;
  656. for (LONG i = 0; i < m_cItems; i++)
  657. {
  658. pUserData = (LPUSERDATA_STRUCT_LABEL)
  659. GetWindowLongPtr( GetDlgItem(hWnd, MAKE_LABEL_ID((int)i)),
  660. GWLP_USERDATA);
  661. if (pUserData != NULL)
  662. {
  663. nLabelHeight = pUserData->nLabelHeight;
  664. }
  665. else
  666. {
  667. nLabelHeight = nBottom - nTop;
  668. }
  669. MoveWindow(GetDlgItem(hWnd, MAKE_LABEL_ID(i)),
  670. m_rcItemLabel.left,
  671. nTop,
  672. m_rcItemLabel.right - m_rcItemLabel.left,
  673. nLabelHeight,
  674. FALSE);
  675. for (j = 0; j < m_cSubItems; j++)
  676. {
  677. MoveWindow(GetDlgItem(hWnd, MAKE_CTRL_ID(i, j + 1)),
  678. m_nCheckPos[j],
  679. nTop,
  680. m_cxCheckBox,
  681. nBottom - nTop,
  682. FALSE);
  683. }
  684. nTop += nLabelHeight + m_nDefaultVerticalSpace;
  685. nBottom += nLabelHeight + m_nDefaultVerticalSpace;
  686. }
  687. TraceLeaveVoid();
  688. }
  689. void
  690. CCheckList::ResetContent(HWND hWnd)
  691. {
  692. LPUSERDATA_STRUCT_LABEL pUserData;
  693. HWND hwndCurrentLabel;
  694. for (LONG i = 0; i < m_cItems; i++)
  695. {
  696. hwndCurrentLabel = GetDlgItem(hWnd, MAKE_LABEL_ID((int)i));
  697. pUserData = (LPUSERDATA_STRUCT_LABEL)
  698. GetWindowLongPtr( hwndCurrentLabel,
  699. GWLP_USERDATA);
  700. if (pUserData != NULL)
  701. {
  702. delete(pUserData);
  703. }
  704. DestroyWindow(hwndCurrentLabel);
  705. for (LONG j = 1; j <= m_cSubItems; j++)
  706. {
  707. DestroyWindow(GetDlgItem(hWnd, MAKE_CTRL_ID(i, j)));
  708. }
  709. }
  710. // Hide the scroll bar
  711. SetScrollRange(hWnd, SB_VERT, 0, 0, FALSE);
  712. m_cItems = 0;
  713. }
  714. LONG
  715. CCheckList::GetVisibleCount(HWND hWnd)
  716. {
  717. LONG nCount = 0;
  718. RECT rc;
  719. LONG nTopIndex;
  720. LONG nAmountShown = 0;
  721. LONG nAmountObscured = 0;
  722. LPUSERDATA_STRUCT_LABEL pUserData;
  723. if (!GetClientRect(hWnd, &rc))
  724. {
  725. return 1;
  726. }
  727. nTopIndex = GetTopIndex(hWnd, &nAmountObscured);
  728. if (nTopIndex == -1)
  729. {
  730. return 1;
  731. }
  732. while ((nTopIndex < m_cItems) && (nAmountShown < rc.bottom))
  733. {
  734. pUserData = (LPUSERDATA_STRUCT_LABEL)
  735. GetWindowLongPtr( GetDlgItem(hWnd, MAKE_LABEL_ID((int)nTopIndex)),
  736. GWLP_USERDATA);
  737. nAmountShown += (m_nDefaultVerticalSpace + pUserData->nLabelHeight - nAmountObscured);
  738. nAmountObscured = 0; // nAmountObscured only matters for the first iteration where
  739. // the real top index's amount shown is being calculated
  740. nCount++;
  741. nTopIndex++;
  742. }
  743. //
  744. // since that last one may be obscured see if we need to adjust nCount
  745. //
  746. if (nAmountShown > rc.bottom)
  747. {
  748. nCount--;
  749. }
  750. return max(1, nCount);
  751. }
  752. LONG
  753. CCheckList::GetTopIndex(HWND hWnd, LONG *pnAmountObscured)
  754. {
  755. LONG nIndex = 0;
  756. LPUSERDATA_STRUCT_LABEL pUserData;
  757. SCROLLINFO si;
  758. si.cbSize = sizeof(si);
  759. si.fMask = SIF_POS;
  760. //
  761. // initialize
  762. //
  763. if (pnAmountObscured != NULL)
  764. {
  765. *pnAmountObscured = 0;
  766. }
  767. if (GetScrollInfo(hWnd, SB_VERT, &si) && m_rcItemLabel.bottom > 0)
  768. {
  769. pUserData = (LPUSERDATA_STRUCT_LABEL)
  770. GetWindowLongPtr( GetDlgItem(hWnd, MAKE_LABEL_ID((int)nIndex)),
  771. GWLP_USERDATA);
  772. //
  773. // if there are no items get out
  774. //
  775. if (pUserData == NULL)
  776. {
  777. return -1;
  778. }
  779. while (si.nPos >= (m_nDefaultVerticalSpace + pUserData->nLabelHeight))
  780. {
  781. si.nPos -= (m_nDefaultVerticalSpace + pUserData->nLabelHeight);
  782. nIndex++;
  783. pUserData = (LPUSERDATA_STRUCT_LABEL)
  784. GetWindowLongPtr( GetDlgItem(hWnd, MAKE_LABEL_ID((int)nIndex)),
  785. GWLP_USERDATA);
  786. }
  787. if (pnAmountObscured != NULL)
  788. {
  789. *pnAmountObscured = si.nPos;
  790. }
  791. }
  792. return nIndex;
  793. }
  794. void
  795. CCheckList::SetTopIndex(HWND hWnd, LONG nIndex)
  796. {
  797. int i;
  798. int nPos = 0;
  799. LPUSERDATA_STRUCT_LABEL pUserData;
  800. for (i=0; i<nIndex; i++)
  801. {
  802. pUserData = (LPUSERDATA_STRUCT_LABEL)
  803. GetWindowLongPtr( GetDlgItem(hWnd, MAKE_LABEL_ID((int)i)),
  804. GWLP_USERDATA);
  805. nPos += (m_nDefaultVerticalSpace + pUserData->nLabelHeight);
  806. }
  807. m_cWheelDelta = 0;
  808. MsgVScroll(hWnd, SB_THUMBPOSITION, nPos);
  809. }
  810. void
  811. CCheckList::EnsureVisible(HWND hWnd, LONG nItemIndex)
  812. {
  813. LONG nAmountObscured = 0;
  814. LONG nTopIndex;
  815. RECT rc;
  816. LPUSERDATA_STRUCT_LABEL pUserData;
  817. nTopIndex = GetTopIndex(hWnd, &nAmountObscured);
  818. if (nTopIndex == -1)
  819. {
  820. return;
  821. }
  822. // Note that the top item may only be partially visible,
  823. // so we need to test for equality here. Raid #208449
  824. if (nItemIndex < nTopIndex)
  825. {
  826. SetTopIndex(hWnd, nItemIndex);
  827. }
  828. else if (nItemIndex == nTopIndex)
  829. {
  830. if (nAmountObscured != 0)
  831. {
  832. SetTopIndex(hWnd, nItemIndex);
  833. }
  834. }
  835. else
  836. {
  837. LONG nVisible = GetVisibleCount(hWnd);
  838. if (nItemIndex >= nTopIndex + nVisible)
  839. {
  840. if (!GetClientRect(hWnd, &rc))
  841. {
  842. //
  843. // This is just best effort
  844. //
  845. SetTopIndex(hWnd, nItemIndex - nVisible + 1);
  846. }
  847. else
  848. {
  849. //
  850. // Calculate what the top index should be to allow
  851. // nItemIndex to be fully visible
  852. //
  853. nTopIndex = nItemIndex + 1;
  854. do
  855. {
  856. nTopIndex--;
  857. pUserData = (LPUSERDATA_STRUCT_LABEL)
  858. GetWindowLongPtr( GetDlgItem(hWnd, MAKE_LABEL_ID((int)nTopIndex)),
  859. GWLP_USERDATA);
  860. if (pUserData != NULL)
  861. {
  862. rc.bottom -= (pUserData->nLabelHeight + m_nDefaultVerticalSpace);
  863. if (rc.bottom < 0)
  864. {
  865. nTopIndex++;
  866. }
  867. }
  868. else
  869. {
  870. //
  871. // Should not hit this, just added to make things safe
  872. //
  873. rc.bottom = 0;
  874. nTopIndex = 0;
  875. }
  876. } while (rc.bottom > 0);
  877. SetTopIndex(hWnd, nTopIndex);
  878. }
  879. }
  880. }
  881. }
  882. void
  883. CCheckList::DrawCheckFocusRect(HWND hWnd, HWND hwndCheck, BOOL fDraw)
  884. {
  885. RECT rcCheck;
  886. TraceEnter(TRACE_CHECKLIST, "CCheckList::DrawCheckFocusRect");
  887. TraceAssert(hWnd != NULL);
  888. TraceAssert(hwndCheck != NULL);
  889. GetWindowRect(hwndCheck, &rcCheck);
  890. MapWindowPoints(NULL, hWnd, (LPPOINT)&rcCheck, 2);
  891. InflateRect(&rcCheck, 2, 2); // draw *outside* the checkbox
  892. HDC hdc = GetDC(hWnd);
  893. if (hdc)
  894. {
  895. // Always erase before drawing, since we may already be
  896. // partially visible and drawing is an XOR operation.
  897. // (Don't want to leave any turds on the screen.)
  898. FrameRect(hdc, &rcCheck, GetSysColorBrush(BK_COLOR));
  899. if (fDraw)
  900. {
  901. SetTextColor(hdc, GetSysColor(TEXT_COLOR));
  902. SetBkColor(hdc, GetSysColor(BK_COLOR));
  903. DrawFocusRect(hdc, &rcCheck);
  904. }
  905. ReleaseDC(hWnd, hdc);
  906. }
  907. TraceLeaveVoid();
  908. }
  909. LRESULT
  910. CALLBACK
  911. CCheckList::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  912. {
  913. LRESULT lResult = 0;
  914. LPUSERDATA_STRUCT_LABEL pUserData = NULL;
  915. CCheckList *pThis = (CCheckList*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
  916. TraceEnter(TRACE_CHECKLIST, "CCheckList::WindowProc");
  917. TraceAssert(hWnd != NULL);
  918. switch (uMsg)
  919. {
  920. case WM_NCCREATE:
  921. pThis = new CCheckList(hWnd, (LPCREATESTRUCT)lParam);
  922. if (pThis != NULL)
  923. {
  924. SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pThis);
  925. lResult = TRUE;
  926. }
  927. break;
  928. case WM_DESTROY:
  929. pThis->ResetContent(hWnd);
  930. break;
  931. case WM_NCDESTROY:
  932. delete pThis;
  933. break;
  934. case WM_COMMAND:
  935. TraceAssert(pThis != NULL);
  936. lResult = pThis->MsgCommand(hWnd,
  937. GET_WM_COMMAND_ID(wParam, lParam),
  938. GET_WM_COMMAND_CMD(wParam, lParam),
  939. GET_WM_COMMAND_HWND(wParam, lParam));
  940. break;
  941. case WM_CTLCOLORSTATIC:
  942. TraceAssert(pThis != NULL);
  943. SetBkMode((HDC)wParam, TRANSPARENT);
  944. SetTextColor((HDC)wParam, GetSysColor(TEXT_COLOR));
  945. SetBkColor((HDC)wParam, GetSysColor(BK_COLOR));
  946. lResult = (LRESULT)GetSysColorBrush(BK_COLOR);
  947. break;
  948. case WM_PAINT:
  949. TraceAssert(pThis != NULL);
  950. pThis->MsgPaint(hWnd, (HDC)wParam);
  951. break;
  952. case WM_VSCROLL:
  953. TraceAssert(pThis != NULL);
  954. pThis->MsgVScroll(hWnd,
  955. (int)(short)GET_WM_VSCROLL_CODE(wParam, lParam),
  956. (int)(short)GET_WM_VSCROLL_POS(wParam, lParam));
  957. break;
  958. case WM_MOUSEWHEEL:
  959. TraceAssert(pThis != NULL);
  960. pThis->MsgMouseWheel(hWnd,
  961. LOWORD(wParam),
  962. (int)(short)HIWORD(wParam));
  963. break;
  964. case WM_LBUTTONDOWN:
  965. TraceAssert(pThis != NULL);
  966. pThis->MsgButtonDown(hWnd,
  967. wParam,
  968. (int)(short)LOWORD(lParam),
  969. (int)(short)HIWORD(lParam));
  970. break;
  971. case WM_ENABLE:
  972. TraceAssert(pThis != NULL);
  973. pThis->MsgEnable(hWnd, (BOOL)wParam);
  974. break;
  975. case WM_SETFONT:
  976. TraceAssert(pThis != NULL);
  977. {
  978. for (LONG i = 0; i < pThis->m_cItems; i++)
  979. SendDlgItemMessage(hWnd,
  980. MAKE_LABEL_ID(i),
  981. WM_SETFONT,
  982. wParam,
  983. lParam);
  984. }
  985. break;
  986. case WM_SIZE:
  987. TraceAssert(pThis != NULL);
  988. pThis->MsgSize(hWnd, (DWORD)wParam, LOWORD(lParam), HIWORD(lParam));
  989. break;
  990. case CLM_ADDITEM:
  991. TraceAssert(pThis != NULL);
  992. lResult = pThis->AddItem(hWnd, (LPCTSTR)wParam, lParam);
  993. break;
  994. case CLM_GETITEMCOUNT:
  995. TraceAssert(pThis != NULL);
  996. lResult = pThis->m_cItems;
  997. break;
  998. case CLM_SETSTATE:
  999. TraceAssert(pThis != NULL);
  1000. pThis->SetState(hWnd, LOWORD(wParam), HIWORD(wParam), (LONG)lParam);
  1001. break;
  1002. case CLM_GETSTATE:
  1003. TraceAssert(pThis != NULL);
  1004. lResult = pThis->GetState(hWnd, LOWORD(wParam), HIWORD(wParam));
  1005. break;
  1006. case CLM_SETCOLUMNWIDTH:
  1007. TraceAssert(pThis != NULL);
  1008. {
  1009. RECT rc;
  1010. LONG cxDialog;
  1011. GetClientRect(hWnd, &rc);
  1012. cxDialog = rc.right;
  1013. rc.right = (LONG)lParam;
  1014. MapDialogRect(GetParent(hWnd), &rc);
  1015. pThis->SetColumnWidth(hWnd, cxDialog, rc.right);
  1016. }
  1017. break;
  1018. case CLM_SETITEMDATA:
  1019. TraceAssert(GET_ITEM(wParam) < (ULONG)pThis->m_cItems);
  1020. pUserData = (LPUSERDATA_STRUCT_LABEL)
  1021. GetWindowLongPtr( GetDlgItem(hWnd, MAKE_LABEL_ID((int)wParam)),
  1022. GWLP_USERDATA);
  1023. if (pUserData != NULL)
  1024. pUserData->lParam = lParam;
  1025. break;
  1026. case CLM_GETITEMDATA:
  1027. TraceAssert(GET_ITEM(wParam) < (ULONG)pThis->m_cItems);
  1028. pUserData = (LPUSERDATA_STRUCT_LABEL)
  1029. GetWindowLongPtr( GetDlgItem(hWnd, MAKE_LABEL_ID((int)wParam)),
  1030. GWLP_USERDATA);
  1031. if (pUserData != NULL)
  1032. lResult = pUserData->lParam;
  1033. break;
  1034. case CLM_RESETCONTENT:
  1035. TraceAssert(pThis != NULL);
  1036. pThis->ResetContent(hWnd);
  1037. break;
  1038. case CLM_GETVISIBLECOUNT:
  1039. TraceAssert(pThis != NULL);
  1040. lResult = pThis->GetVisibleCount(hWnd);
  1041. break;
  1042. case CLM_GETTOPINDEX:
  1043. TraceAssert(pThis != NULL);
  1044. lResult = pThis->GetTopIndex(hWnd);
  1045. break;
  1046. case CLM_SETTOPINDEX:
  1047. TraceAssert(pThis != NULL);
  1048. pThis->SetTopIndex(hWnd, (LONG)wParam);
  1049. break;
  1050. case CLM_ENSUREVISIBLE:
  1051. TraceAssert(pThis != NULL);
  1052. pThis->EnsureVisible(hWnd, (LONG)wParam);
  1053. break;
  1054. //
  1055. // Always refer to the chklist window for help. Don't pass
  1056. // one of the child window handles here.
  1057. //
  1058. case WM_HELP:
  1059. ((LPHELPINFO)lParam)->hItemHandle = hWnd;
  1060. lResult = SendMessage(GetParent(hWnd), uMsg, wParam, lParam);
  1061. break;
  1062. case WM_CONTEXTMENU:
  1063. lResult = SendMessage(GetParent(hWnd), uMsg, (WPARAM)hWnd, lParam);
  1064. break;
  1065. case WM_SETCURSOR:
  1066. if (LOWORD(lParam) == HTCLIENT)
  1067. {
  1068. SetCursor(LoadCursor(NULL, IDC_ARROW));
  1069. lResult = TRUE;
  1070. break;
  1071. }
  1072. // Fall Through
  1073. default:
  1074. lResult = DefWindowProc(hWnd, uMsg, wParam, lParam);
  1075. }
  1076. TraceLeaveValue(lResult);
  1077. }