text: //+-------------------------------------------------------------------------
  //
  //  Microsoft Windows
  //
  //  Copyright (C) Microsoft Corporation, 1996 - 1999
  //
  //  File:       chklist.cpp
  //
  //  This file contains the implementation of the CheckList control.
  //
  //--------------------------------------------------------------------------
  
  #include "aclpriv.h"
  
  //
  // Text and Background colors
  //
  #define TEXT_COLOR  COLOR_WINDOWTEXT
  #define BK_COLOR    COLOR_WINDOW
  
  //
  // Default dimensions for child controls. All are in dialog units.
  // Currently only the column width is user-adjustable (via the
  // CLM_SETCOLUMNWIDTH message).
  //
  #define DEFAULT_COLUMN_WIDTH    40
  #define DEFAULT_CHECK_WIDTH     9
  #define DEFAULT_HORZ_SPACE      7
  #define DEFAULT_VERTICAL_SPACE  3
  #define DEFAULT_ITEM_HEIGHT     8
  
  
  //
  // 16 bits are used for the control ID's, divided into n bits for
  // the subitem (least significant) and 16-n bits for the item index.
  //
  // ID_SUBITEM_BITS can be adjusted to control the maximum number of
  // items and subitems. For example, to allow up to 7 subitems and 8k
  // items, set ID_SUBITEM_BITS to 3.
  //
  
  // Use the low 2 bits for the subitem index, the rest for the item index.
  // (4 subitems max, 16k items max)
  #define ID_SUBITEM_BITS         2
  
  #define ID_SUBITEM_MASK         ((1 << ID_SUBITEM_BITS) - 1)
  #define GET_ITEM(id)            ((id) >> ID_SUBITEM_BITS)
  #define GET_SUBITEM(id)         ((id) & ID_SUBITEM_MASK)
  
  #define MAKE_CTRL_ID(i, s)      (0xffff & (((i) << ID_SUBITEM_BITS) | ((s) & ID_SUBITEM_MASK)))
  #define MAKE_LABEL_ID(i)        MAKE_CTRL_ID(i, 0)
  // Note that the subitem (column) index is one-based for the checkboxes
  // (the zero column is the label).  The item (row) index is zero-based.
  
  #define MAX_CHECK_COLUMNS       ID_SUBITEM_MASK
  
  
  TCHAR const c_szStaticClass[]   = TEXT("STATIC");
  TCHAR const c_szButtonClass[]   = TEXT("BUTTON");
  
  
  class CCheckList
  {
  private:
      LONG m_cItems;
      LONG m_cSubItems;
      RECT m_rcItemLabel;
      LONG m_nCheckPos[MAX_CHECK_COLUMNS];
      LONG m_cxCheckBox;
      LONG m_cxCheckColumn;
      HWND m_hwndCheckFocus;
      LPTSTR m_pszColumnDesc[MAX_CHECK_COLUMNS];
  
      int m_cWheelDelta;
      static UINT g_ucScrollLines;
  
  private:
      CCheckList(HWND hWnd, LPCREATESTRUCT lpcs);
      ~CCheckList(void);
  
      LRESULT MsgCommand(HWND hWnd, WORD idCmd, WORD wNotify, HWND hwndCtrl);
      void MsgPaint(HWND hWnd, HDC hdc);
      void MsgVScroll(HWND hWnd, int nCode, int nPos);
      void MsgMouseWheel(HWND hWnd, WORD fwFlags, int zDelta);
      void MsgButtonDown(HWND hWnd, WPARAM fwFlags, int xPos, int yPos);
      void MsgEnable(HWND hWnd, BOOL fEnabled);
      void MsgSize(HWND hWnd, DWORD dwSizeType, LONG nWidth, LONG nHeight);
  
      LONG AddItem(HWND hWnd, LPCTSTR pszLabel, LPARAM lParam);
      void SetState(HWND hWnd, WORD iItem, WORD iSubItem, LONG lState);
      LONG GetState(HWND hWnd, WORD iItem, WORD iSubItem);
      void SetColumnWidth(HWND hWnd, LONG cxDialog, LONG cxColumn);
      void ResetContent(HWND hWnd);
      LONG GetVisibleCount(HWND hWnd);
      LONG GetTopIndex(HWND hWnd);
      void SetTopIndex(HWND hWnd, LONG nIndex)
      { m_cWheelDelta = 0; MsgVScroll(hWnd, SB_THUMBPOSITION, nIndex * m_rcItemLabel.bottom); }
      void EnsureVisible(HWND hWnd, LONG nIndex);
      void DrawCheckFocusRect(HWND hWnd, HWND hwndCheck, BOOL fDraw);
      void GetColumnDescriptions(HWND hWnd);
  
  public:
      static LRESULT CALLBACK WindowProc(HWND hWnd,
                                         UINT uMsg,
                                         WPARAM wParam,
                                         LPARAM lParam);
  };
  
  
  BOOL RegisterCheckListWndClass(void)
  {
      WNDCLASS wc;
  
      wc.style            = 0;
      wc.lpfnWndProc      = CCheckList::WindowProc;
      wc.cbClsExtra       = 0;
      wc.cbWndExtra       = 0;
      wc.hInstance        = hModule;
      wc.hIcon            = NULL;
      wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground    = (HBRUSH)(BK_COLOR+1);
      wc.lpszMenuName     = NULL;
      wc.lpszClassName    = TEXT(WC_CHECKLIST);
  
      return (BOOL)RegisterClass(&wc);
  }
  
  
  UINT CCheckList::g_ucScrollLines = (UINT)-1;
  
  
  CCheckList::CCheckList(HWND hWnd, LPCREATESTRUCT lpcs)
  : m_cItems(0), m_hwndCheckFocus(NULL), m_cWheelDelta(0)
  {
      TraceEnter(TRACE_CHECKLIST, "CCheckList::CCheckList");
      TraceAssert(hWnd != NULL);
      TraceAssert(lpcs != NULL);
  
      //
      // Get number of check columns
      //
      m_cSubItems = lpcs->style & CLS_CHECKMASK;
  
      //
      // Convert default coordinates from dialog units to pixels
      //
      RECT rc;
      rc.left = DEFAULT_CHECK_WIDTH;
      rc.right = DEFAULT_COLUMN_WIDTH;
      rc.top = rc.bottom = 0;
      MapDialogRect(lpcs->hwndParent, &rc);
  
      // Save the converted values
      m_cxCheckBox = rc.left;
      m_cxCheckColumn = rc.right;
  
      rc.left = DEFAULT_HORZ_SPACE;
      rc.top = DEFAULT_VERTICAL_SPACE;
      rc.right = 10;              // bogus (unused)
      rc.bottom = DEFAULT_VERTICAL_SPACE + DEFAULT_ITEM_HEIGHT;
      MapDialogRect(lpcs->hwndParent, &rc);
  
      // Save the converted values
      m_rcItemLabel = rc;
  
      //
      // Get info for mouse wheel scrolling
      //
      if ((UINT)-1 == g_ucScrollLines)
      {
          g_ucScrollLines = 3; // default
          SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &g_ucScrollLines, 0);
      }
  
      TraceLeaveVoid();
  }
  
  CCheckList::~CCheckList(void)
  {
      for (LONG j = 0; j < m_cSubItems; j++)
      {
          LocalFreeString(&m_pszColumnDesc[j]);
      }
  }
  
  LRESULT
  CCheckList::MsgCommand(HWND hWnd, WORD idCmd, WORD wNotify, HWND hwndCtrl)
  {
      TraceEnter(TRACE_CHECKLIST, "CCheckList::MsgCommand");
  
      // Should only get notifications from visible, enabled, check boxes
      TraceAssert(GET_ITEM(idCmd) < m_cItems);
      TraceAssert(0 < GET_SUBITEM(idCmd) && GET_SUBITEM(idCmd) <= m_cSubItems);
      TraceAssert(hwndCtrl && IsWindowEnabled(hwndCtrl));
  
      switch (wNotify)
      {
      case BN_CLICKED:
          {
              NM_CHECKLIST nmc;
              nmc.hdr.hwndFrom = hWnd;
              nmc.hdr.idFrom = GetDlgCtrlID(hWnd);
              nmc.hdr.code = CLN_CLICK;
              nmc.iItem = GET_ITEM(idCmd);
              nmc.iSubItem = GET_SUBITEM(idCmd);
              nmc.dwState = (DWORD)SendMessage(hwndCtrl, BM_GETCHECK, 0, 0);
              if (!IsWindowEnabled(hwndCtrl))
                  nmc.dwState |= CLST_DISABLED;
              nmc.dwItemData = GetWindowLongPtr(GetDlgItem(hWnd, MAKE_LABEL_ID(nmc.iItem)),
                                                GWLP_USERDATA);
              nmc.cchTextMax = 0;
              nmc.pszText = NULL;
  
              SendMessage(GetParent(hWnd),
                          WM_NOTIFY,
                          nmc.hdr.idFrom,
                          (LPARAM)&nmc);
          }
          break;
  
      case BN_SETFOCUS:
          if (m_hwndCheckFocus != hwndCtrl)   // Has the focus moved?
          {
              // Remember where the focus is
              m_hwndCheckFocus = hwndCtrl;
  
              // Make sure the row is scrolled into view
              EnsureVisible(hWnd, GET_ITEM(idCmd));
          }
          // Always draw the focus rect
          DrawCheckFocusRect(hWnd, hwndCtrl, TRUE);
          break;
  
      case BN_KILLFOCUS:
          // Remove the focus rect
          m_hwndCheckFocus = NULL;
          DrawCheckFocusRect(hWnd, hwndCtrl, FALSE);
          break;
      }
  
      TraceLeaveValue(0);
  }
  
  
  void
  CCheckList::MsgPaint(HWND hWnd, HDC hdc)
  {
      if (hdc == NULL && m_hwndCheckFocus != NULL)
      {
          // This will cause a focus rect to be drawn after the window and
          // all checkboxes have been painted.
          PostMessage(hWnd,
                      WM_COMMAND,
                      GET_WM_COMMAND_MPS(GetDlgCtrlID(m_hwndCheckFocus), m_hwndCheckFocus, BN_SETFOCUS));
      }
  
      // Default paint
      DefWindowProc(hWnd, WM_PAINT, (WPARAM)hdc, 0);
  }
  
  
  void
  CCheckList::MsgVScroll(HWND hWnd, int nCode, int nPos)
  {
      UINT cScrollUnitsPerLine;
      SCROLLINFO si;
      si.cbSize = sizeof(si);
      si.fMask = SIF_ALL;
  
      if (!GetScrollInfo(hWnd, SB_VERT, &si))
          return;
  
      cScrollUnitsPerLine = m_rcItemLabel.bottom;
  
      // One page is always visible, so adjust the range to a more useful value
      si.nMax -= si.nPage - 1;
  
      switch (nCode)
      {
      case SB_LINEUP:
          // "line" is the height of one item (includes the space in between)
          nPos = si.nPos - cScrollUnitsPerLine;
          break;
  
      case SB_LINEDOWN:
          nPos = si.nPos + cScrollUnitsPerLine;
          break;
  
      case SB_PAGEUP:
          nPos = si.nPos - si.nPage;
          break;
  
      case SB_PAGEDOWN:
          nPos = si.nPos + si.nPage;
          break;
  
      case SB_TOP:
          nPos = si.nMin;
          break;
  
      case SB_BOTTOM:
          nPos = si.nMax;
          break;
  
      case SB_ENDSCROLL:
          nPos = si.nPos;     // don't go anywhere
          break;
  
      case SB_THUMBTRACK:
          // Do nothing here to allow tracking
          // nPos = si.nPos;    // Do this to prevent tracking
      case SB_THUMBPOSITION:
          // nothing to do here... nPos is passed in
          break;
      }
  
      // Make sure the new position is within the range
      if (nPos < si.nMin)
          nPos = si.nMin;
      else if (nPos > si.nMax)
          nPos = si.nMax;
  
      if (nPos != si.nPos)  // are we moving?
      {
          SetScrollPos(hWnd, SB_VERT, nPos, TRUE);
          ScrollWindow(hWnd, 0, si.nPos - nPos, NULL, NULL);
      }
  }
  
  
  void
  CCheckList::MsgMouseWheel(HWND hWnd, WORD fwFlags, int iWheelDelta)
  {
      int cDetants;
  
      if ((fwFlags & (MK_SHIFT | MK_CONTROL)) || 0 == g_ucScrollLines)
          return;
  
      TraceEnter(TRACE_CHECKLIST, "CCheckList::MsgMouseWheel");
  
      // Update count of scroll amount
      m_cWheelDelta -= iWheelDelta;
      cDetants = m_cWheelDelta / WHEEL_DELTA;
      if (0 == cDetants)
          TraceLeaveVoid();
      m_cWheelDelta %= WHEEL_DELTA;
  
      if (WS_VSCROLL & GetWindowLong(hWnd, GWL_STYLE))
      {
          SCROLLINFO  si;
          UINT        cScrollUnitsPerLine;
          UINT        cLinesPerPage;
          UINT        cLinesPerDetant;
  
          // Get the scroll amount of one line
          cScrollUnitsPerLine = m_rcItemLabel.bottom;
          TraceAssert(cScrollUnitsPerLine > 0);
  
          si.cbSize = sizeof(SCROLLINFO);
          si.fMask = SIF_PAGE | SIF_POS;
          if (!GetScrollInfo(hWnd, SB_VERT, &si))
              TraceLeaveVoid();
  
          // The size of a page is at least one line, and
          // leaves one line of overlap
          cLinesPerPage = (si.nPage - cScrollUnitsPerLine) / cScrollUnitsPerLine;
          cLinesPerPage = max(1, cLinesPerPage);
  
          // Don't scroll more than one page per detant
          cLinesPerDetant = min(cLinesPerPage, g_ucScrollLines);
  
          si.nPos += cDetants * cLinesPerDetant * cScrollUnitsPerLine;
  
          MsgVScroll(hWnd, SB_THUMBTRACK, si.nPos);
      }
      TraceLeaveVoid();
  }
  
  
  void
  CCheckList::MsgButtonDown(HWND hWnd, WPARAM /*fwFlags*/, int /*xPos*/, int yPos)
  {
      LONG nItemIndex;
      HWND hwndCheck;
      RECT rc;
  
      // Get position of the top visible item in client coords
      nItemIndex = GetTopIndex(hWnd);
      hwndCheck = GetDlgItem(hWnd, MAKE_CTRL_ID(nItemIndex, 0));
      GetWindowRect(hwndCheck, &rc);
      MapWindowPoints(NULL, hWnd, (LPPOINT)&rc, 2);
  
      // Find nearest item
      nItemIndex += (yPos - rc.top + m_rcItemLabel.top/2)/m_rcItemLabel.bottom;
      nItemIndex = max(0, min(nItemIndex, m_cItems - 1)); // 0 <= y < m_cItems
  
      // Set focus to first subitem that is enabled
      for (LONG j = 1; j <= m_cSubItems; j++)
      {
          hwndCheck = GetDlgItem(hWnd, MAKE_CTRL_ID(nItemIndex, j));
          if (IsWindowEnabled(hwndCheck))
          {
              SetFocus(hwndCheck);
              break;
          }
      }
  }
  
  
  void
  CCheckList::MsgEnable(HWND hWnd, BOOL fEnabled)
  {
      static BOOL bInMsgEnable = FALSE;
  
      if (!bInMsgEnable)
      {
          bInMsgEnable = TRUE;
          for (LONG i = 0; i < m_cItems; i++)
          {
              for (LONG j = 1; j <= m_cSubItems; j++)
              {
                  EnableWindow(GetDlgItem(hWnd, MAKE_CTRL_ID(i, j)), fEnabled);
              }
          }
          if (!fEnabled)
              EnableWindow(hWnd, TRUE);
          bInMsgEnable = FALSE;
      }
  }
  
  
  void
  CCheckList::MsgSize(HWND hWnd, DWORD dwSizeType, LONG nWidth, LONG nHeight)
  {
      TraceEnter(TRACE_CHECKLIST, "CCheckList::MsgSize");
      TraceAssert(hWnd != NULL);
  
      if (dwSizeType == SIZE_RESTORED)
      {
          RECT rc;
          SCROLLINFO si;
  
          si.cbSize = sizeof(si);
          si.fMask = SIF_RANGE | SIF_PAGE;
          si.nMin = 0;
          si.nMax = m_cItems * m_rcItemLabel.bottom + m_rcItemLabel.top - 1;
          si.nPage = nHeight;                         // ^^^^^^^^^ extra space
  
          SetScrollInfo(hWnd, SB_VERT, &si, FALSE);
  
          // Don't trust the width value passed in, since SetScrollInfo may
          // affect it if the scroll bar is turning on or off.
          GetClientRect(hWnd, &rc);
          nWidth = rc.right;
  
          // If the scrollbar is turned on, artificially bump up the width
          // by the width of the scrollbar, so the boxes don't jump to the left
          // when we have a scrollbar.
          if ((UINT)si.nMax >= si.nPage)
              nWidth += GetSystemMetrics(SM_CYHSCROLL);
  
          SetColumnWidth(hWnd, nWidth, m_cxCheckColumn);
      }
  
      TraceLeaveVoid();
  }
  
  
  LONG
  CCheckList::AddItem(HWND hWnd, LPCTSTR pszLabel, LPARAM lParam)
  {
      HWND hwndNew;
      HWND hwndPrev;
      RECT rc;
      LONG cyOffset;
  
      TraceEnter(TRACE_CHECKLIST, "CCheckList::AddItem");
      TraceAssert(hWnd != NULL);
      TraceAssert(pszLabel != NULL && !IsBadStringPtr(pszLabel, MAX_PATH));
  
      // If this is the first item, get column descriptions
      if (0 == m_cItems)
          GetColumnDescriptions(hWnd);
  
      // Calculate the position of the new static label
      rc = m_rcItemLabel;
      cyOffset = m_cItems * m_rcItemLabel.bottom;
      OffsetRect(&rc, 0, cyOffset);
  
      // Create a new label control
      hwndNew = CreateWindowEx(WS_EX_NOPARENTNOTIFY,
                               c_szStaticClass,
                               pszLabel,
                               WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP | SS_NOPREFIX,// | WS_GROUP,
                               rc.left,
                               rc.top,
                               rc.right - rc.left,
                               rc.bottom - rc.top,
                               hWnd,
                               (HMENU)IntToPtr(MAKE_LABEL_ID(m_cItems)),
                               hModule,
                               NULL);
      if (!hwndNew)
          TraceLeaveValue(-1);
  
      // Save item data
      SetWindowLongPtr(hwndNew, GWLP_USERDATA, lParam);
  
      // Set the font
      SendMessage(hwndNew,
                  WM_SETFONT,
                  SendMessage(GetParent(hWnd), WM_GETFONT, 0, 0),
                  0);
  
      // Set Z-order position just after the last checkbox. This keeps
      // tab order correct.
      if (m_cItems > 0)
      {
          hwndPrev = GetDlgItem(hWnd, MAKE_CTRL_ID(m_cItems - 1, m_cSubItems));
          SetWindowPos(hwndNew,
                       hwndPrev,
                       0, 0, 0, 0,
                       SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
      }
  
      // Create new checkboxes
      for (LONG j = 0; j < m_cSubItems; j++)
      {
          // Build window text for the control. The text is
          // hidden, but used for accessibility. (341042)
          LPCTSTR pszCheckText = pszLabel;
          LPTSTR pszT = NULL;
          if (m_pszColumnDesc[j] &&
              FormatStringID(&pszT, hModule, IDS_FMT_CHECKLABEL, pszLabel, m_pszColumnDesc[j]))
          {
              pszCheckText = pszT;
          }
  
          hwndPrev = hwndNew;
          hwndNew = CreateWindowEx(WS_EX_NOPARENTNOTIFY,
                                   c_szButtonClass,
                                   pszCheckText,
                                   WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_NOTIFY | BS_FLAT | BS_AUTOCHECKBOX,
                                   m_nCheckPos[j],
                                   rc.top,
                                   m_cxCheckBox,
                                   rc.bottom - rc.top,
                                   hWnd,
                                   (HMENU)IntToPtr(MAKE_CTRL_ID(m_cItems, j + 1)),
                                   hModule,
                                   NULL);
  
          LocalFreeString(&pszT);
  
          if (!hwndNew)
          {
              while (j >= 0)
              {
                  DestroyWindow(GetDlgItem(hWnd, MAKE_CTRL_ID(m_cItems, j)));
                  j--;
              }
  
              TraceLeaveValue(-1);
          }
  
          SetWindowPos(hwndNew,
                       hwndPrev,
                       0, 0, 0, 0,
                       SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
      }
  
      // We now officially have a new item
      m_cItems++;
  
      //
      // The last thing is to calculate the scroll range
      //
      LONG nBottom = rc.bottom;
      GetClientRect(hWnd, &rc);
  
      SCROLLINFO si;
      si.cbSize = sizeof(si);
      si.fMask = SIF_RANGE | SIF_PAGE;
      si.nMin = 0;
      si.nMax = nBottom + m_rcItemLabel.top - 1;
      si.nPage = rc.bottom; // ^^^^^^^^^ extra space
  
      SetScrollInfo(hWnd, SB_VERT, &si, FALSE);
  
      TraceLeaveValue(m_cItems - 1);  // return the index of the new item
  }
  
  
  void
  CCheckList::SetState(HWND hWnd, WORD iItem, WORD iSubItem, LONG lState)
  {
      TraceEnter(TRACE_CHECKLIST, "CCheckList::SetState");
      TraceAssert(hWnd != NULL);
      TraceAssert(iItem < m_cItems);
      TraceAssert(0 < iSubItem && iSubItem <= m_cSubItems);
  
      HWND hwndCtrl = GetDlgItem(hWnd, MAKE_CTRL_ID(iItem, iSubItem));
      if (hwndCtrl != NULL)
      {
          SendMessage(hwndCtrl, BM_SETCHECK, lState & CLST_CHECKED, 0);
          EnableWindow(hwndCtrl, !(lState & CLST_DISABLED));
      }
  
      TraceLeaveVoid();
  }
  
  
  LONG
  CCheckList::GetState(HWND hWnd, WORD iItem, WORD iSubItem)
  {
      LONG lState = 0;
  
      TraceEnter(TRACE_CHECKLIST, "CCheckList::GetState");
      TraceAssert(hWnd != NULL);
      TraceAssert(iItem < m_cItems);
      TraceAssert(0 < iSubItem && iSubItem <= m_cSubItems);
  
      HWND hwndCtrl = GetDlgItem(hWnd, MAKE_CTRL_ID(iItem, iSubItem));
  
      if (hwndCtrl != NULL)
      {
          lState = (LONG)SendMessage(hwndCtrl, BM_GETCHECK, 0, 0);
          TraceAssert(!(lState & BST_INDETERMINATE));
  
          if (!IsWindowEnabled(hwndCtrl))
              lState |= CLST_DISABLED;
      }
  
      TraceLeaveValue(lState);
  }
  
  
  void
  CCheckList::SetColumnWidth(HWND hWnd, LONG cxDialog, LONG cxColumn)
  {
      LONG j;
  
      TraceEnter(TRACE_CHECKLIST, "CCheckList::SetColumnWidth");
      TraceAssert(hWnd != NULL);
      TraceAssert(cxColumn > 10);
  
      m_cxCheckColumn = cxColumn;
  
      if (m_cSubItems > 0)
      {
          m_nCheckPos[m_cSubItems-1] = cxDialog                       // dlg width
                                      - m_rcItemLabel.left            // right margin
                                      - (cxColumn + m_cxCheckBox)/2;  // 1/2 col & 1/2 checkbox
  
          for (j = m_cSubItems - 1; j > 0; j--)
              m_nCheckPos[j-1] = m_nCheckPos[j] - cxColumn;
  
          //              (leftmost check pos) - (horz margin)
          m_rcItemLabel.right = m_nCheckPos[0] - m_rcItemLabel.left;
      }
      else
          m_rcItemLabel.right = cxDialog - m_rcItemLabel.left;
  
      LONG nTop = m_rcItemLabel.top;
      LONG nBottom = m_rcItemLabel.bottom;
  
      for (LONG i = 0; i < m_cItems; i++)
      {
          MoveWindow(GetDlgItem(hWnd, MAKE_LABEL_ID(i)),
                     m_rcItemLabel.left,
                     nTop,
                     m_rcItemLabel.right - m_rcItemLabel.left,
                     nBottom - nTop,
                     FALSE);
  
          for (j = 0; j < m_cSubItems; j++)
          {
              MoveWindow(GetDlgItem(hWnd, MAKE_CTRL_ID(i, j + 1)),
                         m_nCheckPos[j],
                         nTop,
                         m_cxCheckBox,
                         nBottom - nTop,
                         FALSE);
          }
  
          nTop += m_rcItemLabel.bottom;
          nBottom += m_rcItemLabel.bottom;
      }
  
      TraceLeaveVoid();
  }
  
  
  void
  CCheckList::ResetContent(HWND hWnd)
  {
      for (LONG i = 0; i < m_cItems; i++)
          for (LONG j = 0; j <= m_cSubItems; j++)
              DestroyWindow(GetDlgItem(hWnd, MAKE_CTRL_ID(i, j)));
  
    // Hide the scroll bar
    ShowScrollBar(hWnd, SB_VERT, FALSE);
    m_cItems = 0;
  }
  
  
  LONG
  CCheckList::GetVisibleCount(HWND hWnd)
  {
      LONG nCount = 1;
      RECT rc;
  
      if (GetClientRect(hWnd, &rc) && m_rcItemLabel.bottom > 0)
          nCount = max(1, rc.bottom / m_rcItemLabel.bottom);
  
      return nCount;
  }
  
  
  LONG
  CCheckList::GetTopIndex(HWND hWnd)
  {
      LONG nIndex = 0;
      SCROLLINFO si;
      si.cbSize = sizeof(si);
      si.fMask = SIF_POS;
  
      if (GetScrollInfo(hWnd, SB_VERT, &si) && m_rcItemLabel.bottom > 0)
          nIndex = max(0, si.nPos / m_rcItemLabel.bottom);
  
      return nIndex;
  }
  
  
  void
  CCheckList::EnsureVisible(HWND hWnd, LONG nItemIndex)
  {
      LONG nTopIndex = GetTopIndex(hWnd);
  
      // Note that the top item may only be partially visible,
      // so we need to test for equality here.  Raid #208449
      if (nItemIndex <= nTopIndex)
      {
          SetTopIndex(hWnd, nItemIndex);
      }
      else
      {
          LONG nVisible = GetVisibleCount(hWnd);
  
          if (nItemIndex >= nTopIndex + nVisible)
              SetTopIndex(hWnd, nItemIndex - nVisible + 1);
      }
  }
  
  
  void
  CCheckList::DrawCheckFocusRect(HWND hWnd, HWND hwndCheck, BOOL fDraw)
  {
      RECT rcCheck;
      HDC hdc;
  
      TraceEnter(TRACE_CHECKLIST, "CCheckList::DrawCheckFocusRect");
      TraceAssert(hWnd != NULL);
      TraceAssert(hwndCheck != NULL);
  
      GetWindowRect(hwndCheck, &rcCheck);
      MapWindowPoints(NULL, hWnd, (LPPOINT)&rcCheck, 2);
      InflateRect(&rcCheck, 2, 2);    // draw *outside* the checkbox
  
      hdc = GetDC(hWnd);
      if (hdc)
      {
          // Always erase before drawing, since we may already be
          // partially visible and drawing is an XOR operation.
          // (Don't want to leave any turds on the screen.)
  
          FrameRect(hdc, &rcCheck, GetSysColorBrush(BK_COLOR));
  
          if (fDraw)
          {
              SetTextColor(hdc, GetSysColor(TEXT_COLOR));
              SetBkColor(hdc, GetSysColor(BK_COLOR));
              DrawFocusRect(hdc, &rcCheck);
          }
  
          ReleaseDC(hWnd, hdc);
      }
  
      TraceLeaveVoid();
  }
  
  
  void
  CCheckList::GetColumnDescriptions(HWND hWnd)
  {
      //
      // Get column descriptions for accessibility
      //
      TCHAR szDescription[MAX_PATH];
      NM_CHECKLIST nmc;
      nmc.hdr.hwndFrom = hWnd;
      nmc.hdr.idFrom = GetDlgCtrlID(hWnd);
      nmc.hdr.code = CLN_GETCOLUMNDESC;
      nmc.iItem = 0;
      nmc.dwState = 0;
      nmc.dwItemData = 0;
      nmc.cchTextMax = ARRAYSIZE(szDescription);
      nmc.pszText = szDescription;
  
      for (LONG j = 0; j < m_cSubItems; j++)
      {
          szDescription[0] = TEXT('\0');
          nmc.iSubItem = j+1;
  
          SendMessage(GetParent(hWnd),
                      WM_NOTIFY,
                      nmc.hdr.idFrom,
                      (LPARAM)&nmc);
  
          LocalFreeString(&m_pszColumnDesc[j]);
          if (szDescription[0])
              LocalAllocString(&m_pszColumnDesc[j], szDescription);
      }
  }
  
  
  LRESULT
  CALLBACK
  CCheckList::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  {
      LRESULT lResult = 0;
      CCheckList *pThis = (CCheckList*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
  
      TraceEnter(TRACE_CHECKLIST, "CCheckList::WindowProc");
      TraceAssert(hWnd != NULL);
  
      switch (uMsg)
      {
      case WM_NCCREATE:
          pThis = new CCheckList(hWnd, (LPCREATESTRUCT)lParam);
          if (pThis != NULL)
          {
              SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pThis);
              lResult = TRUE;
          }
          break;
  
      case WM_NCDESTROY:
          delete pThis;
          break;
  
      case WM_COMMAND:
          TraceAssert(pThis != NULL);
          lResult = pThis->MsgCommand(hWnd,
                                      GET_WM_COMMAND_ID(wParam, lParam),
                                      GET_WM_COMMAND_CMD(wParam, lParam),
                                      GET_WM_COMMAND_HWND(wParam, lParam));
          break;
  
      case WM_CTLCOLORSTATIC:
          TraceAssert(pThis != NULL);
          SetBkMode((HDC)wParam, TRANSPARENT);
          SetTextColor((HDC)wParam, GetSysColor(TEXT_COLOR));
          SetBkColor((HDC)wParam, GetSysColor(BK_COLOR));
          lResult = (LRESULT)GetSysColorBrush(BK_COLOR);
          break;
  
      case WM_PAINT:
          TraceAssert(pThis != NULL);
          pThis->MsgPaint(hWnd, (HDC)wParam);
          break;
  
      case WM_VSCROLL:
          TraceAssert(pThis != NULL);
          pThis->MsgVScroll(hWnd,
                            (int)(short)GET_WM_VSCROLL_CODE(wParam, lParam),
                            (int)(short)GET_WM_VSCROLL_POS(wParam, lParam));
          break;
  
      case WM_MOUSEWHEEL:
          TraceAssert(pThis != NULL);
          pThis->MsgMouseWheel(hWnd,
                               LOWORD(wParam),
                               (int)(short)HIWORD(wParam));
          break;
  
      case WM_LBUTTONDOWN:
          TraceAssert(pThis != NULL);
          pThis->MsgButtonDown(hWnd,
                               wParam,
                               (int)(short)LOWORD(lParam),
                               (int)(short)HIWORD(lParam));
          break;
  
      case WM_ENABLE:
          TraceAssert(pThis != NULL);
          pThis->MsgEnable(hWnd, (BOOL)wParam);
          break;
  
      case WM_SETFONT:
          TraceAssert(pThis != NULL);
          {
              for (LONG i = 0; i < pThis->m_cItems; i++)
                  SendDlgItemMessage(hWnd,
                                     MAKE_LABEL_ID(i),
                                     WM_SETFONT,
                                     wParam,
                                     lParam);
          }
          break;
  
      case WM_SIZE:
          TraceAssert(pThis != NULL);
          pThis->MsgSize(hWnd, (DWORD)wParam, LOWORD(lParam), HIWORD(lParam));
          break;
  
      case CLM_ADDITEM:
          TraceAssert(pThis != NULL);
          lResult = pThis->AddItem(hWnd, (LPCTSTR)wParam, lParam);
          break;
  
      case CLM_GETITEMCOUNT:
          TraceAssert(pThis != NULL);
          lResult = pThis->m_cItems;
          break;
  
      case CLM_SETSTATE:
          TraceAssert(pThis != NULL);
          pThis->SetState(hWnd, LOWORD(wParam), HIWORD(wParam), (LONG)lParam);
          break;
  
      case CLM_GETSTATE:
          TraceAssert(pThis != NULL);
          lResult = pThis->GetState(hWnd, LOWORD(wParam), HIWORD(wParam));
          break;
  
      case CLM_SETCOLUMNWIDTH:
          TraceAssert(pThis != NULL);
          {
              RECT rc;
              LONG cxDialog;
  
              GetClientRect(hWnd, &rc);
              cxDialog = rc.right;
  
              rc.right = (LONG)lParam;
              MapDialogRect(GetParent(hWnd), &rc);
  
              pThis->SetColumnWidth(hWnd, cxDialog, rc.right);
          }
          break;
  
      case CLM_SETITEMDATA:
          TraceAssert(GET_ITEM(wParam) < (ULONG)pThis->m_cItems);
          SetWindowLongPtr(GetDlgItem(hWnd, MAKE_LABEL_ID((int)wParam)),
                           GWLP_USERDATA,
                           lParam);
          break;
  
      case CLM_GETITEMDATA:
          TraceAssert(GET_ITEM(wParam) < (ULONG)pThis->m_cItems);
          lResult = GetWindowLongPtr(GetDlgItem(hWnd, MAKE_LABEL_ID((int)wParam)),
                                     GWLP_USERDATA);
          break;
  
      case CLM_RESETCONTENT:
          TraceAssert(pThis != NULL);
          pThis->ResetContent(hWnd);
          break;
  
      case CLM_GETVISIBLECOUNT:
          TraceAssert(pThis != NULL);
          lResult = pThis->GetVisibleCount(hWnd);
          break;
  
      case CLM_GETTOPINDEX:
          TraceAssert(pThis != NULL);
          lResult = pThis->GetTopIndex(hWnd);
          break;
  
      case CLM_SETTOPINDEX:
          TraceAssert(pThis != NULL);
          pThis->SetTopIndex(hWnd, (LONG)wParam);
          break;
  
      case CLM_ENSUREVISIBLE:
          TraceAssert(pThis != NULL);
          pThis->EnsureVisible(hWnd, (LONG)wParam);
          break;
  
      //
      // Always refer to the chklist window for help. Don't pass
      // one of the child window handles here.
      //
      case WM_HELP:
          ((LPHELPINFO)lParam)->hItemHandle = hWnd;
          lResult = SendMessage(GetParent(hWnd), uMsg, wParam, lParam);
          break;
      case WM_CONTEXTMENU:
          lResult = SendMessage(GetParent(hWnd), uMsg, (WPARAM)hWnd, lParam);
          break;
  
      default:
          lResult = DefWindowProc(hWnd, uMsg, wParam, lParam);
      }
  
      TraceLeaveValue(lResult);
  }
  