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

// DsLavListView.cpp : implementation file
//

#include "stdafx.h"
#include "DsLavSplit.h"
#include "DsLavListView.h"
#include "DsLavDoc.h"

#include "DsLavTreeView.h"	// drag and drop
#include "Clipboard.h"		// copy paste


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

/////////////////////////////////////////////////////////////////////////////
// CDsLavListView

IMPLEMENT_DYNCREATE(CDsLavListView, CView) //CListView)

CDsLavListView::CDsLavListView()
{
	m_redrawVid = FALSE;
	m_numberOfListViewColumns = 0;
	m_bColWidthSet = FALSE;
	m_bInitialized = FALSE;
	m_curSelIndex = -1;

	// Dragging
	m_pImageList = NULL;
	m_bDragging = FALSE;
	m_iDragListIndex = -1;
	m_hDragTreeItem = NULL;
	m_bDragInTreeCtrl = 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

	m_bDragable = TRUE;
}

CDsLavListView::~CDsLavListView()
{
	// Dragging
	// Delete the image list created by CreateDragImage.
	if (m_pImageList != NULL)
	{
		delete m_pImageList;
		m_pImageList = NULL; // 8/22-2
	}
	TRACE(_T("Destructing CDsLavListView\n"));
}

BEGIN_MESSAGE_MAP(CDsLavListView, CListView) //CView) //CListView)
	//{{AFX_MSG_MAP(CDsLavListView)
	ON_WM_MOUSEWHEEL()
	ON_WM_SIZE()
	ON_WM_ERASEBKGND()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_KEYDOWN()
	ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnBegindrag)
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONUP()
	ON_WM_TIMER()
	ON_WM_CONTEXTMENU()
	ON_WM_SETFOCUS()
	ON_WM_DESTROY()
	ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnclick)
	//}}AFX_MSG_MAP

	ON_MESSAGE(WM_USER_SIZE_COLUMNS, OnSizeColumns)	// WM_USER_SIZE_COLUMNS

END_MESSAGE_MAP()

// Called by Framework during creation.
BOOL CDsLavListView::PreCreateWindow(CREATESTRUCT& cs) 
{
	TRACE(_T("In CDsLavListView::PreCreateWindow()\n"));

	cs.style |= LVS_SHOWSELALWAYS	| 
				LVS_REPORT; //			|	test multi
			//	LVS_AUTOARRANGE |
			//	LVS_SMALLICON |
			//	LVS_ALIGNTOP  |
			//	LVS_ALIGNLEFT |
			//	LVCFMT_CENTER	|
			//	LVS_SHAREIMAGELISTS |
			//	LVS_SINGLESEL;			// By default, multiple items can be selected

	return CListView::PreCreateWindow(cs);	
	// Note - styles also set in InitializeListView() & OnInitialUpdate() 
}

/////////////////////////////////////////////////////////////////////////////
// CDsLavListView drawing

void CDsLavListView::OnDraw(CDC* pDC)
{
	TRACE(_T("In CDsLavListView::OnDraw\n"));
}

/////////////////////////////////////////////////////////////////////////////
// CDsLavListView message handlers

// Called from CDsLavSplitterView::OnInitialUpdate().
void CDsLavListView::OnInitialUpdate() 
{
	TRACE(_T("In CDsLavListView::OnInitialUpdate()\n"));

	CListCtrl & listCtrl = GetListCtrl();

	// Call only once.
	if(m_bInitialized)
		return;
	m_bInitialized = TRUE;

	// Set image list
	if(!m_imageList.m_hImageList)
	{
		HICON hIcon[3];
		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 - used in TreeView
		hIcon[2] = AfxGetApp()->LoadIcon(IDR_ICON_03);	// yellow folder
		for(n = 0; n < 3; n++) 
		{
			m_imageList.Add(hIcon[n]);
		}
		listCtrl.SetImageList(&m_imageList, LVSIL_SMALL); //small = 16x16
	}

	InitializeListView();

	// Set up TreeView font.
	CDsLavDoc * pDoc = GetDocument();
	if(pDoc)
	{
		if(!(HFONT)m_font)
		{
			LOGFONT lf = pDoc->m_listLf;
			m_font.CreateFontIndirect(&lf);
			listCtrl.SetFont(&m_font);
		}
	}
	// 11/22-3 test - full row select:
//	listCtrl.SetExtendedStyle(listCtrl.GetExtendedStyle() | LVS_EX_FULLROWSELECT);

	CListView::OnInitialUpdate();
}

// Call Control Bar with wheel data.
BOOL CDsLavListView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) 
{
	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return CView::OnMouseWheel(nFlags, zDelta, pt);

	CMainFrame * pFrame = pDoc->GetMainFrame();		// could make class func
	if(!pFrame)
		return CView::OnMouseWheel(nFlags, zDelta, pt);

	HWND hwndBar = pFrame->m_controlsBar.GetSafeHwnd();
	if(!hwndBar)
		return CView::OnMouseWheel(nFlags, zDelta, pt);

	BOOL stepUp = zDelta > 0;
	LONG newVol = 0;

	// If Video is playing - audio files only as no video playback in ListView.
	if(pFrame->m_player.IsRunningOrPaused())
	{
		if(pFrame->m_player.IsRunning() &&
		   pFrame->m_player.StepVolume(stepUp, newVol))
		{
			// Set bar vol.
			::PostMessage(hwndBar, WM_USER_BAR_VOL, 0, (LPARAM)newVol);

			// Return 1 to disable scrolling window.
			return 1;
		}
	}
	// Else Scroll right pane Text.
	else
	{
		if(GetFocus() == this)
			int g = 0;
	}
	// Base class.
	return CView::OnMouseWheel(nFlags, zDelta, pt);
}

// Video/List window size changing.
void CDsLavListView::OnSize(UINT nType, int cx, int cy) 
{
	// Note: 
	//	Not called when resizing columns.
	//	Is called when dragging divider - nType = 0.
	//	Is called when resizing frame	- nType = 0.

	// Base first
	CView::OnSize(nType, cx, cy);

	// Avoid start up call.
	if(!IsWindowVisible())
		return;

	// Ensure something to size.
	if(!cx && !cy)
		return;

	TRACE(_T("In CDsLavListView::OnSize nType = %d\n"), nType);

	// Update Video size.
	// If here, there is no video stream being displayed as
	// player only plays audio streams with the ListView
	// showing. We'll set the pPlayer size anyway.
	CPlayer * pPlayer = GetPlayer();	
	if(pPlayer && pPlayer->IsRunningOrPaused())
	{
		TRACE(_T("In CDsLavListView::OnSize - player\n"));
		// Get ListView client size.
		CRect rect;
		GetClientRect(&rect);

		// Set the size of the video window.
		if(pPlayer->SetVideoRect(rect))
		{
			// Set flag to redraw new video
			// size and View's background.
			m_redrawVid = TRUE;
		}
	}
	// Resize List columns.
	PostMessage(WM_USER_SIZE_COLUMNS, 0, 0); 
}

BOOL CDsLavListView::OnEraseBkgnd(CDC* pDC) 
{
	// Get player ptr from CMainFrame.
	CPlayer * pPlayer = GetPlayer();

	// Redraw the video and bkgnd once.
	// Note no video playback in ListView, only audio.
	// See OnDraw().
	if(pPlayer && pPlayer->IsRunningOrPaused())
	{
		TRACE(_T("In CDsLavListView::OnEraseBkgnd player running\n"));

		m_redrawVid = TRUE;
		// No - ListCtrl not repainted.
		// Only needed for video stream - see CVideoView.
		//return 1;			// 1 = handled 
	}
	// Base
	return CView::OnEraseBkgnd(pDC);
}

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

//////////////////////////////////////////////////////////////////
// CPlayer Video related

// Get CMainFrame CPlayer ptr.
CPlayer * CDsLavListView::GetPlayer()
{
//	TRACE(_T("In CDsLavListView::GetPlayer\n"));

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

	CMainFrame * pFrame = pDoc->GetMainFrame();		// could make class func
	if(!pFrame)
		return 0;

	// Return adress of CMainFrame CPlayer.
	return &pFrame->m_player;
}

//////////////////////////////////////////////////////////////////
// m_listCtrl related

// Initialize ListView Cols and Data.
void CDsLavListView::InitializeListView()
{
	TRACE(_T("In CDsLavListView::InitializeListView()\n"));

	CListCtrl & theListCtrl = GetListCtrl();
	theListCtrl.SetRedraw(FALSE); 
	
	// Set columns.
	CRect rect;
	theListCtrl.GetClientRect(rect);
	int width = rect.Width() / 5;
	theListCtrl.InsertColumn(0, _T("Name"), LVCFMT_LEFT, width);
	theListCtrl.InsertColumn(1, _T("Type"), LVCFMT_LEFT, width);
	theListCtrl.InsertColumn(2, _T("Topic"), LVCFMT_LEFT, width);
	theListCtrl.InsertColumn(3, _T("Favorite"), LVCFMT_LEFT, width);
	theListCtrl.InsertColumn(4, _T("URL - Path"), LVCFMT_LEFT, width);

	m_numberOfListViewColumns = 5;

	// Update display
	theListCtrl.SetRedraw(TRUE);

	// Set Row Data.
	SetListData();
}

// Set ListView column Data.
void CDsLavListView::SetListData()
{
	TRACE(_T("In CDsLavListView::SetListData()\n"));

	CListCtrl & listCtrl = GetListCtrl();
	listCtrl.SetRedraw(FALSE);

	// Clear existing list.
	listCtrl.DeleteAllItems();

	// Set Row Data.
	//
	// Get Doc.
	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return;
	CTopic & curTopic = pDoc->m_curTopic;

	// Copy current media list from CDoc - STR_FAVS "favorites" tree topic at start up.
	m_mediaAr = curTopic.m_topicMediaAr;

	// Get child topic names from CDoc.
	if(pDoc->m_curTopic.GetTopicName() != STR_FAVS)
	{
		m_childTopicsAr.Copy(curTopic.m_childTopicAr);
	}
	else
	{
		m_childTopicsAr.RemoveAll();
	}

	// Set curSel to 0 for now,
	m_curSelIndex = 0;

	CMediaItem item;
	CString type, isFavorite;

	// Load media items from currently selected tree topic.
	int nItems = m_mediaAr.GetSize();
	int index = 0;
	for(index = 0; index < nItems; index ++)
	{
		item = m_mediaAr[index];
		type = item.GetStreamTypeString();
		CString topicName = item.GetMediaTopicName();

		// If DVD files.
		if(pDoc->m_bCurTopicDrive)
		{
			if(item.m_mediaName.Find(_T("VIDEO_TS"), 0) != -1)
				topicName = _T("Main Movie");
			else if(item.m_mediaName.Find(_T("VOB"), 0) != -1)
				topicName = _T("movie part");
		}

		if(item.m_isFavorite)
			isFavorite = _T("yes");
		else
			isFavorite = _T("no");
		// Name col & icon.
		listCtrl.InsertItem (index, (LPCTSTR)item.m_mediaName, 0);	//  0 green triangle
		// Other cols.
		listCtrl.SetItemText(index, 1, (LPCTSTR)type);
		listCtrl.SetItemText(index, 2, (LPCTSTR)topicName);
		listCtrl.SetItemText(index, 3, (LPCTSTR)isFavorite);
		listCtrl.SetItemText(index, 4, (LPCTSTR)item.m_mediaUrl);
	}
	// Set sel empty.
	if(!nItems)
	{
		m_curSelIndex = -1;
	}

	// Load child topic names for the selected TreeView Topic.
	//
	// Set icon with the yellow folder icon for topics with
	// disk files, and the yellow triangle if a stream.
	//
	// These may be double-clicked to expand the topic in
	// the TreeView and display in the ListView.
	CString parentStr = curTopic.GetTopicName();
	CString nameStr;

	// Check if media items are disc files.
	BOOL isDiscFile = FALSE;
	MEDIA_TYPE mediaType = curTopic.m_topicMediaType;
	if(mediaType == VIDF || mediaType == AUDF)
		isDiscFile = TRUE;

	int sz  = m_childTopicsAr.GetSize();
	int nextIndex = index;
	for(int i = 0; i < sz; i++, nextIndex++)
	{
		// Name column & icon.
		nameStr = m_childTopicsAr[i].GetTopicName();
		if(isDiscFile)
			listCtrl.InsertItem(nextIndex, (LPCTSTR)nameStr, 2); // 2 yellow folder
		else
			listCtrl.InsertItem(nextIndex, (LPCTSTR)nameStr, 1); // 1 yellow triangle

		// Topic column - parent topic str.
		listCtrl.SetItemText(nextIndex, 2, (LPCTSTR)parentStr);
	}

	// Update display
	listCtrl.SetRedraw(TRUE);
	listCtrl.Invalidate();

	// Set cols per string widths.
	PostMessage(WM_USER_SIZE_COLUMNS, 0, 0);	

	// Restore list to state before mods. 11/22-2
	TRACE(_T("In CDsLavListView::SetListData() calling StateRestore\n"));
	StateRestore();
}

// 11/22-2
////////////////////////////////////////////////////////////
// Save restore:
//
// The ListView is built from CTopicTree data. 
// The user modifies the Topic Tree via editing the TreeView 
// or Listview, after which the Views are completely rebuilt.
// This means ListView selected item(s) and scroll position
// data are lost and cannot be restored.

// The following State() functions save the data in CDoc and
// restore it once the ListView is rebuilt.

// Save ListView state for restoring after list is rebuilt.
// Called from:
//  OnContextMenu()
void CDsLavListView::StateSave(BOOL bIsNewMediaItem, BOOL bIsAddFiles,
							   BOOL bIsMultiDelete)
{
	// Get Doc.
	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return;

	TRACE(_T("In CDsLavListView::SaveState\n"));

	// Get list ctrl.
	CListCtrl & listCtrl = GetListCtrl();

	// Prepare state. 12/22-2
	StateZero();

	// Save state.
	pDoc->m_lvCurSelIndex = m_curSelIndex;
	pDoc->m_lvCurTopicName = pDoc->m_curTopic.GetTopicName();
	pDoc->m_lvTopIndex = listCtrl.GetTopIndex();

	// New media item added to list.
	if(bIsNewMediaItem)
		pDoc->m_lvIsNewMediaItem = TRUE;

	// New media files added to list.
	else if(bIsAddFiles)
	{
		pDoc->m_lvIsAddFiles = TRUE;
		pDoc->m_lvNumItems = listCtrl.GetItemCount();
	}

	// Multi delete.
	else if(bIsMultiDelete)
	{
		// Get top selection in muliple sel.
		int iTopSel = -1;
		POSITION pos = listCtrl.GetFirstSelectedItemPosition();
		if(pos)
			iTopSel = listCtrl.GetNextSelectedItem(pos);
		else
		{
			TRACE(_T("In CDsLavListView::SaveState - no muli sel selection\n"));
			StateZero();
			return;
		}
		// The m_curSelIndex is the last item clicked. 
		// We'll use iTopSel (the first sel) instead to
		// highlight the item above in StateRestore(), 
		// and use pDoc->m_lvTopIndex to scroll.
		pDoc->m_lvCurSelIndex = iTopSel;

		// Set flag.
		pDoc->m_lvIsMultiDelete = TRUE;
	}
}

// 12/22-2 rework
// Restore ListView state.
void CDsLavListView::StateRestore()
{
	TRACE(_T("In CDsLavListView::RestoreState\n"));

	// Get Doc.
	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return;

	// Get list ctrl.
	CListCtrl & listCtrl = GetListCtrl();

	// Get num items in new list - must have.
	int nItems = listCtrl.GetItemCount();
	if(nItems == 0)
	{
		TRACE(_T("In CDsLavListView::RestoreState - no items\n"));
		return;
	}

	/////////////////////////////////////////////////////////
	// Special media item edits:
	//
	// If adding a new media item to a topic.
	if(pDoc->m_lvIsNewMediaItem)
	{
		TRACE(_T("In CDsLavListView::RestoreState new media item\n"));
		// Should have a topic name.
		if(pDoc->m_lvCurTopicName == _T(""))
		{
			TRACE(_T("In CDsLavListView::RestoreState new media item - no topic name\n"));
		}
		// Should have a top index. Note m_lvCurSelIndex will be -1
		if(pDoc->m_lvTopIndex == -1)
		{
			TRACE(_T("In CDsLavListView::RestoreState new media item - no top index\n"));
		}
		// The new media item will be at the end of list.
		int iLast = nItems - 1;

		// Scroll.
		listCtrl.EnsureVisible(iLast, FALSE);

		// Set new item selected and focused.
		if(GetFocus() == this)
		{
			listCtrl.SetItemState(iLast, LVIS_SELECTED, LVIS_SELECTED);
			listCtrl.SetItemState(iLast, LVIS_FOCUSED, LVIS_FOCUSED); 
		}
		// Done.
		StateZero();
		return;
	}
	// If adding files to a topic.
	else if(pDoc->m_lvIsAddFiles)
	{
		TRACE(_T("In CDsLavListView::RestoreState add disk file items\n"));

		// New file media items will have been added 
		// to bottom of list. Highlight all.
		int nItemsPrev = pDoc->m_lvNumItems;
		int nHighlight = nItems - nItemsPrev;

		// Start at the first new item index.
		int iStart = nItemsPrev;
		// End nHighlight later less start index.
		int iEnd = iStart + nHighlight - 1;
		if(nHighlight)
		{
			// Scroll to last.
			listCtrl.EnsureVisible(iEnd, FALSE);

			// Highlight each.
			if(GetFocus() == this)
			{
				for(int i = iStart; i <= iEnd; i++)
				{
					listCtrl.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
					listCtrl.SetItemState(i, LVIS_FOCUSED, LVIS_FOCUSED); 
				}
			}
		}
		else
		{
			TRACE(_T("In CDsLavListView::RestoreState add disk file items - no files added\n"));
		}
		// Done.
		StateZero();
		return;
	}
	// If deleting multiple media items.
	else if(pDoc->m_lvIsMultiDelete)
	{
		TRACE(_T("In CDsLavListView::RestoreState Multi Delete\n"));

		// Get prev top index, and line size.
		int iTop = pDoc->m_lvTopIndex;
		if(iTop >= nItems)
			iTop = 0;
		CRect rect;
		listCtrl.GetItemRect(iTop, &rect, LVIR_LABEL);
		CSize sizeItem(rect.Size());

		// Get size of list rect above top index.
		CSize sizeScroll(0, sizeItem.cy * iTop);

		// Scroll.
		BOOL bScroll = listCtrl.Scroll(sizeScroll);	
		
		// Set focus to item above first deleted item if visible - see StateSave().
		int iFirst = pDoc->m_lvCurSelIndex - 1;
		int nItemsVisible = listCtrl.GetCountPerPage();
		if(iFirst >= iTop && iFirst < iTop + nItemsVisible)
		{
			listCtrl.SetItemState(iFirst, LVIS_SELECTED, LVIS_SELECTED);
			listCtrl.SetItemState(iFirst, LVIS_FOCUSED, LVIS_FOCUSED); 
		}		
		// Done.
		StateZero();
		return;
	}

	////////////////////////////////////////////////////////
	// Other media item edits, player stopping, and 
	// TreeView topic changing:
	//
	// Check all:
	BOOL bRestore = TRUE;

	// Need all state data - quick out.
	if(pDoc->m_lvCurTopicName == _T("") ||
	   pDoc->m_lvCurSelIndex == -1		||
	   pDoc->m_lvTopIndex == -1			)
	{
		TRACE(_T("In CDsLavListView::RestoreState - state data uninitialized\n"));
		return;
	}
	// Need a current topic name.
	CString strCurTopic = pDoc->m_curTopic.GetTopicName();
	if(strCurTopic == _T(""))
	{
		TRACE(_T("In CDsLavListView::RestoreState - no CDoc topic name\n"));
		bRestore = FALSE;
	}
	// Current topic name must match state topic.
	if(strCurTopic != pDoc->m_lvCurTopicName)
	{
		TRACE(_T("In CDsLavListView::RestoreState - topic names don't match\n"));
		bRestore = FALSE;
	}
	// Previous curSelIndex in bounds.
	if(pDoc->m_lvCurSelIndex >= nItems || pDoc->m_lvCurSelIndex < 0)
	{
		TRACE(_T("In CDsLavListView::RestoreState - prev curSelIndex out of bounds\n"));
		bRestore = FALSE;
	}
	// Previous Top Index in bounds.
	if(pDoc->m_lvTopIndex >= nItems || pDoc->m_lvTopIndex < 0)
	{
		TRACE(_T("In CDsLavListView::RestoreState - prev topIndex out of bounds\n"));
		bRestore = FALSE;
	}
	// Restore state;
	//
	if(bRestore)
	{
		/*dbg
		CString topicName = pDoc->m_lvCurTopicName;
		int iCurSel = pDoc->m_lvCurSelIndex;
		int iTopIndex = pDoc->m_lvTopIndex;
		TRACE(_T("In CDsLavListView::RestoreState - Restoring topicName=%s, iCurSel=%d, iTopSel=%d\n"),
				  topicName, iCurSel, iTopIndex);*/

		// Player running flag.
		BOOL bPlaying = FALSE;
		CPlayer * pPlayer = GetPlayer();	
		if(pPlayer && pPlayer->IsRunningOrPaused())
		{
			TRACE(_T("In CDsLavListView::RestoreState - player running\n"));
			bPlaying = TRUE;
		}
		// Set member. 
		m_curSelIndex = pDoc->m_lvCurSelIndex;

		// Scroll to previous position.
		//
		// Get new top position - will be zero after video
		// playback, and any position after audio playback
		// or item editing
		int newTopIndex = listCtrl.GetTopIndex();

		// Get item 0 rect size.
		CRect rect;
		listCtrl.GetItemRect(0, &rect, LVIR_LABEL);
		CSize sizeItem(rect.Size());

		// Get size of list rect above previous top index.
		int nTop = pDoc->m_lvTopIndex - newTopIndex;
		CSize sizeScroll(0, sizeItem.cy * nTop);

		// Scroll.
		BOOL bScroll = listCtrl.Scroll(sizeScroll);

		// Ensure ListView has focus - will not have after playback stops.
		// Note:
		//  If player is playing and user changes TreeView topics then returns  
		//  to the original topic via mouse clicking, the item playing is 
		//  highlighed grey in the ListView and the focus stays in the TreeView. 
		//  If arrow keys are used in the TreeView, the focus does switch.
		if(GetFocus() != this)
		{
			// Call SetFocus() - note this will reset m_curSelIndex 
			// to zero, and select it as well.
			TRACE(_T("In CDsLavListView::RestoreState - restoring - setting focus to ListView\n"));
			SetFocus();
			RemoveSelections();
			m_curSelIndex = pDoc->m_lvCurSelIndex;
		}
		// Set curSel selected and focused.
		listCtrl.SetItemState(m_curSelIndex, LVIS_SELECTED, LVIS_SELECTED);
		listCtrl.SetItemState(m_curSelIndex, LVIS_FOCUSED, LVIS_FOCUSED); 

		// If the player is running, keep the state data so if user
		// switches topics and returns to the original, the playing
		// media item gets highlighted.
		if(!bPlaying)
			StateZero(); 
	}
	// Done
	return;
}

// Zero ListView state.
void CDsLavListView::StateZero()
{
	// Get Doc.
	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return;

	pDoc->m_lvCurTopicName = _T("");
	pDoc->m_lvCurSelIndex = -1;
	pDoc->m_lvTopIndex = -1;
	pDoc->m_lvNumItems = 0;
	pDoc->m_lvIsNewMediaItem = FALSE;
	pDoc->m_lvIsAddFiles = FALSE;
	pDoc->m_lvIsMultiDelete = FALSE;

	// Note: CDoc m_lvSetColWidths and m_lvColWidths
	//  are not item selection states - they are 
	//  ListView column states, and are used thoughout
	//  a DsLav instance. See UpdateColumnWidths().
}

// User tabbing into ListView. 7/22
void CDsLavListView::OnSetFocus(CWnd* pOldWnd) 
{
	// Base first.
	CListView::OnSetFocus(pOldWnd);
	
	// Valid previous focus.
	if(pOldWnd)
	{
		// 8/22-5 rework - fixes tabbing selections problems
		// If user tabbed into ListView, some item(s) should
		// be selected to show the focus has changed.
		//
		TRACE(_T("In CDsLavListView::OnSetFocus.\n"));

		// Get list ctrl.
		CListCtrl & listCtrl = GetListCtrl();

		// Get num items in list.
		int nItems = listCtrl.GetItemCount();

		// Get num selections.
		int nSels = listCtrl.GetSelectedCount();

		// Get num media items.
		int nMediaItems = m_mediaAr.GetSize();
		int nChildTopics = m_childTopicsAr.GetSize();

		// If no selection(s), and have list items,
		// select first.
		if(!nSels && nItems)
		{
			TRACE(_T("In CDsLavListView::OnSetFocus - setting selection 0. nSels=%d\n"), nSels);

			// Set selected and focused first media or topic item.
			listCtrl.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
			// Set focus rect.
			listCtrl.SetItemState(0, LVIS_FOCUSED, LVIS_FOCUSED); 

			// If media items - always first in list with Topics last.
			// Note curSelIndex is -1 if Topic selected.
			if(nMediaItems)
				m_curSelIndex = 0;
			else
				m_curSelIndex = -1;

		}
		return;
	}
}

// Size ListView list columns.
//
// Calling UpdateColumnWidths() from OnSize() messes up
// the CListCtrl window position for unknown reasons.
// Using PostMessage to do it fixes - apparently allowing
// time for MFC cludge to get proccessed.
LRESULT CDsLavListView::OnSizeColumns(WPARAM wParam, LPARAM lParam)	// WM_USER_SIZE_COLUMNS
{
	UpdateColumnWidths();
	return 0;
}

// 11/22-3 rework for post video playback
// Called from OnSizeColumns().
void CDsLavListView::UpdateColumnWidths()
{
	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
	{
		TRACE(_T("In CDsLavListView::UpdateColumnWidths no CDoc\n"));
		return;
	}
	// Get CDoc column data.
	int nColWidths = pDoc->m_lvColWidths.GetSize();
	BOOL bSetColWidths = pDoc->m_lvSetColWidths;
	
	TRACE(_T("In CDsLavListView::UpdateColumnWidths nColWidths=%d bSetColWidths=%d\n"), 
				nColWidths, bSetColWidths);

	// If we have widths and are not setting cols, bail.
	if(nColWidths && !bSetColWidths)
		return;

	// Get column widths as needed - done at start up.
	if(!nColWidths)
	{
		TRACE(_T("In CDsLavListView::UpdateColumnWidths getting initial col widths\n"));
		GetColWidths();
	}
	// Set column widths as needed - done at start up and after video playback.
	//  Flag set in CDoc constructor and CDsLavListView::OnDestroy()
	if(bSetColWidths)
	{
		CListCtrl & theListCtrl = GetListCtrl();
		theListCtrl.SetRedraw(FALSE);

		int width = 0;
		int sz = pDoc->m_lvColWidths.GetSize();
		for(int i = 0; i < sz; i++)
		{
			width = pDoc->m_lvColWidths[i];
			theListCtrl.SetColumnWidth (i, width);
		}
		// Set flag.
		pDoc->m_lvSetColWidths = FALSE;

		theListCtrl.SetRedraw(TRUE);
	}
}
/* orig Called from OnSizeColumns().
void CDsLavListView::UpdateColumnWidths()
{
	// Setting just once allows user column adjustments
	// to remain during client rect resizing.
	if(m_bColWidthSet)
		return;

	CListCtrl & theListCtrl = GetListCtrl();
	theListCtrl.SetRedraw(FALSE);

	// Get col widths set to list string widths.
	int col1, col2, col3, col4, col5;
	GetColWidths(col1, col2, col3, col4, col5);

	// Set widths.
	theListCtrl.SetColumnWidth (0, col1);
	theListCtrl.SetColumnWidth (1, col2);
	theListCtrl.SetColumnWidth (2, col3);
	theListCtrl.SetColumnWidth (3, col4);
	theListCtrl.SetColumnWidth (4, col5);

	// Set flag used above.
	m_bColWidthSet = TRUE;

	theListCtrl.SetRedraw(TRUE);
}*/
// 11/22-3 rework
// Get 5 col widths based on string lengths in Doc array.
BOOL CDsLavListView::GetColWidths()
{
	// Note Topic strings are not considered here.

	// Default widths from KCRW radio media item:
	int col1 = 278;
	int col2 = 126;
	int col3 = 121;
	int col4 = 83;
	int col5 = 566;

	// Get list ctrl.
	CListCtrl & listCtrl = GetListCtrl();

	int w, sz = m_mediaAr.GetSize();

	// If no media items.
	//  Note: Favorites topic is displayed at start up. If no items are marked 
	//        favorites, default col widths (set above) are used below.
	if(!sz)	
	{
		TRACE(_T("WARNING In CDsLavListView::GetColWidths m_mediaAr empty - using default widths\n"));
		//dbg - default width
		//int width = listCtrl.GetColumnWidth(0); // 158
	}
	CString type, isFavorite;
	CString str;
	CMediaItem item;

	// Padding
	str = _T("XX");		// 11/22-3 XX
	int iPad = listCtrl.GetStringWidth(str);	// CListCtrl func

	// Run through array.
	for(int i = 0; i < sz; i ++)
	{
		/*Col defs for reference
		theListCtrl.InsertColumn(0, _T("Name"), LVCFMT_LEFT, width);
		theListCtrl.InsertColumn(1, _T("Type"), LVCFMT_LEFT, width);
		theListCtrl.InsertColumn(2, _T("Topic"), LVCFMT_LEFT, width);
		theListCtrl.InsertColumn(3, _T("Favorite"), LVCFMT_LEFT, width);
		theListCtrl.InsertColumn(4, _T("URL - Path"), LVCFMT_LEFT, width);
		*/

		// Get the Media item and strings.
		item = m_mediaAr[i];
		type = item.GetStreamTypeString();
		CString topicName = item.GetMediaTopicName();
		isFavorite = _T("No");

		// Name.
		str = item.m_mediaName;
		w = listCtrl.GetStringWidth(str) + iPad;
		if(w > col1)
			col1 = w + 32; // add 16 pix for icon 11/22-3 use 32

		// TYPE.
		str = type;
		w = listCtrl.GetStringWidth(str) + iPad;							
		if(w > col2)
			col2 = w;

		// Topic.
		str = topicName;
		w = listCtrl.GetStringWidth(str) + iPad;							
		if(w > col3)
			col3 = w;

		// Favorites.
		str = _T("Favorite"); //isFavorite;
		w = listCtrl.GetStringWidth(str) + iPad;
			col4 = w;

		// URL.
		str = item.m_mediaUrl;
		w = listCtrl.GetStringWidth(str) + iPad;
		if(w > col5)
			col5 = w;
	}
	// Set CDoc array.
	CDsLavDoc * pDoc = GetDocument();
	if(pDoc)
	{
		// Prep.
		pDoc->m_lvColWidths.RemoveAll();
		// Set 5 widths.
		pDoc->m_lvColWidths.Add(col1);
		pDoc->m_lvColWidths.Add(col2);
		pDoc->m_lvColWidths.Add(col3);
		pDoc->m_lvColWidths.Add(col4);
		pDoc->m_lvColWidths.Add(col5);
	}
	return TRUE;
}

// 11/22-3
// ListView is being destroyed - save column widths for any next creation.
void CDsLavListView::OnDestroy() 
{
	TRACE(_T("In CDsLavListView::OnDestroy - saving column widths.\n"));
	CListView::OnDestroy();
	CDsLavDoc * pDoc = GetDocument();
	CListCtrl & listCtrl = GetListCtrl();
	if(pDoc && listCtrl.m_hWnd)
	{
		int width = 0;
		// Clean existing.
		pDoc->m_lvColWidths.RemoveAll();

		// Get current widths - may be user adjusted.
		width = listCtrl.GetColumnWidth(0);
		pDoc->m_lvColWidths.Add(width);
		width = listCtrl.GetColumnWidth(1);
		pDoc->m_lvColWidths.Add(width);
		width = listCtrl.GetColumnWidth(2);
		pDoc->m_lvColWidths.Add(width);
		width = listCtrl.GetColumnWidth(3);
		pDoc->m_lvColWidths.Add(width);
		width = listCtrl.GetColumnWidth(4);
		pDoc->m_lvColWidths.Add(width);

		// Set flag so ListView sets column widths next time
		// it is instantiated.
		pDoc->m_lvSetColWidths = TRUE;
	}
}

// Called at start up.
// Called when TopicTree is modified.
void CDsLavListView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) 
{
	if(lHint == LIST_UPDATE)
	{
		TRACE(_T("CDsLavListView::OnUpdate - lHint = UPDATE_LIST calling SetListData.\n"));
		SetListData();
	}
	// Update font.
	else if(lHint == LIST_FONT_UPDATE)
	{
		// Set up TreeView font.
		CDsLavDoc * pDoc = GetDocument();
		if(pDoc)
		{
			// Get list ctrl.
			CListCtrl & listCtrl = GetListCtrl();

			// Clean.
			if((HFONT)m_font)
				m_font.DeleteObject();
			// Set up font.
			LOGFONT lf = pDoc->m_listLf;
			m_font.CreateFontIndirect(&lf);
			listCtrl.SetFont(&m_font);
		}
	}
}

// LButton Down.
void CDsLavListView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// Get list ctrl.
	CListCtrl & listCtrl = GetListCtrl();

	// Get zero based index of the item the user clicked on.
	UINT uFlags;
	int nItem = listCtrl.HitTest(point, &uFlags);

	// Check bounds.
	int nMediaItems = m_mediaAr.GetSize();

	// Clicked an empty space.
	if(nItem == -1)
	{
		// De-select cur sel. This also gets any 
		// Topic sel which we do not track.
		RemoveSelections();

		// No valid sel.
		m_curSelIndex = -1;
	}
	// Save item index if clicked.
	else if(nItem >= 0 && nItem < nMediaItems)
	{
		m_curSelIndex = nItem;
	}
	// Error - or Topic name selected.
	else
	{
		// No Valid mediaItem sel
		m_curSelIndex = -1;
	}
	// Else base class.
	CListView::OnLButtonDown(nFlags, point);
}

// Deselect selected media items.
// todo - update code
void CDsLavListView::RemoveSelections() 
{
	// Get list ctrl.
	CListCtrl & listCtrl = GetListCtrl();

	int nItem = -1;
	int nSel = listCtrl.GetSelectedCount();

	// Clear the selected items.
	if(nSel > 0)
	{
		for(int i = 0; i < nSel; i++)
		{
			// Get first selected item with nItem == -1.		8/22-5
			nItem = listCtrl.GetNextItem(nItem, LVNI_SELECTED);
			// Ensure.
			if(nItem == -1)
			{
				TRACE(_T("ERROR CDsLavListView::RemoveSelections nItem = -1\n"));
				break;
			}
			// Not selected.
			listCtrl.SetItemState(nItem, 0, LVIS_SELECTED);
			// No focus rect.
			listCtrl.SetItemState(nItem, 0, LVIS_FOCUSED); 
		}
	}
}

// Play selected media item
void CDsLavListView::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
	CDsLavDoc * pLavDoc = GetDocument();
	if(!pLavDoc)
		return;

	// If currebtly running.
	CPlayer * pPlayer = GetPlayer();	
	if(pPlayer && pPlayer->IsRunningOrPaused())
	{
		// restart ok.
	}

	// Get list ctrl.
	CListCtrl & listCtrl = GetListCtrl();

	// Get zero based index of the item the user clicked on.
	UINT uFlags;
	int nItem = listCtrl.HitTest(point, &uFlags);

	// Get list count.
	int nMediaItems = m_mediaAr.GetSize();

	// If double clicking an empty space,
	if(nItem == -1)
	{
		// If a previous selection, un-hilight.
		if(nMediaItems && m_curSelIndex > 0 && m_curSelIndex < nMediaItems)
		{
			// Not selected.
			listCtrl.SetItemState(m_curSelIndex, 0, LVIS_SELECTED);
			// No focus rect.
			listCtrl.SetItemState(m_curSelIndex, 0, LVIS_FOCUSED); 
		}
		// Update item index.
		m_curSelIndex = nItem; // -1
		return;
	}

	// Check bounds - m_mediaAr items at start of list.
	if(nItem >= 0 && nItem < nMediaItems)
	{
		// Get item in array.
		CMediaItem item = m_mediaAr[nItem];

		if(item.m_mediaUrl.IsEmpty())
			return;

		// Get main frame ptr.
		CMainFrame * pFrame = pLavDoc->GetMainFrame();
		if(!pFrame)
			return;

		// If a pls or m3u URL, bail.
		CString url = item.m_mediaUrl;
		int len = url.GetLength();
		int startAt = len > 7 ? len - 7 : 0;

		// 11/22 m3u8 ok
		if(url.Find(_T(".m3u8"), startAt) != -1)
		{
			// ffmpeg can play.
		}
		else if(url.Find(_T(".pls"), startAt) != -1 ||
				url.Find(_T(".m3u"), startAt) != -1  )
		{
			AfxMessageBox(_T("Cannot play. Right-click to import."), MB_OK | MB_ICONINFORMATION);
			return;
		}

		// If a CD Audio .cda file.
		if(item.m_mediaUrl.Find(_T(".cda"), 0) != -1)
		{
			TRACE(_T("In CDsLavListView::OnLButtonDown - post msg play CD media\n"));

			// Set path.
			pFrame->SetMediaPath(item.m_mediaUrl);

			// Set Caption Name. 8/22-3
			CString title;
			title.Format(_T("DsLav CD: %s"), item.m_mediaName);
			pFrame->SetMediaName(title);

			// Post - note ListView remains during playback.
			//  Calls CMainFrame::OnPlaybackDvdStart().
			::PostMessage(pFrame->GetSafeHwnd(), WM_USER_CD_START, 0, 0);
			return;
		}

		// If a dvd - VIDEO_TS.
		if(pLavDoc->m_bCurTopicDrive)
		{
			if(item.m_mediaName == _T("VIDEO_TS.IFO") ||
			   item.m_mediaName == _T("VIDEO_TS.VOB") ||
			   item.m_mediaName == _T("VIDEO_TS.BUP") )
			{
				// Path is to the VIDEO_TS directory.
				CString path = item.m_mediaUrl;

				// Remove file name - "E:\VIDEO_TS\VIDEO_TS.VOB"
				int pos = path.ReverseFind(_T('\\'));
				if(pos != -1)
				{
					TRACE(_T("In CDsLavListView::OnLButtonDown - post msg play DVD media\n"));

					CString root = path.Left(pos + 1); // "E:\VIDEO_TS\"
					// Set path.
					pFrame->SetMediaPath(root);
					// Set temp caption name.
					pFrame->SetMediaName(_T("DsLav DVD"));

					// Post as this window will be destroyed upon playback.
					// Calls CMainFrame::OnPlaybackDvdStart().
					::PostMessage(pFrame->GetSafeHwnd(), WM_USER_DVD_START, 0, 0);
					return;
				}
			}			
		}
		// Else a file - may be a DVD VOB file 
		// which plays like any other file.
		TRACE(_T("In CDsLavListView::OnLButtonDown - post msg play media\n"));

		// Set path.
		pFrame->SetMediaPath(item.m_mediaUrl);
		// Set name.
		pFrame->SetMediaName(item.m_mediaName);

		// 11/22-2
		TRACE(_T("In CDsLavListView::OnLButtonDownDblClk - calling stateSave\n"));
		StateSave();

		// Post as this window may be destroyed upon playback.
		// Calls CMainFrame::OnPlaybackStart().
		::PostMessage(pFrame->GetSafeHwnd(), WM_USER_VID_START, 0, 0);
		return;
	}

	// If a topic selected. Note Topics are always listed
	// after MediaItems, and are in seperate arrays.
	int nChildTopics = m_childTopicsAr.GetSize();
	int iTopicSel = nItem - nMediaItems;

	if(iTopicSel >= 0 && iTopicSel < nChildTopics)
	{
		// If a Drive topic - no go for now.
		if(pLavDoc->m_bCurTopicDrive)
			return;

		// Get Topic.
		CTopic & topic = m_childTopicsAr[iTopicSel];

		// Go to the child topic. CDsLavDoc will update its Lists, 
		// the TreeView, and this ListView to the child topic.
		pLavDoc->ListViewSelectedTopic(topic.m_nodesAr);
		return;
	}
	// Else base class.	
	CListView::OnLButtonDblClk(nFlags, point);
}

// Play/Pause with Space Bar.
void CDsLavListView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	TRACE(_T("In CDsLavListView::OnKeyDown\n"));

	// Space bar down.
	// 
	// 11/22-4 
	// Pause/depause - Mute/unmute a playing Video file or stream. 
	//  Same in DsLavListView for Audio playback.
	if(nChar == VK_SPACE && ((nFlags & KF_REPEAT) == 0) )  
	{
		CPlayer * pPlayer = GetPlayer();	
		if(pPlayer && pPlayer->IsRunningOrPaused())
		{
			// If playing a video stream.
			if(pPlayer->IsStream())
			{
				// Toggle Mute.
				pPlayer->ToggleVolumeMute();

				// Update Controls Bar.
				//
				// Unmute.
				if(pPlayer->IsMuted())
				{
					// Set bar text to "Mute".
					HWND hwndBar = ((CDsLavApp*)AfxGetApp())->m_hwndControlBar;
					if(hwndBar)
						::PostMessage(hwndBar, WM_USER_BAR_MUTE, 0, 1); //m_hwndControlBar
				}
				// Mute.
				else
				{
					// Set bar text to "Unmute".
					HWND hwndBar = ((CDsLavApp*)AfxGetApp())->m_hwndControlBar;
					if(hwndBar)
						::PostMessage(hwndBar, WM_USER_BAR_MUTE, 0, 0);
				}
			}
			// Else if playing a file or disc.
			else
			{
				// Toggle Pause.
				pPlayer->TogglePlaybackPause();

				// Update Controls Bar.
				//
				// Paused.
				if(pPlayer->IsPaused())
				{
					// Set bar text to "Play" (depause).
					HWND hwndBar = ((CDsLavApp*)AfxGetApp())->m_hwndControlBar;
					if(hwndBar)
						::PostMessage(hwndBar, WM_USER_BAR_PAUSE, 0, 1); //m_hwndControlBar
				}
				// Not paused.
				else
				{
					// Set bar text to "Pause".
					HWND hwndBar = ((CDsLavApp*)AfxGetApp())->m_hwndControlBar;
					if(hwndBar)
						::PostMessage(hwndBar, WM_USER_BAR_PAUSE, 0, 0);
				}
			}
			// Ensure focus here and call base below. 8/22-4 comment
			SetFocus();
		}
	}
	// 6/22 delete key
	else if(nChar == VK_DELETE)
	{	
		CDsLavDoc * pDoc = GetDocument();
		if(!pDoc)
			return;

		// If in favs list, bail - nothing to delete.
		if(pDoc->m_curTopic.GetTopicName() == STR_FAVS)
			return;

		// Get list ctrl.
		CListCtrl & listCtrl = GetListCtrl();

		// Need at least 1 selection.
		int nSelected = listCtrl.GetSelectedCount();
		if(nSelected < 1)
		{
			return;
		}

		// Get selected items.
		// Save selected item indices in m_iDragListIndexAr.
		CMediaArray mediaAr;
		int nMediaItems = m_mediaAr.GetSize();
		int nChildTopics = m_childTopicsAr.GetSize();
		int iTopicSel = 0, iSel = 0;
		POSITION pos = listCtrl.GetFirstSelectedItemPosition();
		if(pos == NULL)
			return;

		while(pos)
		{
			iSel = listCtrl.GetNextSelectedItem(pos);

			// Topics not deleted here.
			iTopicSel = iSel - nMediaItems;
			if(iTopicSel >= 0 && iTopicSel < nChildTopics)
			{
				AfxMessageBox(_T("Topics can only be delete from the Contents Pane."),
							  MB_OK | MB_ICONINFORMATION);//todo
				return;
			}
			// Else save media items to mediaItem to ar.
			else
			{
				if(iSel < 0 || iSel >= nMediaItems)
				{
					TRACE(_T("CDsLavListView::OnKeyDown item index out of bounds.\n"));
					return;
				}
				// Copy media item to mediaAr from the m_curListMediaAr.
				mediaAr.Add(m_mediaAr[iSel]);
			}
		}
		// Ensure mediaItems saved to ar.
		int nSels = mediaAr.GetSize();
		if(nSels < 1)
			return;

		// Get nodes for the media items from the curListTopic.
		// Note m_mediaAr is a copy of curListTopic's mediaAr.
		CNodeArray nodesAr = pDoc->m_curTopic.m_nodesAr;

		// If multiple sels.
		if(nSels > 1)
		{
			// Ask user. 
			if(AfxMessageBox(_T("Delete multiple selections?"),
							  MB_YESNO | MB_ICONINFORMATION) == IDYES)
			{
				// Save state for multi deletions. 11/22-2
				StateSave(FALSE, FALSE, TRUE);

				// Delete multiple.
				if(!pDoc->DeleteMediaItemMulti(nodesAr, mediaAr))
				{
					TRACE(_T("CDsLavListView OnKeyDown DeleteMediaItem failed.\n"));
				}
			}
		}
		// Single sel - one item in array.
		else
		{
			CMediaItem mediaItem = mediaAr[0];
			// Ask user. 
			//todo for all delete func list and contents
			if(AfxMessageBox(_T("Delete single selection?"),
							  MB_YESNO | MB_ICONINFORMATION) == IDYES)
			{
				// 11/22-2
				StateSave();

				// Delete.
				if(!pDoc->DeleteMediaItem(nodesAr, mediaItem))
				{
					TRACE(_T("CDsLavListView OnKeyDown DeleteMediaItem failed.\n"));
				}
			}
		}
	}
	// Tab to TreeView or Cntrols Bar. 8/22-7 update go to controls bar ok
	else if(nChar == VK_TAB)
	{
		// Check if left pane hidden.
		//  Note the parent of this View is CMySplitterWnd, 
		//  which is a member of CDsLavSplitterView.
		BOOL bHidden = FALSE;
		CDsLavSplitterView * pSplitterView = ((CDsLavApp*)AfxGetApp())->m_pSplitterView;
		if(pSplitterView && pSplitterView->LeftPaneIsHidden()) 
			bHidden = TRUE;

		// Get hWnds.
		HWND hWndControlsBar = ((CDsLavApp*)AfxGetApp())->m_hwndControlBar;
		HWND hWndListView = ((CDsLavApp*)AfxGetApp())->m_hwndListView;
		HWND hWndTreeView = ((CDsLavApp*)AfxGetApp())->m_hwndTreeView;
		HWND hWndVideoView = ((CDsLavApp*)AfxGetApp())->m_hwndVideoView;

		// Tab to ControlsBar. 
		//  Note even if no media playing, the HideLeft
		//  button is available.
		if(hWndControlsBar)
		{
			::SetFocus(hWndControlsBar);
			TRACE(_T("CDsLavListView OnKeyDown focus to ControlsBar.\n"));
		}
		
		// Else tab to TreeView if showing - should not get here.
		else if(hWndTreeView && !bHidden)
		{
			::SetFocus(hWndTreeView);
			TRACE(_T("CDsLavListView OnKeyDown focus to TreeView.\n"));
		}
		// To Base Class below.
	}
	// Enter key pressed - play a media item. 8/22
	else if(nChar == VK_RETURN)
	{
		// Rework for sel 8/22-4

		// Get list ctrl.
		CListCtrl & listCtrl = GetListCtrl();

		// Get sel - zero based.
		POSITION pos = listCtrl.GetFirstSelectedItemPosition();
		if(!pos)
			return;
		int iSel = listCtrl.GetNextSelectedItem(pos);

		// Get media item count.
		int nMediaItems = m_mediaAr.GetSize();

		// Get num selections - can Play only one sel.
		int nSels = listCtrl.GetSelectedCount();

		// If single sel and in bounds.
		//  Note Topics are listed after media items.
		if(nSels == 1 && iSel >= 0 && iSel < nMediaItems)
		{
			//
			// Code from OnLButtonDblClk().
			//
			// Get item in array.
			CMediaItem item = m_mediaAr[iSel];

			if(item.m_mediaUrl.IsEmpty())
				return;

			// Get document.
			CDsLavDoc * pLavDoc = GetDocument();

			// Get main frame ptr.
			CMainFrame * pFrame = pLavDoc->GetMainFrame();
			if(!pFrame)
				return;

			// If a pls or m3u URL, bail.
			CString url = item.m_mediaUrl;
			int len = url.GetLength();
			int startAt = len > 7 ? len - 7 : 0;
			if(url.Find(_T(".pls"), startAt) != -1 ||
			   url.Find(_T(".m3u"), startAt) != -1  )
			{
				AfxMessageBox(_T("Cannot play. Right-click to import."), MB_OK | MB_ICONINFORMATION);
				return;
			}

			// If a CD Audio .cda file.
			if(item.m_mediaUrl.Find(_T(".cda"), 0) != -1)
			{
				TRACE(_T("In CDsLavListView::OnKeyDown - post msg play CD media\n"));

				// Set path.
				pFrame->SetMediaPath(item.m_mediaUrl);
				// Set temp caption name.
				pFrame->SetMediaName(_T("DsLav CD"));

				// Save state. 11/22-2 - CD play.
				TRACE(_T("In CDsLavListView::OnKeyDown - calling stateSave\n"));
				StateSave();

				// Post as this window will be destroyed upon playback.
				//  Calls CMainFrame::OnPlaybackDvdStart().
				::PostMessage(pFrame->GetSafeHwnd(), WM_USER_CD_START, 0, 0);
				return;
			}

			// If a dvd - VIDEO_TS.
			if(pLavDoc->m_bCurTopicDrive)
			{
				if(item.m_mediaName == _T("VIDEO_TS.IFO") ||
				   item.m_mediaName == _T("VIDEO_TS.VOB") ||
				   item.m_mediaName == _T("VIDEO_TS.BUP") )
				{
					// Path is to the VIDEO_TS directory.
					CString path = item.m_mediaUrl;

					// Remove file name - "E:\VIDEO_TS\VIDEO_TS.VOB"
					int pos = path.ReverseFind(_T('\\'));
					if(pos != -1)
					{
						TRACE(_T("In CDsLavListView::OnKeyDown - post msg play DVD media\n"));

						CString root = path.Left(pos + 1); // "E:\VIDEO_TS\"
						// Set path.
						pFrame->SetMediaPath(root);
						// Set temp caption name.
						pFrame->SetMediaName(_T("DsLav DVD"));

						// Save state. 11/22-2 DVD play
						TRACE(_T("In CDsLavListView::OnKeyDown - calling stateSave\n"));
						StateSave();

						// Post as this window will be destroyed upon playback.
						// Calls CMainFrame::OnPlaybackDvdStart().
						::PostMessage(pFrame->GetSafeHwnd(), WM_USER_DVD_START, 0, 0);
						return;
					}
				}			
			}
			// Else a file - may be a DVD VOB file 
			// which plays like any other file.
			TRACE(_T("In CDsLavListView::OnKeyDown - post msg play media\n"));

			// Set path.
			pFrame->SetMediaPath(item.m_mediaUrl);
			// Set name.
			pFrame->SetMediaName(item.m_mediaName);

			// Save state. 11/22-2 video file play
			TRACE(_T("In CDsLavListView::OnKeyDown - calling stateSave\n"));
			StateSave();

			// Post as this window may be destroyed upon playback.
			// Calls CMainFrame::OnPlaybackStart().
			::PostMessage(pFrame->GetSafeHwnd(), WM_USER_VID_START, 0, 0);
			return;
		}
	}
	// Arrow key pressed. 8/22-6 set m_curSel
	else if(nChar == VK_LEFT		||
			nChar == VK_UP		||
			nChar == VK_RIGHT	||
			nChar == VK_DOWN		)
	{
		// Get list ctrl.
		CListCtrl & listCtrl = GetListCtrl();

		// Get num items in list - ones based.
		int nItems = listCtrl.GetItemCount();

		// Get media item count.
		int nMediaItems = m_mediaAr.GetSize();

		// Get the sel before the key press.
		int iSel = -1;
		iSel = listCtrl.GetNextItem(iSel, LVNI_SELECTED);
		
		// Adjust iSel per arrow.
		if(nChar == VK_UP || nChar == VK_LEFT)
			iSel--;
		else
			iSel++;

		// Check bounds.
		if(iSel < 0)
			iSel = 0;
		else if(iSel > nItems - 1)
			iSel = nItems - 1;

		// If a topic selected.
		if(iSel >= nMediaItems)
			m_curSelIndex = -1;
		else
			m_curSelIndex = iSel;	
}

	// Base.
	CListView::OnKeyDown(nChar, nRepCnt, nFlags);
}

// Column header clicked for sorting.
void CDsLavListView::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult) 
{
	*pResult = 0;
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

	// No sorting items from the favorites folder.
	CDsLavDoc * pDoc = GetDocument();
	if(pDoc->m_curTopic.GetTopicName() == STR_FAVS)
		return;

	// No dragging a Drive item/topic.
	if(pDoc->m_bCurTopicDrive)
		return;

	// Get the col clicked - 0 is Name col, the only one we sort.
	int nCol = pNMListView->iSubItem;
	if(nCol != 0)
		return;

	// Must have media items in local array.
	int nMediaItems = m_mediaAr.GetSize();
	if(nMediaItems < 2)
		return;

	// Ascend or descend - can only guess.
	CString strTop = m_mediaAr[0].m_mediaName;
	CString strBot = m_mediaAr[nMediaItems - 1].m_mediaName;
	BOOL ascend = TRUE;
//	if(strTop.Compare(strBot) < 0)
	if(strTop.CompareNoCase(strBot) < 0)
		ascend = FALSE;

	// Get topic nodes array.
	CNodeArray nodesAr;
	nodesAr = pDoc->m_curTopic.m_nodesAr;

	// CDoc calls topictree to sort the media items, 
	// then updates this ListView.
	pDoc->ListViewSortMediaItems(nodesAr, ascend);
}

/////////////////////////////////////////////////////////////////////////////
// Context menu
// 11/22-4 rework favs having too many options
// 08/22-6 rework for keyboard
//
// Called by the framework when the user has clicked the 
// right mouse button (right clicked) in the window, or
// pressed the Menu key or Shift-F10.
void CDsLavListView::OnContextMenu(CWnd* pWnd, CPoint point) 
{
	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return;

	// In favs list.
	BOOL inFavs = FALSE;
	if(pDoc->m_curTopic.GetTopicName() == STR_FAVS)
		inFavs = TRUE;

	// Shift down - na - Shift-F10 should be like Menu key.
	BOOL bShift = FALSE;
	int nVirtKey = GetKeyState(VK_SHIFT);	// -127
    if(nVirtKey & 0x8000)					// 0x8000 
		bShift = TRUE;     
	
	// Ctrl down.
	BOOL bCtrl = FALSE;
	nVirtKey = GetKeyState(VK_CONTROL);		// -127
    if(nVirtKey & 0x8000)					// 0x8000 
		bCtrl = TRUE;                            

	// Menu down - no go.
	BOOL bMenu = FALSE;
	nVirtKey = GetKeyState(VK_MENU);		// 0
    if(nVirtKey & 0x8000)					// 0x8000 
		bMenu = TRUE;                     

	// F10 down.
	BOOL bF10 = FALSE;
	nVirtKey = GetKeyState(VK_F10);			// -127
    if(nVirtKey & 0x8000)					// 0x8000 
		bF10 = TRUE;
	
	int nItem = -1;

	// Get list ctrl.
	CListCtrl & listCtrl = GetListCtrl();

	// Num sels.
	int nSels = listCtrl.GetSelectedCount();

	// Num media items..
	int nMediaItems = m_mediaAr.GetSize();

	// Num items in list.
	int nListItems = listCtrl.GetItemCount();

	// If a Drive topic.
	BOOL bIsDrive = FALSE;
	if(pDoc->m_bCurTopicDrive)
		bIsDrive = TRUE;

	// If multiple selections, Multi handles.
	if(nSels > 1)
	{
		OnContextMenuMulti(pWnd, point);
		return;
	}
	// If Keyboard Shift-F10, Ctrl-Shift-F10, or Menu key.
	if(point.x == -1 && point.y == -1)
	{
		// Get first selected item with nItem set to -1.
		nItem = listCtrl.GetNextItem(nItem, LVNI_SELECTED); 

		// If in favorites, use the "Media Item" 
		// context which requires a selection.
		//  Note the "New Item" context does 
		//  not apply to Favorites.
		if(inFavs )
		{
			// If no favorites or no selection, bail.
			if(!nListItems || nItem == -1)
				return;
		}
		// If Ctrl-Shift-F10.
		else if(bShift && bCtrl)
			nItem = -1;

		// If nItem is a Topic or Drive.
		else if(nItem >= nMediaItems || bIsDrive)
			nItem = -1;

		// If no sels, or no list items, or above 2 tests,
		// use "New Item" context.
		if(nItem == -1)
		{
			// Get mid-left list client.
			CRect client;
			listCtrl.GetClientRect(&client);
			point = client.CenterPoint();
			point.x = client.left + 10;
			// To screen.
			listCtrl.ClientToScreen(&point);
		}
		// Else use "Media Item" context.
		else
		{
			// Get bot-right Media Item icon rect.
			CRect itemRect;
			listCtrl.GetItemRect(nItem, &itemRect, LVIR_ICON); // LVIR_ICON LVIR_LABEL
			point = itemRect.BottomRight();
			// To screen.
			listCtrl.ClientToScreen(&point);
		}
	}
	// Else a Mouse click.
	else
	{
		// Ignore Drives.
		if(bIsDrive)
			return;

		// Need point param in client coords.
		CPoint client = point;
		listCtrl.ScreenToClient(&client);

		// Get zero based index of the item the user clicked on.
		// Will be -1 if not on an item (on a blank area).
		UINT uFlags = 0;
		nItem = listCtrl.HitTest(client, &uFlags); // LVHT_ONITEM 

		// Click must be on lable - done already somewhere else.
		if(! (uFlags & LVHT_ONITEMLABEL) )
			int g = 0;
	}
	// If a Drive topic - no context for now.
	if(pDoc->m_bCurTopicDrive)
		return;

	///////////////////////////////////
	// Create the context.
	CString str;
	CMediaItem mediaItem;
	CNodeArray nodesAr;

	CMenu menu;
	menu.CreatePopupMenu();

	// Clicked an empty space - create "New Item" context.
	if(nItem == -1)
	{
		// No valid media item sel.
		m_curSelIndex = -1;

		// Clear sels.
		RemoveSelections();

		// If not in favs list, add MediaItem to the topic,
		// add New Child Topic to the topic.
		if(pDoc->m_curTopic.GetTopicName() != STR_FAVS)
		{
			// Add disk files.
			if(pDoc->m_curTopic.m_topicMediaType == VIDF || 
			   pDoc->m_curTopic.m_topicMediaType == AUDF )
			{
				str = _T("Add Files...");
				menu.AppendMenu(MF_STRING, ID_CTX_LIST_ADD_FILES, str);
			}
			// Else add Media items.
			else
			{
				str = _T("Add New MediaItem...");
				menu.AppendMenu(MF_STRING, ID_CTX_LIST_NEW_ITEM, str);
			}
			// Add a new Child Topic.
			str = _T("Add New Topic...");
			menu.AppendMenu(MF_STRING, ID_CTX_LIST_NEW_TOPIC, str);

			/* Add cancel - no - not standard UI.
			str = _T("Cancel (Esc)");
			menu.AppendMenu(MF_STRING, ID_CTX_LIST_ESCAPE, str);*/
		}
	}
	// Else clicked on a valid Media Item, create "Media Item" context.
	else if(nItem >= 0 && nItem < nMediaItems)
	{
		// Update item index.
		m_curSelIndex = nItem;

		// Get the selected CMediaItem - copy of CDoc.
		mediaItem = m_mediaAr[m_curSelIndex];

		// Media item is a favorite flag.
		BOOL bIsFavorite = FALSE;
		if(mediaItem.m_isFavorite)
			bIsFavorite = TRUE;

		// Set Menu items:
		//
		// If in favorites.
		if(inFavs)
		{
			// If a favorite, allow remove.
			if(bIsFavorite)
			{
				str = _T("Remove From Favorites");
				menu.AppendMenu(MF_STRING, ID_CTX_LIST_REMOVE_FAV, str);
			}
		}
		// Else not in favs.
		else
		{
			// If a pls or m3u URL, user can import, delete, 
			// edit, or copy to clipboard - but not add to
			// favorites.
			BOOL bIsPlaylist = FALSE;
			CString url = mediaItem.m_mediaUrl;
			int len = url.GetLength();
			int startAt = len > 7 ? len - 7 : 0;
			if(url.Find(_T(".pls"), startAt) != -1 ||
			   url.Find(_T(".m3u"), startAt) != -1  )
			{
				if(url.Find(_T(".pls"), startAt) != -1)
					str = _T("Import .pls Playlist...");
				else
					str = _T("Import .m3u Playlist...");

				// Add menu item.
				menu.AppendMenu(MF_STRING, ID_CTX_LIST_IMPORT, str);
				// Set flag.
				bIsPlaylist = TRUE;
			}
			// Add menu item: Remove from favorites.
			if(bIsFavorite)
			{
				str = _T("Remove From Favorites");
				menu.AppendMenu(MF_STRING, ID_CTX_LIST_REMOVE_FAV, str);
			}
			// Add menu item: Edit media item.
			str = _T("Edit...");
			menu.AppendMenu(MF_STRING, ID_CTX_LIST_EDIT, str);

			// Add menu item: Delete media item.
			str = _T("Delete");
			menu.AppendMenu(MF_STRING, ID_CTX_LIST_DELETE, str);

			// Add menu item: Add to favorites.
			if(!bIsPlaylist && !bIsFavorite)
			{
				str = _T("Add To Favorites");
				menu.AppendMenu(MF_STRING, ID_CTX_LIST_ADD_FAV, str);
			}
			// Add menu item: Copy media item to clipboard.
			str = _T("Copy as text to Clipboard");
			menu.AppendMenu(MF_STRING, ID_CTX_LIST_COPY, str);

			/* Add menu item: Cancel - not standard UI.
			str = _T("Cancel (Esc)");
			menu.AppendMenu(MF_STRING, ID_CTX_LIST_ESCAPE, str);*/
		}
		// Ensure.
		if(menu.GetMenuItemCount() == 0)
			return;
	}
	// Error.
	else
		return;

	//////////////////////
	// Show Context Menu.
	int nCmd = menu.TrackPopupMenu(	TPM_LEFTALIGN  | 
									TPM_LEFTBUTTON |
									TPM_RETURNCMD,		// see sdk TrackPopupMenu
									point.x, point.y,	// screen
									this);
	// If right clicked an empty space,
	// user can add a new Media Item, Topic, or Disc Files.
	if(nItem == -1)
	{
		// Media item.
		if(nCmd == ID_CTX_LIST_NEW_ITEM)
		{
			// Create a dummy media item.
			//
			// Set nodesList.
			nodesAr = pDoc->m_curTopic.m_nodesAr;
			nodesAr.GetTopicNameStrings(mediaItem.m_nodesList);
			// Set others.
			mediaItem.m_isFavorite = FALSE;
			mediaItem.m_mediaName = _T("");
			mediaItem.m_mediaType = pDoc->m_curTopic.m_topicMediaType;
			mediaItem.m_mediaUrl = _T("");

			// 11/22-2
			// Save state - new media item.
			StateSave(TRUE);
			
			// Open dlg for new media item.
			pDoc->EditMediaItem(nodesAr, mediaItem, TRUE);
			int g = 0;
		}
		// Add New Child Topic to existing. 
		// 11/22-2 - ok mew topic shown in TreeView, with no media items in ListView
		else if(nCmd == ID_CTX_LIST_NEW_TOPIC)
		{
			nodesAr = pDoc->m_curTopic.m_nodesAr;
			MEDIA_TYPE type = pDoc->m_curTopic.m_topicMediaType;

			// Open dlg for new Topic.
			pDoc->EditTopic(nodesAr, type, TRUE);

		}
		// Add Disc Files
		// 11/22-2 new file(s) are added to bottom of list ok - 
		// need to scroll and highlight file(s). Could save num
		// items in list here, subtract from num items in new
		// list when restoring, and highlight difference at 
		// bot of list.
		else if(nCmd == ID_CTX_LIST_ADD_FILES)
		{
			nodesAr = pDoc->m_curTopic.m_nodesAr;
			// Type to set file extensions in file dlg.
			MEDIA_TYPE type = pDoc->m_curTopic.m_topicMediaType;

			// Save state - add files.
			StateSave(FALSE, TRUE);

			// Open dlg to add files.
			pDoc->AddMediaFilesToTopic(nodesAr, type);
		}

		// Note: Must return - m_curSelIndex == -1, do not go below.
		return;
	}
	// Save list state in case list gets modified. 11/22-2
	StateSave();

	// Get the selected CMediaItem - copy of cDoc.
	mediaItem = m_mediaAr[m_curSelIndex];

	// Get the CNodeArray for selected MediaItem.
	if(inFavs)
		nodesAr = pDoc->m_favsNodesAr[m_curSelIndex];
	else
		nodesAr = pDoc->m_curTopic.m_nodesAr;

	// Add/Remove from Favorites folder.
	if(nCmd == ID_CTX_LIST_REMOVE_FAV || nCmd == ID_CTX_LIST_ADD_FAV)
	{
		BOOL fav = nCmd == ID_CTX_LIST_REMOVE_FAV ? FALSE : TRUE;
		if(!pDoc->SetMediaItemFavorites(nodesAr, mediaItem, fav))
		{
			TRACE(_T("CDsLavListView OnContextMenu SetMediaItemFavorites failed.\n"));
		}
	}
	// Edit Media Item.
	else if(nCmd == ID_CTX_LIST_EDIT)
	{
		pDoc->EditMediaItem(nodesAr, mediaItem);
	}
	// Delete Media Item.
	else if(nCmd == ID_CTX_LIST_DELETE)
	{
		// Ask user.
		if(AfxMessageBox(_T("Delete single selection?"),
						  MB_YESNO | MB_ICONINFORMATION) == IDYES)
		{
			// Delete.
			if(!pDoc->DeleteMediaItem(nodesAr, mediaItem))
			{
				TRACE(_T("CDsLavListView OnContextMenu DeleteMediaItem failed.\n"));
			}
		}
	}
	// Copy MediaItem to clipboard.
	else if(nCmd  == ID_CTX_LIST_COPY)
	{
		CClipboard cb;
		if(!cb.SetClipboardMediaItem(mediaItem))
		{
			TRACE(_T("CDsLavListView OnContextMenu SetClipboardMediaItem failed.\n"));
		}
	}
	// Import pls m3u Playlist.
	else if(nCmd  == ID_CTX_LIST_IMPORT)
	{
		// CDoc handles.
		pDoc->ImportMediaItemPlaylist(nodesAr, mediaItem);
	}	
}

// 8/22-6 rework keyboard
// Multiple selection context.
//  Called from OnContextMenu().
void CDsLavListView::OnContextMenuMulti(CWnd* pWnd, CPoint point) 
{
	CDsLavDoc * pDoc = GetDocument();
	if(!pDoc)
		return;

	// If in favs list, bail - no contect handling.
	if(pDoc->m_curTopic.GetTopicName() == STR_FAVS)
		return;

	// Get list ctrl.
	CListCtrl & listCtrl = GetListCtrl();

	// If clicked on empty space or single item.
	if(listCtrl.GetSelectedCount() < 2)
	{
		// Clear sels.
		RemoveSelections();
		return;
	}
	int nItem = -1;

	// If Keyboard Shift-F10, Ctrl-Shift-F10, or Menu key.
	if(point.x == -1 && point.y == -1)
	{
		// Get first selected item with nItem param set to -1.
		nItem = listCtrl.GetNextItem(nItem, LVNI_SELECTED);
		
		// Ensure.
		if(nItem == -1)
		{
			// Clear sels.
			RemoveSelections();
			return;
		}
		// Get bot-right Item icon rect.
		CRect itemRect;
		listCtrl.GetItemRect(nItem, &itemRect, LVIR_ICON); // LVIR_ICON LVIR_LABEL
		point = itemRect.BottomRight();
		// To screen.
		listCtrl.ClientToScreen(&point);
	}
	// Else a mouse click.
	else
	{
		// Point is screen coords.
		CPoint client = point;
		listCtrl.ScreenToClient(&client);

		// Get zero based index of the item the user right clicked on.
		// Will be one of many sels.
		UINT uFlags = 0;
		nItem = listCtrl.HitTest(client, &uFlags); // LVHT_ONITEM 
	}
	// Update item index to one right clicked.
	m_curSelIndex = nItem;

	// Get selected items.
	// Save selected item indices in m_iDragListIndexAr.
	BOOL bTopicSelected = FALSE;
	CMediaArray mediaAr;
	int nMediaItems = m_mediaAr.GetSize();
	int nChildTopics = m_childTopicsAr.GetSize();
	int iTopicSel = 0, iSel = 0;
	POSITION pos = listCtrl.GetFirstSelectedItemPosition();
	while(pos)
	{
		iSel = listCtrl.GetNextSelectedItem(pos);

		// No context handling for topics in multi sel.
		iTopicSel = iSel - nMediaItems;
		if(iTopicSel >= 0 && iTopicSel < nChildTopics)
		{
			bTopicSelected = TRUE;
		}
		// Else save to mediaItem to array.
		else
		{
			if(iSel < 0 || iSel >= nMediaItems)
			{
				TRACE(_T("CDsLavListView::OnContextMenuMulti item index out of bounds.\n"));
				return;
			}
			// Copy media item to mediaAr from the m_curListMediaAr.
			mediaAr.Add(m_mediaAr[iSel]);
		}
	}
	// Must have mediaItems.
	if(mediaAr.GetSize() < 2)
		return;

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

	// Add menu item - delete media item.
	CString str = _T("Delete");
	menu.AppendMenu(MF_STRING, ID_CTX_LIST_DELETE, str);

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

	//////////////////////
	// Show Context Menu.
	int nCmd = menu.TrackPopupMenu(	TPM_LEFTALIGN  | 
									TPM_LEFTBUTTON |
									TPM_RETURNCMD,		// see sdk TrackPopupMenu
									point.x, point.y,	// screen
									this);
	// Delete Media Items.
	if(nCmd  == ID_CTX_LIST_DELETE)
	{
		// If topics selected, inform user.
		if(bTopicSelected)
		{
			AfxMessageBox(_T("Cannot delete topics in list. Delete in Contents."));
			return;
		}
		// Get nodes for the media items from the curListTopic.
		// Note m_mediaAr is a copy of curListTopic's mediaAr.
		CNodeArray nodesAr = pDoc->m_curTopic.m_nodesAr;

		// Ask user. 6/22
		if(AfxMessageBox(_T("Delete multiple selections?"),
						  MB_YESNO | MB_ICONINFORMATION) == IDYES)
		{
			// Save state for multi deletions. 11/22-2
			StateSave(FALSE, FALSE, TRUE);

			// Delete multiple.
			if(!pDoc->DeleteMediaItemMulti(nodesAr, mediaAr))
			{
				TRACE(_T("CDsLavListView OnContextMenuMulti DeleteMediaItem failed.\n"));
			}
		}
	}
}

/////////////////////////////////////////////////////////////////////////////
// Drag and Drop

// Called when user drags.
void CDsLavListView::OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult) 
{
	*pResult = 0;

	// Set flag used to show the IDC_NO cursor in OnMouseMove().
	m_bDragable = TRUE;

	// No dragging items from the favorites folder.
	CDsLavDoc * pDoc = GetDocument();
	if(pDoc->m_curTopic.GetTopicName() == STR_FAVS)
	{
		// Set flag used in OnMouseMove().
		m_bDragable = FALSE;
	}

	// No dragging a Drive item/topic.
	if(pDoc->m_bCurTopicDrive)
		return;

	// Note image list created in OnInitialUpdate().
	CListCtrl & listCtrl = GetListCtrl();

	// If dragging multi sels.
	int nSels = listCtrl.GetSelectedCount();
	if(nSels > 1)
	{
		OnBegindragMulti(pNMHDR, pResult);
		return;
	}

	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

	// Get the index of the item being dragged.
	m_iDragListIndex = pNMListView->iItem;

	// No dragging topics.
	// Check if a topic selected. Note Topics are always listed
	// after MediaItems, and are in seperate arrays.
	int nMediaItems = m_mediaAr.GetSize();
	int nChildTopics = m_childTopicsAr.GetSize();
	int iTopicSel = m_iDragListIndex - nMediaItems;

	if(iTopicSel >= 0 && iTopicSel < nChildTopics)
	{
		// Set flag used in OnMouseMove().
		m_bDragable = FALSE;
	}
	// Get the NM_LISTVIEW ptAction - CListctrl client coordinates of the mouse 
	CPoint actionPt = pNMListView->ptAction; 

	// Create drag image - pt set to TL of image in View coords.
	CPoint pt(0, 0);
	m_pImageList = listCtrl.CreateDragImage(m_iDragListIndex, &pt);
	if(!m_pImageList)
	{
		TRACE(_T("CDsLavListView::OnBegindrag m_pImageList NULL\n"));
		return;
	}
	// Capture the mouse and begin dragging.
	SetCapture();

	// Begin drag - hot spot relative to drag image.
	m_pImageList->BeginDrag(0, CPoint(0, 0));

	// Enter drag - CWnd of NULL and GetDesktopWindow() are the same.
	// CWnd of this needs actionPt converted somehow. 
	m_pImageList->DragEnter(GetDesktopWindow(), actionPt);

	m_bDragging = TRUE;	
}

// Called when user drags multiple items.
void CDsLavListView::OnBegindragMulti(NMHDR* pNMHDR, LRESULT* pResult) 
{
	CListCtrl & listCtrl = GetListCtrl();

	// Clean array storage.
	m_iDragListIndexAr.RemoveAll();

	// Get num sels.
	int nSels = listCtrl.GetSelectedCount();
	if(nSels < 2)
		return;

	// Set flag used to show the IDC_NO cursor in OnMouseMove().
	m_bDragable = TRUE;

	// No dragging items from the favorites folder.
	CDsLavDoc * pDoc = GetDocument();
	if(pDoc->m_curTopic.GetTopicName() == STR_FAVS)
	{
		// Set flag used in OnMouseMove().
		m_bDragable = FALSE;
	}
	// Note image list created in OnInitialUpdate().
	NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

	// Save selected item indices in m_iDragListIndexAr.
	int nMediaItems = m_mediaAr.GetSize();
	int nChildTopics = m_childTopicsAr.GetSize();
	int iTopicSel = 0, iSel = 0;
	POSITION pos = listCtrl.GetFirstSelectedItemPosition();
	while(pos)
	{
		iSel = listCtrl.GetNextSelectedItem(pos);

		// No dragging topics. See notes in OnBegindrag().
		iTopicSel = iSel - nMediaItems;
		if(iTopicSel >= 0 && iTopicSel < nChildTopics)			
			m_bDragable = FALSE;

		// Save.
		m_iDragListIndexAr.Add(iSel);
	}

	// Get the NM_LISTVIEW ptAction - CListctrl client coordinates of the mouse 
	CPoint actionPt = pNMListView->ptAction; 

	// Create drag image.
	CPoint pt(0, 0);
	m_pImageList = CreateMultiDragImage();
	if(!m_pImageList)
	{
		TRACE(_T("CDsLavListView::OnBegindrag m_pImageList NULL\n"));
		return;
	}
	// Set the index member .
	m_iDragListIndex = pNMListView->iItem;


	// Capture the mouse and begin dragging.
	SetCapture();

	// Begin drag - zeroed hot spot is top left of drag image.
	m_pImageList->BeginDrag(0, CPoint(0, 0));

	// Enter drag - CWnd of NULL and GetDesktopWindow() are the same.
	// CWnd of this would need actionPt converted somehow. 
	m_pImageList->DragEnter(GetDesktopWindow(), actionPt);

	m_bDragging = TRUE;	
}

// Multi Sel Drag Image.
CImageList * CDsLavListView::CreateMultiDragImage()
{
	CListCtrl & listCtrl = GetListCtrl();
	CRect rect, rectUnion(0, 0, 0, 0);
	int	iSel;

	// Get a bk color from existing images built in OnInitialUpdate().
	// This makes for a tranparent drag image.
	COLORREF bkColor = m_imageList.GetBkColor();

	// Get bounding rect of selected item lables (list column #0).
	POSITION pos = listCtrl.GetFirstSelectedItemPosition();
	while(pos)
	{
		iSel = listCtrl.GetNextSelectedItem(pos);
		listCtrl.GetItemRect(iSel, rect, LVIR_LABEL); //LVIR_BOUNDS);
		rectUnion.UnionRect(rectUnion, rect);
	}
	// Create a mem dc based on the ListView.
	CClientDC dc(this);
	CDC memDC;	
	if(!memDC.CreateCompatibleDC(&dc))		
		return NULL;
	
	// Create bounding rect sized bitmap.
	CBitmap	bitmap;
	if(!bitmap.CreateCompatibleBitmap(&dc, rectUnion.Width(), rectUnion.Height()))
		return NULL;

	// Select bitmap into mem dc.
	CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);

	// Fill dc rect with bk color.
	memDC.FillSolidRect(0, 0, rectUnion.Width(), rectUnion.Height(), bkColor); 

	// Get a temp drag image for each item, and draw each
	// image into the mem dc bitmap per list location.
	CImageList * pImageList;
	CPoint pt, ptPos;
	pos = listCtrl.GetFirstSelectedItemPosition();
	while(pos)
	{
		iSel = listCtrl.GetNextSelectedItem(pos);
		listCtrl.GetItemRect(iSel, rect, LVIR_BOUNDS);
		ptPos.x = rect.left - rectUnion.left;
		ptPos.y = rect.top  - rectUnion.top;

		pImageList = listCtrl.CreateDragImage(iSel, &pt);
		if(pImageList)
		{
			pImageList->Draw(&memDC, 0, ptPos, ILD_MASK);
			delete pImageList;
		}
	}
	// Clean bitmap.
	memDC.SelectObject(pOldBitmap);

	// Allocate a new image list based on the bounding rect.
	pImageList = new CImageList;	
	pImageList->Create(rectUnion.Width(), rectUnion.Height(), ILC_MASK, 0, 1);

	// Add bitmap to image list - bk color is used to generate a mask. 
	pImageList->Add(&bitmap, bkColor); 

	// Clean bitmap.
	bitmap.DeleteObject();

	// Return allocated image list. Caller must delete.
	return(pImageList);
}

// Moving a drag item.
void CDsLavListView::OnMouseMove(UINT nFlags, CPoint point) 
{
	// Base class.
	CListView::OnMouseMove(nFlags, point);

	// Get list ctrl ref.
	CListCtrl & listCtrl = GetListCtrl();

	// Point param conversions.
	CPoint screenPt(point);
	ClientToScreen(&screenPt);
	CPoint clientPt;

	// Must have access to the TreeView.
	CDsLavTreeView * pTreeView = GetTreeViewPtr();
	if(pTreeView)
	{
		clientPt = screenPt;
		pTreeView->ScreenToClient(&clientPt);
	}

	if(m_bDragging && m_pImageList != NULL) 
	{
		// Stop the scroll timer if it's running.
		KillTimer(1);

		// Move the drag image
		m_pImageList->DragMove(screenPt);

		// Unlock window updates
		m_pImageList->DragShowNolock (FALSE);

		// Get the CWnd pointer of the window that is under the mouse cursor
		CWnd* pDropWnd = WindowFromPoint(screenPt);

		// If window is TreeView's CTreeCtrl.
		if(pDropWnd->IsKindOf(RUNTIME_CLASS(CDsLavTreeView)) && 
		   pTreeView   && 
		   m_bDragable )
		{
			m_bDragInTreeCtrl = TRUE;

			// Modify the cursor to provide visual feedback to the user.
			// Note: It's important to do this AFTER the call to DragMove.
			::SetCursor((HCURSOR)::GetClassLong(m_hWnd, GCL_HCURSOR));

			// Get tree ctrl.
			CTreeCtrl & treeCtrl = pTreeView->GetTreeCtrl();

			TRACE(_T("In CDsLavListView::OnMouseMove calling treeCtrl.HitTest.\n"));

			// Get the item that is below cursor - top left of drag image.
			UINT uFlags;
			m_hDragTreeItem = treeCtrl.HitTest(clientPt, &uFlags);

			// If dropping on favs.
			BOOL onFavs = FALSE;
			if(m_hDragTreeItem && treeCtrl.GetItemText(m_hDragTreeItem) == STR_FAVS)
				onFavs = TRUE;

			// If dropping a multiSel on favs.
			if(onFavs && m_iDragListIndexAr.GetSize())
			{
				// Modify the cursor to provide visual feedback to the user.
				// Note: It's important to do this AFTER the call to DragMove.
				::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_NO));
			}

			// Highlight, scroll & expand.
			else if(m_hDragTreeItem)
			{
				// Highlight.
				TRACE(_T("In CDsLavListView::OnMouseMove calling treeCtrl.SelectDropTarget.\n"));
				treeCtrl.SelectDropTarget(m_hDragTreeItem);

				// Auto scroll & expand:
				//
				// Set a timer if the cursor is at the top or bottom of the 
				// TreeView CTreeCtrl window, or if it's over a collapsed item.
				CRect rect;
				pTreeView->GetClientRect(rect);
				int cy = rect.Height ();

				if( (point.y >= 0 && point.y <= m_nScrollMargin)			||
					(point.y >= cy - m_nScrollMargin && point.y <= cy)		||
					( m_hDragTreeItem != NULL							&& 
					  treeCtrl.ItemHasChildren(m_hDragTreeItem)			&&
					  !IsTreeViewItemExpanded(m_hDragTreeItem, treeCtrl) )	 )
				{
					SetTimer(1, m_nDelayInterval, NULL); // see constructor
				}
			}
		}
		// Else not a drag target.
		else
		{
			// Modify the cursor to provide visual feedback to the user.
			// Note: It's important to do this AFTER the call to DragMove.
			::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_NO));

			// Unselect any TreeView selection.
			if(pTreeView && m_hDragTreeItem)
			{
				TRACE(_T("In CDsLavListView::OnMouseMove calling treeCtrl.SetItemState.\n"));
				CTreeCtrl & treeCtrl = pTreeView->GetTreeCtrl();
				treeCtrl.SetItemState(m_hDragTreeItem, 0, LVIS_DROPHILITED);
				treeCtrl.SelectDropTarget(NULL); // must
				m_hDragTreeItem = NULL;
			}
			// Not dragging in the TreeView.
			m_bDragInTreeCtrl = FALSE;

			// No tree item to drop on.
			m_hDragTreeItem = NULL;

			// If in CListCtrl window.
			if(pDropWnd->IsKindOf(RUNTIME_CLASS(CDsLavListView)))
			{ int g = 0; }
		}
		// Lock window updates for dragging.
		m_pImageList->DragShowNolock (TRUE);
	}
}

// Dropping a dragged item.
void CDsLavListView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	// Dragging for a drop.
	if(m_bDragging)
	{
		// Get list ctrl ref.
		CListCtrl & listCtrl = GetListCtrl();

		TRACE(_T("in CDsLavListView::OnLButtonUp drag over.\n"));
		// Release mouse capture
		::ReleaseCapture();
		m_bDragging = FALSE;

		// End dragging
		m_pImageList->DragLeave(GetDesktopWindow());
		m_pImageList->EndDrag();
		m_bDragable = TRUE;

		// Drag multiple.
		int nSels = listCtrl.GetSelectedCount();

		// If making a TreeView drop.
		// Note m_iDragListIndex will be the last sel made.
		if(m_bDragInTreeCtrl && m_hDragTreeItem && m_iDragListIndex != -1)
		{
			// Remove hilight from TreeView drop target.
			CDsLavTreeView * pTreeView = GetTreeViewPtr();
			if(pTreeView)
			{
				CTreeCtrl & treeCtrl = pTreeView->GetTreeCtrl();
				treeCtrl.SelectDropTarget(NULL); // must
			}
			// If Single sel.
			if(nSels == 1)
			{
				// TreeView proccesses.
				pTreeView->ListViewDropMediaItem(m_iDragListIndex, m_hDragTreeItem);
			}

			// Else Multi sel. Ar initialized in OnBeginDragMulti.
			else if(nSels > 1 && m_iDragListIndexAr.GetSize())
			{
				// TreeView proccesses. Note passing ref to member ar.
				pTreeView->ListViewDropMediaItemMulti(m_iDragListIndexAr, m_hDragTreeItem);
			}
			// Finished with drag and drop - 
			//  CTreeView will proccess the drop.
			//
			// Zero.
			m_bDragInTreeCtrl = FALSE;
			m_hDragTreeItem = NULL;
			m_iDragListIndex = -1;
		}
	}	
	CListView::OnLButtonUp(nFlags, point);
}



// This is used for scrolling and expanding TreeView
// topics while dragging a ListView media item,
void CDsLavListView::OnTimer(UINT nIDEvent) 
{	
	CListView::OnTimer(nIDEvent);

	// Must be dragging.
	if(!m_bDragging)
	{
		KillTimer(1); // needed?
		return;
	}
	// Must be in the TreeView.
	if(!m_bDragInTreeCtrl)
		return;

	TRACE(_T("CDsLavListView::OnTimer - dragging in TreeView.\n"));

	// Must have TreeView.
	CDsLavTreeView * pTreeView = GetTreeViewPtr();
	if(!pTreeView)
		return;

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

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

	// Get the current cursor position.
	DWORD dwPos = ::GetMessagePos();
	CPoint point(LOWORD (dwPos), HIWORD (dwPos));
	pTreeView->ScreenToClient(&point);

	// Get TreeView client.
	CRect rect;
	pTreeView->GetClientRect(rect);

	// Client height.
	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);

		// Message the TreeView.
		pTreeView->SendMessage(WM_VSCROLL, MAKEWPARAM (SB_LINEUP, 0), NULL);
		m_pImageList->DragShowNolock (TRUE);

		// Kill the timer if the CTreeCtrl window did not scroll, or 
		// redraw the drop target highlight if the window did scroll.
		if(treeCtrl.GetFirstVisibleItem() == hFirstVisible)
		{
			KillTimer(1);
		}
		else 
		{
			// Highlight the Drop Target.
			//
			// 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;
		}
	}
	else if(point.y >= cy - m_nScrollMargin && point.y <= cy) 
	{
		HTREEITEM hFirstVisible = treeCtrl.GetFirstVisibleItem();

		m_pImageList->DragShowNolock(FALSE);
		pTreeView->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 
		{
			// Highlight the Drop Target.
			//
			// 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;
		}
	}

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

	if(hItem != NULL							&& 
	   treeCtrl.ItemHasChildren(hItem)			&& 
	   !IsTreeViewItemExpanded(hItem, treeCtrl) ) 
	{
		// 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;
	}
}

// Is a TreeView topic expanded.
// Called from:
//	CDsLavListView::OnTimer
//	CDsLavListView::OnMouseMove
BOOL CDsLavListView::IsTreeViewItemExpanded(HTREEITEM hItem, CTreeCtrl & treeCtrl)
{
	// Be shure.
	if(!hItem)
		return FALSE;
	return treeCtrl.GetItemState(hItem, TVIS_EXPANDED) & TVIS_EXPANDED;
}

/////////////////////////////////////////////////////////////////////////////
// CDsLavListView diagnostics

#ifdef _DEBUG
void CDsLavListView::AssertValid() const
{
	//CListView::AssertValid();
	CView::AssertValid();
}

void CDsLavListView::Dump(CDumpContext& dc) const
{
	//CListView::Dump(dc);
	CView::Dump(dc);
}
// See DsLavSplitter
CDsLavDoc* CDsLavListView::GetDocument() // non-debug version is inline
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CDsLavDoc)));
	return (CDsLavDoc*)m_pDocument;
}
#endif //_DEBUG






