Click here to Skip to main content
15,897,371 members
Articles / Desktop Programming / MFC

Easy Profiler - Compile-time Profiler for C++

Rate me:
Please Sign up or sign in to vote.
4.94/5 (34 votes)
14 Nov 2009Apache23 min read 124.1K   5.1K   154  
Easily instrument your code, visualize, interpret results, track optimization, compare and decide.
// XHtmlTree.cpp  Version 1.6 - article available at www.codeproject.com
//
// Author:  Hans Dietrich
//          hdietrich@gmail.com
//
// History
//     Version 1.6 - 2007 December 19
//     - Bug fixes and enhancements;  see CodeProject article for details
//
//     Version 1.5 - 2007 November 7
//     - Bug fixes and enhancements;  see CodeProject article for details
//
//     Version 1.4 - 2007 November 4
//     - Bug fixes and enhancements;  see CodeProject article for details
//
//     Version 1.3 - 2007 October 16
//     - Bug fixes and enhancements;  see CodeProject article for details
//
//     Version 1.2 - 2007 October 7
//     - Bug fixes and enhancements;  see CodeProject article for details
//
//     Version 1.1 - 2007 October 6
//     - Bug fixes and enhancements;  see CodeProject article for details
//
//     Version 1.0 - 2007 August 9
//     - Initial public release
//
// License:
//     This software is released into the public domain.  You are free to use
//     it in any way you like, except that you may not sell this source code.
//
//     This software is provided "as is" with no expressed or implied warranty.
//     I accept no liability for any damage or loss of business that this 
//     software may cause.
//
///////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#pragma warning(disable : 4786)
#include "XHtmlTree.h"
#include "XNamedColors.h"
#include "XHtmlDraw.h"
#include "CreateCheckboxImageList.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#ifndef __noop
#if _MSC_VER < 1300
#define __noop ((void)0)
#endif
#endif

#undef TRACE
#define TRACE __noop


//=============================================================================
// if you want to see the TRACE output, uncomment this line:
//#include "XTrace.h"
//=============================================================================


//=============================================================================
// REGISTERED XHTMLTREE MESSAGES
//=============================================================================
UINT WM_XHTMLTREE_CHECKBOX_CLICKED = ::RegisterWindowMessage(_T("WM_XHTMLTREE_CHECKBOX_CLICKED"));
UINT WM_XHTMLTREE_ITEM_EXPANDED    = ::RegisterWindowMessage(_T("WM_XHTMLTREE_ITEM_EXPANDED"));
UINT WM_XHTMLTREE_DISPLAY_TOOLTIP  = ::RegisterWindowMessage(_T("WM_XHTMLTREE_DISPLAY_TOOLTIP"));
UINT WM_XHTMLTREE_INIT_TOOLTIP     = ::RegisterWindowMessage(_T("WM_XHTMLTREE_INIT_TOOLTIP"));
#ifdef XHTMLDRAGDROP
UINT WM_XHTMLTREE_BEGIN_DRAG       = ::RegisterWindowMessage(_T("WM_XHTMLTREE_BEGIN_DRAG"));
UINT WM_XHTMLTREE_END_DRAG         = ::RegisterWindowMessage(_T("WM_XHTMLTREE_END_DRAG"));
UINT WM_XHTMLTREE_DROP_HOVER       = ::RegisterWindowMessage(_T("WM_XHTMLTREE_DROP_HOVER"));
#endif // XHTMLDRAGDROP
#ifdef XHTMLTREE_DEMO
UINT WM_XHTMLTREE_SCROLL_SPEED     = ::RegisterWindowMessage(_T("WM_XHTMLTREE_SCROLL_SPEED"));
#endif // XHTMLTREE_DEMO

#pragma warning(disable : 4996)	// disable bogus deprecation warning

const UINT HOT_TIMER			= 1;
const UINT LBUTTONDOWN_TIMER	= 2;
const UINT CTRL_UP_TIMER		= 3;
const UINT SHIFT_UP_TIMER		= 4;
const UINT SELECT_TIMER			= 5;
const UINT TOOLTIP_BASE_ID		= 10000;
const DWORD MIN_HOVER_TIME		= 1500;		// 1.5 seconds
const int SCROLL_ZONE			= 16;		// pixels for scrolling

int XHTMLTREEDATA::nCount		= 0;

//=============================================================================
BEGIN_MESSAGE_MAP(CXHtmlTree, CTreeCtrl)
//=============================================================================
	//{{AFX_MSG_MAP(CXHtmlTree)
	ON_WM_DESTROY()
	ON_WM_ERASEBKGND()
	ON_WM_MOUSEMOVE()
	ON_WM_SYSCOLORCHANGE()
	ON_WM_TIMER()
	ON_WM_LBUTTONDOWN()
	ON_WM_SIZE()
	ON_WM_RBUTTONDOWN()
	ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
	ON_NOTIFY_REFLECT_EX(NM_CLICK, OnClick)
	ON_NOTIFY_REFLECT_EX(NM_DBLCLK, OnDblclk)
	ON_NOTIFY_REFLECT_EX(TVN_BEGINLABELEDIT, OnBeginlabeledit)
	ON_NOTIFY_REFLECT_EX(TVN_ENDLABELEDIT, OnEndlabeledit)
	//}}AFX_MSG_MAP

#ifdef XHTMLDRAGDROP
	ON_NOTIFY_REFLECT(TVN_BEGINDRAG, OnBegindrag)
#endif // XHTMLDRAGDROP

#ifdef XHTMLTOOLTIPS
	ON_NOTIFY(UDM_TOOLTIP_DISPLAY, NULL, OnDisplayTooltip)	
#else
	ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
	ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
#endif // XHTMLTOOLTIPS

	ON_NOTIFY_REFLECT_EX(TVN_SELCHANGED, OnSelchanged)
	ON_NOTIFY_REFLECT_EX(TVN_SELCHANGING, OnSelchanging)
END_MESSAGE_MAP()

//=============================================================================
CXHtmlTree::CXHtmlTree()
//=============================================================================
  :	m_bDestroyingTree(FALSE),
	m_bFirstTime(TRUE),
	m_bSmartCheck(FALSE),
	m_bCheckBoxes(FALSE),
	m_bSelectFollowsCheck(TRUE),
	m_bReadOnly(FALSE),
	m_bHtml(TRUE),
	m_bStripHtml(FALSE),
	m_bLogFont(FALSE),
	m_bToolTip(FALSE),
	m_bDragging(FALSE),
	m_bAutoScroll(TRUE),
	m_bImages(TRUE),
	m_pToolTip(0),
	m_hAnchorItem(0),
	m_hHotItem(0),
	m_hPreviousItem(0),
	m_hItemButtonDown(0),
	m_hPreviousDropItem(0),
	m_nPadding(0),
	m_nImageHeight(16),
	m_nToolCount(0),
	m_nDefaultTipWidth(0),
	m_nScrollTime(0),
	m_crCustomWindow(COLOR_NONE),
	m_crCustomWindowText(COLOR_NONE),
	m_nHorzPos(0),
	m_dwDropHoverTime(0),
	m_nNoDropCursor(0),
	m_nDropCopyCursor(0),
	m_nDropMoveCursor(0),
	m_hNoDropCursor(0),
	m_hDropCopyCursor(0),
	m_hDropMoveCursor(0),
	m_hPreviousCursor(0),
	m_hCurrentCursor(0),
	m_dwDragOps(XHTMLTREE_DO_DEFAULT)
{
	TRACE(_T("in CXHtmlTree::CXHtmlTree\n"));
	memset(&m_lf, 0, sizeof(m_lf));
	SetColors();

	m_hPreviousCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
}

//=============================================================================
CXHtmlTree::~CXHtmlTree()
//=============================================================================
{
	if (m_pToolTip)
		delete m_pToolTip;
	m_pToolTip = 0;

	if (m_StateImage.GetSafeHandle())
		m_StateImage.DeleteImageList();

	if (m_hNoDropCursor)
		DestroyCursor(m_hNoDropCursor);
	m_hNoDropCursor = NULL;

	if (m_hDropCopyCursor)
		DestroyCursor(m_hDropCopyCursor);
	m_hDropCopyCursor = NULL;

	if (m_hDropMoveCursor)
		DestroyCursor(m_hDropMoveCursor);
	m_hDropMoveCursor = NULL;

	TRACE(_T("XHTMLTREEDATA::nCount=%d\n"), XHTMLTREEDATA::nCount);
}

//=============================================================================
CXHtmlTree& CXHtmlTree::Initialize(BOOL bCheckBoxes /*= FALSE*/, 
								   BOOL bToolTip /*= FALSE*/)
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::Initialize\n"));
	m_bDestroyingTree = TRUE;

	DeleteAllItems();

	if (m_pToolTip)
		delete m_pToolTip;
	m_pToolTip = 0;

	SetImageList(NULL, TVSIL_STATE);

	if (m_StateImage.GetSafeHandle())
		m_StateImage.DeleteImageList();

	m_bCheckBoxes         = bCheckBoxes;
	m_bToolTip            = bToolTip;
	m_bSmartCheck         = FALSE;
	m_bSelectFollowsCheck = TRUE;
	m_bHtml               = TRUE;
	m_bLogFont            = FALSE;
	m_nPadding            = 0;
	m_nImageHeight        = 16;
	m_bFirstTime          = TRUE;
	m_hAnchorItem         = 0;
	m_hHotItem            = 0;
	memset(&m_lf, 0, sizeof(m_lf));

	SetColors();

	if (m_bToolTip)
	{
		TRACE(_T("creating tooltip\n"));
#ifdef XHTMLTOOLTIPS
		m_pToolTip = new CPPToolTip;
#else
		m_pToolTip = new CToolTipCtrl;
#endif // XHTMLTOOLTIPS
		if (m_pToolTip)
		{
			m_pToolTip->Create(this);
		}
	}

	if (m_bCheckBoxes)
		CreateCheckboxImages();

	m_bDestroyingTree = FALSE;

	return *this;
}

//=============================================================================
// PreCreateWindow() is called when CXHtmlTree is used in a view.
//
BOOL CXHtmlTree::PreCreateWindow(CREATESTRUCT& cs) 
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::PreCreateWindow\n"));

	// style must include "no tooltips"
	cs.style |= TVS_NOTOOLTIPS;

	return CTreeCtrl::PreCreateWindow(cs);
}

//=============================================================================
void CXHtmlTree::PreSubclassWindow()
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::PreSubclassWindow\n"));
	DWORD dwStyle = GetStyle();

	if (dwStyle & TVS_CHECKBOXES)
		m_bCheckBoxes = TRUE;

	// these styles must not be set
	ModifyStyle(TVS_CHECKBOXES, TVS_NOTOOLTIPS);

#ifdef XHTMLDRAGDROP
	ModifyStyle(TVS_DISABLEDRAGDROP, 0);
#else
	ModifyStyle(0, TVS_DISABLEDRAGDROP);
#endif // XHTMLDRAGDROP

	if (m_bCheckBoxes)
		CreateCheckboxImages();

	CTreeCtrl::PreSubclassWindow();
}

//=============================================================================
BOOL CXHtmlTree::PreTranslateMessage(MSG* pMsg) 
//=============================================================================
{
	// allow edit control to receive messages, if 
	// label is being edited
	if (GetEditControl() && 
		((pMsg->message == WM_CHAR) ||
		 (pMsg->message == WM_KEYDOWN) ||
		 GetKeyState(VK_CONTROL)))
	{
		::TranslateMessage(pMsg);
		::DispatchMessage(pMsg);
		return TRUE;
	}

	if (m_pToolTip && IsWindow(m_pToolTip->m_hWnd))
	{
		m_pToolTip->RelayEvent(pMsg);
	}

	//=========================================================================
	// WM_CHAR
	//=========================================================================
	if (pMsg->message == WM_CHAR)
	{
		if ((pMsg->wParam == VK_SPACE) && m_bCheckBoxes && !m_bReadOnly)
		{
			HTREEITEM hItem = GetSelectedItem();
			
			XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);
	
			if (pXTCD && !pXTCD->bSeparator)		//+++1.6
			{
				SetCheck(hItem, !pXTCD->bChecked);
			}

			return TRUE;
		}
	}

	//=========================================================================
	// WM_KEYDOWN
	//=========================================================================
	if (pMsg->message == WM_KEYDOWN)
	{
		TRACE(_T("WM_KEYDOWN: lParam=0x%X\n"), pMsg->lParam);

#ifdef XHTMLDRAGDROP
		//=====================================================================
		// VK_ESCAPE while dragging
		//=====================================================================
		if ((pMsg->wParam == VK_ESCAPE) && m_bDragging)
		{
			TRACE(_T("ESC seen during drag\n"));
			EndDragScroll();
			SendRegisteredMessage(WM_XHTMLTREE_END_DRAG, 0, 0);
			return TRUE;
		}

		//=====================================================================
		// VK_CONTROL while dragging
		//=====================================================================
		if ((pMsg->wParam == VK_CONTROL) && 
			(m_dwDragOps & XHTMLTREE_DO_CTRL_KEY))
		{
			// check if Ctrl key down for first time
			if ((pMsg->lParam & 0x40000000) == 0)
			{
				if (IsOverItem() && m_bDragging)
				{
					SetDragCursor();
				}

				SetTimer(CTRL_UP_TIMER, 100, NULL);
			}
		}

		//=====================================================================
		// VK_SHIFT while dragging
		//=====================================================================
		if ((pMsg->wParam == VK_SHIFT) && 
			(m_dwDragOps & XHTMLTREE_DO_SHIFT_KEY))
		{
			// check if Shift key down for first time
			if ((pMsg->lParam & 0x40000000) == 0)
			{
				HTREEITEM hItem = IsOverItem();
				if (hItem && m_bDragging)
				{
					if (IsSeparator(hItem))					//+++1.6
					{
						SelectDropTarget(NULL);
						SetInsertMark(0, 0);
						SetInsertMark(hItem, TRUE);
					}
					else
					{
						SetInsertMark(0, 0);
						SelectDropTarget(hItem);
					}
				}
				SetTimer(SHIFT_UP_TIMER, 100, NULL);
			}
		}
#endif // XHTMLDRAGDROP

		//=====================================================================
		// VK_RIGHT or VK_LEFT
		//=====================================================================
		if ((pMsg->wParam == VK_RIGHT) || (pMsg->wParam == VK_LEFT))
		{
			BOOL bRight = pMsg->wParam == VK_RIGHT;

			HTREEITEM hItem = GetSelectedItem();
			
			XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);
		
			if (pXTCD && pXTCD->bEnabled && pXTCD->nChildren)
			{
				BOOL bExpanded = pXTCD->bExpanded;
				BOOL bOldExpanded = pXTCD->bExpanded;
				if (!bExpanded && bRight)
					bExpanded = TRUE;
				if (bExpanded && !bRight)
					bExpanded = FALSE;
				if (bOldExpanded != bExpanded)
				{
					Expand(hItem, bExpanded ? TVE_EXPAND : TVE_COLLAPSE);
					return TRUE;
				}
			}
		}

		//=====================================================================
		// VK_DOWN or VK_UP
		//=====================================================================
		if ((pMsg->wParam == VK_DOWN) || (pMsg->wParam == VK_UP))
		{
			BOOL bDown = pMsg->wParam == VK_DOWN;

			HTREEITEM hItem = GetSelectedItem();
			
			if (hItem)
			{
				HTREEITEM hItemNew = bDown ? GetNextVisibleItem(hItem) : 
											 GetPrevVisibleItem(hItem);

				XHTMLTREEDATA *pXTCD = NULL;

				while (hItemNew)
				{
					pXTCD = GetItemDataStruct(hItemNew);
			
					if (pXTCD && pXTCD->bEnabled)
						break;

					// next item is not enabled, just skip it
					hItemNew = bDown ? GetNextVisibleItem(hItemNew) : 
									   GetPrevVisibleItem(hItemNew);
				}

				if (hItemNew)
				{
					SelectItem(hItemNew);
					return TRUE;
				}
			}
		}

		//=====================================================================
		// VK_MULTIPLY
		//=====================================================================
		if (pMsg->wParam == VK_MULTIPLY)
		{
			HTREEITEM hItem = GetSelectedItem();
			
			if (hItem)
			{
				ExpandBranch(hItem);
				EnsureVisible(hItem);
				SendMessage(WM_HSCROLL, SB_LEFT);
				return TRUE;
			}
		}

		//=====================================================================
		// VK_SUBTRACT  VK_ADD
		//=====================================================================
		UINT nCode = 0;
		switch (pMsg->wParam)
		{
			default:			break;
			case VK_SUBTRACT:	nCode = TVE_COLLAPSE; break;
			case VK_ADD:		nCode = TVE_EXPAND; break;
		}
		if (nCode)
		{
			HTREEITEM hItem = GetSelectedItem();
			if (hItem)
			{
				Expand(hItem, nCode);
				return TRUE;	// skip default processing
			}
		}

	}
	return CTreeCtrl::PreTranslateMessage(pMsg);
}

//=============================================================================
void CXHtmlTree::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
//=============================================================================
{
	NMTVCUSTOMDRAW* pCD = reinterpret_cast<NMTVCUSTOMDRAW*>(pNMHDR);

	CDC* pDC = CDC::FromHandle(pCD->nmcd.hdc);

	HTREEITEM hItem = reinterpret_cast<HTREEITEM> (pCD->nmcd.dwItemSpec);

	// Take the default processing unless we set this to something else below.
	*pResult = CDRF_DODEFAULT;

	// First thing - check the draw stage. If it's the control's prepaint
	// stage, then tell Windows we want messages for every item.

	//=========================================================================
	if (pCD->nmcd.dwDrawStage == CDDS_PREPAINT)	// before the painting cycle begins
	//=========================================================================
	{
		*pResult = CDRF_NOTIFYITEMDRAW /*| CDRF_NOTIFYPOSTPAINT*/;
	}
	//=========================================================================
	else if (pCD->nmcd.dwDrawStage == CDDS_ITEMPREPAINT)	// before an item is drawn
	//=========================================================================
	{
		pCD->clrText = pCD->clrTextBk;	// don't want default drawing -
										// set text color = background color
		if (hItem)
		{
			CRect rectItem1;
			GetItemRect(hItem, &rectItem1, FALSE);	// get rect for item
			if (!IsBadRect(rectItem1))
			{
				CBrush brush(m_crWindow);
				pDC->FillRect(&rectItem1, &brush);		// erase entire background
			}
		}
		*pResult = CDRF_NOTIFYPOSTPAINT | CDRF_NEWFONT;
	}
	//=========================================================================
	else if (pCD->nmcd.dwDrawStage == CDDS_ITEMPOSTPAINT)	// after an item has been drawn
	//=========================================================================
	{
		// by doing the drawing at this stage we avoid having to draw lines, etc.

		if (m_bFirstTime)
		{
			if (m_bToolTip)
				CreateToolTipsForTree();

			CImageList *pImageList = GetImageList(TVSIL_NORMAL);
			if (!pImageList)	//+++1.5
			{
				TRACE(_T("WARNING  no image list, setting m_bImages to FALSE\n"));
				m_bImages = FALSE;
			}
		}

		m_bFirstTime = FALSE;

		CRect rectItem;
		GetItemRect(hItem, &rectItem, FALSE);		// get rect for entire item
		CRect rectText;
		GetItemRect(hItem, &rectText, TRUE);		// get rect for text
		rectText.right = rectItem.right;

		XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

		// set up colors

		COLORREF crText = m_crWindowText;
		COLORREF crAnchorText = m_crAnchorText;
		COLORREF crBackground = m_crWindow;
		COLORREF crTextBackground = m_crWindow;

		BOOL bEnabled = TRUE;

		HTREEITEM hSelected = GetSelectedItem();

		if (pXTCD)
		{
			// try to use colors specified for this item
			pXTCD->ds.bIgnoreColorTag = FALSE;
			crText  = pXTCD->ds.crText;
			crTextBackground = pXTCD->ds.crTextBackground;
			crBackground = pXTCD->ds.crBackground;
			//TRACE(_T("crText=%08X  crBkgnd=%08X ~~~~~\n"), crText, crBackground);
			bEnabled = pXTCD->bEnabled;
			if ((hItem == hSelected) || 
				(GetItemState(hItem, TVIF_STATE) & TVIS_DROPHILITED))
			{
				crTextBackground = m_crHighlight;
			}
			else
			{
			 	//crTextBackground = COLOR_NONE;
			}

			if (!bEnabled)
			{
				crText = crAnchorText = m_crGrayText;
				pXTCD->ds.bIgnoreColorTag = TRUE;
			}
			else if ((hItem == hSelected) || 
				(GetItemState(hItem, TVIF_STATE) & TVIS_DROPHILITED))
			{
				crText = crAnchorText = m_crHighlightText;
				pXTCD->ds.bIgnoreColorTag = TRUE;
			}
			else
			{
			}
		}

		if (crBackground == COLOR_NONE)
			crBackground = m_crWindow;

		if (pXTCD && pXTCD->bSeparator)			//+++1.6
		{
			if (crText == COLOR_NONE)
				crText = m_crSeparator;

			if (hItem == hSelected)
				crBackground = m_crHighlight;

			DrawSeparator(pDC, hItem, crText, crBackground, rectText);
		}
		else
		{
			if (crText == COLOR_NONE)
				crText = m_crWindowText;

			CString strText = GetItemText(hItem);

			BOOL bContainsHtml = FALSE;

			// check for html tag and char entity
			if (strText.FindOneOf(_T("<&")) >= 0)
				bContainsHtml = TRUE;

			if (m_bStripHtml && bContainsHtml)
				strText = GetItemText(hItem, TRUE);

#ifdef XHTMLHTML
			if (m_bHtml && bContainsHtml)
				DrawItemTextHtml(pDC, hItem, strText, crText, crTextBackground,
									crBackground, crAnchorText, rectText);
			else
#endif // XHTMLHTML
				DrawItemText(pDC, hItem, strText, crText, crTextBackground, crBackground, rectText);
		}
	
		//*pResult = CDRF_SKIPDEFAULT;	// We've painted everything.
	}
}

//=============================================================================
BOOL CXHtmlTree::SelectItem(HTREEITEM hItem)
//=============================================================================
{
	HTREEITEM hPrevItemSel = GetSelectedItem();

	if (hItem == hPrevItemSel)
		return TRUE;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD && pXTCD->bEnabled)
	{
		NMTREEVIEW nmtv = { 0 };
		
		nmtv.hdr.hwndFrom = m_hWnd;
		nmtv.hdr.idFrom = GetDlgCtrlID();
		nmtv.hdr.code = TVN_SELCHANGED;
		nmtv.itemNew.hItem = hItem;

		CWnd *pWnd = GetParent();
		if (!pWnd)
			pWnd = GetOwner();
		if (pWnd && ::IsWindow(pWnd->m_hWnd))
		{
			pWnd->SendMessage(WM_NOTIFY, GetDlgCtrlID(), (LPARAM)&nmtv);
		}
	}
	else
	{
		if (hPrevItemSel)
			hItem = hPrevItemSel;
		else
			return TRUE;
	}

	return CTreeCtrl::SelectItem(hItem);
}

//=============================================================================
BOOL CXHtmlTree::IsSelected(HTREEITEM hItem)
//=============================================================================
{
	BOOL rc = FALSE;

	if (hItem == GetSelectedItem())
		rc = TRUE;

	return rc;
}

//=============================================================================
BOOL CXHtmlTree::IsEnabled(HTREEITEM hItem)
//=============================================================================
{
	BOOL rc = FALSE;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		rc = pXTCD->bEnabled;
	}

	return rc;
}

//=============================================================================
BOOL CXHtmlTree::IsExpanded(HTREEITEM hItem)
//=============================================================================
{
	BOOL rc = FALSE;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		rc = pXTCD->bExpanded && ItemHasChildren(hItem);
	}

	return rc;
}

//=============================================================================
BOOL CXHtmlTree::IsSeparator(HTREEITEM hItem)			//+++1.6
//=============================================================================
{
	BOOL rc = FALSE;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		rc = pXTCD->bSeparator;
	}

	return rc;
}

//=============================================================================
BOOL CXHtmlTree::GetItemPath(HTREEITEM hItem, CStringArray& sa, CPtrArray& items)
//=============================================================================
{
	BOOL rc = FALSE;

	sa.RemoveAll();
	items.RemoveAll();

	if (hItem == NULL)
		hItem = GetRootItem();

	if (hItem)
	{
		CStringArray path;
		CPtrArray htreeitems;

		// get the path in reverse order
		while (hItem)
		{
			CString strText = GetItemText(hItem);

#ifdef XHTMLHTML
			// remove html tags
			CXHtmlDraw hd;
			TCHAR s[200];
			hd.GetPlainText(strText, s, sizeof(s)/sizeof(TCHAR)-1);
			strText = s;
#endif // XHTMLHTML
			path.Add(strText);
			htreeitems.Add(hItem);
			hItem = GetParentItem(hItem);
		}

		int n = (int) path.GetSize();
		if (n)
		{
			// return path in correct order
			for (int i = n-1; i >= 0 ; i--)
			{
				sa.Add(path[i]);
				items.Add(htreeitems[i]);
			}

			rc = TRUE;
		}
	}

	return rc;
}

//=============================================================================
int CXHtmlTree::GetDefaultTipWidth()
//=============================================================================
{
	int nWidth = 200;

	if (m_nDefaultTipWidth == 0)
	{
		// no default width specified, use a heuristic
		CWnd *pWnd = GetParent();
		if (!pWnd)
			pWnd = GetOwner();

		if (pWnd && ::IsWindow(pWnd->m_hWnd))
		{
			CRect rectParent;
			pWnd->GetWindowRect(&rectParent);
			CRect rectTree;
			GetWindowRect(&rectTree);
			int nWidthTree = (3 * rectTree.Width()) / 4;
			int nWidthParent = rectParent.Width() / 2;
			nWidth = (nWidthTree < 200) ? nWidthParent : nWidthTree;
		}
	}
	else
	{
		nWidth = m_nDefaultTipWidth;
	}

	return nWidth;
}

//=============================================================================
void CXHtmlTree::CreateToolTipsForTree()
//=============================================================================
{
	if ((m_pToolTip == 0) || (!IsWindow(m_pToolTip->m_hWnd)))
		return;

#ifdef XHTMLTOOLTIPS
	m_pToolTip->SetNotify(TRUE);
#endif // XHTMLTOOLTIPS

	m_pToolTip->SetMaxTipWidth(GetDefaultTipWidth());

	// first delete all existing tools
	int nCount = m_nToolCount; //m_pToolTip->GetToolCount();
	for (int j = 0; j < nCount; j++)
		m_pToolTip->DelTool(this, TOOLTIP_BASE_ID+j);
	m_nToolCount = 0;

	CRect rect; 
	GetClientRect(rect);

	CRect rectItem;
	GetItemRect(GetFirstVisibleItem(), &rectItem, FALSE);
	ASSERT(!IsBadRect(rectItem));

	rect.top = 0;
	rect.bottom = rect.top + rectItem.Height()-1;
	UINT n = GetVisibleCount();

	// loop to add a tool for each visible item
	UINT i = 0;
	for (i = 0; i < n; i++)
	{
#ifdef XHTMLTOOLTIPS
		PPTOOLTIP_INFO ti;
		ti.nBehaviour = PPTOOLTIP_MULTIPLE_SHOW;
		ti.nIDTool = 123;
		ti.rectBounds = rect;
		ti.sTooltip = "";
		ti.nMask = PPTOOLTIP_MASK_BEHAVIOUR;
		m_pToolTip->AddTool(this, ti);
#else
		m_pToolTip->AddTool(this, LPSTR_TEXTCALLBACK, rect, TOOLTIP_BASE_ID+i);
#endif // XHTMLTOOLTIPS
		rect.top = rect.bottom+1;
		rect.bottom = rect.top + rectItem.Height()-1;
	}
	m_nToolCount = i;

	if (m_pToolTip)
	{
		// allow parent to perform custom initializatio of tooltip
		SendRegisteredMessage(WM_XHTMLTREE_INIT_TOOLTIP, 0, (LPARAM)m_pToolTip);
	}
}

//=============================================================================
// GetNormalImageWidth
// returns:  width - if image is specified
//          -width - TV_NOIMAGE is specified for this item
//               0 - no image list
int CXHtmlTree::GetNormalImageWidth(HTREEITEM hItem)
//=============================================================================
{
	int nWidth = 0;

	CImageList *pImageList = GetImageList(TVSIL_NORMAL);

	if (pImageList && hItem)
	{
		// there is an image list
		
		int nImage = TV_NOIMAGE;
		int nSelectedImage = TV_NOIMAGE;
		GetItemImage(hItem, nImage, nSelectedImage);

		IMAGEINFO ii = { 0 };
		if (pImageList->GetImageInfo(0, &ii))	// use first image width
		{
			nWidth = ii.rcImage.right - ii.rcImage.left;
		}

		if (nImage == TV_NOIMAGE)
			nWidth = -nWidth;
	}

	return nWidth;
}

//=============================================================================
BOOL CXHtmlTree::CreateCheckboxImages()
//=============================================================================
{
	CDC *pDC = GetDC();
	ASSERT(pDC);
	BOOL rc = HDCheckboxImageList::CreateCheckboxImageList(pDC, m_StateImage, 
				m_nImageHeight, m_crWindow);
	ReleaseDC(pDC);
	SetImageList(&m_StateImage, TVSIL_STATE);
	return rc;
}

//=============================================================================
int CXHtmlTree::DrawItemText(CDC *pDC, 
							 HTREEITEM hItem, 
							 LPCTSTR lpszText,
							 COLORREF crText, 
							 COLORREF crTextBackground, 
							 COLORREF crBackground, 
							 CRect& rect)
//=============================================================================
{
	ASSERT(pDC);
	ASSERT(hItem);

	if (!pDC || !hItem)
	{
		TRACE(_T("ERROR bad parameters\n"));
		return 0;
	}

	if (IsBadRect(rect))
	{
		return 0;
	}

	int nWidth = 0;

	CRect rectText(rect);

	pDC->FillSolidRect(&rectText, crBackground);

	CString str = lpszText;
	//TRACE(_T("CXHtmlTree::DrawItemText:  crText=%08X  crBkgnd=%08X  <%s> ++++++\n"), crText, crBackground, str);

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD && !str.IsEmpty())
	{
		UINT uFormat = DT_VCENTER | DT_SINGLELINE | DT_LEFT | DT_NOPREFIX;

		CFont *pOldFont = NULL;
		CFont font;

		CFont *pFont = pDC->GetCurrentFont();
		if (pFont)
		{
			LOGFONT lf;
			pFont->GetLogFont(&lf);

			lf.lfWeight    = pXTCD->ds.bBold ? FW_BOLD : FW_NORMAL;
			lf.lfUnderline = (BYTE) pXTCD->ds.bUnderline;
			lf.lfItalic    = (BYTE) pXTCD->ds.bItalic;
			lf.lfStrikeOut = (BYTE) pXTCD->ds.bStrikeThrough;

			font.CreateFontIndirect(&lf);
			pOldFont = pDC->SelectObject(&font);
		}

		pDC->SetTextColor(crText);
		if (crTextBackground == COLOR_NONE)
			pDC->SetBkColor(crBackground);
		else
			pDC->SetBkColor(crTextBackground);

		CRect rectOut(rectText);
		pDC->DrawText(str, &rectOut, uFormat | DT_CALCRECT);
		pDC->DrawText(str, &rectOut, uFormat);
		rectOut.InflateRect(m_nPadding, 0);
		nWidth = rectOut.right;

		pXTCD->ds.nRightX = rectOut.right;

		if (pOldFont)
			pDC->SelectObject(pOldFont);
	}

	return nWidth;
}

#ifdef XHTMLHTML
//=============================================================================
int CXHtmlTree::DrawItemTextHtml(CDC *pDC, 
								 HTREEITEM hItem, 
								 LPCTSTR lpszText,
								 COLORREF crText, 
								 COLORREF crTextBackground, 
								 COLORREF crBackground,
								 COLORREF crAnchorText, 
								 CRect& rect)
//=============================================================================
{
	ASSERT(pDC);
	ASSERT(hItem);

	if (!pDC || !hItem)
	{
		TRACE(_T("ERROR bad parameters\n"));
		return 0;
	}

	if (IsBadRect(rect))
	{
		return 0;
	}

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (!pXTCD)
	{
		TRACE(_T("ERROR no XHTMLTREEDATA\n"));
		return 0;
	}

	COLORREF crTextOld, crTextBackgroundOld, crBackgroundOld, crAnchorTextOld;

	crTextOld = pXTCD->ds.crText;
	crTextBackgroundOld = pXTCD->ds.crTextBackground;
	crBackgroundOld = pXTCD->ds.crBackground;
	crAnchorTextOld = pXTCD->ds.crAnchorText;

	pXTCD->ds.crText         = crText;
	pXTCD->ds.crTextBackground   = crTextBackground;
	pXTCD->ds.crBackground   = crBackground;
	pXTCD->ds.crAnchorText   = crAnchorText;
	pXTCD->ds.rect           = rect;
	pXTCD->ds.bUseEllipsis   = FALSE;

	if (m_bLogFont)
	{
		pXTCD->ds.bLogFont = TRUE;
		memcpy(&pXTCD->ds.lf, &m_lf, sizeof(LOGFONT));
	}

	CString strText = lpszText;//GetItemText(hItem);
	TRACE(_T("in CXHtmlTree::DrawItemTextHtml: <%s> crText=%06X  crBk=%06X\n"), strText, crText, crBackground);

	CXHtmlDraw htmldraw;

	int nWidth = htmldraw.Draw(pDC->m_hDC, strText, &pXTCD->ds, hItem == m_hAnchorItem);

	pXTCD->ds.crText = crTextOld;
	pXTCD->ds.crTextBackground = crTextBackgroundOld;
	pXTCD->ds.crBackground = crBackgroundOld;
	pXTCD->ds.crAnchorText = crAnchorTextOld;

	return nWidth;
}
#endif // XHTMLHTML

//=============================================================================
int CXHtmlTree::DrawSeparator(CDC *pDC,						//+++1.6
							  HTREEITEM hItem, 
							  COLORREF crText, 
							  COLORREF crBackground, 
							  CRect& rect)
//=============================================================================
{
	ASSERT(pDC);
	ASSERT(hItem);

	if (!pDC || !hItem)
	{
		TRACE(_T("ERROR bad parameters\n"));
		return 0;
	}

	if (IsBadRect(rect))
	{
		return 0;
	}

	int nWidth = 0;

	CRect rectSep(rect);
	pDC->FillSolidRect(&rectSep, crBackground);

	//TRACE(_T("CXHtmlTree::DrawItemText:  crText=%08X  crBkgnd=%08X  <%s> ++++++\n"), crText, crBackground, str);

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		CPen pen(PS_SOLID, 1, crText);
		CPen * pOldPen = pDC->SelectObject(&pen);

		TRACE(_T("drawing separator\n"));

		rectSep.right -= 1;
		rectSep.left  -= 2;
		rectSep.top   += rectSep.Height()/2;

		pDC->MoveTo(rectSep.left, rectSep.top);
		pDC->LineTo(rectSep.right, rectSep.top);

		pDC->SelectObject(pOldPen);

		nWidth = rectSep.right;

		pXTCD->ds.nRightX = rectSep.right;
	}

	return nWidth;
}

//=============================================================================
void CXHtmlTree::OnDestroy()
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::OnDestroy\n"));

	DeleteMap();
	CTreeCtrl::OnDestroy();
}

//=============================================================================
void CXHtmlTree::DeleteMap()
//=============================================================================
{
	BOOL bOldDestroyingTree = m_bDestroyingTree;
	m_bDestroyingTree = TRUE;
	int n = (int)m_DataMap.GetCount();
	POSITION pos = m_DataMap.GetStartPosition();
	HTREEITEM hItem = 0;
	XHTMLTREEDATA *pXTCD = NULL;

	if (n > 0)
	{
		do
		{
			m_DataMap.GetNextAssoc(pos, hItem, pXTCD);

			if (hItem && pXTCD)
				delete pXTCD;

			n--;
		} while (pos != NULL);
	}

	ASSERT(n == 0);

	m_DataMap.RemoveAll();

	m_bDestroyingTree = bOldDestroyingTree;
}

//=============================================================================
// GetNextItem - Get next item in sequence (as if tree was completely expanded)
//   see http://www.codeguru.com/Cpp/controls/treeview/treetraversal/article.php/c645
//   hItem   - The reference item
//   Returns - The item immediately below the reference item
HTREEITEM CXHtmlTree::GetNextItem(HTREEITEM hItem)
//=============================================================================
{
	HTREEITEM hItemNext = NULL;

	ASSERT(hItem);

	if (hItem)
	{
		if (ItemHasChildren(hItem))
		{
			hItemNext = GetChildItem(hItem);	// first child
		}

		if (hItemNext == NULL)
		{
			// return next sibling item - go up the tree to find 
			// a parent's sibling if needed.
			while ((hItemNext = GetNextSiblingItem(hItem)) == NULL)
			{
				if ((hItem = GetParentItem(hItem)) == NULL)
					return NULL;
			}
		}
	}

	return hItemNext;
}

//=============================================================================
// GetNextItem  - Get previous item as if outline was completely expanded
// Returns      - The item immediately above the reference item
// hItem        - The reference item
HTREEITEM CXHtmlTree::GetPrevItem(HTREEITEM hItem)
//=============================================================================
{
	HTREEITEM hItemPrev;

	hItemPrev = GetPrevSiblingItem(hItem);
	if (hItemPrev == NULL)
		hItemPrev = GetParentItem(hItem);
	else
		hItemPrev = GetLastItem(hItemPrev);
	return hItemPrev;
}

//=============================================================================
// GetLastItem  - Gets last item in the branch
// Returns      - Last item
// hItem        - Node identifying the branch. NULL will 
//                return the last item in outine
HTREEITEM CXHtmlTree::GetLastItem(HTREEITEM hItem)
//=============================================================================
{
	// Last child of the last child of the last child ...
	HTREEITEM hItemNext;
	
	if (hItem == NULL)
	{
		// Get the last item at the top level
		hItemNext = GetRootItem();
		while (hItemNext)
		{
			hItem = hItemNext;
			hItemNext = GetNextSiblingItem(hItemNext);
		}
	}
	
	while (ItemHasChildren(hItem))
	{
		hItemNext = GetChildItem(hItem);
		while (hItemNext)
		{
			hItem = hItemNext;
			hItemNext = GetNextSiblingItem(hItemNext);
		}
	}
	
	return hItem;
}

//=============================================================================
// FindItem  - Finds an item that contains the search string
//
// http://www.codeguru.com/cpp/controls/treeview/treetraversal/article.php/c673/
//
// Returns        - Handle to the item or NULL
//
// str            - String to search for
// bCaseSensitive - Should the search be case sensitive
// bDownDir       - Search direction - TRUE for down
// bWholeWord     - True if search should match whole words
// hItem          - Item to start searching from. NULL for
//                  currently selected item
HTREEITEM CXHtmlTree::FindItem(CString &str, 
							   BOOL bCaseSensitive /*= FALSE*/, 
							   BOOL bDownDir /*= TRUE*/, 
							   BOOL bWholeWord /*= FALSE*/, 
							   BOOL bWrap /* = TRUE */,
							   HTREEITEM hItem /*= NULL*/)
//=============================================================================
{
	int lenSearchStr = str.GetLength();
	if (lenSearchStr == 0) 
		return NULL;

	HTREEITEM hItemSel = hItem ? hItem : GetSelectedItem();
	HTREEITEM hItemCur = bDownDir ? GetNextItem(hItemSel) : GetPrevItem(hItemSel);
	CString sSearch = str;

	if (hItemCur == NULL)
	{
		if (bDownDir)  
			hItemCur = GetRootItem();
		else 
			hItemCur = GetLastItem(NULL);
	}

	if (!bCaseSensitive)
		sSearch.MakeLower();

	while (hItemCur && (hItemCur != hItemSel))
	{
		CString sItemText = GetItemText(hItemCur);

#ifdef XHTMLHTML

		TCHAR s[200];

		// remove html tags
		CXHtmlDraw hd;
		hd.GetPlainText(sItemText, s, sizeof(s)/sizeof(TCHAR)-1);

		sItemText = s;

#endif // XHTMLHTML

		if (!bCaseSensitive)
			sItemText.MakeLower();

		int n = 0;
		while ((n = sItemText.Find(sSearch)) != -1)
		{
			// search string found
			if (bWholeWord)
			{
				// check preceding char
				if (n != 0)
				{
					if (isalpha(sItemText[n-1]) || 
						sItemText[n-1] == '_')
					{
						// Not whole word
						sItemText = sItemText.Right(sItemText.GetLength() - 
											n - lenSearchStr);
						continue;
					}
				}

				// check succeeding char
				if (sItemText.GetLength() > (n + lenSearchStr) &&
					(isalpha(sItemText[n+lenSearchStr]) ||
					sItemText[n+lenSearchStr] == '_' ))
				{
					// Not whole word
					sItemText = sItemText.Right(sItemText.GetLength() 
									- n - sSearch.GetLength());
					continue;
				}
			}

			if (IsFindValid( hItemCur))
				return hItemCur;
			else 
				break;
		}

		hItemCur = bDownDir ? GetNextItem(hItemCur) : GetPrevItem(hItemCur);

		if ((hItemCur == NULL) && !bWrap)
			break;

		if ((hItemCur == NULL) && (hItemSel != NULL))	// wrap only if there 
														// is a selected item
		{
			if (bDownDir)  
				hItemCur = GetRootItem();
			else 
				hItemCur = GetLastItem(NULL);
		}
	}
	return NULL;
}

//=============================================================================
// IsFindValid	- Virtual function used by FindItem to allow this
//		  function to filter the result of FindItem
// Returns	- True if item matches the criteria
// Arg		- Handle of the item
BOOL CXHtmlTree::IsFindValid(HTREEITEM)
//=============================================================================
{
	return TRUE;
}

//=============================================================================
void CXHtmlTree::RedrawItem(HTREEITEM hItem)
//=============================================================================
{
	if (hItem)
	{
		CRect rect;
		GetItemRect(hItem, &rect, FALSE);
		InvalidateRect(&rect, FALSE);
		UpdateWindow();
	}
}

//=============================================================================
// OnMouseMove - handle link underlining and checkbox hot state
void CXHtmlTree::OnMouseMove(UINT nFlags, CPoint point)
//=============================================================================
{
	// hItem will be non-zero if the cursor is anywhere over a valid item
	UINT flags = 0;
	HTREEITEM hItem = HitTest(point, &flags);

#ifdef XHTMLDRAGDROP

	if (!m_bDragging && !IsLeftButtonUp() && IsSeparator(hItem))
	{
		// we must send TVN_BEGINDRAG to ourself, since separator has no text
		NMTREEVIEW nmtv = { 0 };
	
		nmtv.hdr.hwndFrom = m_hWnd;
		nmtv.hdr.idFrom = GetDlgCtrlID();
		nmtv.hdr.code = TVN_BEGINDRAG;
		nmtv.itemNew.hItem = hItem;

		SendMessage(WM_NOTIFY, 0, (LPARAM)&nmtv);
	}
	else if (m_bDragging)
	{
		CPoint cursor_point(point);
		ClientToScreen(&cursor_point);

		LRESULT lResult = 0;				// allow drop if lResult is 0

		BOOL bCopyDrag = IsDragCopy();

		if (hItem)
		{
			// allow parent to decide whether to permit drag
			XHTMLTREEDRAGMSGDATA dragdata = { 0 };
			dragdata.hItem      = m_hItemButtonDown;
			dragdata.hAfter     = hItem;
			dragdata.bCopyDrag  = bCopyDrag;

			lResult = SendRegisteredMessage(WM_XHTMLTREE_DROP_HOVER, 
				m_hItemButtonDown, (LPARAM)&dragdata);
		}

		// Check to see if the drag is over an item in the tree
		if (hItem && !lResult)
		{
			if (m_hPreviousDropItem != hItem)
			{
				SetDragCursor();

				SetInsertMark(0, 0);	// remove previous insert mark
				TRACE(_T("Drag target item 0x%X\n"), hItem);

				m_hPreviousDropItem = hItem;
				m_dwDropHoverTime = GetTickCount();

				// check if Shift key down
				if (GetBit(m_dwDragOps, XHTMLTREE_DO_SHIFT_KEY) &&
					(GetAsyncKeyState(VK_SHIFT) < 0))
				{
					TRACE(_T("VK_SHIFT down\n"));
					if (IsSeparator(hItem))					//+++1.6
					{
						SelectDropTarget(NULL);
						SetInsertMark(hItem, TRUE);
					}
					else
					{
						SelectDropTarget(hItem);
					}
				}
				else
				{
					SetInsertMark(hItem, TRUE);
					SelectDropTarget(NULL);
				}
			}
		}
		else	// not over an item
		{
			SetInsertMark(0, 0);	// remove insert mark
			m_hPreviousDropItem = 0;
			if (m_hNoDropCursor)
				SetCursor(m_hNoDropCursor);			// set to no-drop cursor
			else
				SetCursor(AfxGetApp()->LoadStandardCursor(IDC_NO));		// set to no-drop cursor
		}

		SetFocus();
	}
	else 
		
#endif // XHTMLDRAGDROP
		
	if (hItem)
	{
		// if mouse is on a different item, or has moved off state icon
		if ((m_hHotItem && (m_hHotItem != hItem)) || 
			(m_hHotItem && ((flags & TVHT_ONITEMSTATEICON) == 0)))
		{
			int nState = GetStateImage(m_hHotItem);

			// a hot item can only be UNCHECKED, CHECKED, or CHECKED_TRISTATE

			nState &= ~HDCheckboxImageList::HOT_INDEX;

			// remove hot from previous hot item
			SetItemState(m_hHotItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK);

			m_hHotItem = NULL;
		}

		if ((m_hHotItem == NULL) && (flags & TVHT_ONITEMSTATEICON))
		{
			TRACE(_T("cursor over state image\n"));
			int nState = GetStateImage(hItem);

			if ((nState & HDCheckboxImageList::DISABLED_INDEX) == 0)		// is it enabled?
			{
				// a hot item can only be UNCHECKED, CHECKED, or CHECKED_TRISTATE

				nState |= HDCheckboxImageList::HOT_INDEX;

				// remove hot from previous hot item
				SetItemState(hItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK);

				m_hHotItem = hItem;
				SetTimer(HOT_TIMER, 100, NULL);		// timer in case mouse leaves window
			}
		}

		CRect rect;
		BOOL bOverAnchor = IsOverAnchor(hItem, point, &rect);
		//TRACE(_T("bOverAnchor=%d\n"), bOverAnchor);

#ifdef XHTMLHTML
		if (bOverAnchor)
			m_Links.SetLinkCursor();
#endif // XHTMLHTML

		if ((m_hAnchorItem && (m_hAnchorItem != hItem)) || 
			(m_hAnchorItem && !bOverAnchor))
		{
			TRACE(_T("removing anchor 0x%X-----\n"), m_hAnchorItem);
			GetItemRect(m_hAnchorItem, &rect, FALSE);	// note:  must get entire
														// rect, since text might
														// be shifted left
			m_hAnchorItem = NULL;
			InvalidateRect(&rect, FALSE);
		}
		else if ((m_hAnchorItem == NULL) && bOverAnchor)
		{
			TRACE(_T("adding anchor 0x%X-----\n"), hItem);

			m_hAnchorItem = hItem;
			TRACE(_T("mouse over anchor 0x%X-----\n"), hItem);
			GetItemRect(hItem, &rect, FALSE);	// note:  must get entire
												// rect, since text might
												// be shifted left
			InvalidateRect(&rect, FALSE);
			SetTimer(HOT_TIMER, 80, NULL);		// timer in case mouse leaves window
		}
	}
	
	CTreeCtrl::OnMouseMove(nFlags, point);
}

//=============================================================================
HTREEITEM CXHtmlTree::IsOverItem(LPPOINT lpPoint /*= NULL*/)
//=============================================================================
{
	CPoint point;
	if (lpPoint)
	{
		point = *lpPoint;
	}
	else
	{
		::GetCursorPos(&point);
		ScreenToClient(&point);
	}
	UINT flags = 0;
	HTREEITEM hItem = HitTest(point, &flags);

	return hItem;
}

//=============================================================================
BOOL CXHtmlTree::IsOverAnchor(HTREEITEM hItem, CPoint point, CRect *pRect /*= NULL*/)
//=============================================================================
{
	BOOL rc = FALSE;

	CRect rect(0,0,0,0);

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);
	
	if (pXTCD && pXTCD->ds.bHasAnchor && pXTCD->bEnabled)
	{
		GetItemRect(hItem, &rect, TRUE);

		// set rect to anchor boundaries
		rect.left  = pXTCD->ds.rectAnchor.left;
		rect.right = pXTCD->ds.rectAnchor.right;

		if (rect.PtInRect(point))
		{
			rc = TRUE;
		}
	}

	if (pRect)
		*pRect = rect;

	return rc;
}

//=============================================================================
HCURSOR CXHtmlTree::SetCursor(HCURSOR hCursor)
//=============================================================================
{
	if (hCursor == m_hCurrentCursor)
		return m_hCurrentCursor;

	m_hCurrentCursor = hCursor;

	TRACE(_T("calling ::SetCursor %X\n"), hCursor);

	return ::SetCursor(hCursor);
}

//=============================================================================
BOOL CXHtmlTree::IsLeftButtonUp()
//=============================================================================
{
	BOOL rc = FALSE;

	SHORT state = 0;
	if (GetSystemMetrics(SM_SWAPBUTTON))		// check if buttons have been swapped
		state = GetAsyncKeyState(VK_RBUTTON);	// buttons swapped, get right button state
	else
		state = GetAsyncKeyState(VK_LBUTTON);

	// if the most significant bit is set, the button is down
	if (state >= 0)
		rc = TRUE;

	return rc;
}

//=============================================================================
// OnTimer - check if mouse has left, turn off underlining and hot state
void CXHtmlTree::OnTimer(UINT nIDEvent)
//=============================================================================
{
	CPoint point;
	::GetCursorPos(&point);
	ScreenToClient(&point);

	if (nIDEvent == HOT_TIMER)
	{
		// if mouse has left window, turn off hot and anchor highlighting

		CRect rectClient;
		GetClientRect(&rectClient);

		if (!rectClient.PtInRect(point))
		{
			KillTimer(nIDEvent);

			// mouse has left the window

			if (m_hHotItem)
			{
				int nState = GetStateImage(m_hHotItem);

				// a hot item can only be UNCHECKED, CHECKED, or CHECKED_TRISTATE

				nState &= ~HDCheckboxImageList::HOT_INDEX;

				// remove hot from previous hot item
				SetItemState(m_hHotItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK);

				m_hHotItem = NULL;
			}

			if (m_hAnchorItem)
			{
				// remove underline
				CRect rectAnchor;
				GetItemRect(m_hAnchorItem, &rectAnchor, FALSE);
				m_hAnchorItem = NULL;
				InvalidateRect(&rectAnchor, TRUE);
			}
		}
	}
	else if (nIDEvent == LBUTTONDOWN_TIMER)			// timer set by WM_LBUTTONDOWN
	{
		HTREEITEM hItem = IsOverItem(&point);

		if (IsLeftButtonUp())
		{
			TRACE(_T("mouse button is up >>>>>\n"));

			KillTimer(nIDEvent);

			HTREEITEM hItemSelected = GetSelectedItem();

#ifdef XHTMLDRAGDROP

			if (m_bDragging)				// case 1:  user is dragging
			{
				if (hItem && 
					(hItem != m_hItemButtonDown) && 
					(!IsChildNodeOf(hItem, m_hItemButtonDown)))
				{
					HTREEITEM hAfter = hItem;
					HTREEITEM hParent = GetParentItem(m_hItemButtonDown);
					HTREEITEM hNewParent = GetParentItem(hAfter);
					TRACE(_T("hParent=%X\n"), hParent);

					// check if Shift key down
					if ((m_dwDragOps & XHTMLTREE_DO_SHIFT_KEY) &&
						(GetAsyncKeyState(VK_SHIFT) < 0) &&
						!IsSeparator(hAfter))								//+++1.6
					{
						TRACE(_T("VK_SHIFT down, creating child item\n"));
						hNewParent = hAfter;
						hAfter = TVI_LAST;
					}
					else if (hParent == hAfter)
					{
						// dropping on parent
						hNewParent = hParent;
						hAfter = TVI_FIRST;
					}
					else if (ItemHasChildren(hAfter) && IsExpanded(hAfter))	//+++1.6
					{
						// dropping on node that is expanded
						hNewParent = hAfter;
						hAfter = TVI_FIRST;
					}
					else if (hNewParent == 0)								//+++1.6
					{
						// multiple roots
					}

					onItemDropped(m_hItemButtonDown,hNewParent);
					//StartMoveBranch(m_hItemButtonDown, hNewParent, hAfter);
				}
				else
				{
					TRACE(_T("ERROR can't drop on %X\n"), hItem);
					SendRegisteredMessage(WM_XHTMLTREE_END_DRAG, 0, 0);
				}

				EndDragScroll();
			}
			else 

#endif // XHTMLDRAGDROP
				
			if (hItem && (hItem == hItemSelected)
				&& !IsSeparator(hItem))	// case 2:  user wants to edit a label
			{
				// clicking on a selected item
				CEdit *pEdit = GetEditControl();

				CRect rect;
				GetItemRect(hItem, &rect, TRUE);
				if (rect.PtInRect(point))
				{
					TRACE(_T("sending TVM_EDITLABEL\n"));
					// click on item text, begin edit
					SendMessage(TVM_EDITLABEL, 0, (LPARAM)hItem);
				}
				else if (pEdit && IsWindow(pEdit->m_hWnd))
				{
					TRACE(_T("sending WM_CLOSE to edit box\n"));
					// click outside item text, end edit
					pEdit->SendMessage(WM_CLOSE);
				}
			}
		}

#ifdef XHTMLDRAGDROP

		else		// left button is down
		{
			//TRACE(_T("mouse button is down >>>>>\n"));

			if (m_bDragging)
			{
				// check how long we've been hovering over same item

				if (hItem && (hItem == m_hPreviousDropItem))
				{
					if (GetBit(m_dwDragOps, XHTMLTREE_DO_AUTO_EXPAND))
					{
						// still over same item
						if ((GetTickCount() - m_dwDropHoverTime) > MIN_HOVER_TIME)
						{
							// min hover time has passed, expand node if it has children
							Expand(hItem, TVE_EXPAND);
						}
					}
				}

				if (m_bAutoScroll)
					AutoScroll(hItem);
			}
		}

#endif // XHTMLDRAGDROP

	}

#ifdef XHTMLDRAGDROP

	else if (nIDEvent == CTRL_UP_TIMER)
	{
		// check if Ctrl key down
		if (!IsCtrlDown())
		{
			TRACE(_T("VK_CONTROL up\n"));

			KillTimer(nIDEvent);

			if (IsOverItem() && m_bDragging)
			{
				SetDragCursor();
			}
		}
	}
	else if (nIDEvent == SHIFT_UP_TIMER)
	{
		if ((m_dwDragOps & XHTMLTREE_DO_SHIFT_KEY) &&
			(GetAsyncKeyState(VK_SHIFT) >= 0))
		{
			TRACE(_T("VK_SHIFT up\n"));
			KillTimer(nIDEvent);
			HTREEITEM hItem = IsOverItem();
			if (hItem)
				SetInsertMark(hItem, TRUE);
			SelectDropTarget(NULL);
		}
	}
	else if (nIDEvent == SELECT_TIMER)			// timer set by WM_LBUTTONDOWN
	{
		KillTimer(nIDEvent);
		HTREEITEM hItem = IsOverItem();
		if (hItem)
		{
			SelectItem(hItem);
		}
	}


#endif // XHTMLDRAGDROP

	CTreeCtrl::OnTimer(nIDEvent);
}

//=============================================================================
BOOL CXHtmlTree::OnEraseBkgnd(CDC* pDC)
//=============================================================================
{
	CRect rectClientx;
	GetClientRect(&rectClientx);
	pDC->FillSolidRect(rectClientx, m_crWindow);
	return TRUE;
}

//=============================================================================
CXHtmlTree& CXHtmlTree::SetCheck(HTREEITEM hItem, BOOL fCheck /*= TRUE*/)
//=============================================================================
{
	ASSERT(hItem);

	if (hItem && m_bCheckBoxes)
	{
		TRACE(_T("in CXHtmlTree::SetCheck: %d  <%s>\n"), fCheck, GetItemText(hItem));
		
		XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

		if (pXTCD && pXTCD->bEnabled && !pXTCD->bSeparator)		//+++1.6
		{
			BOOL bOldChecked = pXTCD->bChecked;
			pXTCD->bChecked = fCheck;

			UINT nState = GetStateImage(hItem);

			//TRACE(_T("nState=0x%X  nOldState=0x%X ~~~~~\n"), nState, nOldState);

			SetItemState(hItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK);

			if (m_bSmartCheck && (bOldChecked != fCheck))
			{
				HTREEITEM hParent = hItem;
				int nCount = 0;
				if (fCheck)
					nCount = pXTCD->nChildren - pXTCD->nChecked + 1;//bOldCheck ? 1 : 0;
				else
					nCount = -(pXTCD->nChecked + 1);//bOldCheck ? 1 : 0);

				SetCheckChildren(hItem, fCheck);

				// find all parents, adjust their checked counts
				TRACE(_T("starting nCount=%d\n"), nCount);
				while ((hParent = GetParentItem(hParent)) != NULL)
				{
					nCount = SetCheckParent(hParent, nCount);
				}
			}

			SendRegisteredMessage(WM_XHTMLTREE_CHECKBOX_CLICKED, hItem, fCheck);
		}
	}

	return *this;
}

//=============================================================================
void CXHtmlTree::SetHotItem(HTREEITEM hItem, UINT nFlags)
//=============================================================================
{
	if (m_hHotItem && (m_hHotItem != hItem))
	{
		int nState = GetStateImage(m_hHotItem);

		// a hot item can only be UNCHECKED, CHECKED, or CHECKED_TRISTATE

		nState &= ~HDCheckboxImageList::HOT_INDEX;

		// remove hot from previous hot item
		SetItemState(m_hHotItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK);

		m_hHotItem = NULL;
	}

	if (hItem && (m_hHotItem == NULL) && (nFlags & TVHT_ONITEMSTATEICON))
	{
		TRACE(_T("cursor over state image\n"));
		int nState = GetStateImage(hItem);

		// a hot item can only be UNCHECKED, CHECKED, or CHECKED_TRISTATE

		nState |= HDCheckboxImageList::HOT_INDEX;

		// remove hot from previous hot item
		SetItemState(hItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK);

		m_hHotItem = hItem;
	}
}

//=============================================================================
LRESULT CXHtmlTree::SendRegisteredMessage(UINT nMessage, 
									   HTREEITEM hItem, 
									   LPARAM lParam /*= 0*/)
//=============================================================================
{
	LRESULT lResult = 0;

	CWnd *pWnd = GetParent();
	if (!pWnd)
		pWnd = GetOwner();

	if (pWnd && ::IsWindow(pWnd->m_hWnd))
	{
		XHTMLTREEMSGDATA msgdata = { 0 };
		msgdata.hCtrl    = m_hWnd;
		msgdata.nCtrlId  = GetDlgCtrlID();
		msgdata.hItem    = hItem;

		lResult = pWnd->SendMessage(nMessage, (WPARAM)&msgdata, lParam);
	}

	return lResult;
}

//=============================================================================
int CXHtmlTree::GetStateImage(HTREEITEM hItem)
//=============================================================================
{
	int nState = 0;

	ASSERT(hItem);

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		if (m_bSmartCheck && (pXTCD->nChildren != 0))
		{
			if (pXTCD->nChecked == 0)
				nState = UNCHECKED;
			else if (pXTCD->nChecked == (pXTCD->nChildren - pXTCD->nSeparators))	//+++1.6
				nState = CHECKED;
			else
				nState = TRISTATE;
		}
		else
		{
			if (pXTCD->bChecked)
				nState = CHECKED;
			else
				nState = UNCHECKED;
		}
		if (!pXTCD->bEnabled)
			nState |= HDCheckboxImageList::DISABLED_INDEX;
	}

	TRACE(_T("GetStateImage returning %d ~~~~~\n"), nState);

	return nState;
}

//=============================================================================
int CXHtmlTree::SetCheckParent(HTREEITEM hItem, int nCount)
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::SetCheckParent:  nCount=%d  <%s>\n"), nCount, GetItemText(hItem));
	ASSERT(hItem);

	int nState = 0;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		TRACE(_T("pXTCD->nChecked=%d  pXTCD->nChildren=%d \n"), pXTCD->nChecked, pXTCD->nChildren);
		pXTCD->nChecked += nCount;
		if (pXTCD->nChecked < 0)
			pXTCD->nChecked = 0;

		BOOL bOldCheck = pXTCD->bChecked;
		if (pXTCD->nChecked == (pXTCD->nChildren - pXTCD->nSeparators))	//+++1.6
			pXTCD->bChecked = TRUE;
		else
			pXTCD->bChecked = FALSE;

		if (pXTCD->bChecked != bOldCheck)
			nCount += pXTCD->bChecked ? 1 : -1;

		nState = GetStateImage(hItem);

		SetItemState(hItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK);

		TRACE(_T("nState=%d\n"), nState);
	}

	return nCount;
}

//=============================================================================
CXHtmlTree& CXHtmlTree::SetCheckChildren(HTREEITEM hItem, BOOL fCheck)
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::SetCheckChildren\n"));

	// first set item state for this item
	SetItemStateChildren(hItem, fCheck);

	HTREEITEM hNext = GetChildItem(hItem);
	
	// loop to set item state for children
	while (hNext)
	{
		TRACE(_T("SetCheckChildren: %d  <%s>\n"), fCheck, GetItemText(hNext));

		// recurse into children
		if (ItemHasChildren(hNext))
			SetCheckChildren(hNext, fCheck);

		SetItemStateChildren(hNext, fCheck);

		hNext = GetNextItem(hNext, TVGN_NEXT);
	}

	return *this;
}

//=============================================================================
CXHtmlTree& CXHtmlTree::SetItemStateChildren(HTREEITEM hItem, BOOL fCheck)
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::SetItemStateChildren\n"));

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD && pXTCD->bEnabled)
	{
		int nState = GetStateImage(hItem);
		if (pXTCD->bSeparator)				//+++1.6
		{
			nState = 0;
		}
		else
		{
			int nStateHot = nState & HDCheckboxImageList::HOT_INDEX;			// save hot
			int nStateDisabled = nState & HDCheckboxImageList::DISABLED_INDEX;	// save disabled
			nState &= ~(HDCheckboxImageList::HOT_INDEX | 
						HDCheckboxImageList::DISABLED_INDEX);	// remove hot & disabled

			pXTCD->bChecked = fCheck;

			if (fCheck)
			{
				pXTCD->nChecked = pXTCD->nChildren - pXTCD->nSeparators;	//+++1.6
				if (pXTCD->nChecked < 0)
					pXTCD->nChecked = 0;
				nState = CHECKED;
			}
			else
			{
				pXTCD->nChecked = 0;
				nState = UNCHECKED;
			}

			nState |= nStateHot;		// restore hot
			nState |= nStateDisabled;	// restore disabled
			TRACE(_T("setting state to %d\n"), nState);
		}
		SetItemState(hItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK);
	}

	return *this;
}

//=============================================================================
BOOL CXHtmlTree::EnableItem(HTREEITEM hItem, BOOL bEnabled)
//=============================================================================
{
	BOOL rc = TRUE;

	ASSERT(hItem);

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		rc = pXTCD->bEnabled;

		pXTCD->bEnabled = bEnabled;

		int nState = GetStateImage(hItem);

		if (bEnabled)
			nState &= ~HDCheckboxImageList::DISABLED_INDEX;
		else
			nState |= HDCheckboxImageList::DISABLED_INDEX;

		// set enabled state
		SetItemState(hItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK);
	}

	return rc;
}

//=============================================================================
BOOL CXHtmlTree::EnableBranch(HTREEITEM hItem, BOOL bEnabled)
//=============================================================================
{
	BOOL rc = TRUE;

	if (hItem && !IsSeparator(hItem))	//+++1.6
	{
		rc = EnableItem(hItem, bEnabled);

		hItem = GetChildItem(hItem);

		if (hItem)
		{
			do
			{
				EnableBranch(hItem, bEnabled);

			} while ((hItem = GetNextSiblingItem(hItem)) != NULL);
		}
	}

	return rc;	// return state of first item
}

//=============================================================================
BOOL CXHtmlTree::GetCheck(HTREEITEM hItem)
//=============================================================================
{
	BOOL rc = FALSE;

	ASSERT(hItem);

	if (m_bCheckBoxes && hItem)
	{
		XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

		if (pXTCD)
		{
			if (m_bSmartCheck)
			{
				if (pXTCD->nChildren == 0)
				{
					rc = pXTCD->bChecked;
				}
				else
				{
					if (pXTCD->nChecked == 0)
						rc = FALSE;		// no children are checked
					else if (pXTCD->nChecked == (pXTCD->nChildren - pXTCD->nSeparators))	//+++1.6
						rc = TRUE;		// all children are checked
					else
						rc = FALSE;		// not all children are checked
				}
			}
			else
			{
				rc = pXTCD->bChecked;
			}
		}
	}

	return rc;
}

//=============================================================================
HTREEITEM CXHtmlTree::GetFirstCheckedItem()
//=============================================================================
{
	if (m_bCheckBoxes)
	{
		for (HTREEITEM hItem = GetRootItem(); 
			 hItem != NULL; 
			 hItem = GetNextItem(hItem))
		{
			if (GetCheck(hItem))
				return hItem;
		}
	}

	return NULL;
}

//=============================================================================
HTREEITEM CXHtmlTree::GetNextCheckedItem(HTREEITEM hItem)
//=============================================================================
{
	if (m_bCheckBoxes)
	{
		for (hItem = GetNextItem(hItem); 
			 hItem != NULL; 
			 hItem = GetNextItem(hItem))
		{
			if (GetCheck(hItem))
				return hItem;
		}
	}

	return NULL;
}

//=============================================================================
HTREEITEM CXHtmlTree::GetPrevCheckedItem(HTREEITEM hItem)
//=============================================================================
{
	if (m_bCheckBoxes)
	{
		for (hItem = GetPrevItem(hItem); 
			 hItem != NULL; 
			 hItem = GetPrevItem(hItem))
		{
			if (GetCheck(hItem))
				return hItem;
		}
	}

	return NULL;
}

//=============================================================================
BOOL CXHtmlTree::DeleteItem(HTREEITEM hItem)
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::DeleteItem\n"));
	BOOL bOldDestroyingTree = m_bDestroyingTree;

	if (hItem && ItemHasChildren(hItem))
	{
		DeleteBranch(hItem);
	}
	else if (hItem)
	{
		XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

		if (pXTCD)
		{
			if (pXTCD->bChecked)
				m_nDeletedChecked++;

			HTREEITEM hParent = hItem;

			// find all parents, decrement their children counts,
			// adjust their checked counts and separator counts
			while ((hParent = GetParentItem(hParent)) != NULL)
			{
				IncrementChildren(hParent, -1);
				if (pXTCD->bChecked)
					SetCheckParent(hParent, -1);
				if (pXTCD->bSeparator)					//+++1.6
					IncrementSeparators(hParent, -1);
			}

			m_bDestroyingTree = TRUE;
			m_DataMap.RemoveKey(hItem);
			delete pXTCD;
		}
	}

	m_bDestroyingTree = bOldDestroyingTree;

	m_nDeleted++;

	return CTreeCtrl::DeleteItem(hItem);
}

//=============================================================================
void CXHtmlTree::DeleteBranch(HTREEITEM hItem)
//=============================================================================
{
	if (hItem)
	{
		HTREEITEM hChild = GetChildItem(hItem);
		while (hChild)
		{
			// recursively delete all the items
			HTREEITEM hNext = GetNextSiblingItem(hChild);
			DeleteBranch(hChild);
			hChild = hNext;
		}
		DeleteItem(hItem);
	}
}

//=============================================================================
CString CXHtmlTree::GetItemNote(HTREEITEM hItem, BOOL bStripHtml /*= FALSE*/)
//=============================================================================
{
	CString strNote = _T("");

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD && pXTCD->pszNote)
	{
		strNote = pXTCD->pszNote;

		if (bStripHtml)
		{
#ifdef XHTMLHTML
			// remove html tags
			CXHtmlDraw hd;
			int n = strNote.GetLength();
			if (n > 3)		// minimum html string
			{
				TCHAR *s = new TCHAR [n + 16];
				hd.GetPlainText(strNote, s, n+4);
				strNote = s;
				delete [] s;
			}
#endif // XHTMLHTML
		}
	}

	return strNote;
}

//=============================================================================
int CXHtmlTree::GetItemNoteWidth(HTREEITEM hItem)
//=============================================================================
{
	int nWidth = 0;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD && pXTCD->pszNote)
	{
		nWidth = pXTCD->nTipWidth;
	}

	return nWidth;
}

//=============================================================================
CXHtmlTree& CXHtmlTree::SetItemNote(HTREEITEM hItem, 
									LPCTSTR lpszNote, 
									int nTipWidth /*= 0*/)
//=============================================================================
{
	ASSERT(hItem);
	ASSERT(lpszNote);

	if (hItem && lpszNote)
	{
		XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

		if (pXTCD)
		{
			if (pXTCD->pszNote)
				delete [] pXTCD->pszNote;
			pXTCD->pszNote = NULL;

			size_t len = _tcslen(lpszNote);
			pXTCD->pszNote = new TCHAR [len + 4];
			if (pXTCD->pszNote)
			{
				memset(pXTCD->pszNote, 0, len+4);
				_tcsncpy(pXTCD->pszNote, lpszNote, len+1);
				pXTCD->nTipWidth = nTipWidth;
			}
		}
	}

	return *this;
}

//=============================================================================
HTREEITEM CXHtmlTree::InsertItem(LPTVINSERTSTRUCT lpInsertStruct, 
								 XHTMLTREEDATA * pData /*= NULL*/)
//=============================================================================
{
	XHTMLTREEDATA *pXTCD = new XHTMLTREEDATA;
	ASSERT(pXTCD);
	if (!pXTCD)
	{
		ASSERT(FALSE);
		return 0;
	}

	if (pData)
	{
		// copy user items for XHTMLTREEDATA
		pXTCD->bChecked   = pData->bChecked;
		pXTCD->bEnabled   = pData->bEnabled;
		pXTCD->bSeparator = pData->bSeparator;	//+++1.6
	
		// copy user items for XHTMLDRAWSTRUCT
		pXTCD->ds.crText           = pData->ds.crText;
		pXTCD->ds.crAnchorText     = pData->ds.crAnchorText;
		pXTCD->ds.crBackground     = pData->ds.crBackground;
		pXTCD->ds.crTextBackground = pData->ds.crTextBackground;
		pXTCD->ds.bIgnoreColorTag  = pData->ds.bIgnoreColorTag;
		pXTCD->ds.bTransparent     = pData->ds.bTransparent;
		pXTCD->ds.bBold            = pData->ds.bBold;
		pXTCD->ds.bItalic          = pData->ds.bItalic;
		pXTCD->ds.bUnderline       = pData->ds.bUnderline;
		pXTCD->ds.bStrikeThrough   = pData->ds.bStrikeThrough;
		pXTCD->ds.bUseEllipsis     = pData->ds.bUseEllipsis;
		pXTCD->ds.bLogFont         = pData->ds.bLogFont;
		pXTCD->ds.uFormat          = pData->ds.uFormat;
		pXTCD->ds.lf               = pData->ds.lf;
	}

	pXTCD->hTreeCtrl = m_hWnd;

	TVINSERTSTRUCT tvis;
	memcpy(&tvis, lpInsertStruct, sizeof(TVINSERTSTRUCT));

	if (!m_bImages)
	{
		tvis.item.iImage = TV_NOIMAGE;
		tvis.item.iSelectedImage = TV_NOIMAGE;	//+++1.5
	}

	tvis.item.mask |= TVIF_STATE;
	int nState = UNCHECKED;
	if (pXTCD->bChecked && m_bCheckBoxes)
		nState = CHECKED;
	if (!pXTCD->bEnabled)
		nState |= HDCheckboxImageList::DISABLED_INDEX;
	tvis.item.state = INDEXTOSTATEIMAGEMASK(nState);
	tvis.item.stateMask = TVIS_STATEIMAGEMASK;

	CString strText = tvis.item.pszText;

	if (m_bStripHtml)
	{
#ifdef XHTMLHTML
		// remove html tags
		CXHtmlDraw hd;
		int n = strText.GetLength();
		if (n > 3)		// minimum html string
		{
			TCHAR *s = new TCHAR [n + 16];
			hd.GetPlainText(strText, s, n+4);
			strText = s;
			delete [] s;
		}
#endif // XHTMLHTML
	}

	TRACE(_T("inserting <%s>\n"), strText);

	tvis.item.pszText = strText.LockBuffer();

	HTREEITEM hItem = CTreeCtrl::InsertItem(&tvis);
	ASSERT(hItem);

	strText.UnlockBuffer();

	if (hItem)
	{
		m_DataMap.SetAt(hItem, pXTCD);
		TRACE(_T("count=%d\n"), m_DataMap.GetCount());

		if (m_bSmartCheck)
		{
			HTREEITEM hParent = hItem;

			// find all parents, increment their children counts,
			// adjust their checked counts
			int nCount = pXTCD->bChecked ? 1 : 0;
			while ((hParent = GetParentItem(hParent)) != NULL)
			{
				IncrementChildren(hParent);
				nCount = SetCheckParent(hParent, nCount);
			}
		}
	}

	return hItem;
}

//=============================================================================
HTREEITEM CXHtmlTree::InsertItem(UINT nMask, 
								 LPCTSTR lpszItem, 
								 int nImage, 
								 int nSelectedImage, 
								 UINT nState, 
								 UINT nStateMask, 
								 LPARAM lParam, 
								 HTREEITEM hParent, 
								 HTREEITEM hInsertAfter)
//=============================================================================
{
	TVINSERTSTRUCT tvis = { 0 };

	tvis.item.mask = nMask;
	tvis.item.pszText = (LPTSTR)lpszItem;
	tvis.item.iImage = nImage;
	tvis.item.iSelectedImage = nSelectedImage;
	tvis.item.state = nState;
	tvis.item.stateMask = nStateMask;
	tvis.item.lParam = lParam;
	tvis.hParent = hParent;
	tvis.hInsertAfter = hInsertAfter;

	return InsertItem(&tvis);
}

//=============================================================================
HTREEITEM CXHtmlTree::InsertItem(LPCTSTR lpszItem, 
								 HTREEITEM hParent /*= TVI_ROOT*/, 
								 HTREEITEM hInsertAfter /*= TVI_LAST*/)
//=============================================================================
{
	TVINSERTSTRUCT tvis = { 0 };

	tvis.item.mask = TVIF_TEXT;
	tvis.item.pszText = (LPTSTR)lpszItem;
	tvis.hParent = hParent;
	tvis.hInsertAfter = hInsertAfter;

	return InsertItem(&tvis);
}

//=============================================================================
HTREEITEM CXHtmlTree::InsertItem(LPCTSTR lpszItem, 
								 int nImage, 
								 int nSelectedImage, 
								 HTREEITEM hParent /*= TVI_ROOT*/, 
								 HTREEITEM hInsertAfter /*= TVI_LAST*/)
//=============================================================================
{
	TVINSERTSTRUCT tvis = { 0 };

	tvis.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
	tvis.item.pszText = (LPTSTR)lpszItem;
	tvis.item.iImage = nImage;
	tvis.item.iSelectedImage = nSelectedImage;
	tvis.hParent = hParent;
	tvis.hInsertAfter = hInsertAfter;

	return InsertItem(&tvis);
}

//=============================================================================
HTREEITEM CXHtmlTree::InsertSeparator(HTREEITEM hItem)	//+++1.6
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::InsertSeparator\n"));

	HTREEITEM hAfter = hItem;
	HTREEITEM hParent = GetParentItem(hItem);
	HTREEITEM hNewParent = GetParentItem(hItem);
	TRACE(_T("hParent=%X\n"), hParent);

	if (hParent == hAfter)
	{
		// dropping on parent
		hNewParent = hParent;
		hAfter = TVI_FIRST;
	}
	else if (ItemHasChildren(hAfter) && IsExpanded(hAfter))
	{
		// dropping on node that is expanded
		hNewParent = hAfter;
		hAfter = TVI_FIRST;
	}


	TVINSERTSTRUCT tvis = { 0 };
	tvis.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
	tvis.item.pszText = _T("");
	tvis.item.iImage = TV_NOIMAGE;
	tvis.item.iSelectedImage = TV_NOIMAGE;
	tvis.hParent = hNewParent;
	tvis.hInsertAfter = hAfter;

	HTREEITEM hSep = InsertItem(&tvis);

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hSep);

	if (pXTCD)
	{
		pXTCD->bSeparator = TRUE;
		//SetItemTextColor(hSep, RGB(255,0,0));
	}

	SetItemState(hSep, INDEXTOSTATEIMAGEMASK(0), TVIS_STATEIMAGEMASK);

	// increment separator count in parents
	hParent = hSep;
	while ((hParent = GetParentItem(hParent)) != NULL)
	{
		IncrementSeparators(hParent, 1);
	}

	return hSep;
}

//=============================================================================
int CXHtmlTree::IncrementChildren(HTREEITEM hItem, int n /*= 1*/)
//=============================================================================
{
	int nChildren = 0;

	ASSERT(hItem);

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		pXTCD->nChildren += n;
		if (pXTCD->nChildren < 0)
			pXTCD->nChildren = 0;
		nChildren = pXTCD->nChildren;
	}

	return nChildren;
}

//=============================================================================
int CXHtmlTree::IncrementSeparators(HTREEITEM hItem, int n /*= 1*/)		//+++1.6
//=============================================================================
{
	int nSeparators = 0;

	ASSERT(hItem);

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		pXTCD->nSeparators += n;
		if (pXTCD->nSeparators < 0)
			pXTCD->nSeparators = 0;
		nSeparators = pXTCD->nSeparators;
	}

	return nSeparators;
}

//=============================================================================
XHTMLTREEDATA * CXHtmlTree::GetItemDataStruct(HTREEITEM hItem)
//=============================================================================
{
	XHTMLTREEDATA *pXTCD = NULL;

	if (hItem && !m_bDestroyingTree)
	{
		m_DataMap.Lookup(hItem, pXTCD);
	}

	return pXTCD;
}

#ifdef XHTMLTOOLTIPS

//=============================================================================
// OnDisplayTooltip - display CPPToolTip
void CXHtmlTree::OnDisplayTooltip(NMHDR * pNMHDR, LRESULT * pResult)
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::OnDisplayTooltip\n"));
	*pResult = 0;

	NM_PPTOOLTIP_DISPLAY * pNotify = (NM_PPTOOLTIP_DISPLAY*)pNMHDR;

	CString strToolTip = _T("");

	if (PreDisplayToolTip(FALSE, strToolTip))
	{
		// no limitation on text length
		
		TRACE(_T("setting tooltip to <%s>\n"), strToolTip);

		// change the tooltip's text
		pNotify->ti->sTooltip = strToolTip;
	}
}

#else


//=============================================================================
BOOL CXHtmlTree::OnToolTipText(UINT /*id*/, NMHDR * pNMHDR, LRESULT * pResult)
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::OnToolTipText\n"));

	UINT nID = (UINT)pNMHDR->idFrom;
	
	// check if this is the automatic tooltip of the control
	if (nID == 0) 
		return TRUE;	// do not allow display of automatic tooltip,
						// or our tooltip will disappear
	
	// handle both ANSI and UNICODE versions of the message
	TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
	TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
	
	*pResult = 0;
	
	CString strToolTip = _T("");

	if (PreDisplayToolTip(TRUE, strToolTip))
	{
		// copy item text (up to 80 characters worth, limitation 
		// of the TOOLTIPTEXT structure) into the TOOLTIPTEXT 
		// structure's szText member
		
		strToolTip = strToolTip.Mid(0, sizeof(pTTTA->szText)-2);

#ifndef _UNICODE
		if (pNMHDR->code == TTN_NEEDTEXTA)
			lstrcpyn(pTTTA->szText, strToolTip, sizeof(pTTTA->szText));
		else
			_mbstowcsz(pTTTW->szText, strToolTip, sizeof(pTTTW->szText)/sizeof(TCHAR));
#else
		if (pNMHDR->code == TTN_NEEDTEXTA)
			_wcstombsz(pTTTA->szText, strToolTip, sizeof(pTTTA->szText));
		else
			lstrcpyn(pTTTW->szText, strToolTip, sizeof(pTTTW->szText)/sizeof(TCHAR));
#endif // _UNICODE
	}

	return FALSE;	// we didn't handle the message, let the 
					// framework continue propagating the message
}

#endif  // XHTMLTOOLTIPS

//=============================================================================
// PreDisplayToolTip - returns TRUE if tooltip should be displayed
BOOL CXHtmlTree::PreDisplayToolTip(BOOL bAlwaysRemoveHtml, CString& strToolTip)
//=============================================================================
{
	BOOL rc = FALSE;

	if (m_bDragging)
		return rc;

	strToolTip = _T("");

	// get the mouse position
	const MSG* pMessage;
	pMessage = GetCurrentMessage();
	ASSERT(pMessage);
	CPoint point;
	point = pMessage->pt;		// get the point from the message
	ScreenToClient(&point);		// convert the point's coords to be relative 
								// to this control
	
	// see if the point falls on a tree item
	
	UINT flags = 0;
	HTREEITEM hItem = HitTest(point, &flags);

	if (IsSeparator(hItem))		//+++1.6
		return FALSE;			// no tooltip for separator
		
	if (hItem && (flags & TVHT_ONITEM))
	{
		// it did fall on an item

		TRACE(_T("in PreDisplayToolTip:  mouse on item %X\n"), hItem);

		XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

		if (pXTCD)
		{
			// get rect for item text
			CRect rectClient;
			GetClientRect(&rectClient);
			CRect rectText;
			GetItemRect(hItem, &rectText, TRUE);	// get rect for text
			rectText.right = pXTCD->ds.nRightX;

			//TRACE(_T("nRightX = %d\n"), pXTCD->ds.nRightX);
			//TRACERECT(rectText);
			//TRACERECT(rectClient);

			strToolTip = GetItemNote(hItem);
			BOOL bNote = !strToolTip.IsEmpty();

			// check if text rect falls entirely inside client rect of control
			if (bNote || (rectText.right > (rectClient.right - 3)))
			{
				TRACE(_T("note or overflow\n"));

				// check if parent wants to display this tooltip --
				// if lResult is not zero, don't display 
				LRESULT lResult = 0;
				if (m_pToolTip)
				{
					lResult = SendRegisteredMessage(WM_XHTMLTREE_DISPLAY_TOOLTIP, 
									hItem, (LPARAM)m_pToolTip);
				}

				if (!lResult)
				{
					// get note again - this allows parent to modify note 
					// before it is displayed
					BOOL bStripHtml = bAlwaysRemoveHtml || !bNote;
					strToolTip = GetItemNote(hItem, bStripHtml);
					if (strToolTip.IsEmpty())
						strToolTip = GetItemText(hItem, bStripHtml);

					rc = TRUE;

					// set tip width regardless of whether there is a note
					int nTipWidth = GetItemNoteWidth(hItem);

					if (nTipWidth == 0)
					{
						// no note width specified, use a heuristic
						nTipWidth = GetDefaultTipWidth();
					}

					if (nTipWidth && m_pToolTip && IsWindow(m_pToolTip->m_hWnd))
						m_pToolTip->SetMaxTipWidth(nTipWidth);
				}
			}
		}
	}

	return rc;
}

//=============================================================================
void CXHtmlTree::OnSysColorChange() 
//=============================================================================
{
	CTreeCtrl::OnSysColorChange();
	SetColors();	
}

//=============================================================================
COLORREF CXHtmlTree::GetItemTextBkColor(HTREEITEM hItem)
//=============================================================================
{
	COLORREF rc = 0;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
		rc = pXTCD->ds.crTextBackground;

	return rc;
}

//=============================================================================
COLORREF CXHtmlTree::GetItemTextColor(HTREEITEM hItem)
//=============================================================================
{
	COLORREF rc = 0;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
		rc = pXTCD->ds.crText;

	return rc;
}

//=============================================================================
BOOL CXHtmlTree::SetItemText(HTREEITEM hItem, LPCTSTR lpszItem)		//+++1.6
//=============================================================================
{
	if (IsSeparator(hItem))
		return FALSE;

	return CTreeCtrl::SetItemText(hItem, lpszItem);
}

//=============================================================================
COLORREF CXHtmlTree::SetItemTextBkColor(HTREEITEM hItem, COLORREF rgb)
//=============================================================================
{
	COLORREF rc = 0;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		rc = pXTCD->ds.crTextBackground;
		pXTCD->ds.crTextBackground = rgb;
	}

	return rc;
}

//=============================================================================
COLORREF CXHtmlTree::SetItemTextColor(HTREEITEM hItem, COLORREF rgb)
//=============================================================================
{
	COLORREF rc = 0;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		rc = pXTCD->ds.crText;
		pXTCD->ds.crText = rgb;
	}

	return rc;
}

//=============================================================================
COLORREF CXHtmlTree::SetBkColor(COLORREF rgb) 
//=============================================================================
{
	COLORREF old = m_crWindow; 
	if (rgb == COLOR_NONE)
		rgb = GetSysColor(COLOR_WINDOW);
	m_crCustomWindow = m_crWindow = rgb; 
	return old; 
}

//=============================================================================
COLORREF CXHtmlTree::SetTextColor(COLORREF rgb) 
//=============================================================================
{
	COLORREF old = m_crWindowText; 
	if (rgb == COLOR_NONE)
		rgb = GetSysColor(COLOR_WINDOWTEXT);
	m_crCustomWindowText = m_crWindowText = rgb; 
	return old; 
}

//=============================================================================
COLORREF CXHtmlTree::SetInsertMarkColor(COLORREF rgb)
//=============================================================================
{
	if (rgb == COLOR_NONE)
		rgb = GetSysColor(COLOR_HIGHLIGHT);
	m_crInsertMark = rgb; 
	return CTreeCtrl::SetInsertMarkColor(rgb);
}

//=============================================================================
CXHtmlTree& CXHtmlTree::SetSeparatorColor(COLORREF rgb)	//+++1.6
//=============================================================================
{
	if (rgb == COLOR_NONE)
		rgb = GetSysColor(COLOR_GRAYTEXT);
	m_crSeparator = rgb; 
	return *this;
}

//=============================================================================
void CXHtmlTree::SetColors()
//=============================================================================
{
	m_crWindow        = GetSysColor(COLOR_WINDOW);
	m_crWindowText    = GetSysColor(COLOR_WINDOWTEXT);
	m_crAnchorText    = RGB(0,0,255);
	m_crGrayText      = GetSysColor(COLOR_GRAYTEXT);
	m_crHighlight     = GetSysColor(COLOR_HIGHLIGHT);
	m_crHighlightText = GetSysColor(COLOR_HIGHLIGHTTEXT);
	m_crInsertMark    = GetSysColor(COLOR_HIGHLIGHT);
	m_crSeparator     = GetSysColor(COLOR_GRAYTEXT);		//+++1.6

	if (m_crCustomWindow != COLOR_NONE)
		m_crWindow = m_crCustomWindow;

	if (m_crCustomWindowText != COLOR_NONE)
		m_crWindowText = m_crCustomWindowText;
}

//=============================================================================
BOOL CXHtmlTree::EnableWindow(BOOL bEnable /*= TRUE*/)
//=============================================================================
{
	BOOL rc = CTreeCtrl::EnableWindow(bEnable);

	if (bEnable)
	{
		if (m_crCustomWindow != COLOR_NONE)
			m_crWindow = m_crCustomWindow;
		else
			m_crWindow = GetSysColor(COLOR_WINDOW);

		if (m_crCustomWindowText != COLOR_NONE)
			m_crWindowText = m_crCustomWindowText;
		else
			m_crWindowText = GetSysColor(COLOR_WINDOWTEXT);
	}
	else
	{
		m_crWindow = GetDisabledColor(GetSysColor(COLOR_WINDOW));
		m_crWindowText = GetSysColor(COLOR_GRAYTEXT);
	}

	return rc;
}

//=============================================================================
COLORREF CXHtmlTree::GetDisabledColor(COLORREF color)
//=============================================================================
{
	BYTE r = GetRValue(color);
	BYTE g = GetGValue(color);
	BYTE b = GetBValue(color);
	const BYTE disabled_value = 10;

	r = (r >= disabled_value) ? (BYTE)(r - disabled_value) : r;
	g = (g >= disabled_value) ? (BYTE)(g - disabled_value) : g;
	b = (b >= disabled_value) ? (BYTE)(b - disabled_value) : b;

	return RGB(r, g, b);
}

//=============================================================================
CXHtmlTree& CXHtmlTree::SetLogfont(LOGFONT * pLogFont)
//=============================================================================
{
	ASSERT(pLogFont);

	if (pLogFont)
		m_lf = *pLogFont;

	return *this;
}

//=============================================================================
BOOL CXHtmlTree::GetItemBold(HTREEITEM hItem)
//=============================================================================
{
	BOOL rc = FALSE;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
		rc = pXTCD->ds.bBold;

	return rc;
}

//=============================================================================
BOOL CXHtmlTree::SetItemBold(HTREEITEM hItem, BOOL bBold)
//=============================================================================
{
	BOOL rc = FALSE;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		rc = pXTCD->ds.bBold;
		pXTCD->ds.bBold = bBold;
	}

	return rc;
}

//=============================================================================
BOOL CXHtmlTree::DeleteAllItems()
//=============================================================================
{
	// avoid unneeded selchange notifications
	SelectItem(NULL);

	CollapseAll();
	BOOL bOldDestroyingTree = m_bDestroyingTree;
	m_bDestroyingTree = TRUE;
	BOOL rc = CTreeCtrl::DeleteAllItems();
	DeleteMap();
	m_bDestroyingTree = bOldDestroyingTree;
	return rc;
}

//=============================================================================
void CXHtmlTree::CollapseAll()
//=============================================================================
{
	HTREEITEM hItemRoot = GetRootItem();

	HTREEITEM hItem = hItemRoot;

	if (hItem)
	{
		do 
		{
			CollapseBranch(hItem);

		} while ((hItem = GetNextSiblingItem(hItem)) != NULL);

		SelectItem(hItemRoot);

		SendMessage(WM_HSCROLL, SB_LEFT);
		UpdateWindow();
	}
}

//=============================================================================
void CXHtmlTree::ExpandAll()
//=============================================================================
{
	HTREEITEM hItemSel = GetSelectedItem();
	if (!hItemSel)
		hItemSel = GetRootItem();

	if (hItemSel)
	{
		HTREEITEM hItem = GetRootItem();	// must start with root for best performance

		SetRedraw(FALSE);

		do 
		{
			ExpandBranch(hItem);

		} while ((hItem = GetNextSiblingItem(hItem)) != NULL);

		SelectItem(hItemSel);
		SetScrollPos(SB_VERT, 0);
		EnsureVisible(hItemSel);
		SendMessage(WM_HSCROLL, SB_LEFT);

		SetRedraw(TRUE);
	}
}

//=============================================================================
void CXHtmlTree::ExpandBranch(HTREEITEM hItem)
//=============================================================================
{
	if (hItem && ItemHasChildren(hItem))
	{
		Expand(hItem, TVE_EXPAND);

		hItem = GetChildItem(hItem);

		if (hItem)
		{
			do
			{
				ExpandBranch(hItem);

			} while ((hItem = GetNextSiblingItem(hItem)) != NULL);
		}
	}
}

//=============================================================================
void CXHtmlTree::CollapseBranch(HTREEITEM hItem)
//=============================================================================
{
	if (hItem && ItemHasChildren(hItem))
	{
		Expand(hItem, TVE_COLLAPSE);

		hItem = GetChildItem(hItem);

		if (hItem && ItemHasChildren(hItem))
		{
			do 
			{
				CollapseBranch(hItem);

			} while ((hItem = GetNextSiblingItem(hItem)) != NULL);
		}
	}
}

//=============================================================================
BOOL CXHtmlTree::Expand(HTREEITEM hItem, UINT nCode)
//=============================================================================
{
	if (hItem && ItemHasChildren(hItem))
	{
		XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

		if (pXTCD && pXTCD->bEnabled)
		{
			BOOL bOldExpanded = pXTCD->bExpanded;

			if (nCode == TVE_COLLAPSE || nCode == TVE_COLLAPSERESET)
			{
				pXTCD->bExpanded = FALSE;
			}
			else if (nCode == TVE_EXPAND)
			{
				pXTCD->bExpanded = TRUE;
			}
			else if (nCode == TVE_TOGGLE)
			{
				if (bOldExpanded)
					pXTCD->bExpanded = FALSE;
				else
					pXTCD->bExpanded = TRUE;
			}
			else
			{
				TRACE(_T("ERROR bad nCode=%u\n"), nCode);
			}

			if (pXTCD->bExpanded)
				pXTCD->bHasBeenExpanded = TRUE;

			if (pXTCD->bExpanded != bOldExpanded)
				SendRegisteredMessage(WM_XHTMLTREE_ITEM_EXPANDED, hItem, 
					pXTCD->bExpanded);
		}
	}

	TRACE(_T("calling CTreeCtrl::Expand()\n"));
	BOOL rc = CTreeCtrl::Expand(hItem, nCode);

	return rc;
}

//=============================================================================
BOOL CXHtmlTree::GetHasBeenExpanded(HTREEITEM hItem)
//=============================================================================
{
	BOOL rc = FALSE;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		rc = pXTCD->bHasBeenExpanded;
	}

	return rc;
}

//=============================================================================
void CXHtmlTree::CheckAll(BOOL bCheck)
//=============================================================================
{
	if (m_bCheckBoxes)
	{
		if (m_bSmartCheck)
		{
			// check all root-level items
			HTREEITEM hRoot = GetRootItem();

			while (hRoot)
			{
				SetCheck(hRoot, bCheck);
				hRoot = GetNextItem(hRoot, TVGN_NEXT);	// get next root item
			}
		}
		else
		{
			// check all items
			HTREEITEM hItem = GetRootItem();

			while (hItem)
			{
				SetCheck(hItem, bCheck);
				hItem = GetNextItem(hItem);		// get next sequential item
			}
		}
	}
}

//=============================================================================
int CXHtmlTree::GetCheckedCount()
//=============================================================================
{
	int rc = 0;

	if (!m_bCheckBoxes)
		return 0;

	for (HTREEITEM hItem = GetRootItem();
		 hItem != NULL; 
		 hItem = GetNextItem(hItem))
	{
		if (GetCheck(hItem))
			rc++;
	}

	return rc;
}

//=============================================================================
int CXHtmlTree::GetChildrenCheckedCount(HTREEITEM hItem)
//=============================================================================
{
	int rc = 0;

	if (!m_bCheckBoxes)
		return 0;

	if (!hItem)
		hItem = GetRootItem();

	if (hItem && m_bSmartCheck)
	{
		XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

		if (pXTCD)
		{
			rc = pXTCD->nChecked;
		}
	}
	else if (hItem && ItemHasChildren(hItem))
	{
		HTREEITEM hChild = GetChildItem(hItem);
		if (hChild)
		{
			do
			{
				if (GetCheck(hChild))
					rc++;

				rc += GetChildrenCheckedCount(hChild);

			} while ((hChild = GetNextSiblingItem(hChild)) != NULL);
		}
	}

	return rc;
}

//=============================================================================
int CXHtmlTree::GetChildrenDisabledCount(HTREEITEM hItem)
//=============================================================================
{
	int rc = 0;

	if (!hItem)
		hItem = GetRootItem();

	if (hItem && ItemHasChildren(hItem))
	{
		HTREEITEM hChild = GetChildItem(hItem);
		if (hChild)
		{
			do
			{
				if (!IsEnabled(hChild))
					rc++;

				rc += GetChildrenDisabledCount(hChild);

			} while ((hChild = GetNextSiblingItem(hChild)) != NULL);
		}
	}

	return rc;
}

//=============================================================================
int CXHtmlTree::GetChildrenCount(HTREEITEM hItem)
//=============================================================================
{
	int rc = 0;

	if (!hItem)
		hItem = GetRootItem();

	if (hItem && m_bSmartCheck)
	{
		XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

		if (pXTCD)
		{
			rc = pXTCD->nChildren;
		}
	}
	else if (hItem && ItemHasChildren(hItem))
	{
		HTREEITEM hChild = GetChildItem(hItem);
		if (hChild)
		{
			do
			{
				rc++;
				rc += GetChildrenCount(hChild);

			} while ((hChild = GetNextSiblingItem(hChild)) != NULL);
		}
	}

	return rc;
}

//=============================================================================
int CXHtmlTree::GetSeparatorCount(HTREEITEM hItem)			//+++1.6
//=============================================================================
{
	int rc = 0;

	if (!hItem)
		hItem = GetRootItem();

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		rc = pXTCD->nSeparators;
	}

	return rc;
}

//=============================================================================
CString CXHtmlTree::GetItemText(HTREEITEM hItem, BOOL bStripHtml /*= FALSE*/) const
//=============================================================================
{
	CString strText = _T("");

	if (hItem)
	{
		strText = CTreeCtrl::GetItemText(hItem);

		if (bStripHtml)
		{
#ifdef XHTMLHTML
			// remove html tags
			CXHtmlDraw hd;
			int n = strText.GetLength();
			if (n > 3)		// minimum html string
			{
				TCHAR *s = new TCHAR [n + 16];
				hd.GetPlainText(strText, s, n+4);
				strText = s;
				delete [] s;
			}
#endif // XHTMLHTML
		}
	}

	return strText;
}

//=============================================================================
void CXHtmlTree::OnLButtonDown(UINT nFlags, CPoint point)
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::OnLButtonDown\n"));

	UINT uFlags = 0;
	HTREEITEM hItem = HitTest(point, &uFlags);
	HTREEITEM hItemSelected = GetSelectedItem();
	CEdit *pEdit = GetEditControl();

	if (hItem && (TVHT_ONITEMBUTTON & uFlags))
	{
		TRACE(_T("TVHT_ONITEMBUTTON\n"));
		if (pEdit && IsWindow(pEdit->m_hWnd))
		{
			TRACE(_T("sending WM_CLOSE to edit box\n"));
			pEdit->SendMessage(WM_CLOSE);
		}

		XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

		if (pXTCD && !pXTCD->bEnabled)
			return;

		if (pXTCD && pXTCD->bEnabled && ItemHasChildren(hItem))
		{
			BOOL bExpanded = !pXTCD->bExpanded;
			Expand(hItem, bExpanded ? TVE_EXPAND : TVE_COLLAPSE);
			return;
		}
	}
	else if (hItem && (TVHT_ONITEMSTATEICON & uFlags))
	{
		TRACE(_T("TVHT_ONITEMSTATEICON\n"));
		// click on checkbox
	}
	else if (hItem && IsSeparator(hItem))		//+++1.6
	{
		if (hItem != hItemSelected)
			SelectItem(hItem);

		m_hItemButtonDown = hItem;
		TRACE(_T("setting LBUTTONDOWN_TIMER >>>>>\n"));
		SetTimer(LBUTTONDOWN_TIMER, 100, NULL);
		return;
	}
	else if (hItem && (hItem == hItemSelected))
	{
		// item was already selected, so catch the button up state -
		// this will be start of edit. We don't need to check if disabled, 
		// because disabled items can't be selected
		//
		// NOTE: we use timer because we don't always get WM_LBUTTONUP message.
		//
		m_hItemButtonDown = hItem;
		TRACE(_T("setting LBUTTONDOWN_TIMER >>>>>\n"));
		SetTimer(LBUTTONDOWN_TIMER, 100, NULL);

#ifndef XHTMLDRAGDROP
		// We disable the return to prevent edit box opening on button down -
		// it should open only on button up.
		// For drag & drop, we need to call base function to allow 
		// OnBegindrag() to be called.
		return;
#endif // XHTMLDRAGDROP

	}
#ifdef XHTMLDRAGDROP
	else if (hItem && (hItem != hItemSelected))
	{
		SetTimer(SELECT_TIMER, 50, NULL);
	}
#endif // XHTMLDRAGDROP

	TRACE(_T("calling CTreeCtrl::OnLButtonDown\n"));

	CTreeCtrl::OnLButtonDown(nFlags, point);
}

//=============================================================================
void CXHtmlTree::OnRButtonDown(UINT nFlags, CPoint point) 
//=============================================================================
{
#ifdef XHTMLDRAGDROP
	EndDragScroll();
	SendRegisteredMessage(WM_XHTMLTREE_END_DRAG, 0, 0);
#endif // XHTMLDRAGDROP
	CTreeCtrl::OnRButtonDown(nFlags, point);
}

//=============================================================================
// OnClick - handle clicking on checkbox and link
BOOL CXHtmlTree::OnClick(NMHDR* /*pNMHDR*/, LRESULT* pResult)
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::OnClick\n"));

	BOOL rc = FALSE;	// allow parent to handle

	CPoint point;
	::GetCursorPos(&point);
	ScreenToClient(&point);

	UINT flags = 0;
	HTREEITEM hItem = HitTest(point, &flags);
	TRACE(_T("in CXHtmlTree::OnClick:  hItem=%X\n"), hItem);

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)		// will not be NULL if hItem is valid
	{
		if (pXTCD->bSeparator)			//+++1.6
		{
			return TRUE;	// can't set checkbox if separator
		}
		else if (flags & TVHT_ONITEMSTATEICON)
		{
			TRACE(_T("click on checkbox\n"));

			if (pXTCD->bEnabled && !m_bReadOnly)
			{
				SetCheck(hItem, !pXTCD->bChecked);

				//BOOL bCheck = GetCheck(hItem);
				//TRACE(_T("item %s checked\n"), bCheck ? _T("is") : _T("is not"));

				if (m_bSelectFollowsCheck)
					SelectItem(hItem);

				int nState = GetStateImage(hItem);
				nState |= HDCheckboxImageList::HOT_INDEX;

				SetItemState(hItem, INDEXTOSTATEIMAGEMASK(nState), TVIS_STATEIMAGEMASK);

				m_hHotItem = hItem;
			}
			else
			{
				rc = TRUE;
			}
		}
		else
		{
			TRACE(_T("not on checkbox\n"));
			// can't use TVHT_ONITEMLABEL because text might be shifted left
			if (pXTCD->ds.bHasAnchor && pXTCD->bEnabled)
			{
				if (IsOverAnchor(hItem, point))
				{
#ifdef XHTMLHTML
					TRACE(_T("click on link\n"));
					m_Links.GotoURL(pXTCD->ds.pszAnchor, SW_SHOW, (LPARAM)(UINT_PTR)hItem);
#endif // XHTMLHTML
				}
				else
				{
					TRACE(_T("not in link rect\n"));
				}
			}
			else if (!pXTCD->bEnabled)
			{
				TRACE(_T("click on disabled item\n"));
				rc = TRUE;
			}
		}
	}

	*pResult = 0;
	return rc;
}

//=============================================================================
BOOL CXHtmlTree::OnDblclk(NMHDR* /*pNMHDR*/, LRESULT* pResult) 
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::OnDblclk\n"));
	BOOL rc = FALSE;	// allow parent to handle

	CPoint point;
	::GetCursorPos(&point);
	ScreenToClient(&point);

	UINT flags = 0;
	HTREEITEM hItem = HitTest(point, &flags);
	TRACE(_T("in CXHtmlTree::OnDblclk:  hItem=%X\n"), hItem);

	if (hItem && (flags & (TVHT_ONITEM | TVHT_ONITEMBUTTON | TVHT_ONITEMSTATEICON)))
	{
		XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

		if (pXTCD && !pXTCD->bEnabled)
		{
			TRACE(_T("double click on disabled item\n"));
			rc = TRUE;		// don't allow default processing
		}
		else if (pXTCD && pXTCD->bEnabled && ItemHasChildren(hItem))
		{
			pXTCD->bExpanded = !pXTCD->bExpanded;
			pXTCD->bHasBeenExpanded = TRUE;
			SendRegisteredMessage(WM_XHTMLTREE_ITEM_EXPANDED, hItem, 
				pXTCD->bExpanded);
		}
	}

	*pResult = rc;
	return rc;
}

//=============================================================================
BOOL CXHtmlTree::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult) 
//=============================================================================
{
	BOOL rc = FALSE;	// allow parent to handle

	NMTREEVIEW* pNMTreeView = (NMTREEVIEW*)pNMHDR;
	
	HTREEITEM hItem = pNMTreeView->itemNew.hItem;
	HTREEITEM hOldItem = pNMTreeView->itemOld.hItem;

	CString strItem = GetItemText(hItem);
	TRACE(_T("in CXHtmlTree::OnSelchanged:  <%s>\n"), strItem);

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD && !pXTCD->bEnabled)
	{
		if (hOldItem)
		{
			SelectItem(hOldItem);
		}
		rc = TRUE;
	}
	else
	{
		if (hItem == m_hPreviousItem)
			rc = TRUE;
	}
	
	if (!rc)
		m_hPreviousItem = hItem;

	*pResult = rc;
	return rc;
}

//=============================================================================
BOOL CXHtmlTree::OnSelchanging(NMHDR* pNMHDR, LRESULT* pResult) 
//=============================================================================
{
	BOOL rc = FALSE;	// allow parent to handle

	NMTREEVIEW* pNMTreeView = (NMTREEVIEW*)pNMHDR;
	
	HTREEITEM hItem = pNMTreeView->itemNew.hItem;

	CString strItem = GetItemText(hItem);
	TRACE(_T("in CXHtmlTree::OnSelchanging:  <%s>\n"), strItem);

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	if (pXTCD && !pXTCD->bEnabled)
	{
		rc = TRUE;
	}

	*pResult = rc;
	return rc;
}

//=============================================================================
void CXHtmlTree::OnSize(UINT nType, int cx, int cy) 
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::OnSize\n"));

	CTreeCtrl::OnSize(nType, cx, cy);

	m_bFirstTime = TRUE;	// this will cause tooltips to be re-created
}

//=============================================================================
int CXHtmlTree::GetIndentLevel(HTREEITEM hItem)
//=============================================================================
{
	int nIndent = 0;

	while ((hItem = GetParentItem(hItem)) != NULL)
		nIndent++;

	return nIndent;
}

#ifdef XHTMLXML

//=============================================================================
BOOL CXHtmlTree::ConvertBuffer(const BYTE * inbuf, 
							   DWORD inlen,				// in bytes
							   BYTE ** outbuf,
							   DWORD& outlen,			// in bytes
							   ConvertAction eConvertAction /*= NoConvertAction*/)
//=============================================================================
{
	BOOL rc = FALSE;

	ASSERT(inbuf);
	ASSERT(inlen != 0);

	outlen = 0;

	if (inbuf && (inlen != 0))
	{
		TRACE(_T("eConvertAction=%d\n"), eConvertAction);

		// copy buffer to ensure it's null terminated
		BYTE * buf = new BYTE [inlen+16];
		memset(buf, 0, inlen+16);
		memcpy(buf, inbuf, inlen);

		if (eConvertAction == ConvertToUnicode)
		{
			TRACE(_T("converting to unicode\n"));
			int wlen = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)buf, -1, NULL, 0);
			TRACE(_T("wlen=%d\n"), wlen);
			WCHAR *pszText = new WCHAR [wlen+16];
			memset(pszText, 0, (wlen+16) * sizeof(WCHAR));
			MultiByteToWideChar(CP_ACP, 0, (LPCSTR)buf, -1, pszText, wlen+2);
			outlen = (DWORD)wcslen(pszText) * sizeof(WCHAR);	// bytes
			*outbuf = (BYTE*) pszText;
			delete [] buf;
		}
		else if (eConvertAction == ConvertToAnsi)
		{
			TRACE(_T("converting to ansi\n"));
			LPCWSTR wp = (LPCWSTR) buf;
			int alen = WideCharToMultiByte(CP_ACP, 0, wp, -1, NULL, 0, NULL, NULL);
			TRACE(_T("alen=%d\n"), alen);
			char *pszText = new char [alen+16];
			memset(pszText, 0, alen+16);
			WideCharToMultiByte(CP_ACP, 0, wp, -1, pszText, alen+2, NULL, NULL);
			outlen = (DWORD)strlen(pszText);	// bytes
			*outbuf = (BYTE*) pszText;
			delete [] buf;
		}
		else
		{
			// no conversion
			outlen = inlen;						// bytes
			*outbuf = buf;
		}

		TRACE(_T("outlen=%d\n"), outlen);

		rc = TRUE;
	}

	return rc;
}

//=============================================================================
BOOL CXHtmlTree::LoadXmlFromBuffer(const BYTE * pBuf, 
								   DWORD len,	// bytes
								   ConvertAction eConvertAction)
//=============================================================================
{
	BOOL rc = FALSE;

	ASSERT(pBuf);

	if (pBuf)
	{
		DWORD outlen = 0;
		BYTE * outbuf = 0;

		rc = ConvertBuffer(pBuf, len, &outbuf, outlen, 
				eConvertAction);

		if (rc)
		{
			CString strXML = (TCHAR *) outbuf;

			delete [] outbuf;

			// remove endofline and tabs
			strXML.Remove(_T('\n'));
			strXML.Remove(_T('\r'));
			strXML.Remove(_T('\t'));

			m_nXmlCount = 0;

			CXmlDocument xml;
			BOOL ret = xml.Parse(strXML);
			TRACE(_T("Parse returned %d\n"), ret);

			if (ret)		// useless, always returns TRUE
			{
				TRACE(_T("loading root +++++\n"));
				HTREEITEM hRoot = InsertXmlItem(xml.GetRootElement(), 0);
				if (hRoot)
					m_nXmlCount++;

				TRACE(_T("loading children +++++\n"));
				LoadXml(xml, xml.GetRootElement(), hRoot, m_nXmlCount);

				rc = TRUE;
			}
			else
			{
				TRACE(_T("ERROR - Parse failed\n"));
			}
		}
	}

	return rc;
}

//=============================================================================
BOOL CXHtmlTree::LoadXmlFromResource(HINSTANCE hInstance, 
									 LPCTSTR lpszResId, 
									 LPCTSTR lpszResType,
									 ConvertAction eConvertAction)
//=============================================================================
{
	BOOL rc = FALSE;

	ASSERT(lpszResId);
	ASSERT(lpszResType);

	if (lpszResId && lpszResType)
	{
		TCHAR *pszRes = NULL;

		// is this a resource name string or an id?
		if (HIWORD(lpszResId) == 0)
		{
			// id
			pszRes = MAKEINTRESOURCE(LOWORD((UINT)(UINT_PTR)lpszResId));
		}
		else
		{
			// string
			pszRes = (TCHAR *)lpszResId;
			TRACE(_T("pszRes=%s\n"), pszRes);
		}

		HRSRC hrsrc = FindResource(hInstance, pszRes, lpszResType);
		_ASSERTE(hrsrc);

		if (hrsrc)
		{
			DWORD dwSize = SizeofResource(hInstance, hrsrc);	// in bytes
			TRACE(_T("dwSize=%d\n"), dwSize);

			HGLOBAL hglob = LoadResource(hInstance, hrsrc);
			_ASSERTE(hglob);

			if (hglob)
			{
				LPVOID lplock = LockResource(hglob);
				_ASSERTE(lplock);

				if (lplock)
				{
					BYTE *pBuf = new BYTE [dwSize+16];
					ASSERT(pBuf);

					if (pBuf)
					{
						memcpy(pBuf, lplock, dwSize);

						pBuf[dwSize] = 0;
						pBuf[dwSize+1] = 0;
						pBuf[dwSize+2] = 0;

						LoadXmlFromBuffer(pBuf, dwSize, eConvertAction);
							
						delete [] pBuf;

						rc = TRUE;
					}
				}
			}
		}
	}

	return rc;
}

//=============================================================================
BOOL CXHtmlTree::LoadXmlFromFile(LPCTSTR lpszFile, ConvertAction eConvertAction)
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::LoadXmlFromFile: %s\n"), lpszFile);

	BOOL rc = FALSE;

	DWORD dwFileSize = 0;
	HANDLE hFile = NULL;

	ASSERT(lpszFile);

	if (lpszFile)
	{
		hFile = CreateFile(lpszFile,
						   GENERIC_READ,
						   FILE_SHARE_READ | FILE_SHARE_WRITE,
						   NULL,
						   OPEN_EXISTING,
						   FILE_ATTRIBUTE_NORMAL,
						   NULL);

		if (hFile == INVALID_HANDLE_VALUE)
		{
			TRACE(_T("ERROR - CreateFile failed\n"));
			hFile = 0;
		}
		else
		{
			dwFileSize = GetFileSize(hFile, NULL);

			if ((dwFileSize != INVALID_FILE_SIZE))
			{
				BYTE * pBuf = new BYTE [dwFileSize+16];
				ASSERT(pBuf);

				if (pBuf)
				{
					DWORD dwBytesRead = 0;
					BOOL bRet = ReadFile(hFile, 
										 (LPVOID) pBuf, 
										 dwFileSize, 
										 &dwBytesRead, 
										 NULL);

					if (bRet)
					{
						pBuf[dwFileSize] = 0;
						pBuf[dwFileSize+1] = 0;
						pBuf[dwFileSize+2] = 0;

						LoadXmlFromBuffer(pBuf, dwFileSize, 
							eConvertAction);

						rc = TRUE;
					}
					else
					{
						TRACE(_T("ERROR - ReadFile failed\n"));
						dwFileSize = 0;
					}

					delete [] pBuf;
				}
				else
				{
					TRACE(_T("ERROR - failed to allocate memory\n"));
					dwFileSize = 0;
				}
			}
			else
			{
				TRACE(_T("ERROR - GetFileSize failed\n"));
				dwFileSize = 0;
			}
		}
	}
	else
	{
		TRACE(_T("ERROR - bad parameter\n"));
	}

	if (hFile)
		CloseHandle(hFile);

	return rc;
}

//=============================================================================
HTREEITEM CXHtmlTree::InsertXmlItem(CXmlElement *pElement, HTREEITEM hParent)
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::InsertXmlItem\n"));

	HTREEITEM hItem = 0;

	ASSERT(pElement);
	if (pElement)
	{
		CString strName = pElement->GetValue(_T("name"));
		TRACE(_T("InsertXmlItem: <%s>\n"), strName);

		// hack to stop display
		if (/*!strName.IsEmpty() &&*/ strName.Compare(_T("...")) != 0)
		{
			strName.TrimLeft();
			strName.TrimRight();

#if 0  // -----------------------------------------------------------
			if (strName.GetLength() <= 0)
			{
				TRACE(_T("ERROR - name is empty\n"));
				ASSERT(FALSE);
			}
#endif // -----------------------------------------------------------

			XHTMLTREEDATA xhtd;

			CString strSeparator = pElement->GetValue(_T("separator"));		//+++1.6
			if (!strSeparator.IsEmpty())
				xhtd.bSeparator = _ttoi(strSeparator);
			CString strChecked = pElement->GetValue(_T("checked"));
			if (!strChecked.IsEmpty() && !xhtd.bSeparator)
				xhtd.bChecked = _ttoi(strChecked);
			CString strEnabled = pElement->GetValue(_T("enabled"));
			if (!strEnabled.IsEmpty() && !xhtd.bSeparator)
				xhtd.bEnabled = _ttoi(strEnabled);
			TRACE(_T("name=<%s>, <%s>, <%s> \n"), strName, strChecked, strEnabled);
			TRACE(_T("bChecked=%d  bEnabled=%d  bSeparator=%d\n"), xhtd.bChecked, xhtd.bEnabled, xhtd.bSeparator);

			// get colors - must be named colors, e.g. "red"
			CString strTextColor = pElement->GetValue(_T("text-color"));
			CString strBackgroundColor = pElement->GetValue(_T("text-background-color"));

			if (!strTextColor.IsEmpty())
			{
				CXNamedColors crText(strTextColor);
				xhtd.ds.crText = crText.GetRGB();
			}

			if (!strBackgroundColor.IsEmpty())
			{
				CXNamedColors crBackground(strBackgroundColor);
				xhtd.ds.crTextBackground = crBackground.GetRGB();
			}

			int nImage = -1;
			CString strImage = pElement->GetValue(_T("image"));
			if (!strImage.IsEmpty() && !xhtd.bSeparator)
			{
				nImage = _ttoi(strImage);
				CImageList *pImageList = GetImageList(TVSIL_NORMAL);
				if (pImageList)		//+++1.5
				{
					int nCount = pImageList->GetImageCount();
					if (nImage >= nCount)
					{
						TRACE(_T("ERROR - invalid image\n"));
						ASSERT(FALSE);
						nImage = -1;
					}
				}
				else
				{
					TRACE(_T("WARNING  no image list, setting m_bImages to FALSE\n"));
					m_bImages = FALSE;
				}
			}

			//static int nItem = 1;

			// line not empty - add item
			TVINSERTSTRUCT tvis      = { 0 };
			if (!xhtd.bSeparator)
			{
				tvis.item.mask       = TVIF_TEXT;
				tvis.item.cchTextMax = strName.GetLength();
				tvis.item.pszText    = strName.LockBuffer();
			}
			tvis.hParent             = hParent;
			tvis.item.iImage         = TV_NOIMAGE;
			tvis.item.iSelectedImage = TV_NOIMAGE;
			tvis.item.mask          |= TVIF_IMAGE | TVIF_SELECTEDIMAGE;
			//tvis.item.mask          |= TVIF_PARAM;
			//tvis.item.lParam         = nItem++;		// for debugging

			if (m_bImages && (nImage != -1) && !xhtd.bSeparator)		//+++1.5
			{
				tvis.item.iImage = tvis.item.iSelectedImage = nImage;
			}

			hItem = InsertItem(&tvis, &xhtd);
			ASSERT(hItem);

			if (!xhtd.bSeparator)
				strName.UnlockBuffer();

			if (hItem)
			{
				if (xhtd.bSeparator)
				{
					SetItemState(hItem, INDEXTOSTATEIMAGEMASK(0), TVIS_STATEIMAGEMASK);

					// increment separator count in parents
					HTREEITEM hParentSep = hItem;
					while ((hParentSep = GetParentItem(hParentSep)) != NULL)
					{
						IncrementSeparators(hParentSep, 1);
					}
				}
				else
				{
					CString strNote = pElement->GetValue(_T("note"));
					int nNoteWidth = 0;
					CString strNoteWidth = pElement->GetValue(_T("note-width"));
					if (!strNoteWidth.IsEmpty())
						nNoteWidth = _ttoi(strNoteWidth);
					if (!strNote.IsEmpty())
						SetItemNote(hItem, strNote, nNoteWidth);
				}
			}
		}
	}

	return hItem;
}

//=============================================================================
BOOL CXHtmlTree::LoadXml(CXmlDocument& xml, 
						 CXmlElement *pElement, 
						 HTREEITEM hParent,
						 int& nCount)
//=============================================================================
{
	BOOL rc = TRUE;

	ASSERT(pElement);
	if (pElement)
	{
		CXmlElement *pChild = xml.GetFirstChild(pElement);

		while (pChild != NULL)
		{
			HTREEITEM hItem = InsertXmlItem(pChild, hParent);

			if (!hItem)
			{
				// found '...'
				rc = FALSE;
				break;
			}

			nCount++;

			rc = LoadXml(xml, pChild, hItem, nCount);
			if (!rc)
				break;
			
			pChild = xml.GetNextSibling(pElement);
		}
	}

	return rc;
}

//=============================================================================
CString CXHtmlTree::GetXmlText(HTREEITEM hItem, LPCTSTR lpszElem)
//=============================================================================
{
	ASSERT(lpszElem);

	CString strElem = lpszElem;

	XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

	CString s = _T("");

	s = GetItemText(hItem);
	s.Replace(_T("&"), _T("&amp;"));
	s.Replace(_T("<"), _T("&lt;"));
	s.Replace(_T(">"), _T("&gt;"));
	s.Replace(_T("\""), _T("&quot;"));

	CString strXml = _T("");
	strXml.Format(_T("<%s name=\"%s\""), strElem, s);

	if (pXTCD)
	{
		s.Format(_T(" separator=\"%s\""), pXTCD->bSeparator ? _T("1") : _T("0"));
		strXml += s;

		s.Format(_T(" checked=\"%s\""), pXTCD->bChecked ? _T("1") : _T("0"));
		strXml += s;

		s.Format(_T(" enabled=\"%s\""), pXTCD->bEnabled ? _T("1") : _T("0"));
		strXml += s;

		if (pXTCD->ds.crText != COLOR_NONE)
		{
			TCHAR szColor[50];
			szColor[0] = _T('\0');
			CXNamedColors nc(pXTCD->ds.crText);
			if (nc.IsKnownColor())
				nc.GetName(szColor, sizeof(szColor)/sizeof(TCHAR)-1);
			else
				nc.GetHex(szColor, sizeof(szColor)/sizeof(TCHAR)-1);
			if (szColor[0] != _T('\0'))
			{
				s.Format(_T(" text-color=\"%s\""), szColor);
				strXml += s;
			}
		}

		if (pXTCD->ds.crTextBackground != COLOR_NONE)
		{
			TCHAR szColor[50];
			szColor[0] = _T('\0');
			CXNamedColors nc(pXTCD->ds.crTextBackground);
			if (nc.IsKnownColor())
				nc.GetName(szColor, sizeof(szColor)/sizeof(TCHAR)-1);
			else
				nc.GetHex(szColor, sizeof(szColor)/sizeof(TCHAR)-1);
			if (szColor[0] != _T('\0'))
			{
				s.Format(_T(" text-background-color=\"%s\""), szColor);
				strXml += s;
			}
		}
	}

	int nImage, nSelectedImage;
	GetItemImage(hItem, nImage, nSelectedImage);

	if (nImage != TV_NOIMAGE)
	{
		s.Format(_T(" image=\"%d\""), nImage);
		strXml += s;
	}

	s = GetItemNote(hItem);
	if (!s.IsEmpty())
	{
		s.Replace(_T("&"), _T("&amp;"));
		s.Replace(_T("<"), _T("&lt;"));
		s.Replace(_T(">"), _T("&gt;"));
		s.Replace(_T("\""), _T("&quot;"));

		strXml += _T(" note=\"");
		strXml += s;
		strXml += _T("\"");

		int nNoteWidth = 0;
		if (pXTCD)
			nNoteWidth = pXTCD->nTipWidth;
		if (nNoteWidth)
		{
			s.Format(_T(" note-width=\"%d\""), nNoteWidth);
			strXml += s;
		}
	}

	return strXml;
}

//=============================================================================
BOOL CXHtmlTree::SaveXml(HTREEITEM hItem, 
						 LPCTSTR lpszFile, 
						 BOOL bSaveAsUTF16)	// useful only on VS2005
//=============================================================================
{
	BOOL rc = FALSE;

	ASSERT(lpszFile);

	if (lpszFile)
	{
		FILE *f = NULL;

#if _MSC_VER >= 1400
		if (bSaveAsUTF16)
			f = _tfopen(lpszFile, _T("w, ccs=UTF-16LE"));
		else
#else
		UNUSED_ALWAYS(bSaveAsUTF16);
#endif
			f = _tfopen(lpszFile, _T("w"));

		if (f && hItem)
		{
			_ftprintf(f, _T("<?xml version=\"1.0\" encoding=\"ISO-8859-15\"?>\n"));

			int nPrevIndent = -1;
			int nIndent = 0;
			int nLevel = 1;
			BOOL bInItem = FALSE;
			CString ws = _T("");
			CString strText = _T("");

			HTREEITEM hNext = hItem;
			TCHAR *elem = _T("root");

			while (hNext)
			{
				strText = GetXmlText(hNext, elem);
				elem = _T("item");

				nIndent = GetIndentLevel(hNext);
				if (nIndent > nPrevIndent)
				{
					if (bInItem)
						_ftprintf(f, _T(">\n"));
					ws = _T("");
					for (int i = 0; i < nIndent; i++)
						ws += _T('\t');
					_ftprintf(f, _T("%s%s"), ws, strText);
					bInItem = TRUE;
					nLevel++;
				}
				else if (nIndent < nPrevIndent)
				{
					_ftprintf(f, _T("/>\n"), ws);
					ws = _T("");
					while (nPrevIndent > nIndent)
					{
						nPrevIndent--;
						ws = _T("");
						for (int i = 0; i < nPrevIndent; i++)
							ws += _T('\t');
						_ftprintf(f, _T("%s</item>\n"), ws);
					}
					_ftprintf(f, _T("%s%s"), ws, strText);
					bInItem = TRUE;
				}
				else
				{
					ws = _T("");
					for (int i = 0; i < nIndent; i++)
						ws += _T('\t');
					if (bInItem)
						//_ftprintf(f, _T("%s</item>\n"), ws);
						_ftprintf(f, _T("/>\n"));
					_ftprintf(f, _T("%s%s"), ws, strText);
					bInItem = TRUE;
				}
				nPrevIndent = nIndent;
				hNext = GetNextItem(hNext);			
			}

			_ftprintf(f, _T("/>\n"));
			nIndent--;
			while (nIndent > 0)
			{
				ws = _T("");
				for (int i = 0; i < nIndent; i++)
					ws += _T('\t');
				_ftprintf(f, _T("%s</item>\n"), ws);
				nIndent--;
			}

			_ftprintf(f, _T("</root>\n"));
			fflush(f);
			fclose(f);
			rc = TRUE;
		}
	}

	return rc;
}

#endif	// XHTMLXML


//=============================================================================
LRESULT CXHtmlTree::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
//=============================================================================
{
	LRESULT rc = CTreeCtrl::WindowProc(message, wParam, lParam);
	return rc;
}

//=============================================================================
// This function does special processing for bTextOnly=TRUE, since
// the text rect is shifted left when there is no image.
//
BOOL CXHtmlTree::GetItemRect(HTREEITEM hItem, LPRECT lpRect, BOOL bTextOnly)
//=============================================================================
{
	BOOL rc = CTreeCtrl::GetItemRect(hItem, lpRect, bTextOnly);

	if (bTextOnly)
	{
		int width = GetNormalImageWidth(hItem);		// get width of normal image

		if (width < 0)
		{
			// TV_NOIMAGE specified, shift text to the left
			lpRect->left += width;
			lpRect->right += width;
		}

		XHTMLTREEDATA *pXTCD = GetItemDataStruct(hItem);

		if (pXTCD)
		{
			lpRect->right = pXTCD->ds.nRightX;
		}

		//TRACERECT(*lpRect);
	}
	return rc;
}

//=============================================================================
BOOL CXHtmlTree::OnBeginlabeledit(NMHDR* /*pNMHDR*/, LRESULT* pResult) 
//=============================================================================
{
	*pResult = 0;

	HTREEITEM hItem = IsOverItem();

	TRACE(_T("in CXHtmlTree::OnBeginlabeledit: %X\n"), hItem);

	if (hItem && IsSeparator(hItem))	//+++1.6
	{
		*pResult =  1;		// separator item, don't allow edit
	}
	else if (m_bReadOnly)
	{
		*pResult =  1;		// tree is read-only, don't allow edit
	}
	else
	{
		m_nHorzPos = GetScrollPos(SB_HORZ);	// save initial scroll position
	}
	return (BOOL)*pResult;	// 0 = allow parent to handle
}

//=============================================================================
BOOL CXHtmlTree::OnEndlabeledit(NMHDR* /*pNMHDR*/, LRESULT* pResult) 
//=============================================================================
{
	TRACE(_T("in CXHtmlTree::OnEndlabeledit\n"));
	
	*pResult = 0;
	if ((m_nHorzPos == 0) && (GetScrollPos(SB_HORZ) != 0))
		SendMessage(WM_HSCROLL, SB_LEFT);	// editing caused tree to be 
											// scrolled, so scroll it back
	return 0;								// 0 = allow parent to handle
}

//=============================================================================
// following code contributed by David McMinn
BOOL CXHtmlTree::OnCommand(WPARAM wParam, LPARAM lParam)
//=============================================================================
{
	CEdit *pEdit = GetEditControl();
	if (((HIWORD(wParam) == EN_SETFOCUS) || (HIWORD(wParam) == EN_CHANGE))  && 
		pEdit && 
		(pEdit->GetSafeHwnd() == (HWND)lParam))
	{
		AdjustEditRect();
	}
	return CTreeCtrl::OnCommand(wParam, lParam);
}

//=============================================================================
// AdjustEditRect() moves the in-place edit box to line up with item text
//
void CXHtmlTree::AdjustEditRect()
//=============================================================================
{
	CEdit *pEdit = GetEditControl();

	if (pEdit && IsWindow(pEdit->m_hWnd))
	{
		HTREEITEM hItemEdit = GetSelectedItem();
		if (hItemEdit)
		{
			// adjust position of edit box for all items, even
			// those with image
			CRect rectClient;
			GetClientRect(&rectClient);
			CRect rectEdit;
			GetItemRect(hItemEdit, &rectEdit, TRUE);
			rectEdit.InflateRect(1, 1);				// allow for border
			rectEdit.OffsetRect(-1, -1);			// overlay existing text
			if (rectEdit.left < 0)
				rectEdit.left = 0;
			rectEdit.right = rectClient.right - 1;	// extend edit box full width
													// of tree control
			pEdit->MoveWindow(&rectEdit);
		}
	}
}

#ifdef XHTMLDRAGDROP

//=============================================================================
BOOL CXHtmlTree::IsCtrlDown()
//=============================================================================
{
	BOOL rc = FALSE;

	// always return FALSE if XHTMLTREE_DO_CTRL_KEY is not set
	if (m_dwDragOps & XHTMLTREE_DO_CTRL_KEY)
		if (GetAsyncKeyState(VK_CONTROL) < 0)
			rc = TRUE;

	return rc;
}

//=============================================================================
BOOL CXHtmlTree::IsDragCopy()
//
// returns TRUE if copy, FALSE if move
//
//  Ctrl key    COPY_DRAG flag    Action
// ----------+------------------+--------
//     up    |      false       |  move
//    down   |      false       |  copy
//     up    |      true        |  copy
//    down   |      true        |  move
//=============================================================================
{
	BOOL rc = FALSE;

	BOOL bCopyDrag = GetBit(m_dwDragOps, XHTMLTREE_DO_COPY_DRAG);
	BOOL bCtrlDown = IsCtrlDown();

	if (bCtrlDown && !bCopyDrag)		// Ctrl down, !bCopyDrag: copy
		rc = TRUE;
	else if (!bCtrlDown && bCopyDrag)	// Ctrl up, bCopyDrag: copy
		rc = TRUE;

	return rc;
}

//=============================================================================
HCURSOR CXHtmlTree::GetDragCursor()
//=============================================================================
{
	BOOL bCopyDrag = IsDragCopy();

	HCURSOR hCursor = bCopyDrag ? m_hDropCopyCursor : m_hDropMoveCursor;

	return hCursor;
}

//=============================================================================
void CXHtmlTree::SetDragCursor()
//=============================================================================
{
	HCURSOR hCursor = GetDragCursor();

	if (hCursor)
		SetCursor(hCursor);
	else
		SetCursor(m_hPreviousCursor);
}

//=============================================================================
void CXHtmlTree::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult) 
//=============================================================================
{
	ASSERT(!m_bDragging);

	NMTREEVIEW* pNMTreeView = (NMTREEVIEW*)pNMHDR;
	HTREEITEM hItem = pNMTreeView->itemNew.hItem;
	ASSERT(hItem);

	if (!hItem)
		return;

	BOOL bCopyDrag = IsDragCopy();

#ifdef _DEBUG
	CString strText = GetItemText(hItem);
	TRACE(_T("in CXHtmlTree::OnBegindrag: %s  bCopyDrag=%d\n"), strText, bCopyDrag);
#endif

	// allow parent to decide whether to permit drag
	XHTMLTREEDRAGMSGDATA dragdata = { 0 };
	dragdata.hItem = hItem;
	dragdata.bCopyDrag = bCopyDrag;

	LRESULT lResult = SendRegisteredMessage(WM_XHTMLTREE_BEGIN_DRAG, hItem, 
		(LPARAM)&dragdata);

	if (lResult)
	{
		return;
	}

	m_hItemButtonDown = hItem;
	TRACE(_T("setting LBUTTONDOWN_TIMER >>>>>\n"));
	SetTimer(LBUTTONDOWN_TIMER, 100, NULL);

	m_bDragging = TRUE;

	if (m_nNoDropCursor && (m_hNoDropCursor == NULL))
		m_hNoDropCursor = AfxGetApp()->LoadCursor(m_nNoDropCursor);
	if (m_nDropCopyCursor && (m_hDropCopyCursor == NULL))
		m_hDropCopyCursor = AfxGetApp()->LoadCursor(m_nDropCopyCursor);
	if (m_nDropMoveCursor && (m_hDropMoveCursor == NULL))
		m_hDropMoveCursor = AfxGetApp()->LoadCursor(m_nDropMoveCursor);

	SetDragCursor();

	m_dwDropHoverTime = GetTickCount();
	SetInsertMarkColor(m_crInsertMark);
	m_hPreviousDropItem = NULL;
	
	SetCapture();
	SetFocus();
	
	*pResult = 0;
}

//=============================================================================
// Determines if hItem is a child of hitemSuspectedParent
// Called in OnTimer to prevent the case where an item is attempted
// to be made a child of its own child.
//
BOOL CXHtmlTree::IsChildNodeOf(HTREEITEM hItem, HTREEITEM hitemSuspectedParent)
//=============================================================================
{
	do
	{
		if (hItem == hitemSuspectedParent)
			break;
	}
	while ((hItem = GetParentItem(hItem)) != NULL);

	return (hItem != NULL);
}

//=============================================================================
// CopyItem   - Copies an item to a new location
// Returns    - Handle of the new item
// hItem      - Item to be copied
// hNewParent - Handle of the parent for new item
// hAfter     - Item after which the new item should be created
HTREEITEM CXHtmlTree::MoveItem(HTREEITEM hItem, 
							   HTREEITEM hNewParent, 
							   HTREEITEM hAfter /*= TVI_LAST*/)
//=============================================================================
{
	HTREEITEM hNewItem = NULL;
	XHTMLTREEDATA *pXTCD = NULL;

	if (hItem)
		pXTCD = GetItemDataStruct(hItem);

	if (pXTCD)
	{
		TVINSERTSTRUCT tvis;
		CString strText = _T("");
		
		// get information of the source item
		tvis.item.hItem = hItem;
		tvis.item.mask = TVIF_CHILDREN | TVIF_HANDLE | TVIF_PARAM |
			TVIF_IMAGE | TVIF_SELECTEDIMAGE;
		GetItem(&tvis.item);  
		strText = GetItemText(hItem);
		
		tvis.item.cchTextMax = strText.GetLength();
		tvis.item.pszText = strText.LockBuffer();
		
		// insert the item at proper location
		tvis.hParent = hNewParent;
		tvis.hInsertAfter = hAfter;
		tvis.item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT | TVIF_PARAM;

		hNewItem = InsertItem(&tvis, pXTCD);

		strText.UnlockBuffer();

		if (pXTCD->bSeparator)			//+++1.6
		{
			SetItemState(hNewItem, INDEXTOSTATEIMAGEMASK(0), TVIS_STATEIMAGEMASK);

			// increment separator count
			HTREEITEM hParent = hNewItem;
			while ((hParent = GetParentItem(hParent)) != NULL)
			{
				IncrementSeparators(hParent, 1);
			}
		}

#if 0  // -----------------------------------------------------------
		SetItemState(hNewItem, GetItemState(hItem, TVIS_STATEIMAGEMASK), 
			TVIS_STATEIMAGEMASK);
#endif // -----------------------------------------------------------
		
	}

	return hNewItem;
}

//=============================================================================
// MoveBranch - Moves all items in a branch to a new location
// Returns    - The new branch node
// hBranch    - The node that starts the branch
// hNewParent - Handle of the parent for new branch
// hAfter     - Item after which the new branch should be created
HTREEITEM CXHtmlTree::MoveBranch(HTREEITEM hBranch, 
								 HTREEITEM hNewParent, 
								 HTREEITEM hAfter /*= TVI_LAST*/)
//=============================================================================
{
	HTREEITEM hChild;
	
	HTREEITEM hNewItem = MoveItem(hBranch, hNewParent, hAfter);
	HTREEITEM hNext = GetChildItem(hBranch);
	hChild = hNext;
	while (hChild)
	{
		// recursively transfer all the items
		MoveBranch(hChild, hNewItem);
		hNext = GetNextSiblingItem(hChild);
		hChild = hNext;
	}
	return hNewItem;
}

//=============================================================================
BOOL CXHtmlTree::StartMoveBranch(HTREEITEM hItem, 
								 HTREEITEM hNewParent, 
								 HTREEITEM hAfter /*= TVI_LAST*/)
//=============================================================================
{
	BOOL bCopyDrag = IsDragCopy();

	TRACE(_T("in CXHtmlTree::StartMoveBranch: %s %X:  new parent=%X  after=%X\n"), 
		bCopyDrag ? _T("copying") : _T("moving"), hItem, hNewParent, hAfter);

	BOOL rc = FALSE;

	ASSERT(hItem);

	if (hItem)
	{
		// allow parent to decide whether to permit drag
		XHTMLTREEDRAGMSGDATA dragdata = { 0 };
		dragdata.hItem      = hItem;
		dragdata.hNewParent = hNewParent;
		dragdata.hAfter     = hAfter;
		dragdata.bCopyDrag  = bCopyDrag;

		LRESULT lResult = SendRegisteredMessage(WM_XHTMLTREE_END_DRAG, hItem, 
			(LPARAM)&dragdata);

		rc = lResult == 0;				// allow drop if lResult is 0

		if (rc)
		{
			MoveBranch(hItem, hNewParent, hAfter);

			if (!bCopyDrag)
			{
				// moving, so delete original item
				m_nDeleted = 0;
				m_nDeletedChecked = 0;
				DeleteBranch(hItem);	// this was a move, not a copy
				TRACE(_T("m_nDeleted=%d  m_nDeletedChecked=%d\n"), m_nDeleted, m_nDeletedChecked);
			}
		}
	}

	return rc;
}

//=============================================================================
void CXHtmlTree::AutoScroll(HTREEITEM hItem)
//=============================================================================
{
	DWORD dwSpeedFlags = XHTMLTREE_DO_SCROLL_NORMAL | XHTMLTREE_DO_SCROLL_FAST;

#ifdef _DEBUG
	if ((m_dwDragOps & dwSpeedFlags) == dwSpeedFlags)
	{
		TRACE(_T("ERROR - only one speed flag should be set\n"));
		ASSERT(FALSE);
	}
#endif // _DEBUG

	if ((m_dwDragOps & dwSpeedFlags) == 0)
	{
		// no speed flags, don't scroll
		return;
	}
		
	if (m_dwDragOps & XHTMLTREE_DO_SCROLL_NORMAL)
	{
		// scroll every other time, about 5 times a second
		if ((++m_nScrollTime & 1) == 0)
			return;
	}

	int n = 0;

	if (hItem)
	{
		int nScrollZone = GetItemHeight() + 5;

		CPoint point;
		GetCursorPos(&point);	// screen coords
		CRect rect;
		GetClientRect(&rect);
		ClientToScreen(&rect);	// screen coords

		int nDistance = 0;
		BOOL bUp = TRUE;

		if (point.y < (rect.top + nScrollZone))
		{
			nDistance = point.y - rect.top;
			bUp = TRUE;		// up
		}
		else if (point.y > (rect.bottom - nScrollZone))
		{
			nDistance = rect.bottom - point.y;
			bUp = FALSE;	// down
		}

		if (nDistance > 0)
		{
			SetInsertMark(0, 0);	// remove previous insert mark

			if (nDistance > ((nScrollZone/3)*2))
			{
				// in region farthest from border, scroll slow
				SendMessage(WM_VSCROLL, bUp ? SB_LINEUP : SB_LINEDOWN);
				n = 1;
			}
			else if (nDistance > (nScrollZone/3))
			{
				// in middle region, scroll faster
				SendMessage(WM_VSCROLL, bUp ? SB_LINEUP : SB_LINEDOWN);
				SendMessage(WM_VSCROLL, bUp ? SB_LINEUP : SB_LINEDOWN);
				n = 2;
			}
			else
			{
				// in region closest to border, scroll very fast
				SendMessage(WM_VSCROLL, bUp ? SB_PAGEUP : SB_PAGEDOWN);
				n = 3;
			}

			ScreenToClient(&point);

			HTREEITEM hItem = IsOverItem(&point);

			if (hItem)
			{
				if ((m_dwDragOps & XHTMLTREE_DO_SHIFT_KEY) &&
					(GetAsyncKeyState(VK_SHIFT) < 0))
				{
					TRACE(_T("VK_SHIFT down\n"));
					if (IsSeparator(hItem))					//+++1.6
					{
						SelectDropTarget(NULL);
						SetInsertMark(hItem, TRUE);
					}
					else
					{
						SelectDropTarget(hItem);
					}
				}
				else
				{
					SetInsertMark(hItem, TRUE);
					SelectDropTarget(NULL);
				}
			}
		}
	}

#ifdef XHTMLTREE_DEMO
	CWnd *pWnd = GetParent();
	if (!pWnd)
		pWnd = GetOwner();
	if (pWnd && ::IsWindow(pWnd->m_hWnd))
		pWnd->SendMessage(WM_XHTMLTREE_SCROLL_SPEED, n, 0);
#endif // XHTMLTREE_DEMO
}
#endif // XHTMLDRAGDROP

//=============================================================================
void CXHtmlTree::EndDragScroll()
//=============================================================================
{
#ifdef XHTMLDRAGDROP

	TRACE(_T("in CXHtmlTree::EndDragScroll\n"));

	KillTimer(LBUTTONDOWN_TIMER);
	KillTimer(SHIFT_UP_TIMER);
	SetInsertMark(0, 0);
	m_nScrollTime = 0;
	if (m_bAutoScroll)
		AutoScroll(NULL);
	SelectDropTarget(NULL);
	ReleaseCapture();
	m_bDragging = FALSE;
	SetCursor(m_hPreviousCursor);

#endif // XHTMLDRAGDROP
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0


Written By
Technical Lead
Tunisia Tunisia
Services:
http://www.pushframework.com/?page_id=890

Comments and Discussions