//
// CControlTreeListView.cpp
//
// (c)Copyright 2004 Steven J. Eschweiler. All Rights Reserved.
//

#define _WIN32_WINNT 0x0500   // for Mouse Wheel support
#include <winsock2.h> // Always include before windows.h for SocketTools 5.x to work
#include <windows.h>
#include <assert.h>
#include "CControlTreeListView.h"
#include "resource.h"
//#include <commctrl.h> // for _TrackMouseEvent

// Base class is CBaseClassWindow
CControlTreeListView::CControlTreeListView(void)
{
   m_hwnd = NULL;
   m_nContentWidth = 0;
   m_nContentHeight = 0;
   m_nHotColumn = -1;
   m_nMinColumnWidth = 8; // Absolute safest minimum is 8 - otherwise it might crash... 
   m_BufferRGB = NULL;
   m_nWhatsCaptured = m_enumWC_NothingOrScrollBar;

   m_pAnchorNode=NULL;
   m_xDragRectangleAnchor=0;
   m_yDragRectangleAnchor=0;
   m_xDragRectanglePos=0;
   m_yDragRectanglePos=0;
   m_bTimerSet[0] = false;
   m_bTimerSet[1] = false;
   m_bTimerSet[2] = false;
   m_bTimerSet[3] = false;

   pCControlTreeListView_DragAndDrop = NULL;
   nFindFirstNextSelectedIndex = -1;

   m_pPrevNodeClick = NULL;
}

CControlTreeListView::~CControlTreeListView(void)
{
   if (m_BufferRGB!=NULL)
      delete [] m_BufferRGB;

   // Traverse the tree and delete all the nodes
   RecursivelyDeleteNodes(&m_Root);
   m_Root.vpChild.clear();
   m_Root.Child.clear();
   m_Root.strColumn.clear();
   m_Root.strColumnSortValue.clear();
   m_Root.smIcon.clear();
}

void CControlTreeListView::RecursivelyDeleteNodes(CNode *pNode)
{
   //for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++)
   for (std::vector<CNode *>::reverse_iterator iter=pNode->vpChild.rbegin(); iter!=pNode->vpChild.rend(); iter++)
      RecursivelyDeleteNodes(*iter);
   if (pNode->pNodeParent!=NULL) // This is not a root node so go ahead and delete it
      delete pNode;
}

void CControlTreeListView::OnCreate()
{
   // We create our font here
   HDC hdc = GetDC(NULL); // We release it laster
   assert(hdc!=NULL);
   int nPointSize = 8;
   int nHeight = -MulDiv(nPointSize, GetDeviceCaps(hdc, LOGPIXELSY), 72);
   m_hFont = CreateFont( nHeight,
      0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
      ANSI_CHARSET,
      OUT_DEFAULT_PRECIS,
      CLIP_DEFAULT_PRECIS,
      DEFAULT_QUALITY,
      VARIABLE_PITCH,
      "Tahoma");
   if (m_hFont==NULL)
      m_hFont = CreateFont( nHeight,
      0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
      ANSI_CHARSET,
      OUT_DEFAULT_PRECIS,
      CLIP_DEFAULT_PRECIS,
      DEFAULT_QUALITY,
      VARIABLE_PITCH,
      "Arial");
   SetFont(m_hFont,true);

   m_hCursorHand = LoadCursor(NULL, IDC_HAND);
   m_hCursorSizeWE = LoadCursor(NULL, IDC_SIZEWE);
   m_hCursorArrow = LoadCursor(NULL, IDC_ARROW);
   m_hCursorArrowCopy = LoadCursor(GetModuleHandle(NULL), MAKEINTRESOURCE(IDC_ARROW_COPY)); 

   m_bNeedToRestartMouseTrackEvent = true;

   // Determine scrollbar width and height
   ShowScrollBar(m_hwnd,SB_HORZ,TRUE);
   ShowScrollBar(m_hwnd,SB_VERT,TRUE);
   SCROLLBARINFO sbi;
   sbi.cbSize = sizeof(SCROLLBARINFO);
   GetScrollBarInfo(m_hwnd,OBJID_VSCROLL,&sbi);
   m_nVScrollWidth = sbi.rcScrollBar.right - sbi.rcScrollBar.left;
   GetScrollBarInfo(m_hwnd,OBJID_HSCROLL,&sbi);
   m_nHScrollHeight = sbi.rcScrollBar.bottom - sbi.rcScrollBar.top;

   // Now hide it initially
   ShowScrollBar(m_hwnd,SB_HORZ,FALSE);
   ShowScrollBar(m_hwnd,SB_VERT,FALSE);

   //Update(false);
}

bool CControlTreeListView::Init(HWND hwndParent,UINT nControlID,int x,int y,int width,int height,UINT ResourceIDExpandPlusBMP,UINT ResourceIDExpandMinusBMP,
                                bool bWholeLineSelect,
                                bool bWholeLineHighlight,
                                bool bMultilineSelect,
                                bool bShowRootExpandTabs,
                                COLORREF clrBackground,UINT ResourceIDCheckedBMP,UINT ResourceIDUncheckedBMP,UINT ResourceIDPartiallyCheckedBMP)
{
   // Make sure it's not already created
   assert(m_hwnd==NULL);

   if (ResourceIDCheckedBMP!=0 &&
      ResourceIDUncheckedBMP!=0 &&
      ResourceIDPartiallyCheckedBMP!=0)
   {
      m_bUsingCheckboxes = true;
      m_bmpChecked.Init(ResourceIDCheckedBMP);
      m_bmpUnchecked.Init(ResourceIDUncheckedBMP);
      m_bmpPartiallyChecked.Init(ResourceIDPartiallyCheckedBMP);
   }
   else
   {
      m_bUsingCheckboxes = false;
   }
   //m_bmpExpandPlus.Init(ResourceIDExpandPlusBMP);
   //m_bmpExpandMinus.Init(ResourceIDExpandMinusBMP);
   m_ResourceIDExpandPlusBMP = ResourceIDExpandPlusBMP;
   m_ResourceIDExpandMinusBMP = ResourceIDExpandMinusBMP;
   if (m_ResourceIDExpandPlusBMP!=0)
      m_bmpExpandPlus.Init(m_ResourceIDExpandPlusBMP);		
   if (m_ResourceIDExpandMinusBMP!=0)
      m_bmpExpandMinus.Init(m_ResourceIDExpandMinusBMP);

   m_hwnd = NULL;
   m_hwndParent = NULL;

   m_bWholeLineSelect = bWholeLineSelect;
   m_bWholeLineHighlight = bWholeLineHighlight;
   m_bMultilineSelect = bMultilineSelect;
   m_bShowRootExpandTabs = bShowRootExpandTabs;

   // We determine node heights now
   CElementText cetTmp;
   cetTmp.Init("Tahoma",8,RGB(0,0,0),"Mg");
   m_nNodeHeight = cetTmp.GetHeight();
   if (m_nNodeHeight<16)
      m_nNodeHeight=16;
   if (m_nNodeHeight<GetSystemMetrics(SM_CYSMICON))
      m_nNodeHeight=GetSystemMetrics(SM_CYSMICON);

   // Node indent amount
   m_nIndentMultiplier = 16;

   // By default, there is one column width, so we set the width to the window width
   bUsingColumns = false;
   m_vnColumnRightOffset.push_back(width);
   m_nColumnHeaderHeight = 0;

   // Initialize default root node
   m_Root.nIndentLevel = -1;
   m_Root.pNodeParent = NULL;
   m_Root.strNodeID = "ROOT";
   //m_Root.Child.clear();
   //m_Root.vpChild.clear();
   m_Root.bTVExpandedAndVisible = true;

   return (CBaseClassWindow::Init( NULL,
      NULL,
      NULL,
      WS_HSCROLL|WS_VSCROLL| WS_TABSTOP|WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS, // WS_CLIPSIBLINGS avoid problems with moved by SplitterBar
      x,y,width,height,
      reinterpret_cast<HMENU>(nControlID),
      clrBackground ) );
}

void CControlTreeListView::PaintDragRectangle(HDC hdc,int xDragRectangleAnchor,int yDragRectangleAnchor,int xDragRectanglePos,int yDragRectanglePos)
{
   if (m_nWhatsCaptured==m_enumWC_DragRect)
   {
      BYTE nDstR, nDstG, nDstB;
      BYTE nNewR, nNewG, nNewB;
      BYTE nSrcR, nSrcG, nSrcB, nSrcA;

      int yStart,yEnd,xStart,xEnd;
      xStart = xDragRectangleAnchor;
      yStart = yDragRectangleAnchor;
      xEnd = xDragRectanglePos;
      yEnd = yDragRectanglePos;
      if (yDragRectangleAnchor>yDragRectanglePos)
      {
         yStart = yDragRectanglePos;
         yEnd = yDragRectangleAnchor;
      }
      if (xDragRectangleAnchor>xDragRectanglePos)
      {
         xStart = xDragRectanglePos;
         xEnd = xDragRectangleAnchor;
      }
      if (yStart<m_nColumnHeaderHeight)
         yStart=m_nColumnHeaderHeight;
      if (xStart<0)
         xStart=0;
      if (yEnd>(this->GetHeight()-0)) // -m_nHScrollHeight
         yEnd=(this->GetHeight()-0); // -m_nHScrollHeight
      if (xEnd>(this->GetWidth()-0)) // -m_nVScrollWidth
         xEnd=(this->GetWidth()-0); // -m_nVScrollWidth

      // OK, we got bugs regarding this so we do this for safety
      if (yEnd<m_nColumnHeaderHeight)
         yEnd=m_nColumnHeaderHeight;
      if (xEnd<0)
         xEnd=0;
      if (yStart>(this->GetHeight()-0)) // -m_nHScrollHeight
         yStart=(this->GetHeight()-0); // -m_nHScrollHeight
      if (xStart>(this->GetWidth()-0)) // -m_nVScrollWidth
         xStart=(this->GetWidth()-0); // -m_nVScrollWidth

      // No need to render and waste time... this could happen for a lot of reasons
      if (yStart==yEnd || xStart==xEnd)
         return;


      BITMAPINFO bi;
      bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
      bi.bmiHeader.biWidth = 2000;
      bi.bmiHeader.biHeight = -2000;
      bi.bmiHeader.biPlanes = 1;
      bi.bmiHeader.biBitCount = 32;
      bi.bmiHeader.biCompression = BI_RGB;
      bi.bmiHeader.biSizeImage = 2000 * 4 * 2000;
      bi.bmiHeader.biClrUsed = 0;
      bi.bmiHeader.biClrImportant = 0;

      //GetDIBits(m_hdcBuffer, m_hBufferBitmap, 0, 2000, m_BufferRGB, &bi, DIB_RGB_COLORS);
      //GetBitmapBits(m_hBufferBitmap,2000*4*2000,m_BufferRGB);
      //GetDIBits(m_hdcBuffer, m_hBufferBitmap, 1900, 100, m_BufferRGB, &bi, DIB_RGB_COLORS);
      GetDIBits(m_hdcBuffer, m_hBufferBitmap, 2000-(yEnd+1), yEnd+1, m_BufferRGB, &bi, DIB_RGB_COLORS);

      nSrcB = 197;
      nSrcG = 106;
      nSrcR = 49;
      nSrcA = 64;

      int nOffset=0;
      for (int y=yStart; y<yEnd; y++)
      {
         for (int x=xStart; x<xEnd; x++)
         {
            nOffset = (((y*2000)+x)*4);

            // Get pixel color
            nDstB = m_BufferRGB[nOffset+0];
            nDstG = m_BufferRGB[nOffset+1];
            nDstR = m_BufferRGB[nOffset+2];

            // Create alpha blended color
            nNewR = nDstR+(((nSrcR-nDstR)*nSrcA)/255);
            nNewG = nDstG+(((nSrcG-nDstG)*nSrcA)/255);
            nNewB = nDstB+(((nSrcB-nDstB)*nSrcA)/255);

            // Set new color
            m_BufferRGB[nOffset+0] = nNewB;
            m_BufferRGB[nOffset+1] = nNewG;
            m_BufferRGB[nOffset+2] = nNewR;
         }
      }

      for (int y=yStart; y<yEnd; y++)
      {
         nOffset = (((y*2000)+xStart)*4);
         m_BufferRGB[nOffset+0] = nSrcB;
         m_BufferRGB[nOffset+1] = nSrcG;
         m_BufferRGB[nOffset+2] = nSrcR;
         nOffset = (((y*2000)+(xEnd-1))*4);
         m_BufferRGB[nOffset+0] = nSrcB;
         m_BufferRGB[nOffset+1] = nSrcG;
         m_BufferRGB[nOffset+2] = nSrcR;
      }
      for (int x=xStart; x<xEnd; x++)
      {
         nOffset = (((yStart*2000)+x)*4);
         m_BufferRGB[nOffset+0] = nSrcB;
         m_BufferRGB[nOffset+1] = nSrcG;
         m_BufferRGB[nOffset+2] = nSrcR;
         nOffset = ((((yEnd-1)*2000)+x)*4);
         m_BufferRGB[nOffset+0] = nSrcB;
         m_BufferRGB[nOffset+1] = nSrcG;
         m_BufferRGB[nOffset+2] = nSrcR;
      }
      nOffset = ((((yEnd-1)*2000)+(xEnd-1))*4);
      m_BufferRGB[nOffset+0] = nSrcB;
      m_BufferRGB[nOffset+1] = nSrcG;
      m_BufferRGB[nOffset+2] = nSrcR;

      //SetDIBits(m_hdcBuffer, m_hBufferBitmap, 0, 2000, m_BufferRGB, &bi, DIB_RGB_COLORS);
      //SetBitmapBits(m_hBufferBitmap,2000*4*2000,m_BufferRGB);
      //SetDIBits(m_hdcBuffer, m_hBufferBitmap, 1900, 100, m_BufferRGB, &bi, DIB_RGB_COLORS);
      SetDIBits(m_hdcBuffer, m_hBufferBitmap, 2000-(yEnd+1), yEnd+1, m_BufferRGB, &bi, DIB_RGB_COLORS);
   }

/* Slow:
	if (?
	{
		COLORREF clrDst;
		BYTE nDstR, nDstG, nDstB;
		BYTE nNewR, nNewG, nNewB;
		BYTE nSrcR, nSrcG, nSrcB, nSrcA;
		HPEN hNewPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
		HPEN hOldPen = (HPEN) SelectObject(hdc, hNewPen);

		int yStart = yDragRectangleAnchor;
		int yEnd = yDragRectanglePos;
		int xStart = xDragRectangleAnchor;
		int xEnd = xDragRectanglePos;
		if (yDragRectangleAnchor>yDragRectanglePos)
		{
			yStart = yDragRectanglePos;
			yEnd = yDragRectangleAnchor;
		}
		if (xDragRectangleAnchor>xDragRectanglePos)
		{
			xStart = xDragRectanglePos;
			xEnd = xDragRectangleAnchor;
		}

		nSrcB = 255;
		nSrcG = 0;
		nSrcR = 0;
		nSrcA = 64;

		for (int y=yStart; y<yEnd; y++)
		{
			for (int x=xStart; x<xEnd; x++)
			{
				// Read pixel color at dst x,y...
				clrDst = GetPixel(hdc,x,y);
				nDstR = GetRValue(clrDst);
				nDstG = GetGValue(clrDst);
				nDstB = GetBValue(clrDst);

				// Create alpha blended color
				nNewR = nDstR+(((nSrcR-nDstR)*nSrcA)/255);
				nNewG = nDstG+(((nSrcG-nDstG)*nSrcA)/255);
				nNewB = nDstB+(((nSrcB-nDstB)*nSrcA)/255);

				// Set new color
				SetPixel(hdc,x,y,RGB(nNewR,nNewG,nNewB));
			}
		}

		SelectObject(hdc, hOldPen);
		DeleteObject(hNewPen);
	}
*/
}

void CControlTreeListView::PaintColumnHeader(int nColumnIndex,HDC hdc)
{
   HPEN hNewPen;
   HPEN hOldPen;
   HBRUSH hNewBrush;
   HBRUSH hOldBrush;
   COLORREF clrColumnHeader = RGB(160,160,160);
   RECT rcColumn,rcTextBox;

   if (nColumnIndex==m_nHotColumn)
      clrColumnHeader = RGB(160,160,0);

   if (bUsingColumns)
   {
      // Render column headers
      hNewPen = CreatePen(PS_SOLID,1,this->GetBkColor());
      hOldPen = (HPEN) SelectObject(hdc, hNewPen);
      hNewBrush = CreateSolidBrush(clrColumnHeader);
      hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush);
      this->GetColumnRect(nColumnIndex,&rcColumn);
      Rectangle(hdc, rcColumn.left, 0,
         rcColumn.right+1,
         m_nColumnHeaderHeight);
      SelectObject(hdc, hOldBrush);
      DeleteObject(hNewBrush);
      SelectObject(hdc, hOldPen);
      DeleteObject(hNewPen);

      SetBkColor(hdc, clrColumnHeader);
      SetTextColor(hdc, RGB(255,255,255));
      //DrawText(hdc,m_vstrColumnHeader[nColumnIndex].c_str(),static_cast<UINT>(m_vstrColumnHeader[nColumnIndex].length()),&rcTextBox,DT_RIGHT|DT_VCENTER);
      rcTextBox = rcColumn;
      rcTextBox.top = 3;
      rcTextBox.left += 3;
      rcTextBox.bottom = m_nColumnHeaderHeight;
      rcTextBox.right -= 6;
      if (m_vbRightJustify[nColumnIndex])
         DrawText(hdc,m_vstrColumnHeader[nColumnIndex].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_RIGHT);
      else
         DrawText(hdc,m_vstrColumnHeader[nColumnIndex].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT);
      //ExtTextOut(hdc, rcColumn.left+3, 3, ETO_OPAQUE, NULL, m_vstrColumnHeader[nColumnIndex].c_str(), static_cast<UINT>(m_vstrColumnHeader[nColumnIndex].length()), NULL);
   }
}

void CControlTreeListView::PaintFalseColumnAtEnd(HDC hdc)
{
   HPEN hNewPen;
   HPEN hOldPen;
   HBRUSH hNewBrush;
   HBRUSH hOldBrush;
   COLORREF clrColumnHeader = RGB(160,160,160);

   int nLastColumnIndex = static_cast<int>(m_vnColumnRightOffset.size()) - 1;
   if (bUsingColumns)
   {
      // Render column headers
      hNewPen = CreatePen(PS_SOLID,1,this->GetBkColor());
      hOldPen = (HPEN) SelectObject(hdc, hNewPen);
      hNewBrush = CreateSolidBrush(clrColumnHeader);
      hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush);

      // Paint blank column header
      Rectangle(hdc, m_nRenderX+m_vnColumnRightOffset[nLastColumnIndex], 0,
         this->GetWidth()+this->m_nVScrollWidth,
         m_nColumnHeaderHeight);

      // Erase Column Nodes area
      hNewBrush = CreateSolidBrush(this->GetBkColor());
      SelectObject(hdc, hNewBrush);
      Rectangle(hdc, m_nRenderX+m_vnColumnRightOffset[nLastColumnIndex], m_nColumnHeaderHeight,
         this->GetWidth()+this->m_nVScrollWidth,
         this->GetHeight());

      SelectObject(hdc, hOldBrush);
      DeleteObject(hNewBrush);
      SelectObject(hdc, hOldPen);
      DeleteObject(hNewPen);
   }
}

void CControlTreeListView::PaintNodesColumn(int nColumnIndex,HDC hdc,CNode *pNode)
{
   HPEN hNewPen;
   HPEN hOldPen;
   HBRUSH hNewBrush;
   HBRUSH hOldBrush;
   COLORREF clrHighlighted = RGB(49,106,197);
   COLORREF clrColumnHeader = RGB(160,160,160);
   //RECT rcColumn;
   RECT rcTextBox;

   if (nColumnIndex==0)
   {
      PaintNodes(hdc,pNode);
   }
   else
   {
      if (pNode->pNodeParent!=NULL) // This is not the invisible root node so go ahead and display it
      {
         if (m_nRenderY>(-m_nNodeHeight)+m_nColumnHeaderHeight && m_nRenderY<this->GetHeight()+m_nNodeHeight)
         {
            rcTextBox.left = m_nRenderX;
            rcTextBox.top = m_nRenderY;
            rcTextBox.right = m_nRenderX + 
               GetColumnWidth(nColumnIndex) +
               + 1;
            rcTextBox.bottom = m_nRenderY+m_nNodeHeight;

            if (pNode->bSelected && m_bWholeLineHighlight)
            {
               hNewPen = CreatePen(PS_SOLID,1,clrHighlighted);
               hOldPen = (HPEN) SelectObject(hdc, hNewPen);
               hNewBrush = CreateSolidBrush(clrHighlighted);
               hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush);
               Rectangle(hdc, rcTextBox.left, rcTextBox.top,
                  rcTextBox.right,
                  rcTextBox.bottom);
               SelectObject(hdc, hOldBrush);
               DeleteObject(hNewBrush);
               SelectObject(hdc, hOldPen);
               DeleteObject(hNewPen);

               SetBkColor(hdc, clrHighlighted);
               SetTextColor(hdc, RGB(255,255,255));

               rcTextBox.top += 1;
               rcTextBox.left += 1;
               rcTextBox.right -= 6;
               if (m_vbRightJustify[nColumnIndex])
                  DrawText(hdc,pNode->strColumn[nColumnIndex].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_RIGHT);
               else
                  DrawText(hdc,pNode->strColumn[nColumnIndex].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT);
               //ExtTextOut(hdc, m_nRenderX+1,m_nRenderY+1, ETO_OPAQUE, NULL, pNode->strColumn[nColumnIndex].c_str(), static_cast<UINT>(pNode->strColumn[nColumnIndex].length()), NULL);
            }
            else
            {
               hNewPen = CreatePen(PS_SOLID,1,this->GetBkColor());
               hOldPen = (HPEN) SelectObject(hdc, hNewPen);
               hNewBrush = CreateSolidBrush(this->GetBkColor());
               hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush);
               Rectangle(hdc, rcTextBox.left, rcTextBox.top,
                  rcTextBox.right,
                  rcTextBox.bottom);
               SelectObject(hdc, hOldBrush);
               DeleteObject(hNewBrush);
               SelectObject(hdc, hOldPen);
               DeleteObject(hNewPen);

               SetBkColor(hdc, this->GetBkColor());
               SetTextColor(hdc, RGB(0,0,0));
               rcTextBox.top += 1;
               rcTextBox.left += 1;
               rcTextBox.right -= 6;
               if (m_vbRightJustify[nColumnIndex])
                  DrawText(hdc,pNode->strColumn[nColumnIndex].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_RIGHT);
               else
                  DrawText(hdc,pNode->strColumn[nColumnIndex].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT);
               //ExtTextOut(hdc, m_nRenderX+1,m_nRenderY+1, ETO_OPAQUE, NULL, pNode->strColumn[nColumnIndex].c_str(), static_cast<UINT>(pNode->strColumn[nColumnIndex].length()), NULL);
            }
         }
         // Always update this
         m_nRenderY += m_nNodeHeight;
      }
      if (pNode->bTVExpandedAndVisible)
      {
         for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++)
         {
            PaintNodesColumn(nColumnIndex,hdc,(*iter));
         }
      }
   }
}

void CControlTreeListView::PaintNodes(HDC hdc,CNode *pNode)
{
   COLORREF clrHighlighted = RGB(49,106,197);
   //COLORREF clrHighlighted = RGB(255,171,9);
   RECT rcTextBox,rcColumn;

   if (pNode->pNodeParent!=NULL) // This is not the invisible root node so go ahead and display it
   {
      // Optimize rendering performance by rendering only if within visible bounds
      if (m_nRenderY>(-m_nNodeHeight)+m_nColumnHeaderHeight && m_nRenderY<this->GetHeight()+m_nNodeHeight)
      {
         RECT rResult;
         HPEN hNewPen;
         HPEN hOldPen;
         HBRUSH hNewBrush;
         HBRUSH hOldBrush;
         int nX = m_nRenderX;
         if (pNode->bSelected && m_bWholeLineHighlight)
         {
            hNewPen = CreatePen(PS_SOLID,1,clrHighlighted);
            hOldPen = (HPEN) SelectObject(hdc, hNewPen);
            hNewBrush = CreateSolidBrush(clrHighlighted);
            hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush);
            Rectangle(hdc, 0, m_nRenderY,
               this->GetWidth()+1,
               m_nRenderY+m_nNodeHeight);
            SelectObject(hdc, hOldBrush);
            DeleteObject(hNewBrush);
            SelectObject(hdc, hOldPen);
            DeleteObject(hNewPen);
         }
         /*
         else
         {
         hNewPen = CreatePen(PS_SOLID,1,this->GetBkColor());
         hOldPen = (HPEN) SelectObject(hdc, hNewPen);
         hNewBrush = CreateSolidBrush(this->GetBkColor());
         hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush);
         Rectangle(hdc, 0, m_nRenderY,
         this->GetWidth()+1,
         m_nRenderY+m_nNodeHeight);
         SelectObject(hdc, hOldBrush);
         DeleteObject(hNewBrush);
         SelectObject(hdc, hOldPen);
         DeleteObject(hNewPen);
         }
         */
         nX += (pNode->nIndentLevel*m_nIndentMultiplier);
         if (pNode->vpChild.size())
         {
            if (pNode->bTVExpandedAndVisible)
            {
               if (m_ResourceIDExpandMinusBMP!=0)
                  m_bmpExpandMinus.Render(hdc,nX+3,m_nRenderY+4);
            }
            else
            {
               if (m_ResourceIDExpandPlusBMP!=0)
                  m_bmpExpandPlus.Render(hdc,nX+3,m_nRenderY+4);
            }
         }
         nX += (m_nIndentMultiplier);
         if (pNode->bCheckBoxVisible)
         {
            if ( pNode->nCheck==1 )
               m_bmpChecked.Render(hdc,nX,m_nRenderY+2);
            else if ( pNode->nCheck==0 )
               m_bmpUnchecked.Render(hdc,nX,m_nRenderY+2);
            else if ( pNode->nCheck==-1 )
               m_bmpPartiallyChecked.Render(hdc,nX,m_nRenderY+2);
            nX += (m_nIndentMultiplier);
         }

         if (pNode->smIcon.size())
         {
            if (!pNode->bCheckBoxVisible ||
               pNode->nCheck==1 )
               rResult = pNode->smIcon[0].Render(hdc,nX,m_nRenderY);
            else
            {
               if (pNode->bSelected && m_bWholeLineSelect)
                  rResult = pNode->smIcon[0].RenderGrayed(hdc,nX,m_nRenderY,clrHighlighted);
               else
                  rResult = pNode->smIcon[0].RenderGrayed(hdc,nX,m_nRenderY,this->GetBkColor());
            }
            nX += 16;
         }

         // Now render Text
         nX += 4;
         this->GetColumnRect(0,&rcColumn);
         rcTextBox.left = nX-2;
         rcTextBox.top = m_nRenderY;				
         rcTextBox.right = rcColumn.right;
         rcTextBox.bottom = m_nRenderY+m_nNodeHeight;

         if (!pNode->bCheckBoxVisible ||
            pNode->nCheck==1 )
         {
            if (pNode->bSelected)
            {
               if (m_bWholeLineHighlight==false)
               {
                  hNewPen = CreatePen(PS_SOLID,1,clrHighlighted);
                  hOldPen = (HPEN) SelectObject(hdc, hNewPen);
                  hNewBrush = CreateSolidBrush(clrHighlighted);
                  hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush);
                  Rectangle(hdc, rcTextBox.left, rcTextBox.top,
                     nX+pNode->nTextWidth+6,
                     rcTextBox.bottom);
                  SelectObject(hdc, hOldBrush);
                  DeleteObject(hNewBrush);
                  SelectObject(hdc, hOldPen);
                  DeleteObject(hNewPen);
               }
               SetBkColor(hdc, clrHighlighted);
               SetTextColor(hdc, RGB(255,255,255));
               rcTextBox.top += 1;
               rcTextBox.left += 3;
               rcTextBox.right -= 6;
               DrawText(hdc,pNode->strColumn[0].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT);
               //ExtTextOut(hdc, nX+1,m_nRenderY+1, ETO_OPAQUE, NULL, pNode->strColumn[0].c_str(), static_cast<UINT>(pNode->strColumn[0].length()), NULL);
            }
            else
            {
               SetBkColor(hdc, this->GetBkColor());
               SetTextColor(hdc, RGB(0,0,0));
               rcTextBox.top += 1;
               rcTextBox.left += 3;
               rcTextBox.right -= 6;
               DrawText(hdc,pNode->strColumn[0].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT);
               //ExtTextOut(hdc, nX+1,m_nRenderY+1, ETO_OPAQUE, NULL, pNode->strColumn[0].c_str(), static_cast<UINT>(pNode->strColumn[0].length()), NULL);
            }
         }
         else
         {
            if (pNode->bSelected)
            {
               if (m_bWholeLineHighlight==false)
               {
                  hNewPen = CreatePen(PS_SOLID,1,clrHighlighted);
                  hOldPen = (HPEN) SelectObject(hdc, hNewPen);
                  hNewBrush = CreateSolidBrush(clrHighlighted);
                  hOldBrush = (HBRUSH) SelectObject(hdc, hNewBrush);
                  Rectangle(hdc, rcTextBox.left, rcTextBox.top,
                     nX+pNode->nTextWidth+6,
                     rcTextBox.bottom);
                  SelectObject(hdc, hOldBrush);
                  DeleteObject(hNewBrush);
                  SelectObject(hdc, hOldPen);
                  DeleteObject(hNewPen);
               }
               SetBkColor(hdc, clrHighlighted);
               SetTextColor(hdc, RGB(255,255,255));
               rcTextBox.top += 1;
               rcTextBox.left += 3;
               rcTextBox.right -= 6;
               DrawText(hdc,pNode->strColumn[0].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT);
               //ExtTextOut(hdc, nX+1,m_nRenderY+1, ETO_OPAQUE, NULL, pNode->strColumn[0].c_str(), static_cast<UINT>(pNode->strColumn[0].length()), NULL);
            }
            else
            {
               SetBkColor(hdc, this->GetBkColor());
               SetTextColor(hdc, RGB(203,209,170));
               rcTextBox.top += 1;
               rcTextBox.left += 3;
               rcTextBox.right -= 6;
               DrawText(hdc,pNode->strColumn[0].c_str(),-1,&rcTextBox,DT_END_ELLIPSIS|DT_LEFT);
               //ExtTextOut(hdc, nX+1,m_nRenderY+1, ETO_OPAQUE, NULL, pNode->strColumn[0].c_str(), static_cast<UINT>(pNode->strColumn[0].length()), NULL);
            }
         }

      }
      // Always update this
      m_nRenderY += m_nNodeHeight;
   }
   if (pNode->bTVExpandedAndVisible)
   {
      for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++)
      {
         PaintNodes(hdc,(*iter));
      }
   }
}

LRESULT CControlTreeListView::WndProc(HWND hwnd, UINT mMsg, WPARAM wParam, LPARAM lParam)
{
   TRACKMOUSEEVENT trackmouseevent;
   //int xPos;
   //int yPos;
   //int xPosContent,yPosContent;
   SCROLLINFO si;
   HFONT hFontOld;
   int nHorzPos;
   int nVertPos;
   HPEN hNewPen,hOldPen;
   HBRUSH hNewBrush,hOldBrush;
   RECT rcColumn;
   PAINTSTRUCT ps;
   LARGE_INTEGER currentTick;

   switch (mMsg)
   {
   case WM_CREATE:
      {
         HDC hdcDst;
         hdcDst = GetDC(hwnd);
         m_hdcBuffer = CreateCompatibleDC(hdcDst);
         // Maxiumum ***Window*** size (not content size) for this control is:
         m_hBufferBitmap = CreateCompatibleBitmap(hdcDst,2000,2000);
         SelectObject(m_hdcBuffer, m_hBufferBitmap);
         ReleaseDC(hwnd, hdcDst);
         m_BufferRGB = new BYTE[2000*2000*4]; // < 16 MB

         QueryPerformanceFrequency(&m_TickFrequency);
         QueryPerformanceCounter(&m_prevTick);

         //SetClassLongPtr(hwnd,GCL_HCURSOR,reinterpret_cast<LONG>(m_hCursor));
         return 0;
         break;
      }
   case WM_DESTROY:
      {
         DeleteDC(m_hdcBuffer);
         DeleteObject(m_hBufferBitmap);
         //ReleaseDC(hwnd, m_hdcDst);
         break;
      }

   case WM_ERASEBKGND:
      {
         // We never erase the background, we paint everything to avoid flicker
         // and other problems with this control like when another app window is
         // moved around over this one...
         //
         // Besides, we are now using double buffering approach to avoid the
         // nightmare that is Windows
         return 1;
         break;
      }

   case WM_PAINT:
      {
         //RECT rcClient;
         //GetClientRect(hwnd, &rcClient);

         // Get and Save Scroll Positions
         si.cbSize = sizeof(SCROLLINFO);
         si.fMask = SIF_POS;
         GetScrollInfo(hwnd,SB_HORZ,&si);
         nHorzPos = si.nPos;
         si.fMask = SIF_POS;
         GetScrollInfo(hwnd,SB_VERT,&si);
         nVertPos = si.nPos;

         // Double buffer this to avoid flicker
         // http://www.gamedev.net/community/forums/topic.asp?topic_id=411559

         // Erase Buffer First:
         hNewPen = CreatePen(PS_SOLID,1,this->GetBkColor());
         hOldPen = (HPEN) SelectObject(m_hdcBuffer, hNewPen);
         hNewBrush = CreateSolidBrush(this->GetBkColor());
         hOldBrush = (HBRUSH) SelectObject(m_hdcBuffer, hNewBrush);
         Rectangle(m_hdcBuffer, 0, 0, this->GetWidth(), this->GetHeight());
         SelectObject(m_hdcBuffer, hOldBrush);
         DeleteObject(hNewBrush);
         SelectObject(m_hdcBuffer, hOldPen);
         DeleteObject(hNewPen);

         // Render columns
         for (int nColumnIndex=0; nColumnIndex<static_cast<int>(m_vnColumnRightOffset.size()); nColumnIndex++)
         {
            this->GetColumnRect(nColumnIndex,&rcColumn);

            // I don't know if we need to create a pen but it
            // seems like we had a problem once that was solved
            // by having a pen set.
            hNewPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
            hOldPen = (HPEN) SelectObject(m_hdcBuffer, hNewPen);

            // Load up our font
            hFontOld = static_cast<HFONT>(SelectObject(m_hdcBuffer,static_cast<HGDIOBJ>(m_hFont)));

            // Let's setup our font first
            // Note: We render only from children of root
            m_nRenderX = 0;
            m_nRenderY = 0;

            // Adjust for scroll
            m_nRenderX -= nHorzPos;
            m_nRenderY -= (nVertPos*m_nNodeHeight);

            // Adjust for header height
            m_nRenderY += m_nColumnHeaderHeight;

            // Adjust
            m_nRenderX += (m_bShowRootExpandTabs?0:-16);

            if (nColumnIndex>0)
               m_nRenderX = rcColumn.left;

            // Unleash the Recursive Beast
            //PaintNodes(m_hdcBuffer,&m_Root);
            // Paint column here
            PaintNodesColumn(nColumnIndex,m_hdcBuffer,&m_Root);

            // Paint column header, if at all
            PaintColumnHeader(nColumnIndex,m_hdcBuffer);

            // Load up original font
            SelectObject(m_hdcBuffer, static_cast<HGDIOBJ>(hFontOld));

            // Delete Pen
            SelectObject(m_hdcBuffer, hOldPen);
            DeleteObject(hNewPen);
         }

         // This will render only if needs to
         m_nRenderX = 0;
         m_nRenderX -= nHorzPos;
         PaintFalseColumnAtEnd(m_hdcBuffer);

         // Paint Drag Rectangle
         m_nRenderX = 0;
         m_nRenderY = 0;
         m_nRenderX -= nHorzPos;
         m_nRenderY -= (nVertPos*m_nNodeHeight);
         m_nRenderY += m_nColumnHeaderHeight;
         PaintDragRectangle(m_hdcBuffer,
            m_xDragRectangleAnchor+m_nRenderX,
            m_yDragRectangleAnchor+m_nRenderY,
            m_xDragRectanglePos+m_nRenderX,
            m_yDragRectanglePos+m_nRenderY);

         // Seems like BeginPaint()/EndPaint() is neccessary to keep 
         // Windows happy...
         //    bad: BitBlt(m_hdcDst, 0, 0, this->GetWidth()-m_nVScrollWidth, this->GetHeight()-m_nHScrollHeight, m_hdcBuffer, 0, 0, SRCCOPY);
         HDC hdc = BeginPaint(hwnd, &ps);
         // BTW, this is bad:  BitBlt(hdc, 0, 0, this->GetWidth()-m_nVScrollWidth, this->GetHeight()-m_nHScrollHeight, m_hdcBuffer, 0, 0, SRCCOPY);
         BitBlt(hdc, 0, 0, this->GetWidth(), this->GetHeight(), m_hdcBuffer, 0, 0, SRCCOPY);
         EndPaint(hwnd, &ps);

         return 0;
         break;
      }

   case WM_HSCROLL:
      {
         int oldx=0,newx=0;

         si.cbSize = sizeof(SCROLLINFO);
         si.fMask = SIF_ALL;
         GetScrollInfo(hwnd,SB_HORZ,&si);
         int xPos = oldx = si.nPos;

         switch(LOWORD(wParam))
         {
            //case SB_LEFT:
         case SB_LINELEFT:
            {					
               newx = oldx-5;
               if (newx<si.nMin)
                  newx=si.nMin;
               si.nPos = newx;
               SetScrollInfo(hwnd,SB_HORZ,&si,TRUE);
               break;
            }

            //case SB_RIGHT:
         case SB_LINERIGHT:
            {
               newx = oldx+5;
               if (newx>(si.nMax-static_cast<int>(si.nPage))+1)
                  newx=(si.nMax-static_cast<int>(si.nPage))+1;
               si.nPos = newx;
               SetScrollInfo(hwnd,SB_HORZ,&si,TRUE);
               break;
            }

         case SB_PAGELEFT:
            {
               newx = oldx-static_cast<int>(si.nPage);
               if (newx<si.nMin)
                  newx=si.nMin;
               si.nPos = newx;
               SetScrollInfo(hwnd,SB_HORZ,&si,TRUE);
               break;
            }

         case SB_PAGERIGHT:
            {
               newx = oldx+static_cast<int>(si.nPage);
               if (newx>(si.nMax-static_cast<int>(si.nPage))+1)
                  newx=(si.nMax-static_cast<int>(si.nPage))+1;
               si.nPos = newx;
               SetScrollInfo(hwnd,SB_HORZ,&si,TRUE);
               break;
            }


         case SB_THUMBTRACK:
            {
               newx = si.nTrackPos;
               si.nPos = newx;
               SetScrollInfo(hwnd,SB_HORZ,&si,TRUE);
               break;
            }
         }

         // Smooth scrolling
         // If the position has changed, scroll the window 
         if (si.nPos != xPos)
         {
            if (m_nWhatsCaptured==m_enumWC_DragRect)
            {
               // m_xDragRectanglePos;
               // m_yDragRectanglePos;
               m_xDragRectanglePos -= (xPos - si.nPos);
               // Select nodes
               SelectDragRectangleNodes();
               this->Invalidate();
            }
            else
            {
               //ScrollWindowEx(m_hwnd,(xPos - si.nPos),0,NULL,NULL,NULL,NULL,SW_SCROLLCHILDREN);
               //
               // If you use ScrollWindowEx(), you need to set
               // up certain rectangles so that invalid portions of
               // the window get repainted.
               //
               // ScrollWindow() on the other hand, takes care of
               // invalidating the relevant portions of the window 
               // for you.
               //

               //ScrollWindow(hwnd, (xPos - si.nPos), 0, NULL, NULL);

               RECT rcScroll;
               //GetColumnRect(0,&rcScroll); // Incorrect
               rcScroll.left = 0;
               rcScroll.right = this->GetWidth();
               //rcScroll.top = this->m_nColumnHeaderHeight;
               rcScroll.top = 0;
               rcScroll.bottom = this->GetHeight();
               ScrollWindow(hwnd, (xPos - si.nPos), 0, &rcScroll, &rcScroll);
            }
         }

         return 0;
         break;
      }

   case WM_KEYDOWN:
      {
         switch (wParam)
         {
            /*
            case VK_HOME :
            SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0);
            break ;

            case VK_END :
            SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0);
            break ;
            */

         case VK_DELETE:
            SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_DELETE),(LPARAM)this->GetHwnd());
            break;
            /*
            if (m_pAnchorNode!=NULL)
            {
            m_pAnchorNode->pNodeParent->RemoveChild(m_pAnchorNode->strNodeID);
            this->Update();
            this->Invalidate();
            m_pAnchorNode = NULL;
            }						
            break;
            */

         case VK_PRIOR :
            SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0);
            break ;

         case VK_NEXT :
            SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
            break ;

         case VK_UP :
            SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0);
            break ;

         case VK_DOWN :
            SendMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
            break ;

         case VK_LEFT :
            SendMessage (hwnd, WM_HSCROLL, SB_PAGELEFT, 0);
            break ;

         case VK_RIGHT :
            SendMessage (hwnd, WM_HSCROLL, SB_PAGERIGHT, 0);
            break ;
         }

         return 0;
         break;
      }

   case WM_MOUSEWHEEL:
      {
         short nDelta = HIWORD(wParam);

         if (nDelta>0)
         {
            PostMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0) ;
            PostMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0) ;
            PostMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0) ;
         }
         else
         {
            PostMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0) ;
            PostMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0) ;
            PostMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0) ;
         }

         return 0;
         break;
      }

   case WM_VSCROLL:
      {
         int oldy=0,newy=0;

         si.cbSize = sizeof(SCROLLINFO);
         si.fMask = SIF_ALL;
         GetScrollInfo(hwnd,SB_VERT,&si);

         // Save the position for comparison later on
         int yPos = oldy = si.nPos;

         switch(LOWORD(wParam))
         {
            //case SB_TOP:
         case SB_LINEUP:
            {
               newy = oldy-1;
               if (newy<si.nMin)
                  newy=si.nMin;
               si.nPos = newy;
               SetScrollInfo(hwnd,SB_VERT,&si,TRUE);
               break;
            }

            //case SB_BOTTOM:
         case SB_LINEDOWN:
            {
               newy = oldy+1;
               if (newy>(si.nMax-static_cast<int>(si.nPage))+1)
                  newy=(si.nMax-static_cast<int>(si.nPage))+1;
               si.nPos = newy;
               SetScrollInfo(hwnd,SB_VERT,&si,TRUE);
               break;
            }

         case SB_PAGEUP:
            {
               newy = oldy-static_cast<int>(si.nPage);
               if (newy<si.nMin)
                  newy=si.nMin;
               si.nPos = newy;
               SetScrollInfo(hwnd,SB_VERT,&si,TRUE);
               break;
            }

         case SB_PAGEDOWN:
            {
               newy = oldy+static_cast<int>(si.nPage);
               if (newy>(si.nMax-static_cast<int>(si.nPage))+1)
                  newy=(si.nMax-static_cast<int>(si.nPage))+1;
               si.nPos = newy;
               SetScrollInfo(hwnd,SB_VERT,&si,TRUE);
               break;
            }

         case SB_THUMBTRACK:
            {
               newy = si.nTrackPos;
               si.nPos = newy;
               SetScrollInfo(hwnd,SB_VERT,&si,TRUE);
               break;
            }
         }

         // Smooth scrolling
         if (si.nPos != yPos)
         {   
            if (m_nWhatsCaptured==m_enumWC_DragRect)
            {
               // m_xDragRectanglePos;
               // m_yDragRectanglePos;
               m_yDragRectanglePos -= (m_nNodeHeight * (yPos - si.nPos));
               // Select nodes
               SelectDragRectangleNodes();
               this->Invalidate();
            }
            else
            {
               //ScrollWindowEx(m_hwnd,0,m_nNodeHeight * (yPos - si.nPos),NULL,NULL,NULL,NULL,SW_SCROLLCHILDREN);
               //
               // If you use ScrollWindowEx(), you need to set
               // up certain rectangles so that invalid portions of
               // the window get repainted.
               //
               // ScrollWindow() on the other hand, takes care of
               // invalidating the relevant portions of the window 
               // for you.
               //

               //ScrollWindow(hwnd, 0, m_nNodeHeight * (yPos - si.nPos), NULL, NULL);

               RECT rcScroll;
               //GetColumnRect(0,&rcScroll); // Incorrect
               rcScroll.left = 0;
               rcScroll.right = this->GetWidth();
               rcScroll.top = this->m_nColumnHeaderHeight;
               rcScroll.bottom = this->GetHeight();
               ScrollWindow(hwnd, 0, m_nNodeHeight * (yPos - si.nPos), &rcScroll, &rcScroll);
            }
         }

         return 0;
         break;
      }

   case WM_TIMER:
      {
         // The wParam parameter of the WM_TIMER message contains the 
         // value of the nIDEvent parameter. 
         switch (wParam)
         {
         case 0:
            {
               SendMessage(hwnd, WM_HSCROLL, SB_LINELEFT, 0);
               SendMessage(hwnd, WM_HSCROLL, SB_LINELEFT, 0);
               //SendMessage(hwnd, WM_HSCROLL, SB_LINELEFT, 0);
               break;
            }
         case 1:
            {
               SendMessage(hwnd, WM_HSCROLL, SB_LINERIGHT, 0);
               SendMessage(hwnd, WM_HSCROLL, SB_LINERIGHT, 0);
               //SendMessage(hwnd, WM_HSCROLL, SB_LINERIGHT, 0);
               break;
            }
         case 2:
            {
               SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
               SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
               //SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
               break;
            }
         case 3:
            {
               SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
               SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
               //SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
               break;
            }

         }
         return 0;
         break;
      }

      // This is rather complex. We determine a double click using a different method
      /*
      case WM_LBUTTONDBLCLK:
      {
      // CS_DBLCLKS
      SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_DOUBLECLICKED),(LPARAM)this->GetHwnd());
      return 0;
      break;
      }
      */

   case WM_LBUTTONUP:
      {
         QueryPerformanceCounter(&currentTick);
         double dElapsedTime = static_cast<double>((currentTick.LowPart - m_prevTick.LowPart)) / static_cast<double>(m_TickFrequency.LowPart);
         bool bDoubleClick = false;
         if (dElapsedTime<0.25)
            bDoubleClick = true;
         m_prevTick = currentTick;

         // Two step approach required to get negative mouse values:
         signed short ssXPosWindow = static_cast<signed short>(LOWORD(lParam));
         signed short ssYPosWindow = static_cast<signed short>(HIWORD(lParam));
         int nXPosWindow = static_cast<int>(ssXPosWindow);
         int nYPosWindow = static_cast<int>(ssYPosWindow);
         InitMouseAndCursorInfo(nXPosWindow,nYPosWindow);

         // Get and Save Scroll Positions
         si.cbSize = sizeof(SCROLLINFO);
         si.fMask = SIF_POS;
         GetScrollInfo(hwnd,SB_HORZ,&si);
         nHorzPos = si.nPos;
         si.fMask = SIF_POS;
         GetScrollInfo(hwnd,SB_VERT,&si);
         nVertPos = si.nPos;

         // Get mouse click in node content coordinates
         int nXPosContent = nXPosWindow + nHorzPos;
         int nYPosContent = (nYPosWindow + (nVertPos*m_nNodeHeight)) - m_nColumnHeaderHeight;

         // Avoid problems with SetCapture and ScrollBars:
         RECT rcNodeWindow;
         GetNodeClientWindow(&rcNodeWindow);

         if (GetCursor()==m_hCursorArrowCopy)
            SetCursor(m_hCursorArrow);

         // Nodes already selected via WM_MOUSEMOVE. It's safe to send this message.
         if (m_nWhatsCaptured == m_enumWC_DragRect && 
            m_xDragRectangleAnchor!=nXPosContent &&
            m_yDragRectangleAnchor!=nYPosContent)
            SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd());

         if (m_nWhatsCaptured==m_enumWC_DragNodes)
         {
            int nItemIndex = nYPosContent/m_nNodeHeight;

            // NOTE: Be very careful here. User can click somewhere like
            // on the lower horz scroll bar making nItemIndex out of
            // range. Always ensure that nItemIndex is in range if you
            // want to continue.
            if (nItemIndex<0 || nItemIndex>=static_cast<int>(m_mapRenderedNodeLine.size()))
            {
               break;
            }

            // Help for determining what was clicked
            int nXExpandButtonLeft = (m_mapRenderedNodeLine[nItemIndex]->nIndentLevel*m_nIndentMultiplier) + 1;
            nXExpandButtonLeft += (m_bShowRootExpandTabs?0:-16);
            int nXExpandButtonRight = nXExpandButtonLeft + 12;
            int nXCheckBoxLeft = (m_mapRenderedNodeLine[nItemIndex]->nIndentLevel*m_nIndentMultiplier) + 16;
            nXCheckBoxLeft += (m_bShowRootExpandTabs?0:-16);
            int nXCheckBoxRight = nXCheckBoxLeft + (m_mapRenderedNodeLine[nItemIndex]->bCheckBoxVisible?16:0);
            int nXIconLeft = nXCheckBoxRight;
            int nXIconRight = nXIconLeft + ((m_mapRenderedNodeLine[nItemIndex]->smIcon.size()?16:0));
            int nXTextLeft = nXIconRight;
            int nXTextRight = 10 + nXTextLeft + m_mapRenderedNodeLine[nItemIndex]->nTextWidth;

            // Some setup for potential Node Selection
            int nxRight = nXTextRight;
            if (m_bWholeLineSelect==false && bUsingColumns==true)
            {
               if (m_vnColumnRightOffset[0]>nXTextRight)
                  nxRight = nXTextRight;
               else
                  nxRight = m_vnColumnRightOffset[0];
            }

            // Node Selection (mouse up only for drag purposes)
            if ( (m_bWholeLineSelect && (nXPosContent<nXExpandButtonLeft || nXPosContent>nXExpandButtonRight))
               || (false==m_bWholeLineSelect && nXPosContent>nXIconLeft && nXPosContent<nxRight) )
            {

               if (wParam&MK_CONTROL)
               {
                  //if (m_mapRenderedNodeLine[nItemIndex]->bSelected)
                  //m_mapRenderedNodeLine[nItemIndex]->bSelected = false;
                  //m_mapRenderedNodeLine[nItemIndex]->bSelected =! m_mapRenderedNodeLine[nItemIndex]->bSelected;
                  //SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode);
                  //SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd());
               }
               else					
               {
                  // Save this, then select if needed
                  bool bSelectedSaved = m_mapRenderedNodeLine[nItemIndex]->bSelected;
                  int nNumSelectedSaved = GetNumSelected();



                  // Select
                  if (wParam&MK_SHIFT)
                     SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode);
                  else
                     m_pAnchorNode = SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode);

                  if (bDoubleClick && m_pPrevNodeClick==m_mapRenderedNodeLine[nItemIndex])
                  {
                     // A single node was double clicked
                     SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_DOUBLECLICKED),(LPARAM)m_mapRenderedNodeLine[nItemIndex]);
                  }
                  else
                  {
                     // Send NOTIFY_SELECTIONCHANGED only if selection has actually changed
                     // Note: We send this message after we select the nodes because
                     // our parent window which processes this message will
                     // want to know which nodes are selected after the selection
                     // is made.
                     if (nNumSelectedSaved>1)
                        SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd());
                     else if (bSelectedSaved == false)
                        SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd());
                  }

                  m_pPrevNodeClick = m_mapRenderedNodeLine[nItemIndex];
               }
            }

            //ReleaseCapture();
            m_nWhatsCaptured = m_enumWC_NothingOrScrollBar;
            pCControlTreeListView_DragAndDrop = NULL;
         }
         else if (pCControlTreeListView_DragAndDrop!=NULL)
         {
            // Nodes Dropped Here from a different window...
            /*
            //MessageBox(NULL,"Nodes Dropped","Hello",MB_OK);
            pCControlTreeListView_DragAndDrop->m_nWhatsCaptured = m_enumWC_NothingOrScrollBar;

            // Cycle through other Control and add selected nodes here
            for (int i=0; i<static_cast<int>(pCControlTreeListView_DragAndDrop->m_mapRenderedNodeLine.size()); i++)
            {
            if (pCControlTreeListView_DragAndDrop->m_mapRenderedNodeLine[i]->bSelected)
            {
            this->m_Root.AddChild( &pCControlTreeListView_DragAndDrop->m_mapRenderedNodeLine[i]->smIcon[0],
            pCControlTreeListView_DragAndDrop->m_mapRenderedNodeLine[i]->strColumn[0]);
            }
            }
            this->Update();

            pCControlTreeListView_DragAndDrop = NULL;
            */

            SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_NODESDRAGGEDANDDROPPEDHERE),(LPARAM)pCControlTreeListView_DragAndDrop);
            //this->Update();
            pCControlTreeListView_DragAndDrop = NULL;
         }

         KillTimer(hwnd,0);
         KillTimer(hwnd,1);
         KillTimer(hwnd,2);
         KillTimer(hwnd,3);
         m_bTimerSet[0] = false;
         m_bTimerSet[1] = false;
         m_bTimerSet[2] = false;
         m_bTimerSet[3] = false;

         ReleaseCapture();
         m_nWhatsCaptured = m_enumWC_NothingOrScrollBar;
         this->Invalidate();

         return 0;
         break;
      }

   case WM_LBUTTONDOWN:
      {
         // Leave this for good measure. Seems to solve potential problems.
         this->SetFocus();

         // Two step approach required to get negative mouse values:
         signed short ssXPosWindow = static_cast<signed short>(LOWORD(lParam));
         signed short ssYPosWindow = static_cast<signed short>(HIWORD(lParam));
         int nXPosWindow = static_cast<int>(ssXPosWindow);
         int nYPosWindow = static_cast<int>(ssYPosWindow);
         InitMouseAndCursorInfo(nXPosWindow,nYPosWindow);

         // Get and Save Scroll Positions
         si.cbSize = sizeof(SCROLLINFO);
         si.fMask = SIF_POS;
         GetScrollInfo(hwnd,SB_HORZ,&si);
         nHorzPos = si.nPos;
         si.fMask = SIF_POS;
         GetScrollInfo(hwnd,SB_VERT,&si);
         nVertPos = si.nPos;

         // Get mouse click in node content coordinates
         int nXPosContent = nXPosWindow + nHorzPos;
         int nYPosContent = (nYPosWindow + (nVertPos*m_nNodeHeight)) - m_nColumnHeaderHeight;

         // Avoid problems with SetCapture and ScrollBars:
         RECT rcNodeWindow;
         GetNodeClientWindow(&rcNodeWindow);

         if (bUsingColumns && nYPosWindow<m_nColumnHeaderHeight)
         {
            // This is a column header click
            if (m_nWhatsHot==m_enumColumnResize)
            {
               if (nXPosWindow<rcNodeWindow.right)
               {
                  // Not a scroll bar click
                  SetCapture(this->GetHwnd());
                  m_nWhatsCaptured = m_enumWC_ColumnResize;
               }
            }
            else if (m_nWhatsHot==m_enumColumnSort)
            {
               //MessageBox(hwnd,m_SM.Format(m_nHotColumn).c_str(),"Hot Column:",MB_OK);
               m_Root.SortChildren(m_nHotColumn);
               this->Update();
            }
         }
         else
         {
            int nItemIndex = nYPosContent/m_nNodeHeight;

            // NOTE: Be very careful here. User can click somewhere like
            // on the lower horz scroll bar making nItemIndex out of
            // range. Always ensure that nItemIndex is in range if you
            // want to continue.
            if (nItemIndex<0 || nItemIndex>=static_cast<int>(m_mapRenderedNodeLine.size()))
            {
               if (nItemIndex>=static_cast<int>(m_mapRenderedNodeLine.size()))
               {
                  // Possibly a selection drag rectangle
                  if (m_nWhatsHot==m_enumNodes && m_bWholeLineSelect==false && m_bMultilineSelect==true)
                  {
                     if (nXPosWindow<rcNodeWindow.right &&
                        nYPosWindow<rcNodeWindow.bottom )
                     {
                        m_xDragRectangleAnchor = nXPosContent;
                        m_yDragRectangleAnchor = nYPosContent;
                        SetCapture(this->GetHwnd());
                        m_nWhatsCaptured = m_enumWC_DragRect;
                        m_pAnchorNode = NULL;
                     }
                  }
               }
               break;
            }

            // Help for determining what was clicked
            int nXExpandButtonLeft = (m_mapRenderedNodeLine[nItemIndex]->nIndentLevel*m_nIndentMultiplier) + 1;
            nXExpandButtonLeft += (m_bShowRootExpandTabs?0:-16);
            int nXExpandButtonRight = nXExpandButtonLeft + 12;
            int nXCheckBoxLeft = (m_mapRenderedNodeLine[nItemIndex]->nIndentLevel*m_nIndentMultiplier) + 16;
            nXCheckBoxLeft += (m_bShowRootExpandTabs?0:-16);
            int nXCheckBoxRight = nXCheckBoxLeft + (m_mapRenderedNodeLine[nItemIndex]->bCheckBoxVisible?16:0);
            int nXIconLeft = nXCheckBoxRight;
            int nXIconRight = nXIconLeft + ((m_mapRenderedNodeLine[nItemIndex]->smIcon.size()?16:0));
            int nXTextLeft = nXIconRight;
            int nXTextRight = 10 + nXTextLeft + m_mapRenderedNodeLine[nItemIndex]->nTextWidth;

            // Some setup for potential Node Selection
            int nxRight = nXTextRight;
            if (m_bWholeLineSelect==false && bUsingColumns==true)
            {
               if (m_vnColumnRightOffset[0]>nXTextRight)
                  nxRight = nXTextRight;
               else
                  nxRight = m_vnColumnRightOffset[0];
            }

            // Expand/Collapse Selection
            if (m_mapRenderedNodeLine[nItemIndex]->vpChild.size() &&
               nXPosContent>nXExpandButtonLeft && nXPosContent<nXExpandButtonRight )
            {
               // Toggle
               m_mapRenderedNodeLine[nItemIndex]->bTVExpandedAndVisible = !m_mapRenderedNodeLine[nItemIndex]->bTVExpandedAndVisible;

               // This won't always work when collapsing a small tree so leave it X'd out
               //RECT rcInvalidate;
               //rcInvalidate.left = 0;
               //rcInvalidate.right = this->GetWidth();
               //rcInvalidate.top = static_cast<int>(HIWORD(lParam))-m_nNodeHeight;
               //rcInvalidate.bottom = this->GetHeight();
               //this->Invalidate(&rcInvalidate);

               // Now handle special case where a selected node collapses.
               // In other words, select (highlight) parent (this) node
               // if a child node was selected.
               if (AreAnyChildrenSelected(m_mapRenderedNodeLine[nItemIndex]))
               {
                  m_pAnchorNode = SelectNode(m_mapRenderedNodeLine[nItemIndex]);
                  // Always send this because children are being collapsed and
                  // unselected.
                  SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd());
               }

               this->Update();
               //this->Invalidate();

               // This will keep it clean looking if it takes a long time
               // for parent to process expand or collapse message
               UpdateWindow(hwnd);
               if (m_mapRenderedNodeLine[nItemIndex]->bTVExpandedAndVisible)
                  SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_EXPANDED),(LPARAM)m_mapRenderedNodeLine[nItemIndex]);
               else
                  SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_COLLAPSED),(LPARAM)m_mapRenderedNodeLine[nItemIndex]);
            }
            // Checkbox Selection
            else if (m_mapRenderedNodeLine[nItemIndex]->bCheckBoxVisible &&
               nXPosContent>nXCheckBoxLeft && nXPosContent<nXCheckBoxRight )
            {
               // Save this, then select if needed
               bool bSelectedSaved = m_mapRenderedNodeLine[nItemIndex]->bSelected;
               int nNumSelectedSaved = GetNumSelected();

               // Select
               if (wParam&MK_SHIFT)
                  SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode);
               else
                  m_pAnchorNode = SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode);

               // Send NOTIFY_SELECTIONCHANGED only if selection has actually changed
               // Note: We send this message after we select the nodes because
               // our parent window which processes this message will
               // want to know which nodes are selected after the selection
               // is made.
               if (nNumSelectedSaved>1)
                  SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd());
               else if (bSelectedSaved == false)
                  SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd());

               // Toggle checkbox
               if (m_mapRenderedNodeLine[nItemIndex]->nCheck==1 )
               {
                  SetCheckState(m_mapRenderedNodeLine[nItemIndex],false);
                  UpdateWindow(hwnd);
                  SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_UNCHECKED),(LPARAM)m_mapRenderedNodeLine[nItemIndex]);
               }
               else // if (m_mapRenderedNodeLine[nItemIndex]->nCheck==0 || -1 )
               {
                  SetCheckState(m_mapRenderedNodeLine[nItemIndex],true);
                  UpdateWindow(hwnd);
                  SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_CHECKED),(LPARAM)m_mapRenderedNodeLine[nItemIndex]);
               }
            }

            // Node Selection
            else if ( (m_bWholeLineSelect && (nXPosContent<nXExpandButtonLeft || nXPosContent>nXExpandButtonRight))
               || (false==m_bWholeLineSelect && nXPosContent>nXIconLeft && nXPosContent<nxRight) )
            {
               if (m_mapRenderedNodeLine[nItemIndex]->bSelected==false)
               {
                  if (wParam&MK_SHIFT)
                  {
                     SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode);
                  }
                  else
                  {
                     m_pAnchorNode = SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode);
                  }

                  SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd());

                  m_nWhatsCaptured = m_enumWC_DragNodes;
                  pCControlTreeListView_DragAndDrop = this;
               }
               else
               {
                  if (wParam&MK_SHIFT)
                  {
                     SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode);
                     SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd());
                  }
                  else if (wParam&MK_CONTROL)
                  {
                     m_pAnchorNode = SelectNode(m_mapRenderedNodeLine[nItemIndex],(wParam&MK_CONTROL)?true:false,(wParam&MK_SHIFT)?true:false,m_pAnchorNode);
                     SendMessage(this->GetHwndParent(),WM_COMMAND,MAKEWPARAM(this->GetControlId(),this->NOTIFY_SELECTIONCHANGED),(LPARAM)this->GetHwnd());
                  }
                  else if (nXPosWindow<rcNodeWindow.right &&
                     nYPosWindow<rcNodeWindow.bottom )
                  {
                     // Begin drag operation
                     //if (GetCapture()==NULL)
                     //SetCapture(this->GetHwnd());
                     // This is not a normal drag operation
                     m_nWhatsCaptured = m_enumWC_DragNodes;
                     pCControlTreeListView_DragAndDrop = this;
                  }
               }
            }
            // Possibly a selection drag rectangle
            else if (m_nWhatsHot==m_enumNodes && m_bWholeLineSelect==false && m_bMultilineSelect==true)
            {
               if (nXPosWindow<rcNodeWindow.right &&
                  nYPosWindow<rcNodeWindow.bottom )
               {
                  m_xDragRectangleAnchor = nXPosContent;
                  m_yDragRectangleAnchor = nYPosContent;
                  SetCapture(this->GetHwnd());
                  m_nWhatsCaptured = m_enumWC_DragRect;
                  m_pAnchorNode = NULL;
               }
            }

         }
         return 0;
         break;
      }

   case WM_MOUSEMOVE:
      {
         //if (GetCursor()==m_hCursorArrowCopy && (wParam&MK_LBUTTON)==0 )
         if ((wParam&MK_LBUTTON)==0)
         {
            SetCursor(m_hCursorArrow);
            pCControlTreeListView_DragAndDrop = NULL;
            m_nWhatsCaptured = m_enumWC_NothingOrScrollBar;
         }
         /*
         if (GetCapture()==this->GetHwnd())
         m_vstrColumnHeader[0] = "Captured";
         else
         m_vstrColumnHeader[0] = "NOT Captured";
         */

         int nSavedHotColumn = m_nHotColumn;

         // Two step approach required to get negative mouse values:
         signed short ssXPosWindow = static_cast<signed short>(LOWORD(lParam));
         signed short ssYPosWindow = static_cast<signed short>(HIWORD(lParam));
         int nXPosWindow = static_cast<int>(ssXPosWindow);
         int nYPosWindow = static_cast<int>(ssYPosWindow);
         InitMouseAndCursorInfo(nXPosWindow,nYPosWindow);

         // Get and Save Scroll Positions
         si.cbSize = sizeof(SCROLLINFO);
         si.fMask = SIF_POS;
         GetScrollInfo(hwnd,SB_HORZ,&si);
         nHorzPos = si.nPos;
         si.fMask = SIF_POS;
         GetScrollInfo(hwnd,SB_VERT,&si);
         nVertPos = si.nPos;

         // Get mouse click in node content coordinates
         int nXPosContent = nXPosWindow + nHorzPos;
         int nYPosContent = (nYPosWindow + (nVertPos*m_nNodeHeight)) - m_nColumnHeaderHeight;

         // Avoid problems with SetCapture and ScrollBars:
         RECT rcNodeWindow;
         GetNodeClientWindow(&rcNodeWindow);

         if (m_nHotColumn != nSavedHotColumn)
         {
            // Invalidate header to redraw hot header
            RECT rcInvalidate;
            rcInvalidate.left = 0;
            rcInvalidate.right = this->GetWidth();
            rcInvalidate.top = 0;
            rcInvalidate.bottom = m_nColumnHeaderHeight;
            this->Invalidate(&rcInvalidate);
         }
         if (m_bNeedToRestartMouseTrackEvent)
         {
            // Set the event for next movement
            trackmouseevent.cbSize = sizeof(trackmouseevent);
            trackmouseevent.dwFlags = TME_LEAVE;
            trackmouseevent.hwndTrack = hwnd;
            trackmouseevent.dwHoverTime = HOVER_DEFAULT;
            TrackMouseEvent(&trackmouseevent);
            m_bNeedToRestartMouseTrackEvent = false;
         }



         if (m_nWhatsCaptured!=m_enumWC_NothingOrScrollBar)
         {
            if (m_nWhatsCaptured == m_enumWC_DragNodes)
            {
               // TO-DO: Drag nodes (change cursor)
               //MessageBox(this->GetHwnd(),"Dragging Nodes","Note",MB_OK);
            }
            else if (m_nWhatsCaptured==m_enumWC_ColumnResize)
            {
               // Resizing column here...
               int nNewPos = nXPosContent;

               if (nNewPos < (m_nMinColumnWidth*(m_nHotColumn+1)))
                  nNewPos = (m_nMinColumnWidth*(m_nHotColumn+1));

               int nDelta = nNewPos - m_vnColumnRightOffset[m_nHotColumn];

               for (int i=m_nHotColumn; i<static_cast<int>(m_vnColumnRightOffset.size()); i++)
               {
                  m_vnColumnRightOffset[i] += nDelta;
               }

               // Loop through and ensure other columns offsets are correct
               for (int i=static_cast<int>(m_vnColumnRightOffset.size())-2; i>=0; i--)
               {
                  if (m_vnColumnRightOffset[i] > (m_vnColumnRightOffset[i+1]-m_nMinColumnWidth))
                     m_vnColumnRightOffset[i] = (m_vnColumnRightOffset[i+1]-m_nMinColumnWidth);
               }

               this->m_nContentWidth = m_vnColumnRightOffset[m_vnColumnRightOffset.size()-1];
               this->UpdateScrollBars();
               this->Invalidate();
            }
            else if (m_nWhatsCaptured==m_enumWC_DragRect)
            {
               // Determine new drag rectangle position
               m_xDragRectanglePos = nXPosContent;
               m_yDragRectanglePos = nYPosContent;

               // Select nodes
               SelectDragRectangleNodes();

               SCROLLBARINFO sbi;
               sbi.cbSize = sizeof(SCROLLBARINFO);
               GetScrollBarInfo(m_hwnd,OBJID_VSCROLL,&sbi);
               bool bVInvisible = (sbi.rgstate[0]&STATE_SYSTEM_INVISIBLE)?true:false;
               GetScrollBarInfo(m_hwnd,OBJID_HSCROLL,&sbi);
               bool bHInvisible = (sbi.rgstate[0]&STATE_SYSTEM_INVISIBLE)?true:false;

               // Scroll window if needed
               if (nXPosWindow<0)
               {
                  // Scroll Left
                  if (!m_bTimerSet[0])
                  {
                     SetTimer(hwnd,0,10,NULL);
                     m_bTimerSet[0] = true;

                     if (m_bTimerSet[1])
                        KillTimer(hwnd,1);
                     m_bTimerSet[1] = false;
                  }
                  //SendMessage (hwnd, WM_HSCROLL, SB_PAGELEFT, 0);
                  //SendMessage (hwnd, WM_HSCROLL, SB_LINELEFT, 0);
               }
               else if (nXPosWindow>rcNodeWindow.right && !bHInvisible)
               {
                  // Scroll Right
                  if (!m_bTimerSet[1])
                  {
                     SetTimer(hwnd,1,10,NULL);
                     m_bTimerSet[1] = true;

                     if (m_bTimerSet[0])
                        KillTimer(hwnd,0);
                     m_bTimerSet[0] = false;
                  }
                  //SendMessage (hwnd, WM_HSCROLL, SB_PAGERIGHT, 0);
                  //SendMessage (hwnd, WM_HSCROLL, SB_LINERIGHT, 0);
               }
               else
               {
                  if (m_bTimerSet[0])
                     KillTimer(hwnd,0);
                  m_bTimerSet[0] = false;

                  if (m_bTimerSet[1])
                     KillTimer(hwnd,1);
                  m_bTimerSet[1] = false;
               }

               if (nYPosWindow<m_nColumnHeaderHeight)
               {
                  // Scroll Up
                  if (!m_bTimerSet[2])
                  {
                     SetTimer(hwnd,2,10,NULL);
                     m_bTimerSet[2] = true;

                     if (m_bTimerSet[3])
                        KillTimer(hwnd,3);
                     m_bTimerSet[3] = false;
                  }
                  //SendMessage (hwnd, WM_VSCROLL, SB_PAGEUP, 0);
                  //SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0);
               }
               else if (nYPosWindow>rcNodeWindow.bottom && !bVInvisible)
               {
                  // Scroll Down
                  if (!m_bTimerSet[3])
                  {
                     SetTimer(hwnd,3,10,NULL);
                     m_bTimerSet[3] = true;

                     if (m_bTimerSet[2])
                        KillTimer(hwnd,2);
                     m_bTimerSet[2] = false;
                  }
                  //SendMessage (hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
                  //SendMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
               }
               else
               {
                  if (m_bTimerSet[2])
                     KillTimer(hwnd,2);
                  m_bTimerSet[2] = false;

                  if (m_bTimerSet[3])
                     KillTimer(hwnd,3);
                  m_bTimerSet[3] = false;
               }

               this->Invalidate();
            }
         }
         return 0;
         break;
      }

   case WM_MOUSELEAVE:
      {
         //if (m_nHotColumn!=-1)
         {
            m_nHotColumn = -1;
            // Invalidate header to redraw hot header
            RECT rcInvalidate;
            rcInvalidate.left = 0;
            rcInvalidate.right = this->GetWidth();
            rcInvalidate.top = 0;
            rcInvalidate.bottom = m_nColumnHeaderHeight;
            this->Invalidate(&rcInvalidate);
            UpdateWindow(hwnd);
            m_bNeedToRestartMouseTrackEvent = true;
         }			
         return 0;
         break;
      }


/*
	// ms-help://MS.VSCC/MS.MSDNVS/winui/mousinpt_2jas.htm
	case WM_MOUSELEAVE:
		if (m_state!=down && m_state!=disabled)
		{
			// Restore COLD button image
			if (m_state!=cold)
			{
                m_state = cold;
                InvalidateRect(hwnd, NULL, FALSE);
			}
		}
		break;

	case WM_MOUSEMOVE:
		if (m_state!=down && m_state!=disabled)
		{
			if (m_state!=hot)
			{
				m_state = hot;
				InvalidateRect(hwnd, NULL, FALSE);
			}

			// Set the event for next movement
			trackmouseevent.cbSize = sizeof(trackmouseevent);
			trackmouseevent.dwFlags = TME_LEAVE;
			trackmouseevent.hwndTrack = hwnd;
			trackmouseevent.dwHoverTime = HOVER_DEFAULT;
			_TrackMouseEvent(&trackmouseevent);
		}
		break;

	case WM_LBUTTONDOWN:
		if (m_state!=down && m_state!=disabled)
		{
			m_state = down;
			InvalidateRect(hwnd, NULL, FALSE);

			SetCapture(this->GetHwnd());
		}
		break;

	case WM_LBUTTONUP:
		if (m_bMouseCaptured)
		{
			ReleaseCapture();

			if (m_state!=disabled)
			{
				// If button is released in the window in which it was pressed,
				// it is a legitimate button press or push
				signed short nxPos = static_cast<signed short>(LOWORD(lParam));
				signed short nyPos = static_cast<signed short>(HIWORD(lParam));
				xPos = static_cast<int>(nxPos);
				yPos = static_cast<int>(nyPos);
				bool bLegit=false;
				//RECT rcButton;
				//GetClientRect(hwnd, &rcButton);
				if (xPos<=m_Width && yPos<=m_Height)
					bLegit=true;

				// (m_state!=down && m_bPushButton)) = send message only if not previously pushed
				if (m_state!=hot ||(m_state!=down && m_bPushButton))
				//if (m_state!=hot)
				{
					if (m_bPushButton && bLegit)
						m_state = down;
					else
						m_state = hot;
					InvalidateRect(hwnd, NULL, FALSE);

					//ReleaseCapture();
					
					if (bLegit)
						SendMessage(m_hwndParent,WM_COMMAND,MAKEWPARAM(m_nControlID,412),(LPARAM)m_hwnd);
				}
			}
		}
		break;

	case WM_KEYUP:
		if (wParam==VK_RETURN)
		{
			m_state = hot;
			InvalidateRect(hwnd, NULL, FALSE);
			SendMessage(m_hwndParent,WM_COMMAND,MAKEWPARAM(m_nControlID,412),(LPARAM)m_hwnd);
			break;
		}
	case WM_KEYDOWN:
		if (wParam==VK_RETURN)
		{
			if (m_state!=down && m_state!=disabled)
			{
				m_state = down;
				InvalidateRect(hwnd, NULL, FALSE);
			}
			break;
		}
		if (this->m_dwStyle&WS_TABSTOP)
		{
			// wParam could == VK_TAB, etc.
			// lParam could be WM_KEYDOWN, etc.
			PostMessage(this->GetHwndParent(),this->GetControlId(),wParam,static_cast<LPARAM>(mMsg));
		}
		break;
	case WM_SETFOCUS:
		if (m_state!=down && m_state!=disabled)
		{
			if (m_state!=hot)
			{
				m_state = hot;
				InvalidateRect(hwnd, NULL, FALSE);
			}
		}
		break;
*/


   case WM_SIZE:
      {
         // This causes problems when the control initializes!!!!!!!!
         //Update(); // This causes errors with expanding a section creating a wider width for some reason...

         //int test=static_cast<int>(m_Root.Child.size());
         if (m_Root.Child.size())
         {
            UpdateScrollBars();
            this->Invalidate();
            return 0;
         }
         break;
      }

      /*
      case WM_SETFOCUS:
      {
      // Just leave this here for safety in case the wm_paint goes haywire.
      // Seems to be an issue sometimes when another app window
      // is moved over this one.
      //***** UPDATE FIXED!
      this->Invalidate();
      break;
      }

      */
   }

   return CBaseClassWindow::WndProc(hwnd, mMsg, wParam, lParam);
}

// This is the client area excluding scroll bars and header columns, if any
void CControlTreeListView::GetNodeClientWindow(RECT *lpRect)
{
   SCROLLBARINFO sbi;
   sbi.cbSize = sizeof(SCROLLBARINFO);
   GetScrollBarInfo(m_hwnd,OBJID_VSCROLL,&sbi);
   bool bVInvisible = (sbi.rgstate[0]&STATE_SYSTEM_INVISIBLE)?true:false;
   GetScrollBarInfo(m_hwnd,OBJID_HSCROLL,&sbi);
   bool bHInvisible = (sbi.rgstate[0]&STATE_SYSTEM_INVISIBLE)?true:false;

   lpRect->left = 0;
   lpRect->top = m_nColumnHeaderHeight;
   if (bVInvisible)
      lpRect->right = this->GetWidth();
   else
      lpRect->right = this->GetWidth()-this->m_nVScrollWidth;
   if (bHInvisible)
      lpRect->bottom = this->GetHeight();
   else
      lpRect->bottom = this->GetHeight()-this->m_nHScrollHeight;

   lpRect->right -= 1;
   lpRect->bottom -= 1;
}

void CControlTreeListView::InitMouseAndCursorInfo(int xPos,int yPos)
{
   SCROLLINFO si;
   int nHorzPos,nVertPos;

   if (m_nWhatsCaptured==m_enumWC_NothingOrScrollBar)
   {
      // Get and Save Scroll Positions
      si.cbSize = sizeof(SCROLLINFO);
      si.fMask = SIF_POS;
      GetScrollInfo(this->GetHwnd(),SB_HORZ,&si);
      nHorzPos = si.nPos;
      si.fMask = SIF_POS;
      GetScrollInfo(this->GetHwnd(),SB_VERT,&si);
      nVertPos = si.nPos;

      xPos += nHorzPos;
      //yPos += (nVertPos*m_nNodeHeight);

      //enum __M_ENUM_WHATSHOT__ {m_enumNodes,m_enumColumnSort,m_enumColumnResize};
      //int m_nWhatsHot;

      HCURSOR hCursorCurrent = m_hCursorArrow;
      m_nWhatsHot = m_enumNodes;
      m_nHotColumn = -1;
      if (bUsingColumns && yPos<m_nColumnHeaderHeight && xPos<m_vnColumnRightOffset[m_vnColumnRightOffset.size()-1])
      {
         // This is a column header
         hCursorCurrent = m_hCursorHand;
         m_nWhatsHot = m_enumColumnSort;
         int xTmp = 0;
         for (int nColumnIndex=0; nColumnIndex<static_cast<int>(m_vnColumnRightOffset.size()); nColumnIndex++)
         {
            if (xPos>=xTmp+4)
               m_nHotColumn = nColumnIndex;
            xTmp = m_vnColumnRightOffset[nColumnIndex];
            if (xPos>xTmp-4 && xPos<xTmp+4)
            {
               hCursorCurrent = m_hCursorSizeWE;
               m_nWhatsHot = m_enumColumnResize;
            }
         }
      }

      if (pCControlTreeListView_DragAndDrop!=NULL)
         hCursorCurrent = m_hCursorArrowCopy;

      SetCursor(hCursorCurrent);
   }
   else if (m_nWhatsCaptured==m_enumWC_DragNodes || pCControlTreeListView_DragAndDrop!=NULL)
      SetCursor(m_hCursorArrowCopy);
}

void CControlTreeListView::Update()
{
   // Determine content widths and heights
   DetermineContentWidthAndHeight();

   UpdateScrollBars();

   this->Invalidate();
}

void CControlTreeListView::UpdateScrollBars()
{
   int nUsableClientWidth;
   int nUsableClientHeight;
   SCROLLINFO si;

   nUsableClientWidth = this->GetWidth()-m_nVScrollWidth;
   nUsableClientHeight = this->GetHeight()-m_nHScrollHeight;

   if ( m_nContentHeight > nUsableClientHeight )
   {
      si.cbSize = sizeof(SCROLLINFO);
      si.fMask = SIF_RANGE | SIF_PAGE;
      si.nMin = 0;
      si.nMax = m_nContentHeight/m_nNodeHeight;
      si.nMax -= 1; // <--- I'm rather certain this is correct
      si.nPage = static_cast<UINT>(nUsableClientHeight/m_nNodeHeight);
      si.nPage -= 1; // <--- I'm rather certain this is correct
      SetScrollInfo(m_hwnd,SB_VERT,&si,TRUE);
      ShowScrollBar(m_hwnd,SB_VERT,TRUE);
   }
   else
   {
      si.cbSize = sizeof(SCROLLINFO);
      si.fMask = SIF_POS;
      si.nPos = 0;
      SetScrollInfo(m_hwnd,SB_VERT,&si,FALSE);
      ShowScrollBar(m_hwnd,SB_VERT,FALSE);
   }
   if ( m_nContentWidth > nUsableClientWidth )
   {
      si.cbSize = sizeof(SCROLLINFO);
      si.fMask = SIF_RANGE | SIF_PAGE;
      si.nMin = 0;
      si.nMax = m_nContentWidth;
      si.nMax -= 1; // <--- I'm rather certain this is correct
      si.nPage = static_cast<UINT>(nUsableClientWidth);
      si.nPage -= 1; // <--- I'm rather certain this is correct
      SetScrollInfo(m_hwnd,SB_HORZ,&si,TRUE);
      ShowScrollBar(m_hwnd,SB_HORZ,TRUE);
   }
   else
   {
      si.cbSize = sizeof(SCROLLINFO);
      si.fMask = SIF_POS;
      si.nPos = 0;
      SetScrollInfo(m_hwnd,SB_HORZ,&si,FALSE);
      ShowScrollBar(m_hwnd,SB_HORZ,FALSE);
   }

   // Causes a WM_PAINT	
   //this->Invalidate();
   //CBaseClassWindow::Update(); // <-- This causes additional processing time, is not neccessary, and causes a very noticiable delay when resizing the window
}

CControlTreeListView::CNode *CControlTreeListView::CNode::AddCheckboxChild_Worker(CElementIcon *pIcon,std::string strNodeID,std::string strDisplayName,bool bChecked,bool bCheckBoxVisible)
{
   CControlTreeListView::CNode *ChildNode;

   ChildNode = new CControlTreeListView::CNode();

   // NodeID's MUST be unique in order for certain functions to work correctly.
   // For example, FindFirstSelection will return incorrect results
   // if NodeIDs are not unique.
   // Moral: Each child must have a unique strNodeID
   for (std::vector<CNode *>::iterator iter=this->vpChild.begin(); iter!=this->vpChild.end(); iter++)
   {
      assert((*iter)->strNodeID != strNodeID); // strNodeID is not unique!!!
      if ((*iter)->strNodeID == strNodeID)
      {
         //assert((*iter)->strNodeID != strNodeID); // strNodeID is not unique!!!
         return NULL;
      }
   }	
   ChildNode->strNodeID = strNodeID;

   if (pIcon!=NULL)
      ChildNode->smIcon[0].Init(pIcon);

   ChildNode->bSelected = false;
   ChildNode->bTVExpandedAndVisible = true;
   ChildNode->bCheckBoxVisible = bCheckBoxVisible;
   if (bChecked)
      ChildNode->nCheck = 1;
   else
      ChildNode->nCheck = 0;
   ChildNode->pNodeParent = this;
   ChildNode->nIndentLevel = this->nIndentLevel + 1;
   ChildNode->strColumn[0] = strDisplayName;
   ChildNode->nWidth = 0; // Calculated later


   // All cap version of strDisplayName is default sort value.
   // This mimicks Windows Explorer filename sort
   std::string strSort = strDisplayName;
   std::transform(strSort.begin(),strSort.end(),strSort.begin(),(int(*)(int))toupper);
   //toupper(strSort[0]);
   ChildNode->EditColumnData(0,strDisplayName,strSort);


   this->vpChild.push_back(ChildNode);
   this->Child[strNodeID] = ChildNode;

   if (this->Child[strNodeID]->nCheck==1)
   {
      //SetCheckState(this->Child[strNodeID],true);
      CNode *pParentNode = this->Child[strNodeID];
      while (pParentNode!=NULL)
      {
         if (pParentNode->bCheckBoxVisible)
         {
            if (pParentNode->nCheck == 0 || 
               pParentNode->nCheck == -1 )
            {

               if (bChecked) // A little optimization here
               {
                  // If we are here, we don't need to look for children that are 
                  // checked because we know at least one of them is. We just
                  // need to set nocheck on all parents to partial check.
                  if (pParentNode->nCheck==0)
                  {
                     pParentNode->nCheck = -1;
                  }

               }
               else
               {
                  if (AreAnyChildrenFullyChecked(pParentNode))
                     pParentNode->nCheck = -1;
                  else
                     pParentNode->nCheck = 0;
               }
            }
         }
         pParentNode = pParentNode->pNodeParent;
      }
   }

   return this->Child[strNodeID];
}

// This gets called when we add, delete, expand, or collapse a node
// In other words, when we update the treeview control
void CControlTreeListView::DetermineContentWidthAndHeight()
{
   // Some setup
   m_nContentWidth = 0;
   m_nContentHeight = 0;
   m_mapRenderedNodeLine.clear();

   if (m_Root.Child.size()<1)
      return;

   HDC hdc = GetDC(NULL);
   assert(hdc!=NULL);
   HFONT hFontOld = static_cast<HFONT>(SelectObject(hdc,static_cast<HGDIOBJ>(m_hFont)));

   // Unleash the Recursive Beast
   DetermineContentWidthAndHeightRecursiveHelper(hdc,&m_Root);

   SelectObject(hdc, static_cast<HGDIOBJ>(hFontOld));
   ReleaseDC(NULL, hdc);

   // Now adjust m_nContentHeight for m_nNodeHeight
   m_nContentHeight *= m_nNodeHeight;

   // Adjust for any potential column headers

   m_nContentHeight += m_nColumnHeaderHeight;

   // Adjust if more than one column... (HORZ Scroll is not based on width of nodes anymore)
   //std::map<int,int>::iterator iter;
   if (bUsingColumns)
   {
      m_nContentWidth = m_vnColumnRightOffset[static_cast<int>(m_vnColumnRightOffset.size())-1];
   }
   else
   {
      m_vnColumnRightOffset[0] = m_nContentWidth + this->m_nVScrollWidth;
   }
}

void CControlTreeListView::DetermineContentWidthAndHeightRecursiveHelper(HDC hdc,CNode *pNode)
{
   if (pNode->pNodeParent!=NULL) // This is not the invisible root node so go ahead and process it
   {
      //m_nContentHeight += m_nNodeHeight; // We don't do this. We assume m_nContentHeight is for lines now
      m_mapRenderedNodeLine[m_nContentHeight] = pNode;
      m_nContentHeight++;

      // Determine text width
      SIZE TextSize;
      GetTextExtentPoint32(hdc, pNode->strColumn[0].c_str(), static_cast<UINT>(pNode->strColumn[0].length()), &TextSize);
      pNode->nTextWidth = TextSize.cx;
      // Calculate the total indented width of column one for HORZ scroll purposes.
      // Expand = 16
      // Checkbox = 16 if visible, 0 otherwise
      // Icon = 16 if visible, 0 otherwise
      // 4 pixel space, then text:
      // text = text width
      pNode->nWidth  = (m_bShowRootExpandTabs?0:-16) +
         (pNode->nIndentLevel*m_nIndentMultiplier) +
         16 + // expand tab, whether visible or not
         (pNode->bCheckBoxVisible?16:0) +
         (pNode->smIcon.size()?16:0) +
         4 + 
         pNode->nTextWidth;

      if (pNode->nWidth > m_nContentWidth)
         m_nContentWidth = pNode->nWidth;
   }
   if (pNode->bTVExpandedAndVisible)
   {
      for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++)
      {
         DetermineContentWidthAndHeightRecursiveHelper(hdc,*iter);
      }
   }
}

CControlTreeListView::CNode *CControlTreeListView::SelectNode(CNode *pNode,bool bControlKeyDown,bool bShiftKeyDown,CNode *pNodeShiftAnchor)
{
   if (bShiftKeyDown && m_bMultilineSelect)
   {
      RecursivelyDeselectNodes(&m_Root,pNodeShiftAnchor);

      // Now select nodes based on m_mapRenderedNodeLine
      int nMin,nMax;
      int nIndexCurrent = nMin = DetermineNodeLineIndex(pNode);
      int nIndexAnchor = nMax = DetermineNodeLineIndex(pNodeShiftAnchor);
      if (nIndexCurrent==-1 || nIndexAnchor==-1)
         return pNodeShiftAnchor;
      if (nIndexCurrent>nIndexAnchor)
      {
         nMin = nIndexAnchor;
         nMax = nIndexCurrent;
      }
      for (int i=nMin; i<=nMax; i++)
      {
         m_mapRenderedNodeLine[i]->bSelected = true;
         if (m_bWholeLineHighlight)
            Redraw(i,false,false,false,false,true);
         else
            Redraw(i,false,false,true,true);
      }

      return pNodeShiftAnchor;
   }

   if ( m_bMultilineSelect==false || bControlKeyDown==false )
   {
      RecursivelyDeselectNodes(&m_Root,pNode);
   }
   if (pNode->bSelected==false && bControlKeyDown==false)
   {
      pNode->bSelected = true;
      if (m_bWholeLineHighlight)
         Redraw(pNode,false,false,false,false,true);
      else
         Redraw(pNode,false,false,true,true);
   }
   else if (bControlKeyDown)
   {
      // control key is down... toggle this selection on/off
      pNode->bSelected = !pNode->bSelected;
      if (m_bWholeLineHighlight)
         Redraw(pNode,false,false,false,false,true);
      else
         Redraw(pNode,false,false,true,true);

   }
   return pNode;
}

void CControlTreeListView::RecursivelyDeselectNodes(CNode *pNode,CNode *pIgnoreMeNode)
{
   if (pNode->bSelected && pNode!=pIgnoreMeNode)
   {
      pNode->bSelected = false;
      if (m_bWholeLineHighlight)
         Redraw(pNode,false,false,false,false,true);
      else
         Redraw(pNode,false,false,false,true);
   }

   for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++)
      RecursivelyDeselectNodes(*iter,pIgnoreMeNode);
}

bool CControlTreeListView::AreAnyChildrenSelected(CNode *pNode)
{
   bool bAnyChildrenSelected = false;
   RecursivelyDetermineIfAnyChildrenSelected(pNode,&bAnyChildrenSelected,pNode);
   return bAnyChildrenSelected;
}

void CControlTreeListView::RecursivelyDetermineIfAnyChildrenSelected(CNode *pNode,bool *pbAnyChildrenSelected,CNode *pNodeIgnore)
{
   if (pNode->bSelected && pNode!=pNodeIgnore)
   {
      *pbAnyChildrenSelected = true;
      return; // Our goal is complete. We have a true value. We return to gain performance.
   }
   for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++)
      RecursivelyDetermineIfAnyChildrenSelected(*iter,pbAnyChildrenSelected,pNodeIgnore);
}

bool CControlTreeListView::CNode::AreAnyChildrenFullyChecked(CNode *pNode)
{
   bool bAnyChildrenFullyChecked = false;
   RecursivelyDetermineIfAnyChildrenFullyChecked(pNode,&bAnyChildrenFullyChecked);
   return bAnyChildrenFullyChecked;
}

void CControlTreeListView::CNode::RecursivelyDetermineIfAnyChildrenFullyChecked(CNode *pNode,bool *pbAnyChildrenFullyChecked)
{
   if (pNode->nCheck==1)
   {
      *pbAnyChildrenFullyChecked = true;
      return; // Our goal is complete. We have a true value. We return to gain performance.
   }
   for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++)
      RecursivelyDetermineIfAnyChildrenFullyChecked(*iter,pbAnyChildrenFullyChecked);
}

void CControlTreeListView::SetCheckState(CNode *pNode,bool bChecked)
{
   //int nScrollX,nScrollY;

   if (bChecked)
   {
      if (pNode->nCheck!=1)
      {
         pNode->nCheck = 1;
         Redraw(pNode,false,true,true,false);
      }
   }
   else
   {
      if (pNode->nCheck!=0)
      {
         pNode->nCheck = 0;
         Redraw(pNode,false,true,true,false);
      }
   }

   // Go up to the chain to each parent. If the parent is not checked,
   // then check for checked children. If any children are checked, the
   // parent becomes partialy checked.
   CNode *pParentNode = pNode;
   bool bInvalidateCheckBox;
   while (pParentNode!=NULL)
   {
      if (pParentNode->bCheckBoxVisible)
      {
         if (pParentNode->nCheck == 0 || 
            pParentNode->nCheck == -1 )
         {
            bInvalidateCheckBox = false;

            if (bChecked) // A little optimization here
            {
               // If we are here, we don't need to look for children that are 
               // checked because we know at least one of them is. We just
               // need to set nocheck on all parents to partial check.
               if (pParentNode->nCheck==0)
               {
                  bInvalidateCheckBox = true;
                  pParentNode->nCheck = -1;
               }

            }
            else
            {
               if (m_Root.AreAnyChildrenFullyChecked(pParentNode))
               {
                  if (pParentNode->nCheck!=-1)
                     bInvalidateCheckBox = true;
                  pParentNode->nCheck = -1;
               }
               else
               {
                  if (pParentNode->nCheck!=0)
                     bInvalidateCheckBox = true;
                  pParentNode->nCheck = 0;
               }
            }

            if (bInvalidateCheckBox)
            {
               Redraw(pParentNode,false,true,false,false);
            }
         }
      }

      pParentNode = pParentNode->pNodeParent;
   }
   //this->Invalidate();
}


void CControlTreeListView::Redraw(int nLineIndex,bool bExpandTab,bool bCheckBox,bool bIcon,bool bText,bool bWholeLine)
{
   int nScrollX, nScrollY;

   if (nLineIndex==-1)
      return;

   // Get index of top line
   SCROLLINFO si;
   si.cbSize = sizeof(SCROLLINFO);
   si.fMask = SIF_POS;
   GetScrollInfo(this->GetHwnd(),SB_VERT,&si);
   nScrollY = si.nPos;
   // Get left edge
   GetScrollInfo(this->GetHwnd(),SB_HORZ,&si);
   nScrollX = si.nPos;

   // Calculate top line for optimization
   //int nTop = nLineIndex-nScrollY;
   int nTop = ((nLineIndex*m_nNodeHeight))-(nScrollY*m_nNodeHeight);
   nTop += m_nColumnHeaderHeight;
   if (nTop<0)
      return;
   // Since our trees are top down, we don't need to check for bottom 
   // because doing this won't optimize anything anyway

   // Determine a few important items
   int nXExpandButtonLeft = ( -nScrollX + (m_mapRenderedNodeLine[nLineIndex]->nIndentLevel*m_nIndentMultiplier)) + 1;
   nXExpandButtonLeft += (m_bShowRootExpandTabs?0:-16);
   int nXExpandButtonRight = nXExpandButtonLeft + 12;
   int nXCheckBoxLeft = ( -nScrollX + (m_mapRenderedNodeLine[nLineIndex]->nIndentLevel*m_nIndentMultiplier)) + 16;
   nXCheckBoxLeft += (m_bShowRootExpandTabs?0:-16);
   int nXCheckBoxRight = nXCheckBoxLeft + (m_mapRenderedNodeLine[nLineIndex]->bCheckBoxVisible?16:0);
   int nXIconLeft = nXCheckBoxRight;
   int nXIconRight = nXIconLeft + ((m_mapRenderedNodeLine[nLineIndex]->smIcon.size()?16:0));
   int nXTextLeft = nXIconRight;
   int nXTextRight = nXTextLeft + m_mapRenderedNodeLine[nLineIndex]->nTextWidth;
   nXTextRight += 10;

   // Now determine invalidate area for removal of selection highlight
   RECT rcInvalidate;

   // Top and bottom are always like this:
   //rcInvalidate.top = ((nLineIndex*m_nNodeHeight))-(nScrollY*m_nNodeHeight);
   //rcInvalidate.top += m_nColumnHeaderHeight;
   rcInvalidate.top = nTop;
   rcInvalidate.bottom = rcInvalidate.top + m_nNodeHeight;


   if (bWholeLine)
   {
      rcInvalidate.left = 0;
      rcInvalidate.right = this->GetWidth();
      this->Invalidate(&rcInvalidate);
   }
   else
   {
      if (bExpandTab && m_mapRenderedNodeLine[nLineIndex]->vpChild.size())
      {
         rcInvalidate.left = nXExpandButtonLeft;
         rcInvalidate.right = nXExpandButtonRight;
         this->Invalidate(&rcInvalidate);
      }
      if (bCheckBox && m_mapRenderedNodeLine[nLineIndex]->bCheckBoxVisible)
      {
         rcInvalidate.left = nXCheckBoxLeft;
         rcInvalidate.right = nXCheckBoxRight;
         this->Invalidate(&rcInvalidate);
      }
      if (bIcon && m_mapRenderedNodeLine[nLineIndex]->smIcon.size())
      {
         rcInvalidate.left = nXIconLeft;
         rcInvalidate.right = nXIconRight;
         this->Invalidate(&rcInvalidate);
      }
      if (bText && m_mapRenderedNodeLine[nLineIndex]->strColumn[0].length()>0)
      {
         rcInvalidate.left = nXTextLeft;
         rcInvalidate.right = nXTextRight;
         this->Invalidate(&rcInvalidate);
      }
   }
}

void CControlTreeListView::Redraw(CNode *pNode,bool bExpandTab,bool bCheckBox,bool bIcon,bool bText,bool bWholeLine)
{
   Redraw(DetermineNodeLineIndex(pNode),bExpandTab,bCheckBox,bIcon,bText,bWholeLine);
}

int CControlTreeListView::DetermineNodeLineIndex(CNode *pNode)
{
   // Determine index of current node
   int nLineIndex = -1;
   for (int i=0; i<static_cast<int>(m_mapRenderedNodeLine.size()); i++)
   {
      if (m_mapRenderedNodeLine[i]==pNode)
      {
         nLineIndex = i;
         break;
      }
   }
   /*
   // Yes, this could potentially happen and can cause
   // a crash. Leave this be!!
   if (nLineIndex==-1)
   return;
   */

   return nLineIndex;
}

void CControlTreeListView::AddColumn(int nColumn,int nColumnWidth,std::string strColumnHeader,bool bRightJustify)
{
   bUsingColumns = true;
   /*
   if (nColumn<m_vnColumnRightOffset.size() && nColumn!=0)
   return;
   */
   //int nColumnNumber = static_cast<int>(m_nColumnWidth.size()) + 1;
   //if (nColumnNumber==2)
   {
      // Since we now have more than one column, make column
      // header visible. Note, it is invisible if only one column.
      //CElementText cetTmp;
      //cetTmp.Init("Tahoma",8,RGB(0,0,0),"Mg");
      //m_nNodeHeight = cetTmp.GetHeight();
      int nPadding = 4;
      m_nColumnHeaderHeight = m_nNodeHeight + nPadding;
   }
   if (nColumn==0)
      m_vnColumnRightOffset[nColumn] = nColumnWidth; // Should already exist
   //m_vnColumnRightOffset.push_back(nColumnWidth);
   else
      m_vnColumnRightOffset.push_back(m_vnColumnRightOffset[nColumn-1] + nColumnWidth);
   //m_vnColumnRightOffset[nColumn] = m_vnColumnRightOffset[nColumn-1] + nColumnWidth;

   //m_vstrColumnHeader[nColumn] = strColumnHeader;
   m_vstrColumnHeader.push_back(strColumnHeader);

   m_vbRightJustify.push_back(bRightJustify);
}

void CControlTreeListView::GetColumnRect(int nColumn,RECT *rcColumn)
{
   SCROLLINFO si;

   // Get and Save Scroll Positions
   si.cbSize = sizeof(SCROLLINFO);
   si.fMask = SIF_POS;
   GetScrollInfo(this->GetHwnd(),SB_HORZ,&si);
   int nHorzPos = si.nPos;
   //si.fMask = SIF_POS;
   //GetScrollInfo(hwnd,SB_VERT,&si);
   //int nVertPos = si.nPos;

   rcColumn->top = m_nColumnHeaderHeight;
   rcColumn->bottom = this->GetHeight();
   rcColumn->left = 0;
   if (nColumn==0)
      rcColumn->left = 0;
   else
      rcColumn->left = m_vnColumnRightOffset[nColumn-1];
   rcColumn->left -= nHorzPos;
   rcColumn->right = rcColumn->left + GetColumnWidth(nColumn);
   /*
   if (rcColumn->left<0)
   rcColumn->left = 0;
   if (rcColumn->right>this->GetWidth())
   rcColumn->right = this->GetWidth();
   */
   //rcColumn->bottom += 1;
   //rcColumn->right += 1;
}

int CControlTreeListView::GetColumnWidth(int nColumn)
{
   if (nColumn==0)
      return m_vnColumnRightOffset[0];
   else if (nColumn>=1 && nColumn<static_cast<int>(m_vnColumnRightOffset.size()))
   {
      return (m_vnColumnRightOffset[nColumn]-m_vnColumnRightOffset[nColumn-1]);
   }
   else
      return 0;
   return 0;
}

void CControlTreeListView::SelectDragRectangleNodes()
{
   if (m_nWhatsCaptured!=m_enumWC_DragRect)
      return;

   // Select those within range
   int xLeft = (m_xDragRectanglePos<m_xDragRectangleAnchor)?m_xDragRectanglePos:m_xDragRectangleAnchor;
   //int xRight = (m_xDragRectanglePos>m_xDragRectangleAnchor)?m_xDragRectanglePos:m_xDragRectangleAnchor;
   int yTop = (m_yDragRectanglePos<m_yDragRectangleAnchor)?m_yDragRectanglePos:m_yDragRectangleAnchor;
   int yBottom = (m_yDragRectanglePos>m_yDragRectangleAnchor)?m_yDragRectanglePos:m_yDragRectangleAnchor;
   int yPosTmp = yTop;
   //yPosTmp -= m_nColumnHeaderHeight;
   int nItemIndexTop = yPosTmp/m_nNodeHeight;
   yPosTmp = yBottom;
   //yPosTmp -= m_nColumnHeaderHeight;
   int nItemIndexBottom = yPosTmp/m_nNodeHeight;

   if (bUsingColumns==false)
   {
      for (int i=0; i<static_cast<int>(m_mapRenderedNodeLine.size()); i++)
      {
         if (i>=nItemIndexTop && i<=nItemIndexBottom &&
            xLeft<m_mapRenderedNodeLine[i]->nWidth)
         {
            m_mapRenderedNodeLine[i]->bSelected = true;
            m_pAnchorNode = m_mapRenderedNodeLine[i];
         }
         else
            m_mapRenderedNodeLine[i]->bSelected = false;
      }
   }
   else
   {
      int nxRight;
      for (int i=0; i<static_cast<int>(m_mapRenderedNodeLine.size()); i++)
      {
         if (m_vnColumnRightOffset[0]>m_mapRenderedNodeLine[i]->nWidth)
            nxRight = m_mapRenderedNodeLine[i]->nWidth;
         else
            nxRight = m_vnColumnRightOffset[0];
         //nxRight = (m_mapRenderedNodeLine[i]->nWidth>m_vnColumnRightOffset[0])?m_vnColumnRightOffset[0]:m_mapRenderedNodeLine[i];
         if (i>=nItemIndexTop && i<=nItemIndexBottom &&
            xLeft<nxRight)
         {
            m_mapRenderedNodeLine[i]->bSelected = true;
            m_pAnchorNode = m_mapRenderedNodeLine[i];
         }
         else
            m_mapRenderedNodeLine[i]->bSelected = false;
      }
   }
}

void CControlTreeListView::CNode::RemoveChildren()
{
   RemoveChildrenRecursively(this);
   this->vpChild.clear();
}

void CControlTreeListView::CNode::RemoveChild(std::string strNodeID)
{
   CNode *pNode = this->Child[strNodeID];

   if (pNode==NULL)
      return; // strNodeID Not found

   // Erase this child
   std::vector<CNode *>::iterator vpChildIter;
   for (vpChildIter=this->vpChild.begin(); vpChildIter!=this->vpChild.end(); vpChildIter++)
   {
      if (pNode == *vpChildIter)
      {
         this->vpChild.erase(vpChildIter);
         break;
      }
   }	
   this->Child.erase(strNodeID);

   // Cycle through and recursively delete the children of this child,
   // as well as this child itself
   RemoveChildrenRecursively(pNode);
}

void CControlTreeListView::CNode::RemoveChildrenRecursively(CNode *pNode)
{
   for (std::vector<CNode *>::iterator iter=pNode->vpChild.begin(); iter!=pNode->vpChild.end(); iter++)
      RemoveChildrenRecursively(*iter);
   if (pNode->pNodeParent!=NULL) // This is not the root node so go ahead and delete it
   {
      delete pNode;
   }
}

CControlTreeListView::CNode * CControlTreeListView::FindFirstSelected(void)
{
   nFindFirstNextSelectedIndex = -1;

   for (int i=0; i<static_cast<int>(m_mapRenderedNodeLine.size()); i++)
   {
      if (m_mapRenderedNodeLine[i]->bSelected)
      {
         nFindFirstNextSelectedIndex = i;
         break;
      }
   }
   if (nFindFirstNextSelectedIndex == -1)
      return NULL;

   return m_mapRenderedNodeLine[nFindFirstNextSelectedIndex];
}


CControlTreeListView::CNode * CControlTreeListView::FindNextSelected(void)
{
   int nNext = -1;

   for (int i=nFindFirstNextSelectedIndex+1; i<static_cast<int>(m_mapRenderedNodeLine.size()); i++)
   {
      if (m_mapRenderedNodeLine[i]->bSelected)
      {
         nNext = i;
         break;
      }
   }
   if (nNext == -1)
      return NULL;

   nFindFirstNextSelectedIndex = nNext;

   return m_mapRenderedNodeLine[nFindFirstNextSelectedIndex];
}

int CControlTreeListView::GetNumSelected(void)
{
   int nNumSelected = 0;

   for (int i=0; i<static_cast<int>(m_mapRenderedNodeLine.size()); i++)
   {
      if (m_mapRenderedNodeLine[i]->bSelected)
         nNumSelected++;
   }

   return nNumSelected;
}

void CControlTreeListView::CNode::EditColumnData(int nColumn,std::string strText,std::string strSortValue)
{
   this->strColumn[nColumn] = strText;

   //this->ColumnSortType[nColumn] = CCTLV_SORT_STRING;


   if (strSortValue=="")
      this->strColumnSortValue[nColumn].clear();
   else
   {
      this->strColumnSortValue[nColumn] = strSortValue;
      /*
      std::string::iterator Iter;
      bool bIsNumber = true;
      for (Iter=strSortValue.begin(); Iter!=strSortValue.end(); Iter++)
      {
      if ( (*Iter)<'0' && (*Iter)>'9')
      bIsNumber = false;
      }
      if (bIsNumber)
      {
      this->dColumnSortValue[nColumn] = static_cast<double>(strtod(strSortValue.c_str(),NULL));
      }
      */
   }
}
/*
void CControlTreeListView::CNode::EditColumnData(int nColumn,std::string strText,double dSortValue)
{
this->strColumn[nColumn] = strText;
this->strColumnSortValue[nColumn] = " ";

this->ColumnSortType[nColumn] = CCTLV_SORT_DOUBLE;

this->dColumnSortValue[nColumn] = dSortValue;
}
*/

void CControlTreeListView::CNode::SortChildren(int nColumn,bool bToggleSortDirection)
{
   std::string::iterator strIter;
   double dValA = 0.0;
   double dValB = 0.0;
   std::string strLeadingNumberA;
   std::string strLeadingNumberB;
   std::string strSortA;
   std::string strSortB;
   int nASortType, nBSortType;

   if (bToggleSortDirection)
      bSortReverseToggle = !bSortReverseToggle;

   CNode *pNodeTmp;

   // We sort in three ways. These can be reversed.
   //
   // 1. Empty sort value strings get sorted by column[0] (display name)
   // 2. Number sort values get sorted
   // 3. String sort values get sorted (case-insensitive)
   //
   // This makes 9 possible sort scenarios plus reverse = 18.

   for (int i=static_cast<int>(vpChild.size())-1; i>=0; i--)
   {
      for (int j=1; j<=i; j++)
      {
         // If sort value is empty, sort by value of column 0
         if (vpChild[j-1]->strColumnSortValue[nColumn].empty() ||
            vpChild[j-1]->strColumnSortValue[nColumn]=="" )
         {
            strSortA = vpChild[j-1]->strColumnSortValue[0];
            nASortType = 0; // Empty
         }
         else
         {
            strSortA = vpChild[j-1]->strColumnSortValue[nColumn];

            strLeadingNumberA = "";
            dValA = 0.0;
            for (strIter=strSortA.begin(); strIter!=strSortA.end(); strIter++)
            {
               if ( (*strIter)>='0' && (*strIter)<='9')
                  strLeadingNumberA += (*strIter);
               else
                  break;
            }
            if (strLeadingNumberA.size()>0)
            {
               dValA = static_cast<double>(strtod(strLeadingNumberA.c_str(),NULL));
               nASortType = 1; // Number
            }
            else
            {
               nASortType = 2; // Regular string sort
            }
         }


         if (vpChild[j]->strColumnSortValue[nColumn].empty() ||
            vpChild[j]->strColumnSortValue[nColumn]=="" )
         {
            strSortB = vpChild[j]->strColumnSortValue[0];
            nBSortType = 0; // Empty
         }
         else
         {
            strSortB = vpChild[j]->strColumnSortValue[nColumn];

            strLeadingNumberB = "";
            dValB = 0.0;
            for (strIter=strSortB.begin(); strIter!=strSortB.end(); strIter++)
            {
               if ( (*strIter)>='0' && (*strIter)<='9')
                  strLeadingNumberB += (*strIter);
               else
                  break;
            }
            if (strLeadingNumberB.size()>0)
            {
               dValB = static_cast<double>(strtod(strLeadingNumberB.c_str(),NULL));
               nBSortType = 1; // Number
            }
            else
            {
               nBSortType = 2; // Regular string sort
            }
         }


         if (nASortType==1 && nBSortType==1)
         {
            if ( dValA > dValB )
            {
               pNodeTmp = vpChild[j-1];
               vpChild[j-1] = vpChild[j];
               vpChild[j] = pNodeTmp;
            }
         }
         else if (nASortType==0 && nBSortType==0)
         {
            if ( strSortA > strSortB )
            {
               pNodeTmp = vpChild[j-1];
               vpChild[j-1] = vpChild[j];
               vpChild[j] = pNodeTmp;
            }
         }
         else
         {
            if ( strSortA > strSortB )
            {
               pNodeTmp = vpChild[j-1];
               vpChild[j-1] = vpChild[j];
               vpChild[j] = pNodeTmp;
            }
         }

         if (bSortReverseToggle)
         {
            pNodeTmp = vpChild[j-1];
            vpChild[j-1] = vpChild[j];
            vpChild[j] = pNodeTmp;
         }
      }
   }
}