
/*	Copyright (C) G. Jespersen
	See Licence.txt.*/

// DsLavTreeView.cpp : implementation file
//

#include "stdafx.h"
#include "DsLavSplit.h"
#include "DsLavTreeView.h"
#include "DsLavListView.h"	// drag and drop
#include "DsLavVideoView.h"	// 7/22 tab windows

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

/////////////////////////////////////////////////////////////////////////////
// CDsLavTreeView

IMPLEMENT_DYNCREATE(CDsLavTreeView, CTreeView)

CDsLavTreeView::CDsLavTreeView()
{
	TRACE(_T("Constructing CDsLavTreeView\n"));	

	m_bInitialized = FALSE;
	m_manualExpand = FALSE;

	//m_dwDefaultStyle is a documented member of CCtrlView
	m_dwDefaultStyle =  WS_CHILD		| 
						WS_VISIBLE		| 
					//	TVS_HASLINES	|		// shows/hides lines
						TVS_LINESATROOT	|	// must have
						TVS_HASBUTTONS	| 
						TVS_SHOWSELALWAYS;

	// Dragging
	m_hDragItem = NULL;
	m_pImageList = NULL;
	m_bDragging = FALSE;
	m_nDelayInterval = 400;		// Default delay interval = 500 milliseconds
	m_nScrollInterval = 200;		// Default scroll interval = 200 milliseconds
	m_nScrollMargin = ::GetSystemMetrics(SM_CYHSCROLL); // 24 pix 9/22-1

	// Moving
	m_moveType = MOVE_NONE;		// 9/22-1
	m_bMoving = FALSE;
	m_bDrawAfter = TRUE;
	m_bSkipMouse = FALSE;		// 8/22-9
	m_hItemMoveSrc = NULL;
	m_hItemMoveDest = NULL;
}

CDsLavTreeView::~CDsLavTreeView()
{
	// Dragging
	// Delete the image list created by CreateDragImage.
	if (m_pImageList != NULL)
		delete m_pImageList;

	TRACE(_T("Destructing CDsLavTreeView\n"));
}

BEGIN_MESSAGE_MAP(CDsLavTreeView, CTreeView)
	//{{AFX_MSG_MAP(CDsLavTreeView)
	ON_WM_CREATE()
	ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, OnItemexpanding)
	ON_NOTIFY_REFLECT(TVN_SELCHANGED, OnSelchanged)
	ON_NOTIFY_REFLECT(TVN_BEGINDRAG, OnBegindrag)
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONUP()
	ON_WM_TIMER()
	ON_WM_CONTEXTMENU()
	ON_NOTIFY_REFLECT(NM_RCLICK, OnRclick)
	ON_WM_LBUTTONDOWN()
	ON_WM_KEYDOWN()
	//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CDsLavTreeView drawing

void CDsLavTreeView::OnDraw(CDC* pDC)
{
	TRACE(_T("in CDsLavTreeView OnDraw()\n"));
	CDsLavDoc * pLavDoc = GetDocument();
	if(!pLavDoc)
		return;
}

/////////////////////////////////////////////////////////////////////////////
// CDsLavTreeView message handlers

int CDsLavTreeView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	TRACE(_T("in CDsLavTreeView OnCreate()\n"));
	if (CTreeView::OnCreate(lpCreateStruct) == -1)
		return -1;
	return 0;
}

void CDsLavTreeView::OnInitialUpdate() 
{
	CTreeView::OnInitialUpdate();

	// Just once.
	if(m_bInitialized)
		return;
	m_bInitialized = TRUE;
	
	TRACE(_T("In CDsLavTreeView OnInitialUpdate\n"));

	// Add root topics
	AddRootTopics();

	// Set image list
	if(!m_imageList.m_hImageList)
	{
		HICON hIcon[5];
		int n;
		m_imageList.Create(16, 16, 0, 3, 3); // 32, 32 for large icons
		hIcon[0] = AfxGetApp()->LoadIcon(IDR_ICON_01);	// green triangle
		hIcon[1] = AfxGetApp()->LoadIcon(IDR_ICON_02);	// yellow triangle
		hIcon[2] = AfxGetApp()->LoadIcon(IDR_ICON_03);	// yellow folder
		hIcon[3] = AfxGetApp()->LoadIcon(IDR_ICON_04);	// CD drive
		hIcon[4] = AfxGetApp()->LoadIcon(IDR_ICON_05);	// USB drive
		for(n = 0; n < 5; n++) 
		{
			m_imageList.Add(hIcon[n]);
		}
		GetTreeCtrl().SetImageList(&m_imageList, TVSIL_NORMAL);
	}

	// Set up TreeView font.
	CDsLavDoc * pDoc = GetDocument();
	if(pDoc)
	{
		if(!(HFONT)m_font)
		{
			LOGFONT lf = pDoc->m_treeLf;
			m_font.CreateFontIndirect(&lf);
			GetTreeCtrl().SetFont(&m_font);
		}
	}
}

void CDsLavTreeView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) 
{
	TRACE(_T("In CDsLavTreeView OnUpdate\n"));

	// TopicTree data has changed.
	// Caller will update CListView.
	if(lHint == TREE_UPDATE)
	{
		TRACE(_T("CDsLavTreeView OnUpdate - lHint = TOPICTREE_UPDATE\n"));

		// Get tree ref.
		CTreeCtrl & treeCtrl = GetTreeCtrl();

		// Rebuild root topics.
		//
		// Set flag as OnSelChanged() gets called by framework.
		TRACE(_T("CDsLavTreeView OnUpdate - calling AddRootTopics - m_manualExpand = TRUE\n"));
		m_manualExpand = TRUE;
		AddRootTopics();
		m_manualExpand = FALSE;

		// Select the current topic in CDsLavDoc.
		CDsLavDoc * pDoc = GetDocument();
		if(!pDoc)
			return;
		CTopic & curTopic = pDoc->m_curTopic;

		// If Favorites.
		if(curTopic.GetTopicName() == STR_FAVS)
		{
			TRACE(_T("CDsLavTreeView OnUpdate - selecting STR_FAVS\n"));
			SelectTopic(curTopic.GetTopicName());
		}
		// Else a CTopicTree topic.
		else
		{
			// Get topic nodes array.
			CNodeArray nodeAr = curTopic.m_nodesAr;

			// Select.
			if(SelectTopic(nodeAr))
			{
				TRACE(_T("CDsLavTreeView OnUpdate - selected topic using nodesArray\n"));
			}
			// Else select with topic string - prone to errors 
			// if duplicate topic names.
			else
			{
				TRACE(_T("CDsLavTreeView OnUpdate - select topic using nodesArray failed.\n"));

				// Use topic name string.
				if(SelectTopic(curTopic.GetTopicName()))
				{
					TRACE(_T("CDsLavTreeView OnUpdate - selected topic using topic name\n"));
				}
				else
				{
					TRACE(_T("CDsLavTreeView OnUpdate - select topic using topic name failed")
						  _T(" - nothing selected.\n"));
				}
			}
			// 6/22
			// Expand parent of deleted topic as needed.
			// m_expParentNodeAr gets initialized in OnKeyDown()
			// and in OnContextMenu() when a topic is deleted.
			// This is needed because the tree is rebuilt after
			// a topic has been deleted and there is no way to
			// find out if the parent was previously expanded 
			// via GetItemState(TVIS_EXPANDED).
			// Note SelectTopic() will have selected the deleted
			// topic's parent.
			int nNodes = nodeAr.GetSize();
			if(nNodes && m_expParentNodeAr == nodeAr)
			{
				HTREEITEM hItem = treeCtrl.GetSelectedItem();
				if(hItem)
					treeCtrl.Expand(hItem, TVE_EXPAND);
				// Clean for next.
				m_expParentNodeAr.RemoveAll();
			}			
		}
	}
	// Font
	else if(lHint == TREE_FONT_UPDATE)
	{
		// Set up TreeView font.
		CDsLavDoc * pDoc = GetDocument();
		if(pDoc)
		{
			// Clean.
			if((HFONT)m_font)
				m_font.DeleteObject();
			// Set up font.
			LOGFONT lf = pDoc->m_treeLf;
			m_font.CreateFontIndirect(&lf);
			GetTreeCtrl().SetFont(&m_font);
		}
	}
	// Drive - media inserted or removed.
	else if(lHint == TREE_DISC_INSERT)
	{
		UpdateDrives((LPCTSTR)pHint, TRUE);
	}
	else if(lHint == TREE_DISC_REMOVE)
	{
		UpdateDrives((LPCTSTR)pHint, FALSE);
	}
}

// Add Root Topics - others added in OnItemExpanding.
BOOL CDsLavTreeView::AddRootTopics() 
{
	TRACE(_T("In CDsLavTreeView AddRootTopics\n"));

	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return FALSE;

	// Get tree ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Get a TopicTree ref.
	CTopicTree & topicTree = pDoc->m_topicTree;
	CString topicName;
	BOOL isDiscFile = FALSE;
	HTREEITEM  hItem;

	// Empty tree.
	treeCtrl.DeleteAllItems();

	////////////////////////////////////////////////
	// Add default STR_FAVS "favorites" topic first.
	// Note: 
	//  SetItemData is not needed as favorites is not part of CTopicTree.
	//  No "temp" item is set at Favorites has no child topics.
	hItem = treeCtrl.InsertItem(STR_FAVS, 1, 1, TVI_ROOT);	// 0, 0, image, imageSel

	//////////////////////////////
	// Add Topic Tree roots next.
	int sz = topicTree.m_topicTreeAr.GetSize();
	for(int i = 0; i < sz; i++)
	{
		CTopic & topic = topicTree.m_topicTreeAr[i];
		topicName = topic.GetTopicName();

		// If a disc File: VIDF - video file, AUDF - audio file
		isDiscFile = FALSE;
		if(topic.m_topicMediaType == VIDF || topic.m_topicMediaType == AUDF)
			isDiscFile = TRUE;

		// Add the root.
		if(isDiscFile)
			hItem = treeCtrl.InsertItem(topicName, 2, 2, TVI_ROOT);
		else
			hItem = treeCtrl.InsertItem(topicName, 1, 1, TVI_ROOT);	// 0, 0, image, imageSel

		// Set Item Data to array index.
		treeCtrl.SetItemData(hItem, (DWORD) i);

		// Add dummy item for children.
		if(topic.m_childTopicAr.GetSize())// topics only: || topic.m_topicMediaAr.GetSize())
		{
			treeCtrl.InsertItem(_T("temp"), hItem);
		}
	}
	////////////////////////////////
	// Add drive Volumes (CD, USB).
	AddDrives();

	return TRUE;
}

/////////////////////////////////////////////////////////////////
// Drive Volume handlers.

// Update a drive for insertion or removal.
// Called from OnUpdate() & CDsLavDoc::DriveDeviceChanged().
void CDsLavTreeView::UpdateDrives(CString drive, BOOL inserted)
{
	TRACE(_T("In CDsLavTreeView::UpdateDrives\n"));
	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return;

	// Get tree ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();
	CString driveStr;
	BOOL bFound = FALSE;
	BOOL bUpdateList = FALSE;
	BOOL bSelected = FALSE;
	BOOL bExpanded = FALSE;

	// Check tree for existing drive.
	HTREEITEM hItem = treeCtrl.GetRootItem();
	while(hItem != NULL)
	{
		driveStr = treeCtrl.GetItemText(hItem);

		// If existing Drive.
		if(driveStr == drive)
		{
			bFound = TRUE;
			break;
		}
		else
			hItem = treeCtrl.GetNextItem(hItem, TVGN_NEXT);
	}
	// If a new Drive item.
	if(!bFound && inserted)
	{
		AddDriveItem(drive);
		m_driveStrings.Add(drive);
	}
	else if(bFound && hItem)
	{
		// Inserted
		if(inserted)
		{
			// Add dummy child.
			treeCtrl.InsertItem(_T("temp"), hItem);
			treeCtrl.Expand(hItem, TVE_COLLAPSE);	// cancel any expanding
		}
		// Removed.
		else
		{
			// Get type:
			//  If a USB has been pulled, the drive is now invalid
			//  and nType will be DRIVE_NO_ROOT_DIR (1).
			//	A removed CD will be DRIVE_CDROM (5).
			UINT nType = ::GetDriveType(drive);

			// If a pulled USB.
			//
			// Note: Calling DeleteItem() causes the tree to select
			// the previous item in the tree, which creates a call
			// to OnSelchanged(). That call will cause the ListView
			// to be updated to the new selection - so no clean up
			// of the ListView is needed here.
			if(nType == DRIVE_NO_ROOT_DIR)
			{
				TRACE(_T("In CDsLavTreeView::UpdateDrives - USB removed\n"));
				// Delete the Drive.
				treeCtrl.DeleteItem(hItem);

				// Remove Drive from m_driveStrings.
				int sz = m_driveStrings.GetSize();
				int iDelete = -1;
				for(int i = 0; i < sz; i++)
				{
					if(m_driveStrings[i] == drive)
					{
						iDelete = i;
						break;
					}
				}
				if(iDelete != -1)
					m_driveStrings.RemoveAt(iDelete);

				// Done.
				return;
			}
			// Else CD - others may need coding.
			else
			{
				DeleteAllChildren(hItem);
				bUpdateList = TRUE;
			}
		}
	}
	// Update the ListView if Drive selected.
	if(bUpdateList && hItem)
	{
		// If the Drive or one of it's child topics is currently selected, 
		if(pDoc->m_curTopic.m_nodesAr.GetSize()						&& 
		   pDoc->m_bCurTopicDrive									&&
		   pDoc->m_curTopic.m_nodesAr.GetAt(0).m_topicName == drive )  // 1st node is drive
		{
			// Get tree nodes,
			CNodeArray nodesAr;
			if(GetTopicNodesFromItem(hItem, nodesAr))
			{
				// If a Drive selected...
				if(IsDrive(nodesAr))
				{
					// Get nodes as a file path - no ending backslash.
					CString path = nodesAr.GetFilePathFromNodes();

					// Update ListView display. Will clear or show 
					// files/folders.
					pDoc->TreeViewSelectedDrive(path);
				}
			}
		}
	}
}

// For CD drives and removable drives, the TreeView and ListView
// are populated normally through the File Storage SDK -
// no TopicTree handlers are involved.
int CDsLavTreeView::AddDrives()
{
	int nPos = 0;
	int nDrivesAdded = 0;
	CString string = _T ("?:");
	m_driveStrings.RemoveAll();

	DWORD dwDriveList = ::GetLogicalDrives();

	while(dwDriveList) 
	{
		if(dwDriveList & 1) 
		{
			string.SetAt(0, _T('A') + nPos);
			if(AddDriveItem(string))
			{
				m_driveStrings.Add(string);
				nDrivesAdded++;
			}
		}
		dwDriveList >>= 1;
		nPos++;
	}
	return nDrivesAdded;
}

BOOL CDsLavTreeView::AddDriveItem(CString & strDrive)
{
	// Get tree ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	CString string;
	HTREEITEM hItem;

	// Validate path, add wildcard.
    CString strPath = strDrive;
    if(strPath.Right (1) != _T ("\\"))
        strPath += _T ("\\");

	// Get type.
	UINT nType = ::GetDriveType(strPath);

	// AddRoot Drives:
	switch(nType) 
	{
		// CD.
		case DRIVE_CDROM:
			hItem = treeCtrl.InsertItem(strDrive, 3, 3, TVI_ROOT);	// 3 CD
			// Set +- button in child directories.
			SetButtonState(hItem, strDrive);
			break;

		// USB etc.
		case DRIVE_REMOVABLE:
			hItem = treeCtrl.InsertItem(strDrive, 4, 4, TVI_ROOT);	// 4 USB drive
			SetButtonState(hItem, strDrive);
			break;

		default:
			return FALSE;
	}
	return TRUE;
}

// Check if a Drive has directories and add temp
// child if so.
BOOL CDsLavTreeView::SetButtonState(HTREEITEM hItem, CString strDrivePath)
{
	// Get tree ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

    HANDLE hFind;
    WIN32_FIND_DATA fd;
    BOOL bResult = FALSE;

    CString strPath = strDrivePath;
    if (strPath.Right(1) != _T ("\\"))
        strPath += _T ("\\");
    strPath += _T ("*.*");

    if((hFind = ::FindFirstFile(strPath, &fd)) == INVALID_HANDLE_VALUE) // fail
        return bResult;

    do 
	{
        if(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 
		{
            CString strComp = (LPCTSTR)&fd.cFileName;
            if ((strComp != _T (".")) && (strComp != _T (".."))) 
			{
                treeCtrl.InsertItem(_T("temp"), hItem);
                bResult = TRUE;
                break;
            }
        }
    } 
	while (::FindNextFile (hFind, &fd));

    ::FindClose (hFind);
    return bResult;
}

// Is path a root drive - "E:"
BOOL CDsLavTreeView::IsDrive(CString strPath)
{
	int nDrives = m_driveStrings.GetSize();	// list built in AddDrives().
	for(int i = 0; i < nDrives; i++)
	{
		if(strPath == m_driveStrings[i])
			return TRUE;
	}
	return FALSE;
}
// Is path a root drive - "E:"	8/22-9
BOOL CDsLavTreeView::IsDrive(HTREEITEM hItem)
{
	// Get item param text.
	CTreeCtrl & treeCtrl = GetTreeCtrl();
	CString strDrive = treeCtrl.GetItemText(hItem);
	return IsDrive(strDrive);
}
// Is nodeAr a drive path. 
BOOL CDsLavTreeView::IsDrive(CNodeArray & nodesAr)
{
	int sz = nodesAr.GetSize();
	if(sz)
	{
		CNodeItem item = nodesAr.GetAt(0);
		return IsDrive(item.m_topicName);
	}
	return FALSE;
}

int CDsLavTreeView::AddDriveDirectories(HTREEITEM hItem, CString strDrivePath)
{
	HANDLE hFind;
	WIN32_FIND_DATA fd;
	HTREEITEM hNewItem;
	int nCount = 0;

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	CString strPath = strDrivePath;
	if(strPath.Right(1) != _T ("\\"))
		strPath += _T("\\");
	strPath += _T("*.*");

	// If no children.
	if((hFind = ::FindFirstFile(strPath, &fd)) == INVALID_HANDLE_VALUE) 
	{
		// If a root item, add a dummy item. Should never get here.
		if(treeCtrl.GetParentItem(hItem) == NULL)
			treeCtrl.InsertItem(_T("temp"), hItem);
		return 0;
	}
	// Else path contains files/directories.	
	do
	{
		// Get any directories.
		if(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 
		{
			CString strComp = (LPCTSTR)&fd.cFileName;
			if((strComp != _T (".")) && (strComp != _T (".."))) 
			{
				// Inset with folder icon - 2.
				hNewItem = treeCtrl.InsertItem((LPCTSTR)&fd.cFileName, 2, 2, hItem);

				CString strNewPath = strDrivePath;
				if(strNewPath.Right(1) != _T ("\\"))
					strNewPath += _T ("\\");

				strNewPath += (LPCTSTR)&fd.cFileName;
				SetButtonState(hNewItem, strNewPath);
				nCount++;
			}
		}
	} 
	while(::FindNextFile(hFind, &fd));

	::FindClose (hFind);
	return nCount;
}

//////////////////////////////////////////////////////////////////
// Up-Down handlers.

// User clicked the + or - icon.
void CDsLavTreeView::OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult) 
{
    *pResult = FALSE;
	TRACE(_T("In CDsLavTreeView OnItemexpanding\n"));	

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Control calls here.
	// Flag set in:
	//	xxx
	if(m_manualExpand)
	{
		NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
		BOOL expand = pNMTreeView->action == TVE_EXPAND;
		TRACE(_T("CDsLavTreeView::OnItemexpanding manualExpand expanding = %d\n"), expand);
		return;
	}
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
	HTREEITEM hItem = pNMTreeView->itemNew.hItem;
	CString string = treeCtrl.GetItemText(hItem);
	BOOL isDiscFile = FALSE;
	CNodeArray nodesAr;

	// Expanding for child topics.
	if(pNMTreeView->action == TVE_EXPAND) 
	{
		TRACE(_T("In CDsLavTreeView::OnItemexpanding - expanding %s\n"), string);

		// Get nodes from hItem up to root.
		if(GetTopicNodesFromItem(hItem, nodesAr))
		{
			// AddChildrenToTree()
			//
			//test drives
			// If a Drive.
			if(IsDrive(nodesAr))
			{
				// Get nodes as a file path - no ending backslash.
				CString path = nodesAr.GetFilePathFromNodes();

				// Remove "temp" item.
				DeleteFirstChild(hItem);

				// Add directories (folders).
				int nAdded = AddDriveDirectories(hItem, path);
				
				// Return TRUE to prevent the list from expanding or collapsing
				if(!nAdded)
					*pResult = TRUE;
				return; 
			}
			CDsLavDoc * pDoc = GetDocument();
			if(!pDoc)
				return;

			// Get a TopicTree ref.
			CTopicTree & topicTree = pDoc->m_topicTree;

			// Get the CTopic we're expanding.
			CTopic topic;
			if(topicTree.GetTopic(nodesAr, topic))
			{
				int sz = topic.m_childTopicAr.GetSize();
				if(!sz)
				{
					// Error.
					TRACE(_T("ERROR CDsLavTreeView OnItemexpanding; Topic %s has no child topics.\n"), 
								string);
					return;//*pResult = TRUE;
				}
				// Remove "temp" item.
				DeleteFirstChild(hItem);

				// Add child topics.
				CString topicName;
				HTREEITEM hChildItem;
				for(int i = 0; i < sz; i++)
				{
					CTopic & childTopic = topic.m_childTopicAr[i];
					topicName = childTopic.GetTopicName();

					// If a disc File: VIDF - video file, AUDF - audio file
					if(topic.m_topicMediaType == VIDF || topic.m_topicMediaType == AUDF)
						isDiscFile = TRUE;

					// Add the topic.
					if(isDiscFile)
						hChildItem = treeCtrl.InsertItem(topicName, 2, 2, hItem);
					else
						hChildItem = treeCtrl.InsertItem(topicName, 1, 1, hItem);

					// Set Item Data to array index.
					treeCtrl.SetItemData(hChildItem, (DWORD) i);

					// Add dummy item if child topic has topic children.
					if(childTopic.m_childTopicAr.GetSize())
					{
						treeCtrl.InsertItem(_T("temp"), hChildItem);
					}
				}
				// Good
				//*pResult = TRUE; no go?
			}
			else
			{
				// Error.
				TRACE(_T("ERROR CDsLavTreeView OnItemexpanding; GetTopic failed for %s\n"), 
							string);
				return;//*pResult = TRUE;
			}
		}
    }
    else 
	{ // pNMTreeView->action == TVE_COLLAPSE
		TRACE(_T("In CDsLavTreeView::OnItemexpanding - collapsing %s\n"), string);

		int nDeleted = DeleteAllChildren(hItem);
		if(nDeleted)
			treeCtrl.InsertItem(_T("temp"), hItem);
    }
}

// Delete first child of hItem.
void CDsLavTreeView::DeleteFirstChild(HTREEITEM hItem)
{
    HTREEITEM hChildItem;
    if ((hChildItem = GetTreeCtrl().GetChildItem(hItem)) != NULL)
        GetTreeCtrl().DeleteItem(hChildItem);
}

// Delete all children of hItem.
int CDsLavTreeView::DeleteAllChildren(HTREEITEM hItem)
{
	int nDeleted = 0;
    HTREEITEM hChildItem;
    if ((hChildItem = GetTreeCtrl().GetChildItem(hItem)) == NULL)
        return nDeleted;

    do 
	{
        HTREEITEM hNextItem = GetTreeCtrl().GetNextSiblingItem(hChildItem);
        GetTreeCtrl().DeleteItem(hChildItem);
		nDeleted ++;
        hChildItem = hNextItem;
    } 
	while(hChildItem != NULL);
	return nDeleted;
}

// Get list of nodes from hItem up to root.
BOOL CDsLavTreeView::GetTopicNodesFromItem(HTREEITEM hItem, CNodeArray & nodesAr)
{
	if(!hItem)
		return FALSE;

	CTreeCtrl & treeCtrl = GetTreeCtrl();

	CNodeItem nodeItem;

	// Get item str.
    nodeItem.m_topicName = treeCtrl.GetItemText(hItem);
	if(nodeItem.m_topicName.IsEmpty())
		return FALSE;

	// Get item index
	nodeItem.m_arIndex = treeCtrl.GetItemData(hItem);

	// Add to node list.
	int elmt = nodesAr.Add(nodeItem);

	// Get parent nodes of hItem.
    HTREEITEM hParent = hItem;
    while ((hParent = treeCtrl.GetParentItem(hParent)) != NULL) 
	{
		nodeItem.Zero();

		// Get parent item str.
		nodeItem.m_topicName = treeCtrl.GetItemText(hParent);
		if(nodeItem.m_topicName.IsEmpty())
			return FALSE;

		// Get item index
		nodeItem.m_arIndex = treeCtrl.GetItemData(hParent);

		// Insert at front of list.
		nodesAr.InsertAt(0, nodeItem);
    }

	if(nodesAr.GetSize() == 0)
		return FALSE;

    return TRUE;
}

void CDsLavTreeView::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult) 
{
	TRACE(_T("In CDsLavTreeView::OnSelchanged\n"));
	*pResult = 0;

	// Pass on early framework calls.
	if(!m_bInitialized)
		return;

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Control calls here. Called by framework when 
	// AddRootTopics() is called to rebuild tree.
	//
	// Flag set in:
	//	OnUpdate()
	if(m_manualExpand)
	{
		TRACE(_T("CDsLavTreeView OnSelchanged -	m_manualExpand = TRUE\n"));
		return;
	}

	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
	HTREEITEM hItem = pNMTreeView->itemNew.hItem;
	CString string = treeCtrl.GetItemText(hItem);

	TRACE(_T("CDsLavTreeView OnSelchanged - %s selected\n"), string);

	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return;

	// Favorites.
	if(string == STR_FAVS)
	{
		int nMediaItems  = pDoc->m_favsMediaAr.GetSize();
		TRACE(_T("CDsLavTreeView OnSelchanged - %s has %d media\n"), 
					string, nMediaItems);
		pDoc->TreeViewSelectedFavorites();
	}
	// TopicTree or Drive.
	else
	{
		// Get tree nodes,
		CNodeArray nodesAr;
		if(GetTopicNodesFromItem(hItem, nodesAr))
		{
			// If a Drive selected...
			if(IsDrive(nodesAr))
			{
				// Get nodes as a file path - no ending backslash.
				CString path = nodesAr.GetFilePathFromNodes();

				// Get files for ListView display.
				pDoc->TreeViewSelectedDrive(path);
				return;
			}

			// Get files for ListView display.
			pDoc->TreeViewSelectedTopic(nodesAr);
		}
	}
	*pResult = 0;
}

// Get ptr to CListView
CDsLavListView * CDsLavTreeView::GetListViewPtr()
{
	CDsLavListView * pListView = NULL;
	CWnd * pWnd = GetParent();
	if(pWnd->IsKindOf(RUNTIME_CLASS(CMySplitterWnd)))
	{
		CMySplitterWnd * pSplitter = (CMySplitterWnd*)pWnd;
		CWnd * pChild = pSplitter->GetPane(0, 1);
		if(pChild->IsKindOf(RUNTIME_CLASS(CDsLavListView)))
			pListView = (CDsLavListView*)pChild;
	}
	return pListView;
}

// Select a topic - CString version
// Expands tree as needed.
HTREEITEM CDsLavTreeView::SelectTopic(const CString & strTopic)
{
	TRACE(_T("In CDsLavTreeView::SelectTopic - CString version\n"));

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	BOOL itemHasChildren = FALSE;
	BOOL itemIsExpanded = FALSE;

	// Must have topic str.
	if(strTopic.IsEmpty())
		return NULL;

	// Check each root and all children of that root.
	// Entire tree will be searched.
	HTREEITEM root = treeCtrl.GetRootItem();
	while(root != NULL)
	{
		HTREEITEM hFound = SelectChildTopic(strTopic, root);
		if(hFound)
		{
			return hFound; 
		}
		root = treeCtrl.GetNextSiblingItem(root);
	}
   return NULL;
}

// Select a child topic - CString version.
// Expands tree as needed.
HTREEITEM CDsLavTreeView::SelectChildTopic(const CString & strTopic, 
										   HTREEITEM hParentItem)
{
	TRACE(_T("In CDsLavTreeView::SelectChildTopic - CString version\n"));

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	BOOL itemIsExpanded = FALSE;
	HTREEITEM hItemExpanded = NULL;

	// Must have topic str.
	if(strTopic.IsEmpty())
		return NULL;

	// Check whether the param item is the searched one.
	CString strItem = treeCtrl.GetItemText(hParentItem);
	if(strItem == strTopic)
	{
		treeCtrl.SelectItem(hParentItem);
		return hParentItem; 
	}

	// Must be expanded to load child items.
	itemIsExpanded = treeCtrl.GetItemState(hParentItem, TVIS_EXPANDED) & TVIS_EXPANDED;
	if(!itemIsExpanded)
	{
		treeCtrl.Expand(hParentItem, TVE_EXPAND);
		hItemExpanded = hParentItem;
	}

	// Get a handle to the first child item.
	HTREEITEM hChild = treeCtrl.GetChildItem(hParentItem);

	// Iterate as long a new item is found.
	while(hChild)
	{
		// Check the children of the current item
		HTREEITEM hFound = SelectChildTopic(strTopic, hChild);
		if(hFound)
		{
			treeCtrl.SelectItem(hFound);
			return hFound;
		}

		// Get the next sibling of the current item
		hChild = treeCtrl.GetNextSiblingItem(hChild);
	}
	// If an item was expanded and nothing selected, collapse
	// the item,
	if(hItemExpanded)
	{
		treeCtrl.Expand(hParentItem, TVE_COLLAPSE);
	}
	// Return NULL if nothing was found
	return NULL;
}

// Select a topic - CNodeArray version.
// Expands tree as needed.
HTREEITEM CDsLavTreeView::SelectTopic(const CNodeArray & nodeAr)
{
	CStringArray strAr;
	nodeAr.GetTopicNameStrings(strAr);
	return SelectTopic(strAr);
}

// Select a topic - CStringArray version
// Expands tree as needed.
HTREEITEM CDsLavTreeView::SelectTopic(const CStringArray & nodeAr)
{
	TRACE(_T("In CDsLavTreeView::SelectTopic\n"));

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();
	BOOL itemHasChildren = FALSE;
	BOOL itemIsExpanded = FALSE;

	// Must have nodes.
	int nNodes = nodeAr.GetSize();
	if(!nNodes)
		return NULL;

	// Get topic at start of list - the root.
	int iNode = 0;
	CString rootNode = nodeAr.GetAt(iNode);

	HTREEITEM hRoot = treeCtrl.GetRootItem();
	CString itemStr;
	while(hRoot != NULL)
	{
		itemStr = treeCtrl.GetItemText(hRoot);
		if(itemStr == rootNode)
		{
			// If one node.
			if(nNodes == 1)
			{
				// 6/22
				// Select and set focus to root Tree Control topic.
				treeCtrl.SelectItem(hRoot);
				treeCtrl.SetFocus();

				return hRoot;
			}
			// Else check children.
			else
			{
				iNode++;
				return SelectChildTopic(hRoot, nodeAr, iNode);
			}
		}
		else
			hRoot = treeCtrl.GetNextSiblingItem(hRoot);
	}
	// Nothing found.
	return NULL;
}

// Select a child topic - CStringArray version.
// Expands tree as needed.
HTREEITEM CDsLavTreeView::SelectChildTopic(HTREEITEM hParentItem, 
										   const CStringArray & nodeAr, int iNode)
{
	TRACE(_T("In CDsLavTreeView::SelectChildTopic\n"));

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	BOOL itemIsExpanded = FALSE;
	HTREEITEM hChildItem;
	CString itemStr;

	// Must have nodes.
	int nNodes = nodeAr.GetSize();
	if(!nNodes)
		return NULL;

	// Check bounds.
	if(iNode >= nNodes)
		return NULL;

	// Get topic at iNode of list - the child
	// topic we are looking for.
	CString strNode = nodeAr.GetAt(iNode);

	// hItem param must have children.
	if(!treeCtrl.ItemHasChildren(hParentItem))
		return NULL;

	// Must be expanded to load child items.
	itemIsExpanded = treeCtrl.GetItemState(hParentItem, TVIS_EXPANDED) & TVIS_EXPANDED;
	if(!itemIsExpanded)
		treeCtrl.Expand(hParentItem, TVE_EXPAND);

	// Get a handle to the first child item.
	hChildItem = treeCtrl.GetChildItem(hParentItem);

	// Iterate hParentItem's children.
	while(hChildItem)
	{
		itemStr = treeCtrl.GetItemText(hChildItem);
		if(itemStr == strNode)
		{
			// If at the last node.
			if(iNode + 1 == nNodes)
			{
				treeCtrl.SelectItem(hChildItem);

				// 6/22
				// Select and set focus to selected Tree Control child topic.
				treeCtrl.SelectItem(hChildItem);
				treeCtrl.SetFocus();

				return hChildItem;
			}

			// Else check hChildItem's children.
			else
			{
				iNode++;
				return SelectChildTopic(hChildItem, nodeAr, iNode);
			}
		}
		// Else get the next sibling of the current item
		else
			hChildItem = treeCtrl.GetNextSiblingItem(hChildItem);
	}
	return NULL;
}

/////////////////////////////////////////////////////////////////////////////
// Drag and Drop -  from MSDN TreeDemo - August 1998 Prosise


// TVN_BEGINDRAG notification - an item is being 
// dragged with the left mouse button.
void CDsLavTreeView::OnBegindrag(NMHDR* pnmhdr, LRESULT* pResult)
{
	*pResult = 0;
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*) pnmhdr;

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// No dragging favorites folder - it is currently hard 
	// coded to be first in tree and is not included in
	// the CTopicTree. See AddRootTopics().
	HTREEITEM hItem = pNMTreeView->itemNew.hItem;
	if(treeCtrl.GetItemText(hItem) == STR_FAVS)// "Favorites"
	{
		TRACE(_T("CDsLavTreeView::OnBegindrag attempting to drag favorites - bailing.\n"));
		MessageBox(_T("Favorites may not be dragged."));
		return;
	}

	// No dragging Drives.
	//
	// Get nodes to item.
	CNodeArray nodesAr;
	GetTopicNodesFromItem(hItem, nodesAr);
	// If a drive.
	if(IsDrive(nodesAr))
		return;

	// See: CDsLavTreeView::OnInitialUpdate() for image list creation.
	//
	// Create a drag image.
	m_pImageList = treeCtrl.CreateDragImage(hItem);
	if(!m_pImageList)
	{
		TRACE(_T("CDsLavTreeView::OnBegindrag m_pImageList NULL\n"));
		return;
	}
	// Compute the coordinates of the "hot spot"--the location of the
	// cursor relative to the upper left corner of the item rectangle.
	CRect rect;
	treeCtrl.GetItemRect(hItem, rect, TRUE);
	CPoint point (pNMTreeView->ptDrag.x, pNMTreeView->ptDrag.y);
	CPoint hotSpot = point;
	hotSpot.x -= rect.left;
	hotSpot.y -= rect.top;

	// Convert the client coordinates in "point" to coordinates relative
	// to the upper left corner of the control window.
	CPoint client(0, 0);
	treeCtrl.ClientToScreen(&client);
	treeCtrl.GetWindowRect(rect);
	point.x += client.x - rect.left; // no change
	point.y += client.y - rect.top;  // no change

	// Capture the mouse and begin dragging.
	SetCapture();
	m_pImageList->BeginDrag(0, hotSpot);
	m_pImageList->DragEnter(this, point);
	m_hDragItem = hItem;
	m_bDragging = TRUE;
}

BOOL CDsLavTreeView::MoveBeforeFirstSibling(HTREEITEM hItem, CPoint ptScreen)
{
	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// If hItem is Favorites topic, 
	BOOL isFavorites = FALSE;

	CString parentStr;
	CString firstChildStr;
	HTREEITEM hItemParent;
	HTREEITEM hItemFirstChild = NULL;
	CRect itemRect;
	int midRect = 0;
	BOOL aboveMid = FALSE;

	// Get the hItem rect, and determine if mouse is
	// above the middle of the rect.
	treeCtrl.GetItemRect(hItem, &itemRect, TRUE);
	midRect = itemRect.top + itemRect.Height() / 2;
	if(ptScreen.y < midRect)
		aboveMid = TRUE;

	// Get the parent of m_hItemMoveSrc - NULL if a ROOT item.
	hItemParent = treeCtrl.GetParentItem(m_hItemMoveSrc);

	// Get the first child.
	if(hItemParent)
	{
		parentStr = treeCtrl.GetItemText(hItemParent);
		hItemFirstChild = treeCtrl.GetChildItem(hItemParent);
		if(hItemFirstChild)
		{
			firstChildStr = treeCtrl.GetItemText(hItemFirstChild); //dbg
		}
	}
	// If moving a ROOT topic, the InsertMark is always
	// below - including below the Favorites Topic.
	else
	{
		return FALSE;
	}
	// If on the first child (first sibling), and above
	// its mid pt, draw InsertMark above.
	if(hItemFirstChild && hItemFirstChild == hItem && aboveMid)
	{
		return TRUE;
	}
	// No go - draw insertMark below item.
	return FALSE;
}

// 9/22-1 rework
// Mouse move.
void CDsLavTreeView::OnMouseMove(UINT nFlags, CPoint point) 
{
	// Base class.
	CTreeView::OnMouseMove(nFlags, point);

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Check if cursor is near top or bottom of 
	// treeview window - used for scrolling.
	BOOL bNearTop = FALSE, bNearBot = FALSE;
	CRect client;
	GetClientRect(client);
	int height = client.Height();
	if(point.y >= 0 && point.y <= m_nScrollMargin)
		bNearTop = TRUE;
	else if(point.y >= height - m_nScrollMargin && point.y <= height)
		bNearBot = TRUE;

	// Moving a tree item to a new location after selecting
	// "Move Up Down" from context menu.
	//  Flag set in OnContextMenu().
	if(m_bMoving)
	{
		// Ignore - see OnContextMenu - moving.
		if(m_bSkipMouse)
		{
			m_bSkipMouse = FALSE;
			TRACE(_T("In CDsLavTreeView OnMouseMove - moving - skipping msg\n"));
			return;
		}
		// Ensure. See OnContextMenu().
		if(!m_hItemMoveSrc)
		{
			m_bMoving = FALSE;
			return;
		}

		HTREEITEM hItem;
		UINT nFlags;

		// Erase any previous insert mark.
		treeCtrl.SetInsertMark(NULL, m_bDrawAfter);

		// Hit test mouse pos.
		hItem = treeCtrl.HitTest(point, &nFlags);
		if(hItem)
		{
			HTREEITEM hTemp = NULL;
			BOOL isSibling = FALSE;
			CString str;

			// Test if hItem is a sibling of m_hItemMoveSrc.
			// Note isSibling will be false if hItem equals
			// m_hItemMoveSrc.
			//
			// Check siblings above m_hItemMoveSrc.
			hTemp = m_hItemMoveSrc;
			while(hTemp != NULL && isSibling == FALSE)
			{
			   hTemp = treeCtrl.GetPrevSiblingItem(hTemp);

			   // If a sibling.
			   if(hItem == hTemp)
				   isSibling = TRUE;
			}
			// Check siblings below m_hItemMoveSrc.
			hTemp = m_hItemMoveSrc;
			while(hTemp != NULL && isSibling == FALSE)
			{
			   hTemp = treeCtrl.GetNextSiblingItem(hTemp);

			   // If a sibling.
			   if(hItem == hTemp)
				   isSibling = TRUE;
			}

			// If on a valid sibling, draw insert mark.
			if(isSibling)
			{
				// Set to default.
				m_bDrawAfter = TRUE;

				// If on the first sibling, and above its mid
				// point, draw insert mark above/before hItem.
				// That allows moving m_hItemMoveSrc to the 
				// first sibling position or to the begining 
				// of its TopicTree array.
				if(MoveBeforeFirstSibling(hItem, point))
				{
					m_bDrawAfter = FALSE;
				}
				// If hitem is on the item above, there is
				// no moving. Note InsertMark is after the
				// hItemAbove, which is where hItemMoveSrc 
				// already is.
				else
				{
					HTREEITEM hItemAbove = treeCtrl.GetPrevSiblingItem(m_hItemMoveSrc);
					if(hItemAbove && hItemAbove == hItem)
						return;
				}
				// Draw the InsertMark per m_bDrawAfter.
				treeCtrl.SetInsertMark(hItem, m_bDrawAfter);

				// The destination is hItem, with m_hItemMoveSrc
				// placed before or after it per m_bDrawAfter. 
				// See OnLButtonDown().
				m_hItemMoveDest = hItem;

				// Scroll insert mark into view.
				if(bNearBot)
					treeCtrl.EnsureVisible(m_hItemMoveDest);
				else if(bNearTop)
				{
					// Show sibling above.
					hItem = treeCtrl.GetPrevSiblingItem(m_hItemMoveDest);
					if(hItem)
						treeCtrl.EnsureVisible(hItem);
				}
			}
		}
		// Done with Move Up Down.
		return;
	}

	// Else we are dragging for a drop.
	if(m_bDragging && m_pImageList != NULL) 
	{
		// Stop the scroll timer if it's running.
		KillTimer(1);

		// Prep.
		m_moveType = MOVE_NONE;
		BOOL bCanExpand = FALSE;
		CString str;

		// Find out which item (if any) the cursor is over.
		UINT nFlags;
		HTREEITEM hItemHit = treeCtrl.HitTest(point, &nFlags);

		// Remove any previous Insert Mark.
		treeCtrl.SetInsertMark(NULL, TRUE);

		// If nothing hit.
		if(!hItemHit)
		{
			// Remove any previous Insert Mark.
			treeCtrl.SetInsertMark(NULL, TRUE);

			// Erase the old drag image and draw a new one.
			m_pImageList->DragMove(point);

			// Erase current highlight.
			HighlightDropTarget(point);

			// Set NO cursor.
			::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_NO));
			return;
		}

		// Else we hit an item.
		str = treeCtrl.GetItemText(hItemHit);

		// If hit is a removable drive.
		BOOL targetDrive = FALSE;
		if(IsDrive(hItemHit))
			targetDrive = TRUE;

		// Determine if hit is on, above or below item.
		CRect itemRect;
		treeCtrl.GetItemRect(hItemHit, &itemRect, FALSE);
		int height = (itemRect.bottom - itemRect.top) / 4 + 1;
		if(point.y >= itemRect.top && point.y <= itemRect.top + height)
			m_moveType = MOVE_ABOVE;

		else if(point.y <= itemRect.bottom && point.y >= itemRect.bottom - height)
			m_moveType = MOVE_BELOW;

		else
			m_moveType = MOVE_ON;

		// If drag cursor is on top of the hit item.
		if(m_moveType == MOVE_ON)
		{
			// Can't drop above or below drives.
			if(targetDrive)
				return;

			// Can't drop into favs.
			str = treeCtrl.GetItemText(hItemHit);
			if(str == STR_FAVS)
				return;

			// Erase the old drag image and draw a new one.
			m_pImageList->DragMove(point);

			// Remove any previous Insert Mark.
			treeCtrl.SetInsertMark(NULL, TRUE);

			// Redraw item rect.
			CRect rect = itemRect;
			rect.bottom += 5;
			rect.top -= 5;
			treeCtrl.InvalidateRect(&rect);
			treeCtrl.UpdateWindow();

			// Highlight the drop target if the cursor is over an item.
			HTREEITEM hItem = HighlightDropTarget(point);

			// Modify the cursor to provide visual feedback to the user.
			// Note: It's important to do this AFTER the call to DragMove.
			::SetCursor(hItem == NULL ? AfxGetApp()->LoadStandardCursor(IDC_NO)	:
										(HCURSOR)::GetClassLong(m_hWnd, GCL_HCURSOR));

			// Check if hItem has children and is not expanded - for timer beloww.
			if(treeCtrl.ItemHasChildren(hItemHit) &&
			   !IsItemExpanded(hItemHit)		  )
				bCanExpand = TRUE;
		}
		// Else if drag cursor is just below or above the hit item.
		else if(m_moveType == MOVE_ABOVE || m_moveType == MOVE_BELOW)
		{
			// Get item above and below drop source.
			HTREEITEM hItemAbove = treeCtrl.GetPrevSiblingItem(m_hDragItem);
			HTREEITEM hItemBelow = treeCtrl.GetNextSiblingItem(m_hDragItem);

			// Can't drom onto itself.
			if(hItemHit == m_hDragItem)
				return;

			// Can't drop directly above itself.
			if(m_moveType == MOVE_BELOW && hItemHit == hItemAbove)
				return;

			// Can't drop directly below itself.
			if(m_moveType == MOVE_ABOVE && hItemHit == hItemBelow)
				return;

			// Can't drop above favs.
			CString strHit = treeCtrl.GetItemText(hItemHit);
			if(strHit == STR_FAVS)
				return;

			// Can't drop above or below drives.
			if(targetDrive)
				return;

			// Remove any previous Drag Image.
			m_pImageList->DragShowNolock(FALSE);

			// Remove any previous DropTarget selection.
			treeCtrl.SelectDropTarget(NULL);

			// Remove any previous Insert Mark.
			treeCtrl.SetInsertMark(NULL, TRUE);

			// Highlight drop target.
			treeCtrl.SelectDropTarget(hItemHit);

			// Set Insert Mark.
			if(m_moveType == MOVE_ABOVE)
				treeCtrl.SetInsertMark(hItemHit, FALSE);
			else
				treeCtrl.SetInsertMark(hItemHit, TRUE);
		}
		// Set a timer for scrolling the tree window and 
		// expanding a collapsed item.
		if(m_moveType != MOVE_NONE)
		{
			UINT ret = 0;
			// If the cursor is at the top or bottom of the window,
			// or if it's over a collapsed item.
			if(bNearTop || bNearBot || bCanExpand)
				ret = SetTimer(1, m_nDelayInterval, NULL); // see constructor, returns 1
			int g = 0;
		}
	}
}

// 8/22-9
// Set initial Mark for moving a Topic.
//  Called from OnContextMenu().
void CDsLavTreeView::MoveTopicSetInitialMark()
{
	// Ensure. See OnContextMenu().
	if(!m_hItemMoveSrc || !m_bMoving)
	{
		m_bMoving = FALSE;
		return;
	}
	TRACE(_T("In CDsLavTreeView::MoveTopicSetInitialMark\n"));

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Src item being moved.
	HTREEITEM hSrc = m_hItemMoveSrc;
	// Parent of src - NULL if a root.
	HTREEITEM hSrcParent = treeCtrl.GetParentItem(hSrc);
	// Siblings above and below src.
	HTREEITEM hSrcAbove = treeCtrl.GetPrevSiblingItem(hSrc);
	HTREEITEM hSrcBelow = treeCtrl.GetNextSiblingItem(hSrc);
	// 2nd siblings above and below src.
	HTREEITEM hSrcAbove2 = treeCtrl.GetPrevSiblingItem(hSrcAbove);
	HTREEITEM hSrcBelow2 = treeCtrl.GetNextSiblingItem(hSrcBelow);

	//dbg strings
	CString strSrc = treeCtrl.GetItemText(hSrc);
	CString strSrcParent = treeCtrl.GetItemText(hSrcParent);
	CString strSrcAbove = treeCtrl.GetItemText(hSrcAbove);
	CString strSrcBelow = treeCtrl.GetItemText(hSrcBelow);
	CString strSrcAbove2 = treeCtrl.GetItemText(hSrcAbove2);
	CString strSrcBelow2 = treeCtrl.GetItemText(hSrcBelow2);

	HTREEITEM hNewDest = NULL;
	BOOL bDrawAfter = TRUE;

	// Set below.
	if(hSrcBelow)
	{
		hNewDest = hSrcBelow;
		bDrawAfter = TRUE;
	}
	// Else set above.
	else if(hSrcAbove2)
	{
		hNewDest = hSrcAbove2;
		bDrawAfter = TRUE;
	}
	// Special draw above:
	// Else child topic with 2 siblings and lower sibling selected (hSrc).
	else if(hSrcParent && hSrcAbove && !hSrcBelow && !hSrcAbove2)
	{
		hNewDest = hSrcAbove;
		bDrawAfter = FALSE;
	}
	// Finish.
	if(hNewDest)
	{
		// Erase any previous insert Dest.
		treeCtrl.SetInsertMark(NULL, FALSE);

		// Set members.
		m_bDrawAfter = bDrawAfter;
		m_hItemMoveDest = hNewDest;

		// Draw mark
		treeCtrl.SetInsertMark(hNewDest, bDrawAfter);
		treeCtrl.UpdateWindow();
	}
}

// 8/22-6 test
// 8/22-9 fix bug
// Keyboard moving of topics in the Tree.
//  Called from OnKeyDown() - Arrow Keys.
void CDsLavTreeView::MoveTopicKeyboard(UINT nChar)
{
	// Ensure. See OnContextMenu().
	if(!m_hItemMoveSrc || !m_bMoving)
	{
		m_bMoving = FALSE;
		return;
	}
	TRACE(_T("In CDsLavTreeView::MoveTopicKeyboard\n"));

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Src item being moved.
	HTREEITEM hSrc = m_hItemMoveSrc;
	// Parent of src - NULL if a root.
	HTREEITEM hSrcParent = treeCtrl.GetParentItem(hSrc);
	// Siblings above and below src.
	HTREEITEM hSrcAbove = treeCtrl.GetPrevSiblingItem(hSrc);
	HTREEITEM hSrcBelow = treeCtrl.GetNextSiblingItem(hSrc);
	//dbg strings
	CString strSrc = treeCtrl.GetItemText(hSrc);
	CString strSrcParent = treeCtrl.GetItemText(hSrcParent);
	CString strSrcAbove = treeCtrl.GetItemText(hSrcAbove);
	CString strSrcBelow = treeCtrl.GetItemText(hSrcBelow);

	// Note:
	// Typically the insertion mark will be placed below the 
	// destination tree item.
	// If placed above, it's a "Special" case. 

	// Destination item. 
	HTREEITEM hDest = m_hItemMoveSrc;
	// If previously moved with arrow keys. 
	//  Note m_hItemMoveDest set null in OnContextMenu()
	//  and updated here.
	if(m_hItemMoveDest)
		hDest = m_hItemMoveDest;

	// Above and below dest,
	HTREEITEM hDestAbove = treeCtrl.GetPrevSiblingItem(hDest);
	HTREEITEM hDestBelow = treeCtrl.GetNextSiblingItem(hDest);
	//dbg strings
	CString strDest = treeCtrl.GetItemText(hDest);
	CString strDestAbove = treeCtrl.GetItemText(hDestAbove);
	CString strDestBelow = treeCtrl.GetItemText(hDestBelow);

	// Move up.
	if(nChar == VK_LEFT	||
	   nChar == VK_UP	)
	{
		TRACE(_T(" UP: Dest=%s, DestAbove=%s, DestBelow=%s, DrawAfter=%d\n"), 
					strDest, strDestAbove, strDestBelow, m_bDrawAfter);

		// If a dest above.
		if(hDestAbove)
		{
			//dbg
			if(strDestAbove == _T("Public Radio"))
			{
				int g = 0;
			}
			// Get item above destAbove.
			HTREEITEM hDestAbove2 = treeCtrl.GetPrevSiblingItem(hDestAbove);
			CString strDestAbove2 = treeCtrl.GetItemText(hDestAbove2);

			// Get parent of destAbove - null if a Root topic.
			HTREEITEM hParent = treeCtrl.GetParentItem(hDestAbove);
			CString strParent = treeCtrl.GetItemText(hParent);

			// Get first child of parent - null if a Root topic.
			HTREEITEM hFirstChild = treeCtrl.GetChildItem(hParent);
			CString strFirstChild = treeCtrl.GetItemText(hFirstChild);

			// Special: If srcItem is the second child item in a topic. 
			if(!hDestAbove2 && hParent && hDestAbove != hSrc && hDestAbove != hFirstChild)
			{
				// Erase any previous insert Dest.
				treeCtrl.SetInsertMark(NULL, FALSE);

				// Draw new insert above destAbove.
				m_bDrawAfter = FALSE;
				treeCtrl.SetInsertMark(hDestAbove, m_bDrawAfter);

				// Update destination member. 
				m_hItemMoveDest = hDestAbove;

				// Scroll to mark. 9/22-1
				treeCtrl.EnsureVisible(m_hItemMoveDest);
				return;
			}			
			// Dest cannot be below a Drive - should never get here.
			if(IsDrive(hDestAbove))
			{
				TRACE(_T("In CDsLavTreeView::MoveTopicKeyboard - KEY UP isDrive - bailing\n"));
				return;
			}
			// DestAbove cannot be the src item or the item above src item.
			if(hDestAbove == hSrc		||
			   hDestAbove == hSrcAbove	)
			{
				TRACE(_T("In CDsLavTreeView::MoveTopicKeyboard - KEY UP jump up\n"));

				// Get item above srcAbove.
				HTREEITEM hItem = treeCtrl.GetPrevSiblingItem(hSrcAbove);
				// If none, bail.
				if(!hItem)
				{
					// Special: The item above srcAbove may be a parent item.
					if(hParent && hDestAbove2 && hFirstChild && hFirstChild == hDestAbove2)
					{
						// Erase any previous insert Dest.
						treeCtrl.SetInsertMark(NULL, FALSE);

						// Draw new insert above destAbove.
						m_bDrawAfter = FALSE;
						treeCtrl.SetInsertMark(hDestAbove2, m_bDrawAfter);

						// Update destination member. 
						m_hItemMoveDest = hDestAbove2;

						// Scroll to mark. 9/22-1
						treeCtrl.EnsureVisible(m_hItemMoveDest);
						return;
					}
					TRACE(_T("In CDsLavTreeView::MoveTopicKeyboard - KEY UP no item above, bailing\n"));
					return;
				}
				// Ensure not a drive.
				if(IsDrive(hItem))
				{
					TRACE(_T("In CDsLavTreeView::MoveTopicKeyboard - KEY UP isDrive2 - bailing\n"));
					return;
				}
				// Revise to prev sibling of src above..
				hDestAbove = hItem;
			}
			// Erase any previous insert Dest.
			treeCtrl.SetInsertMark(NULL, FALSE);

			// Draw new insert mark just below hDestAbove.
			m_bDrawAfter = TRUE;
			treeCtrl.SetInsertMark(hDestAbove, m_bDrawAfter);

			// Update destination member. 
			m_hItemMoveDest = hDestAbove;

			// Scroll to mark. 9/22-1
			treeCtrl.EnsureVisible(m_hItemMoveDest);
		}
		// Special:
		// If no sibling above, check if a parent above.
		//  Not for ROOT items as they have no parent.
		else
		{
			HTREEITEM hParent = treeCtrl.GetParentItem(hDest);
			if(hParent)
			{
				// Cannot move if this is the parent of the src item.
				if(hDest == hSrc)
					return;

				// Erase any previous insert Dest.
				treeCtrl.SetInsertMark(NULL, FALSE);

				// Draw new insert mark just above hDestAbove.
				m_bDrawAfter = FALSE;
				treeCtrl.SetInsertMark(hDest, m_bDrawAfter);

				// Update destination member. 
				m_hItemMoveDest = hDest;

				// Scroll to mark. 9/22-1
				treeCtrl.EnsureVisible(m_hItemMoveDest);
			}
		}
	}
	// Move down.
	else // VK_RIGHT VK_DOWN
	{
		TRACE(_T(" DOWN: Dest=%s, DestAbove=%s, DestBelow=%s\n"), 
					strDest, strDestAbove, strDestBelow);

		// If a dest below.
		if(hDestBelow)
		{
			// Special:
			if(!m_bDrawAfter)
			{
				// The dest was set in "Move up" to a topic's 
				// top child item, with the mark drawn above. 
				HTREEITEM hNewDest = NULL;
				BOOL bDrawAfter = m_bDrawAfter;

				// If the src item is the destination.
				if(hDestBelow == hSrc && hSrcBelow)
				{
					// Jump down - the destination is item below src, 
					bDrawAfter = TRUE;
					hNewDest = hSrcBelow;
				}
				// Else the destination is just after hDest (draw below). 
				else if(hSrc != hDestBelow)
				{
					bDrawAfter = TRUE;
					hNewDest = hDest;
				}
				if(hNewDest)
				{
					// Clear existing.
					treeCtrl.SetInsertMark(NULL, FALSE);

					// The destination is just after hNewDest. 
					m_bDrawAfter = bDrawAfter;
					m_hItemMoveDest = hNewDest;
					treeCtrl.SetInsertMark(m_hItemMoveDest, m_bDrawAfter);
					// Scroll to mark. 9/22-1
					treeCtrl.EnsureVisible(m_hItemMoveDest);
					// Done.
					return;
				}
				// Else user must use Esc key - not ideal.
			}

			// Dest cannot be below a Drive.
			if(IsDrive(hDestBelow))
			{
				TRACE(_T("In CDsLavTreeView::MoveTopicKeyboard - KEY DOWN isDrive - bailing\n"));
				return;
			}
			// Dest cannot be below src item or below
			// the item just above the src item.
			if(hDestBelow == hSrc		||
			   hDestBelow == hSrcAbove	)
			{
				TRACE(_T("In CDsLavTreeView::MoveTopicKeyboard - KEY DOWN jump down\n"));

				// Jump down to item below src.
				hDestBelow = hSrcBelow;

				// If unable to go down, bail.
				if(!hDestBelow)
				{
					TRACE(_T("In CDsLavTreeView::MoveTopicKeyboard - KEY DOWN no item below - bailing\n"));
					return;
				}
				// Check if a drive.
				if(IsDrive(hDestBelow))
				{
					TRACE(_T("In CDsLavTreeView::MoveTopicKeyboard - KEY DOWN isDrive2 - bailing\n"));
					return;
				}
			}
			// Erase any previous insert mark.
			treeCtrl.SetInsertMark(NULL, FALSE);
			// Draw new insert mark just after hDestBelow.
			m_bDrawAfter = TRUE;
			treeCtrl.SetInsertMark(hDestBelow, m_bDrawAfter);
			// The destination is just after hDestBelow. 
			m_hItemMoveDest = hDestBelow;
			// Scroll to mark. 9/22-1
			treeCtrl.EnsureVisible(m_hItemMoveDest);
		}
	}
}

// 8/22-9 
// Called from OnKeyDown() - Enter Key.
void CDsLavTreeView::MoveTopicKeyboardFinish()
{
	// From OnLButtonDown().

	TRACE(_T("In CDsLavTreeView::MoveTopicKeyboardFinish\n"));

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// MoveTopicKeyboard() may have set up a move with
	// the destination above another Root item.
	//
	// However, if moving a Root topic, the destination  
	// must always be below.
	//
	// This is done because CTopicTree::MoveTopicSibling
	// requires it when moving Root items. 
	HTREEITEM hSrcParent = treeCtrl.GetParentItem(m_hItemMoveSrc);
	if(!hSrcParent && !m_bDrawAfter)
	{
		// Get sibling root above current destination - may be favorites.
		HTREEITEM hRootAbove = treeCtrl.GetPrevSiblingItem(m_hItemMoveDest);
		if(hRootAbove)
		{
			// New destination is below.
			m_hItemMoveDest = hRootAbove;
			// Place below.
			m_bDrawAfter = TRUE;
		}
		else
			return;
	}

	// Errase any previous InsertMark.
	treeCtrl.SetInsertMark(NULL, TRUE);

	// Copy members.
	HTREEITEM hItemMoveSrc = m_hItemMoveSrc;
	HTREEITEM hItemMoveDest = m_hItemMoveDest;
	BOOL bDrawAfter = m_bDrawAfter;

	// Clean up.
	m_bMoving = FALSE;
	m_hItemMoveSrc = NULL;
	m_hItemMoveDest = NULL;
	m_bDrawAfter = TRUE;
	::ReleaseCapture();

	// Ensure.
	if(!hItemMoveSrc || !hItemMoveDest)
		return;

	// Must not be the same.
	if(hItemMoveSrc == hItemMoveDest)
	{
		MessageBox(_T("Topic Location not changed."));
		return;
	}
	//dbg
	CString strMoveDest = treeCtrl.GetItemText(hItemMoveDest);
	CString strMoveSrc = treeCtrl.GetItemText(hItemMoveSrc);

	// Get topic nodes.
	CNodeArray nodesArMoveSrc;
	GetTopicNodesFromItem(hItemMoveSrc, nodesArMoveSrc);
	CNodeArray nodesArMoveDest;
	GetTopicNodesFromItem(hItemMoveDest, nodesArMoveDest);

	// CDoc will move and redraw all.
	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return;
	//dbg
	TRACE(_T(" MoveSrc=%s, MoveDest=%s, Draw After=%d\n"), strMoveSrc, strMoveDest, bDrawAfter);

	pDoc->MoveTopicSibling(nodesArMoveSrc, nodesArMoveDest, bDrawAfter);
}

// Called from OnKeyDown() - Esc Key.
void CDsLavTreeView::MoveTopicCancel()
{
	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Errase any previous InsertMark.
	treeCtrl.SetInsertMark(NULL, TRUE);

	// Clean up.
	m_bMoving = FALSE;
	m_hItemMoveSrc = NULL;
	m_hItemMoveDest = NULL;
	m_bDrawAfter = TRUE;
	::ReleaseCapture();
}

// Get Context Menu func below.
// see: Q222905
void CDsLavTreeView::OnRclick(NMHDR* pNMHDR, LRESULT* pResult) 
{
	TRACE(_T("CDsLavTreeView::OnRclick()\n"));

	// Send WM_CONTEXTMENU to self
	SendMessage(WM_CONTEXTMENU, (WPARAM) m_hWnd, GetMessagePos());

	// Mark message as handled and suppress default handling
	*pResult = 1;
	//*pResult = 0;
}

// 8/22-9 rework - no 2nd context.
// Right click context.
// see: Q222905
void CDsLavTreeView::OnContextMenu(CWnd* pWnd, CPoint point) 
{
	CString itemStr;
	BOOL bIsFavs = FALSE;
	BOOL bIsRoot = FALSE;
	BOOL bIsDrive = FALSE;
	HTREEITEM hItem = NULL;
	CNodeArray nodesAr;
	UINT uFlags = 0;

	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return;

	// If currently moving, no context.
	if(m_bMoving)
		return;

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Get current selection.
	//
	// If Keyboard Menu Key, shift-Menu, Shift-F10.
	if(point.x == -1 && point.y == -1)
	{
		// Get current selection - should always exist.
		hItem = treeCtrl.GetSelectedItem();

		// Ensure.
		if(!hItem)
		{
			TRACE(_T("CDsLavTreeView::OnContextMenu F10 no hItem\n"));
			return;
		}
		// Need point coords for context display location.
		CRect rect;
		treeCtrl.GetItemRect(hItem, &rect, TRUE);
		point = rect.BottomRight();					// client coords
		point.x -= 5;								// on the item a bit
		point.y -= 5;
		treeCtrl.ClientToScreen(&point);
	}
	// Else Right Mouse click.
	else
	{
		// To client coords.
		CPoint pt = point;
		ScreenToClient(&pt);
		// Hit test.
		hItem = treeCtrl.HitTest(pt, &uFlags);
		// Select item clicked - deselects previously selected.
		treeCtrl.Select(hItem, TVGN_CARET);
	}
	// If no item, user right clicked an empty space.
	// Show the "Add Root Topic"
	if(hItem == NULL)
	{
		TRACE(_T("CDsLavTreeView::OnContextMenu no hItem\n"));
		bIsRoot = TRUE;
	}
	// Else if an item, prepare.
	else
	{
		// Root selected.
		HTREEITEM hParent = treeCtrl.GetParentItem(hItem);
		if(hParent == NULL)
			bIsRoot = TRUE;

		// Favs selectcted.
		itemStr = treeCtrl.GetItemText(hItem);
		if(itemStr == STR_FAVS)
			bIsFavs = TRUE;

		// Get topic nodes.
		if(!GetTopicNodesFromItem(hItem, nodesAr))
		{
			TRACE(_T("CDsLavTreeView::OnContextMenu no topic nodes\n"));
			return;
		}
		// Drive selected.
		if(IsDrive(nodesAr))
			bIsDrive = TRUE;
	}
	// Drive selected - no context.
	if(bIsDrive)
		return;

	// Favs selected - no context.
	if(bIsFavs)
		return;

	// Create the context.
	CString str;
	CMenu menu;
	menu.CreatePopupMenu();

	// If a valid topic selected.
	if(hItem)
	{
		// Add menu item - edit topic.
		str.Format(_T("Edit...")); 
		menu.AppendMenu(MF_STRING, ID_CTX_TREE_EDIT, str);

		// Add menu item - delete topic.
		str = _T("Delete...");
		menu.AppendMenu(MF_STRING, ID_CTX_TREE_DELETE, str);

		// Add menu item - move up down.
		str = _T("Move Up Down");
		menu.AppendMenu(MF_STRING, ID_CTX_TREE_MOVE, str);

	}
	// If a Root item or clicked an empty space.
	if(bIsRoot)
	{
		// Add menu item - New Root Topic.
		str = _T("Add New Root Topic...");
		menu.AppendMenu(MF_STRING, ID_CTX_TREE_NEW_ROOT, str);
	}
	// Cancel context.
	//
	// Add menu item - Cancel.
	str = _T("Cancel (Esc)");
	menu.AppendMenu(MF_STRING, ID_CTX_TREE_ESCAPE, str);

	// Ensure menu items added.
	if(menu.GetMenuItemCount() == 0)
		return;

	////////////////////////////////////////
	// Show Context Menu - process results.
	int nCmd = menu.TrackPopupMenu(	TPM_LEFTALIGN  | 
									TPM_LEFTBUTTON |
									TPM_RETURNCMD,		// see sdk TrackPopupMenu
									point.x, point.y,	// screen
									this);
	// Process:
	//
	// New Root Topic.
	if(nCmd == ID_CTX_TREE_NEW_ROOT)
	{
		CNodeArray nodesAr;
		MEDIA_TYPE type = NONE;

		// Open dlg for new Topic.
		pDoc->EditTopic(nodesAr, type, TRUE);
	}
	// Edit topic.
	else if(nCmd == ID_CTX_TREE_EDIT)
	{
		MEDIA_TYPE type = NONE;

		// Open dlg to edit Topic.
		pDoc->EditTopic(nodesAr, type, FALSE);
	}
	// Delete topic.
	else if(nCmd == ID_CTX_TREE_DELETE)
	{
		// Ask user. 6/22
		if(AfxMessageBox(_T("Delete selected Topic?"),
						  MB_YESNO | MB_ICONINFORMATION) == IDYES)
		{
			// 6/22
			// Save the expanded parent topic nodes of topic being 
			// deleted. OnUpdate() will use to re-expand that 
			// parent after child topic deletion.
			//
			// Zero nodeAr.
			m_expParentNodeAr.RemoveAll();
			// Get parent - will be NULL if deleting a root item.
			HTREEITEM hParent = treeCtrl.GetParentItem(hItem);
			if(hParent)
			{
				CString strParent = treeCtrl.GetItemText(hParent);
				// Parent will be expanded.
				if(treeCtrl.GetItemState(hParent, TVIS_EXPANDED) & TVIS_EXPANDED)
				{
					if(!GetTopicNodesFromItem(hParent, m_expParentNodeAr))
					{
						TRACE(_T("CDsLavTreeView::OnContextMenu GetTopicNodesFromItem failed for parent\n"));
					}
				}
			}
			// Delete topic.
			pDoc->DeleteTopic(nodesAr);
		}
	}
	// Moving a Topic with Arrow keys or Mouse.
	//  See OnMouseMove() and OnLButtonDown().
	else if(nCmd == ID_CTX_TREE_MOVE)
	{
		m_bMoving = TRUE;
		m_hItemMoveSrc = hItem;
		// All mouse messages to TreeView.
		SetCapture();
		// Select item to show what is being moved.
		treeCtrl.SelectItem(hItem);

		// Set null for keyboard arrow move.
		//  MoveTopicKeyboard() sets.
		m_hItemMoveDest = NULL;

		// Draw insertion Mark after is default. 
		m_bDrawAfter = TRUE;

		// Draw initial insertion Mark. 
		//  Note OnMouseMove() gets called one time for 
		//  unknown reasons and must be skipped - perhaps
		//  from user selecting a context menu item?. 
		MoveTopicSetInitialMark();
		m_bSkipMouse = TRUE;		// reset in OnMouseMove
	}
	// Escape
	else if(nCmd == ID_CTX_TREE_ESCAPE)
	{
		// Do nothing
	}
}

BOOL CDsLavTreeView::IsItemExpanded(HTREEITEM hItem)
{
	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	return treeCtrl.GetItemState(hItem, TVIS_EXPANDED) & TVIS_EXPANDED;
}

// Highlight drop target.
HTREEITEM CDsLavTreeView::HighlightDropTarget(CPoint point)
{
	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Find out which item (if any) the cursor is over.
	UINT nFlags;
	HTREEITEM hItem = treeCtrl.HitTest(point, &nFlags);

	// Highlight the item, or unhighlight all items if the cursor isn't
	// over an item.
	m_pImageList->DragShowNolock(FALSE);
	treeCtrl.SelectDropTarget(hItem);
	m_pImageList->DragShowNolock(TRUE);

	// Return the handle of the highlighted item.
	return hItem;
}

// 6/22
// Key down.
void CDsLavTreeView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	// If moving a Topic, ignore others. 8/22-6
	if(m_bMoving)
	{
		// Arrow key pressed - moving a Topic.
		if(nChar == VK_LEFT	 || nChar == VK_UP   ||
		   nChar == VK_RIGHT || nChar == VK_DOWN )
		{
			MoveTopicKeyboard(nChar);
			// Must not call base.
			return;
		}
		// Escape moving a topic. 8/22-6
		else if(nChar == VK_ESCAPE)
		{
			MoveTopicCancel();
		}
		// Enter key.
		else if(nChar == VK_RETURN)
		{
			MoveTopicKeyboardFinish();
		}
		// Base.
		CTreeView::OnKeyDown(nChar, nRepCnt, nFlags);
		return;
	}

	// 6/22 
	// Delete key pressed.
	if(nChar == VK_DELETE)
	{	
		CDsLavDoc * pDoc = GetDocument();
		if(!pDoc)
		{
			CTreeView::OnKeyDown(nChar, nRepCnt, nFlags);
			return;
		}

		// If in favs list, bail - nothing to delete.
		if(pDoc->m_curTopic.GetTopicName() == STR_FAVS)
		{
			CTreeView::OnKeyDown(nChar, nRepCnt, nFlags);
			return;
		}

		// Get tree ctrl ref.
		CTreeCtrl & treeCtrl = GetTreeCtrl();

		// Get selected item.
		HTREEITEM hItem = treeCtrl.GetSelectedItem();

		if(hItem != NULL)
		{
			// Get topic nodes.
			CNodeArray nodesAr;
			if(!GetTopicNodesFromItem(hItem, nodesAr))
			{
				TRACE(_T("CDsLavTreeView::OnKeyDown GetTopicNodesFromItem failed\n"));
			}

			// 6/22 If a Drive selected - no context.
			if(IsDrive(nodesAr))
			{
				// Select to show item passed on,
				// and fall through to base.
				treeCtrl.SelectItem(hItem);
			}
			// Delete Topic.
			else
			{
				// Ask user. 6/22. 
				if(AfxMessageBox(_T("Delete selected Topic?"),
								  MB_YESNO | MB_ICONINFORMATION) == IDYES)
				{
					// 6/22
					// Save the expanded parent topic nodes of topic being 
					// deleted. OnUpdate() will use to re-expand that 
					// parent after child topic deletion.
					//
					// Zero nodeAr.
					m_expParentNodeAr.RemoveAll();
					// Get parent - will be NULL if deleting a root item.
					HTREEITEM hParent = treeCtrl.GetParentItem(hItem);
					if(hParent)
					{
						CString strParent = treeCtrl.GetItemText(hParent);
						// Parent will be expanded.
						if(treeCtrl.GetItemState(hParent, TVIS_EXPANDED) & TVIS_EXPANDED)
						{
							if(!GetTopicNodesFromItem(hParent, m_expParentNodeAr))
							{
								TRACE(_T("CDsLavTreeView::OnKeyDown GetTopicNodesFromItem failed for parent\n"));
							}
						}
					}
					// Delete topic. CDoc will post to CTreeView::OnUpdate();
					pDoc->DeleteTopic(nodesAr);
				}
			}
		}
	}
	// 7/22 tab to another window
	else if(nChar == VK_TAB)
	{
		// Tab from TreeView into ListView if available, 
		// or into ControlsBar if in VideoView.
		//
		HWND hWndControlsBar = ((CDsLavApp*)AfxGetApp())->m_hwndControlBar;
		HWND hWndListView = ((CDsLavApp*)AfxGetApp())->m_hwndListView;
		HWND hWndTreeView = ((CDsLavApp*)AfxGetApp())->m_hwndTreeView;
		HWND hWndVideoView = ((CDsLavApp*)AfxGetApp())->m_hwndVideoView;

		// Ensure in TreeView with focus.
		if(hWndTreeView == NULL)
		{
			CTreeView::OnKeyDown(nChar, nRepCnt, nFlags);
			return;
		}
		HWND hWndCurrent = NULL;
		CWnd * pWndCurFocus = GetFocus();
		if(pWndCurFocus)
			hWndCurrent = pWndCurFocus->GetSafeHwnd();
		if(hWndCurrent != hWndTreeView)
		{
			CTreeView::OnKeyDown(nChar, nRepCnt, nFlags);
			return;
		}
		/*dbg trace
		TRACE(_T("CDsLavTreeView::OnKeyDown hWndControlsBar = %d \n"), hWndControlsBar);
		TRACE(_T("CDsLavTreeView::OnKeyDown hWndListView = %d \n"), hWndListView);
		TRACE(_T("CDsLavTreeView::OnKeyDown hWndTreeView = %d \n"), hWndTreeView);
		TRACE(_T("CDsLavTreeView::OnKeyDown hWndVideoView = %d \n"), hWndVideoView);*/

		// Tab to ListView.
		//  Will happen even if an audio file/stream is playing.
		if(hWndListView)
			::SetFocus(hWndListView);

		// Else tab to ControlsBar.
		//  Will happen only if a video is playing.
		else if(hWndVideoView && hWndControlsBar)
			::SetFocus(hWndControlsBar);
	}
	// Base.
	CTreeView::OnKeyDown(nChar, nRepCnt, nFlags);
}

// 9/22-1 rework - nice - easy to bail from move.
// Move up down
void CDsLavTreeView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// Flag set in OnContextMenu.
	if(m_bMoving && m_hItemMoveSrc && m_hItemMoveDest)
	{
		// Get tree ctrl ref.
		CTreeCtrl & treeCtrl = GetTreeCtrl();
	
		// If not clicking on destination item.
		CRect destRect;
		treeCtrl.GetItemRect(m_hItemMoveDest, &destRect, FALSE);
		if(!destRect.PtInRect(point))
		{
			MoveTopicCancel();
			return;
		}
		// Erase any previous InsertMark.
		treeCtrl.SetInsertMark(NULL, TRUE);

		// Copy members.
		HTREEITEM hItemMoveSrc = m_hItemMoveSrc;
		HTREEITEM hItemMoveDest = m_hItemMoveDest;
		BOOL bDrawAfter = m_bDrawAfter;

		// Clean up.
		m_bMoving = FALSE;
		m_hItemMoveSrc = NULL;
		m_hItemMoveDest = NULL;
		m_bDrawAfter = TRUE;
		::ReleaseCapture();

		// Must not be the same - handled above - ensure.
		if(hItemMoveSrc == hItemMoveDest)
		{
			MoveTopicCancel();
			return;
		//	MessageBox(_T("Location not changed."));
		//	return;
		}
		//dbg
		CString strMoveDest = treeCtrl.GetItemText(hItemMoveDest);
		CString strMoveSrc = treeCtrl.GetItemText(hItemMoveSrc);

		// Get topic nodes.
		CNodeArray nodesArMoveSrc;
		GetTopicNodesFromItem(hItemMoveSrc, nodesArMoveSrc);
		CNodeArray nodesArMoveDest;
		GetTopicNodesFromItem(hItemMoveDest, nodesArMoveDest);

		// CDoc will move and redraw all.
		CDsLavDoc * pDoc = GetDocument();
		if(!pDoc)
			return;
		pDoc->MoveTopicSibling(nodesArMoveSrc, nodesArMoveDest, bDrawAfter);
		return;
	}
	// Base
	CTreeView::OnLButtonDown(nFlags, point);
}

// 9/22-1 rework for insert mark
// Dropping a drag.
void CDsLavTreeView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	//bug muve up -down
	// https://forums.codeguru.com/showthread.php?324597-Move-up-down-CTreeCtrl-item
	// SetInsertMark

	CTreeView::OnLButtonUp(nFlags, point);

	if(m_bDragging && m_pImageList != NULL) 
	{
		// Get tree ctrl ref.
		CTreeCtrl & treeCtrl = GetTreeCtrl();

		// Stop the scroll timer if it's running.
		KillTimer (1);

		// Terminate the dragging operation and release the mouse.
		m_pImageList->DragLeave(this);
		m_pImageList->EndDrag();
		::ReleaseCapture();
		m_bDragging = FALSE;
		treeCtrl.SelectDropTarget(NULL);

		// Delete the image list created by CreateDragImage.
		delete m_pImageList;
		m_pImageList = NULL;

		// Get the HTREEITEM of the drop target.
		UINT nFlags;
		HTREEITEM hItem = treeCtrl.HitTest(point, &nFlags);
		if(hItem == NULL)
			return;

		HTREEITEM hItemAbove = treeCtrl.GetPrevSiblingItem(hItem);
		HTREEITEM hItemBelow = treeCtrl.GetNextSiblingItem(hItem);

		CString strItem = treeCtrl.GetItemText(hItem);
		CString strItemAbove = treeCtrl.GetItemText(hItemAbove);
		CString strItemBelow = treeCtrl.GetItemText(hItemBelow);

		// Drop per position.
		HTREEITEM hItemDest = NULL;
		// 
		// Dropping onto hItem - hItem will be the parent of m_hDragItem.
		if(m_moveType == MOVE_ON)
		{
			// Display an error message if the move is illegal.
			if(hItem == m_hDragItem) 
			{
				MessageBox(_T("A topic cannot be dropped onto itself"));
				return;
			}
			else if(hItem == treeCtrl.GetParentItem(m_hDragItem)) 
			{
				MessageBox(_T("A topic cannot be dropped onto its parent"));
				return;
			}
			else if(IsChildOf(hItem, m_hDragItem)) 
			{
				MessageBox(_T("A topic cannot be dropped onto one of its children"));
				return;
			}
			else if(treeCtrl.GetItemText(hItem) == STR_FAVS)// "Favorites"
			{
				CString msg, s = STR_FAVS;
				msg.Format(_T("Topics cannot be added to the %s topic."), s);
				MessageBox(msg);
				return;
			}
			// Move the dragged item and its subitems (if any) to the drop point.
			MoveTreeItems(hItem, m_hDragItem);
		}
		// Else dropping above hItem - hItem will be the sibling below m_hDragItem.
		else if(m_moveType == MOVE_ABOVE)
		{
			MoveTreeItems(hItem, m_hDragItem);
		}
		// Else dropping below hItem - hItem will be the sibling above m_hDragItem.
		else if(m_moveType == MOVE_BELOW)
		{
			MoveTreeItems(hItem, m_hDragItem);
		}
		// Clean up.
		m_hDragItem = NULL;
	}
}

// ListView dropped a MediaItem on tree.
// Called from CDsLavListView::OnLButtonUp.
void CDsLavTreeView::ListViewDropMediaItem(int iSrcListIndex, HTREEITEM hDestTreeItem)
{
	// Note: iSrcListIndex is the ListView's index of dragged item.

	// Get a TopicTree ref.
	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return;
	CTopic & curTopic = pDoc->m_curTopic;

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Get the str of the TreeItem drop target topic.
	CString topicStr = treeCtrl.GetItemText(hDestTreeItem);

	// Dropped a media item on Favorites topic - user 
	// is saying "make this media item a favorite".
	if(topicStr == STR_FAVS)
	{
		// Check bounds.
		int sz = curTopic.m_topicMediaAr.GetSize();
		if(iSrcListIndex < 0 || iSrcListIndex >= sz)
		{
			TRACE(_T("CDsLavTreeView::ListViewDropMediaItem ListView item index out of bounds.\n"));
			return;
		}
		// Get the media item from the m_curListMediaAr.
		CMediaItem mediaItem = curTopic.m_topicMediaAr[iSrcListIndex];

		// Update media item favorites flag if needed.
		if(!mediaItem.m_isFavorite)
		{
			// Get the media item's parent topic nodesAr.
			CNodeArray nodesAr = curTopic.m_nodesAr;

			// Set media item's favorites flag.
			// pDoc will update its cur list and Views.
			BOOL isFavorite = TRUE;
			if(!pDoc->SetMediaItemFavorites(nodesAr, mediaItem, isFavorite))
			{
				TRACE(_T("CDsLavTreeView::ListViewDropMediaItem - SetMediaItemFavorites failed.\n"));
				return;
			}
		}
	}
	// Dropped a media item on a Tree topic - move
	// the media item to the tree target.
	else
	{
		// If dropping on the media item's parent topic, 
		// nothing to do - just ignore.
		if(topicStr == curTopic.GetTopicName())
			return;

		// Check bounds.
		int sz = curTopic.m_topicMediaAr.GetSize();
		if(iSrcListIndex < 0 || iSrcListIndex >= sz)
		{
			TRACE(_T("CDsLavTreeView::ListViewDropMediaItem ListView item index out of bounds.\n"));
			return;
		}
		// Get copy of the dropped media item from the m_curListMediaAr.
		CMediaItem mediaItem = curTopic.m_topicMediaAr[iSrcListIndex];

		// Get the media item nodes from the curListTopic.
		CNodeArray nodesArSrc = curTopic.m_nodesAr;

		// Get the TreeView drop target destination topic nodes.
		CNodeArray nodesArDest;
		if(!GetTopicNodesFromItem(hDestTreeItem, nodesArDest))
		{
			TRACE(_T("CDsLavTreeView::ListViewDropMediaItem GetTopicNodesFromItem failed.\n"));
			return;
		}
		// Move media item.
		// Note pDoc will call OnUpdate() to reset tree and selection.
		if(!pDoc->MoveMediaItem(mediaItem, nodesArSrc, nodesArDest))
		{
			TRACE(_T("ERROR CDsLavTreeView::ListViewDropMediaItem MoveMediaItem failed.\n"));
			return;
		}
	}
}

// ListView dropped multiple MediaItems on tree.
// Called from CDsLavListView::OnLButtonUp.
void CDsLavTreeView::ListViewDropMediaItemMulti(CDWordArray & iSrcListIndexAr, 
												HTREEITEM hDestTreeItem)
{
	// Get a TopicTree ref.
	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return;
	CTopic & curTopic = pDoc->m_curTopic;

	// Copy ar param ref.
	CDWordArray indexAr;
	indexAr.Copy(iSrcListIndexAr);

	// Ensure indices.
	int szIndexAr = indexAr.GetSize();
	if(!szIndexAr)
		return;

	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Get the str of the TreeItem drop target topic.
	CString topicStr = treeCtrl.GetItemText(hDestTreeItem);

	// If dropping multi on Favs, bail.
	// Note for single sels we add to favs.
	if(topicStr == STR_FAVS)
	{
		return;
	}
	// Else dropped multiple media items on a Tree topic. 
	//
	// If dropping on the media item's parent topic, 
	// nothing to do - just ignore.
	if(topicStr == pDoc->m_curTopic.GetTopicName())
		return;

	// For checking bounds.
	int szMediaAr = curTopic.m_topicMediaAr.GetSize();

	// Get mediaItems for each selection.
	CMediaArray mediaAr;
	int index = 0;	
	for(int i = 0; i < szIndexAr; i++)
	{
		index = indexAr[i];
		if(index < 0 || index >= szMediaAr)
		{
			TRACE(_T("CDsLavTreeView::ListViewDropMediaItemMulti item index out of bounds.\n"));
			return;
		}
		// Copy media item to mediaAr from the m_curListMediaAr.
		mediaAr.Add(curTopic.m_topicMediaAr[index]);
	}
	// Get nodes for the media item from the curListTopic.
	CNodeArray nodesArSrc = pDoc->m_curTopic.m_nodesAr;

	// Get nodes for the TreeView drop target destination.
	CNodeArray nodesArDest;
	if(!GetTopicNodesFromItem(hDestTreeItem, nodesArDest))
	{
		TRACE(_T("CDsLavTreeView::ListViewDropMediaItem GetTopicNodesFromItem failed.\n"));
		return;
	}
	// Move the media items.
	// Note pDoc will call OnUpdate() to reset tree and selection.
	if(!pDoc->MoveMediaItemMulti(mediaAr, nodesArSrc, nodesArDest))
	{
		TRACE(_T("ERROR CDsLavTreeView::ListViewDropMediaItem MoveMediaItem failed.\n"));
		return;
	}
}

// Move Topic from hDest to hSrc.
void CDsLavTreeView::MoveTreeItems(HTREEITEM hDest, HTREEITEM hSrc)
{
	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Get a TopicTree ref.
	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return;

	CNodeArray nodesArSrc, nodesArDest;

	// Get the Src nodes.
	if(!GetTopicNodesFromItem(hSrc, nodesArSrc))
		return;

	// Get Dest nodes.
	if(!GetTopicNodesFromItem(hDest, nodesArDest))
		return;

	// 9/22-1 test Move2
	// Move the src topic to dest location.
	if(!pDoc->MoveTopic(nodesArSrc, nodesArDest, m_moveType))
	{
		TRACE(_T("ERROR CDsLavTreeView MoveTreeItems - MoveTopic failed.\n"));
	}
	/* Move the src topic to dest location.
	if(!pDoc->MoveTopic(nodesArSrc, nodesArDest))
	{
		TRACE(_T("ERROR CDsLavTreeView MoveTreeItems - MoveTopic failed.\n"));
	}*/
}

// Not used.
void CDsLavTreeView::CopyTree(HTREEITEM hDest, HTREEITEM hSrc)
{
	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// test flag
	m_manualExpand = TRUE;

	// Get the attributes of item to be copied.
	int nImage, nSelectedImage;
	treeCtrl.GetItemImage(hSrc, nImage, nSelectedImage);
	CString string = treeCtrl.GetItemText(hSrc);

	// Create an exact copy of the item at the destination.
	HTREEITEM hNewItem = treeCtrl.InsertItem(string, nImage, nSelectedImage, hDest);

	// If the item has subitems, copy them, too.
	if(treeCtrl.ItemHasChildren (hSrc))
		CopyChildren(hNewItem, hSrc);

	// Select the newly added item.
	treeCtrl.SelectItem(hNewItem);
	// Expand
	//Expand( HTREEITEM hItem, UINT nCode );

	// test flag
	m_manualExpand = FALSE;
}

BOOL CDsLavTreeView::IsChildOf(HTREEITEM hItem1, HTREEITEM hItem2)
{
	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	HTREEITEM hParent = hItem1;
	while((hParent = treeCtrl.GetParentItem(hParent)) != NULL) 
	{
		if(hParent == hItem2)
			return TRUE;
	}
	return FALSE;
}

void CDsLavTreeView::CopyChildren(HTREEITEM hDest, HTREEITEM hSrc)
{
	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Get the first subitem.
	HTREEITEM hItem = treeCtrl.GetChildItem(hSrc);
	ASSERT(hItem != NULL);

	// Create a copy of it at the destination.
	int nImage, nSelectedImage;
	treeCtrl.GetItemImage(hItem, nImage, nSelectedImage);
	CString string = treeCtrl.GetItemText(hItem);
	HTREEITEM hNewItem = treeCtrl.InsertItem(string, nImage, nSelectedImage, hDest);

	// If the subitem has subitems, copy them, too.
	if(treeCtrl.ItemHasChildren(hItem))
		CopyChildren(hNewItem, hItem);

	// Do the same for other subitems of hSrc.
	while((hItem = treeCtrl.GetNextSiblingItem(hItem)) != NULL) 
	{
		treeCtrl.GetItemImage(hItem, nImage, nSelectedImage);
		string = treeCtrl.GetItemText(hItem);
		hNewItem = treeCtrl.InsertItem(string, nImage, nSelectedImage, hDest);
		if(treeCtrl.ItemHasChildren(hItem))
			CopyChildren(hNewItem, hItem);
	}
}

void CDsLavTreeView::OnTimer(UINT nIDEvent) 
{
	CTreeView::OnTimer(nIDEvent);

	// fixed - bug - expanding when just moving mouse
	// Only if dragging.
	if(!m_bDragging)
	{
		KillTimer(1); // not needed?
		return;
	}
	// Get tree ctrl ref.
	CTreeCtrl & treeCtrl = GetTreeCtrl();

	// Reset the timer.
	UINT nElapse = m_nScrollInterval;
	SetTimer(1, m_nScrollInterval, NULL);

	// Get the current cursor position and window height.
	DWORD dwPos = ::GetMessagePos();
	CPoint point(LOWORD (dwPos), HIWORD (dwPos));
	ScreenToClient(&point);

	CRect rect;
	GetClientRect(rect);
	int cy = rect.Height();

	// Scroll the window if the cursor is near the top or bottom.
	if(point.y >= 0 && point.y <= m_nScrollMargin)  // see constructor
	{
		HTREEITEM hFirstVisible = treeCtrl.GetFirstVisibleItem();

		m_pImageList->DragShowNolock(FALSE);
		SendMessage(WM_VSCROLL, MAKEWPARAM (SB_LINEUP, 0), NULL);
		m_pImageList->DragShowNolock (TRUE);

		// Kill the timer if the window did not scroll, or redraw the
		// drop target highlight if the window did scroll.
		if(treeCtrl.GetFirstVisibleItem() == hFirstVisible)
			KillTimer(1);
		else 
		{
			HighlightDropTarget(point);
			return;
		}
	}
	else if(point.y >= cy - m_nScrollMargin && point.y <= cy) 
	{
		HTREEITEM hFirstVisible = treeCtrl.GetFirstVisibleItem();

		m_pImageList->DragShowNolock(FALSE);
		SendMessage(WM_VSCROLL, MAKEWPARAM (SB_LINEDOWN, 0), NULL);
		m_pImageList->DragShowNolock (TRUE);

		// Kill the timer if the window did not scroll, or redraw the
		// drop target highlight if the window did scroll.
		if(treeCtrl.GetFirstVisibleItem() == hFirstVisible)
			KillTimer(1);
		else 
		{
			HighlightDropTarget(point);
			return;
		}
	}

	// If the cursor is hovering over a collapsed item, expand the tree.
	UINT nFlags;
	HTREEITEM hItem = treeCtrl.HitTest(point, &nFlags);

	if(hItem != NULL					&& 
	   treeCtrl.ItemHasChildren(hItem)	&& 
	   !IsItemExpanded(hItem)			) 
	{
		// dbg trace
		CString s = treeCtrl.GetItemText(hItem);
		TRACE(_T("In CDsLavTreeView::OnTimer - expanding %s\n"), s);

		m_pImageList->DragShowNolock(FALSE);

		// Fix - After expanding once, does not expand again.
		//	See: https://stackoverflow.com/questions/23888041/cant-expand-
		//				treeview-item-using-treeview-expand
		//	See: https://docs.microsoft.com/en-us/windows/win32/controls/tvm-expand
		//	 for explanation re TVIS_EXPANDEDONCE state flag.
		//
		// Re-collapse/reset to fix.
		treeCtrl.Expand(hItem, TVE_COLLAPSE | TVE_COLLAPSERESET);

		// Expand item.
		treeCtrl.Expand(hItem, TVE_EXPAND);

		m_pImageList->DragShowNolock(TRUE);
		KillTimer(1);
		return;
	}
}

/////////////////////////////////////////////////////////////////////////////
// CDsLavTreeView diagnostics

#ifdef _DEBUG
void CDsLavTreeView::AssertValid() const
{
	CTreeView::AssertValid();
}

void CDsLavTreeView::Dump(CDumpContext& dc) const
{
	CTreeView::Dump(dc);
}

// See DsLavSplitter
CDsLavDoc* CDsLavTreeView::GetDocument() // non-debug version is inline
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CDsLavDoc)));
	return (CDsLavDoc*)m_pDocument;
}
#endif //_DEBUG

