// XTPShellTreeView.cpp : implementation file
//
// This file is a part of the XTREME CONTROLS MFC class library.
// (c)1998-2012 Codejock Software, All Rights Reserved.
//
// THIS SOURCE FILE IS THE PROPERTY OF CODEJOCK SOFTWARE AND IS NOT TO BE
// RE-DISTRIBUTED BY ANY MEANS WHATSOEVER WITHOUT THE EXPRESSED WRITTEN
// CONSENT OF CODEJOCK SOFTWARE.
//
// THIS SOURCE CODE CAN ONLY BE USED UNDER THE TERMS AND CONDITIONS OUTLINED
// IN THE XTREME TOOLKIT PRO LICENSE AGREEMENT. CODEJOCK SOFTWARE GRANTS TO
// YOU (ONE SOFTWARE DEVELOPER) THE LIMITED RIGHT TO USE THIS SOFTWARE ON A
// SINGLE COMPUTER.
//
// CONTACT INFORMATION:
// support@codejock.com
// http://www.codejock.com
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Common/XTPVC80Helpers.h"  // Visual Studio 2005 helper functions
#include "Common/XTPResourceManager.h"
#include "Common/XTPSystemHelpers.h"
#include "Common/XTPWinThemeWrapper.h"

#include "XTPDropSource.h"
#include "../Tree/XTPTreeBase.h"
#include "XTPShellSettings.h"
#include "XTPShellPidl.h"
#include "XTPShellTreeBase.h"


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

/////////////////////////////////////////////////////////////////////////////
// CXTPShellTreeBase

CXTPShellTreeBase::CXTPShellTreeBase()
: m_bTunneling(false)
{
	m_bContextMenu = TRUE;
	m_pComboBox = NULL;
	m_uFlags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS;
	m_ulSFGAOFlags = SFGAO_HASSUBFOLDER | SFGAO_FOLDER | SFGAO_DISPLAYATTRMASK | SFGAO_REMOVABLE | SFGAO_COMPRESSED | SFGAO_ENCRYPTED;

	if (m_shSettings.ShowAllFiles() && !m_shSettings.ShowSysFiles())
	{
		m_uFlags |= SHCONTF_INCLUDEHIDDEN;
	}

	m_nRootFolder = CSIDL_DESKTOP;
	m_bShowFiles = FALSE;
	m_bShowShellLinkIcons = FALSE;

	m_bExplorerStyle = FALSE;
}

CXTPShellTreeBase::~CXTPShellTreeBase()
{

}

/////////////////////////////////////////////////////////////////////////////
// CXTPShellTreeBase message handlers

HTREEITEM CXTPShellTreeBase::InsertDesktopItem(int nFolder /*= CSIDL_DESKTOP*/)
{
	HTREEITEM hItem = TVI_ROOT;

	CShellMalloc lpMalloc;

	if (!lpMalloc)
		return NULL;

	// Get ShellFolder Pidl
	LPITEMIDLIST pidlDesktop = NULL;
	if (FAILED(::SHGetSpecialFolderLocation(NULL, nFolder, &pidlDesktop)))
	{
		pidlDesktop = NULL;
	}

	// insert the desktop.
	if (pidlDesktop)
	{
		SHFILEINFO fileInfo;
		::ZeroMemory(&fileInfo, sizeof(fileInfo));

		::SHGetFileInfo((LPCTSTR)pidlDesktop, NULL, &fileInfo, sizeof(fileInfo),
			SHGFI_PIDL | SHGFI_ATTRIBUTES | SHGFI_DISPLAYNAME);

		TV_ITEM  tvi;
		tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;

		// Allocate memory for ITEMDATA struct
		XTP_TVITEMDATA* lptvid = new XTP_TVITEMDATA;
		if (lptvid != NULL)
		{
			GetNormalAndSelectedIcons(pidlDesktop, &tvi);

			// Now, make a copy of the ITEMIDLIST and store the parent folders SF.
			lptvid->lpi = DuplicateItem(lpMalloc, pidlDesktop);
			lptvid->lpsfParent = NULL;
			lptvid->lpifq = ConcatPidls(lpMalloc, NULL, pidlDesktop);

			TCHAR szBuff[MAX_PATH];
			STRCPY_S(szBuff, MAX_PATH, fileInfo.szDisplayName);

			tvi.lParam = (LPARAM)lptvid;
			tvi.pszText = szBuff;
			tvi.cchTextMax = MAX_PATH;

			// Populate the TreeVeiw Insert Struct
			// The item is the one filled above.
			// Insert it after the last item inserted at this level.
			// And indicate this is a root entry.
			TV_INSERTSTRUCT tvins;
			tvins.item = tvi;
			tvins.hInsertAfter = hItem;
			tvins.hParent = hItem;

			// Add the item to the tree
			hItem = m_pTreeCtrl->InsertItem(&tvins);
		}

		if (pidlDesktop)
		{
			lpMalloc.Free(pidlDesktop);
		}
	}

	return hItem;
}


void CXTPShellTreeBase::PopulateTreeView()
{
	// Get a pointer to the desktop folder.
	CShellSpecialFolder lpsfFolder(m_nRootFolder);
	if (!lpsfFolder)
		return;

	// turn off redraw and remove all tree items.
	m_pTreeCtrl->SetRedraw(FALSE);
	m_pTreeCtrl->DeleteAllItems();

	HTREEITEM hItemDesktop = InsertDesktopItem(m_nRootFolder);

	LPITEMIDLIST pidlRoot = NULL;
	if (FAILED(::SHGetSpecialFolderLocation(NULL, m_nRootFolder, &pidlRoot)))
	{
		pidlRoot = NULL;
	}

	// Fill in the tree view from the root.
	InitTreeViewItems(lpsfFolder, pidlRoot, hItemDesktop);

	if (pidlRoot)
	{
		CShellMalloc malloc;
		malloc.Free(pidlRoot);
	}

	// Sort the items in the tree view
	SortChildren(hItemDesktop);

	HTREEITEM hItemRoot = m_pTreeCtrl->GetRootItem();
	m_pTreeCtrl->Expand(hItemRoot, TVE_EXPAND);

	if (hItemDesktop != TVI_ROOT)
	{
		HTREEITEM hItemChild = m_pTreeCtrl->GetChildItem(hItemDesktop);
		m_pTreeCtrl->Select(hItemChild, TVGN_CARET);

		if ((::GetWindowLong(m_pTreeCtrl->m_hWnd, GWL_STYLE) & TVS_SINGLEEXPAND) == 0)
		{
			m_pTreeCtrl->Expand(hItemChild, TVE_EXPAND);
		}

		// get the item path.
		CString strFolderPath;
		GetFolderItemPath(hItemChild, strFolderPath);

		// call virtual change notify method.
		SelectionChanged(hItemChild, strFolderPath);
	}
	else
	{
		m_pTreeCtrl->Select(hItemRoot, TVGN_CARET);

		// get the item path.
		CString strFolderPath;
		GetFolderItemPath(hItemRoot, strFolderPath);

		// call virtual change notify method.
		SelectionChanged(hItemRoot, strFolderPath);
	}

	// turn on redraw and refresh tree.
	m_pTreeCtrl->SetRedraw(TRUE);
	m_pTreeCtrl->RedrawWindow();
	m_pTreeCtrl->SetFocus();
}

void CXTPShellTreeBase::SetAttributes(HTREEITEM hItem, DWORD dwAttributes)
{
	if (hItem != NULL)
	{
		// set the display attributes for the tree item.
		if (dwAttributes != 0)
		{
			MapShellFlagsToItemAttributes(m_pTreeCtrl, hItem, dwAttributes);

			// the item is compress or encrypted, show alt color.
			if (m_shSettings.ShowCompColor())
			{
				if (dwAttributes & SFGAO_COMPRESSED)
					SetItemColor(hItem, m_shSettings.m_crCompColor);

				else if (dwAttributes & SFGAO_ENCRYPTED)
					SetItemColor(hItem, m_shSettings.m_crEncrColor);
			}
		}

	}
}

BOOL CXTPShellTreeBase::InitTreeViewItems(LPSHELLFOLDER lpsf, LPITEMIDLIST  lpifq, HTREEITEM     hParent)
{
	CWaitCursor wait; // show wait cursor.

	// Allocate a shell memory object.
	CShellMalloc lpMalloc;

	if (!lpMalloc)
		return FALSE;

	// Get the IEnumIDList object for the given folder.
	LPENUMIDLIST lpe = NULL;
	if (FAILED(lpsf->EnumObjects(::GetParent(m_pTreeCtrl->m_hWnd), m_uFlags, &lpe)))
		return FALSE;

	if (lpe == NULL)
		return FALSE;

	ULONG        ulFetched = 0;
	HTREEITEM    hPrev = NULL;
	LPITEMIDLIST lpi = NULL;

	// Enumerate through the list of folder and non-folder objects.
	while (lpe->Next(1, &lpi, &ulFetched) == S_OK)
	{
		// Create a fully qualified path to the current item
		// the SH* shell api's take a fully qualified path pidl,
		// (see GetIcon above where I call SHGetFileInfo) whereas the
		// interface methods take a relative path pidl.

		// Determine what type of object we have.
		ULONG ulAttrs = GetAttributes(lpsf, lpi, m_ulSFGAOFlags);

		// We need this next if statement so that we don't add things like
		// the MSN to our tree.  MSN is not a folder, but according to the
		// shell it has subfolders.
		if ((ulAttrs & SFGAO_FOLDER) || m_bShowFiles)
		{
			TV_ITEM  tvi;
			tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;

			if ((ulAttrs & SFGAO_HASSUBFOLDER) || (m_bShowFiles && (ulAttrs & SFGAO_FOLDER)))
			{
				//This item has sub-folders, so let's put the + in the TreeView.
				//The first time the user clicks on the item, we'll populate the
				//sub-folders.
				tvi.cChildren = 1;
				tvi.mask |= TVIF_CHILDREN;
			}

			// Allocate memory for ITEMDATA struct
			CString szBuff;
			XTP_TVITEMDATA* lptvid = new XTP_TVITEMDATA;
			if (lptvid == NULL || GetName(lpsf, lpi, SHGDN_NORMAL, szBuff) == FALSE)
			{
				if (lptvid)
				{
					lpMalloc.Free(lptvid);
				}
				if (lpe)
				{
					lpe->Release();
				}
				if (lpi)
				{
					lpMalloc.Free(lpi);
				}
				return FALSE;
			}


			// Now, make a copy of the ITEMIDLIST and store the parent folders SF.
			lptvid->lpi = DuplicateItem(lpMalloc, lpi);
			lptvid->lpsfParent = lpsf;
			lptvid->lpifq = ConcatPidls(lpMalloc, lpifq, lpi);
			lpsf->AddRef();

			GetNormalAndSelectedIcons(lptvid->lpifq, &tvi);

			tvi.lParam = (LPARAM)lptvid;
			tvi.pszText = (LPTSTR)(LPCTSTR)szBuff;
			tvi.cchTextMax = 0;

			// Populate the TreeVeiw Insert Struct
			// The item is the one filled above.
			// Insert it after the last item inserted at this level.
			// And indicate this is a root entry.
			TV_INSERTSTRUCT tvins;
			tvins.item = tvi;
			tvins.hInsertAfter = hPrev;
			tvins.hParent = hParent;

			// Add the item to the tree
			hPrev = m_pTreeCtrl->InsertItem(&tvins);
			SetAttributes(hPrev, ulAttrs);
		}

		// Free the pidl that the shell gave us.
		if (lpi)
		{
			lpMalloc.Free(lpi);
			lpi = 0;
		}
	}
	if (lpi)
	{
		lpMalloc.Free(lpi);
	}

	if (lpe)
	{
		lpe->Release();
	}

	return TRUE;
}

void CXTPShellTreeBase::GetNormalAndSelectedIcons(LPITEMIDLIST lpifq, LPTV_ITEM lptvitem)
{
	// Note that we don't check the return value here because if GetIcon()
	// fails, then we're in big trouble...
	lptvitem->iImage = GetItemIcon(lpifq,
		SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON);

	lptvitem->iSelectedImage = GetItemIcon(lpifq,
		SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_OPENICON);
}

void CXTPShellTreeBase::OnFolderExpanding(NM_TREEVIEW* pNMTreeView)
{
	if (!(pNMTreeView->itemNew.state & TVIS_EXPANDEDONCE))
	{
		// Long pointer to TreeView item data
		XTP_TVITEMDATA* lptvid = (XTP_TVITEMDATA*)pNMTreeView->itemNew.lParam;
		if (lptvid != NULL && lptvid->lpsfParent != NULL)
		{
			LPSHELLFOLDER lpsf = NULL;
			if (SUCCEEDED(lptvid->lpsfParent->BindToObject(lptvid->lpi,
				0, IID_IShellFolder, (LPVOID *)&lpsf)))
			{
				InitTreeViewItems(lpsf, lptvid->lpifq, pNMTreeView->itemNew.hItem);
			}

			SortChildren(pNMTreeView->itemNew.hItem);

			m_pTreeCtrl->SetItemState(pNMTreeView->itemNew.hItem, TVIS_EXPANDEDONCE, TVIS_EXPANDEDONCE);
		}
	}
}

HTREEITEM CXTPShellTreeBase::GetContextMenu()
{
	CPoint point;
	::GetCursorPos(&point);
	m_pTreeCtrl->ScreenToClient(&point);

	TV_HITTESTINFO tvhti;
	tvhti.pt = point;
	m_pTreeCtrl->HitTest(&tvhti);

	if (tvhti.flags & (TVHT_ONITEMLABEL | TVHT_ONITEMICON))
	{
		// Long pointer to TreeView item data
		XTP_TVITEMDATA*  lptvid = (XTP_TVITEMDATA*)m_pTreeCtrl->GetItemData(tvhti.hItem);

		m_pTreeCtrl->ClientToScreen(&point);

		if (lptvid->lpsfParent == NULL)
		{
			LPSHELLFOLDER lpShellFolder;
			if (FAILED(::SHGetDesktopFolder(&lpShellFolder)))
			{
				return NULL;
			}

			ShowContextMenu(m_pTreeCtrl->m_hWnd,
				lpShellFolder, lptvid->lpi, &point);

			if (lpShellFolder)
			{
				lpShellFolder->Release();
			}
		}
		else
		{
			ShowContextMenu(m_pTreeCtrl->m_hWnd,
				lptvid->lpsfParent, lptvid->lpi, &point);
		}

		return tvhti.hItem;
	}

	return NULL;
}

void CXTPShellTreeBase::SortChildren(HTREEITEM hParent)
{
	TV_SORTCB tvscb;
	tvscb.hParent = hParent;
	tvscb.lParam = 1;
	tvscb.lpfnCompare = TreeViewCompareProc;
	m_pTreeCtrl->SortChildrenCB(&tvscb);
}

BOOL CXTPShellTreeBase::OnFolderSelected(NM_TREEVIEW* pNMTreeView, CString &strFolderPath)
{
	// check tree item handle.
	if (!pNMTreeView || !pNMTreeView->itemNew.hItem)
		return FALSE;

	// set a reference to the tree item struct.
	TVITEM& tvi = pNMTreeView->itemNew;

	// Long pointer to TreeView item data
	XTP_TVITEMDATA*  lptvid = (XTP_TVITEMDATA*)m_pTreeCtrl->GetItemData(tvi.hItem);
	if (lptvid && lptvid->lpi && lptvid->lpsfParent)
	{
		LPSHELLFOLDER lpsf = NULL;
		if (SUCCEEDED(lptvid->lpsfParent->BindToObject(lptvid->lpi,
			0, IID_IShellFolder, (LPVOID*)&lpsf)))
		{
			if (m_pTreeCtrl->ItemHasChildren(tvi.hItem) && (tvi.state & TVIS_EXPANDEDONCE) == 0)
			{
				InitTreeViewItems(lpsf, lptvid->lpifq, tvi.hItem);
				SortChildren(tvi.hItem);
				m_pTreeCtrl->SetItemState(tvi.hItem, TVIS_EXPANDEDONCE, TVIS_EXPANDEDONCE);
			}

			if (lpsf)
			{
				lpsf->Release();
			}
		}
	}

	return GetFolderItemPath(tvi.hItem, strFolderPath);
}

BOOL CXTPShellTreeBase::InitSystemImageLists()
{
	HIMAGELIST himlSmall = GetSystemImageList(SHGFI_SMALLICON);

	if (himlSmall)
	{
		TreeView_SetImageList(m_pTreeCtrl->GetSafeHwnd(), himlSmall, TVSIL_NORMAL);
		return TRUE;
	}

	return FALSE;
}

BOOL CXTPShellTreeBase::GetSelectedFolderPath(CString &strFolderPath)
{
	HTREEITEM hItem = m_pTreeCtrl->GetSelectedItem();
	return GetFolderItemPath(hItem, strFolderPath);
}

BOOL CXTPShellTreeBase::FindTreeItem(HTREEITEM hItem, XTP_LVITEMDATA* lplvid, BOOL bRecursively)
{
	if (lplvid == NULL)
	{
		return FALSE;
	}

	if (!bRecursively)
	{
		hItem = m_pTreeCtrl->GetChildItem(hItem);
	}

	while (hItem)
	{
		// Long pointer to TreeView item data
		XTP_TVITEMDATA*  lptvid = (XTP_TVITEMDATA*)m_pTreeCtrl->GetItemData(hItem);
		if (lptvid)
		{
			if (SCODE_CODE(GetScode(lplvid->lpsfParent->CompareIDs(
				0, lplvid->lpi, lptvid->lpi))) == 0)
			{
				m_pTreeCtrl->EnsureVisible(hItem);
				m_pTreeCtrl->SelectItem(hItem);
				return TRUE;
			}
		}

		if (bRecursively)
		{
			HTREEITEM hNextItem = m_pTreeCtrl->GetChildItem(hItem);
			if (hNextItem)
			{
				if (FindTreeItem(hNextItem, lplvid))
				{
					return TRUE;
				}
			}
		}

		hItem = m_pTreeCtrl->GetNextSiblingItem(hItem);
	}

	return FALSE;
}

void CXTPShellTreeBase::OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult)
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
	OnFolderExpanding(pNMTreeView);
	*pResult = 0;
}

void CXTPShellTreeBase::SelectionChanged(HTREEITEM hItem, CString strFolderPath)
{
	if (hItem != NULL && !m_bTunneling)
	{
		if (!m_pComboBox || !::IsWindow(m_pComboBox->m_hWnd))
			return;

		// update combo box association.
		if (m_pComboBox->IsKindOf(RUNTIME_CLASS(CComboBoxEx)))
		{
			CComboBoxEx* pComboBoxEx = DYNAMIC_DOWNCAST(CComboBoxEx, m_pComboBox);
			ASSERT_VALID(pComboBoxEx);

			int nFound = CB_ERR;
			int nIndex;
			for (nIndex = 0; nIndex < pComboBoxEx->GetCount(); ++nIndex)
			{
				CString strText;
				pComboBoxEx->GetLBText(nIndex, strText);

				if (strFolderPath.Compare(strText) == 0)
				{
					nFound = nIndex;
					pComboBoxEx->SetCurSel(nIndex);
					break;
				}
			}

			if (nFound == CB_ERR)
			{
				HTREEITEM hti = m_pTreeCtrl->GetSelectedItem();
				ASSERT(hti);

				if (strFolderPath.IsEmpty())
				{
					strFolderPath = m_pTreeCtrl->GetItemText(hti);
				}

				int nImage, nSelectedImage;
				m_pTreeCtrl->GetItemImage(hti, nImage, nSelectedImage);

				COMBOBOXEXITEM cbItem = { 0 };

				cbItem.mask = CBEIF_TEXT | CBEIF_IMAGE | CBEIF_SELECTEDIMAGE | CBEIF_INDENT;
				cbItem.iItem = 0;
				cbItem.pszText = (LPTSTR)(LPCTSTR)strFolderPath;
				cbItem.iImage = nImage;
				cbItem.iSelectedImage = nImage;
				cbItem.iIndent = 0;

				pComboBoxEx->InsertItem(&cbItem);
				pComboBoxEx->SetCurSel(0);
				pComboBoxEx->SetItemDataPtr(0, hti);
			}
		}

		else if (m_pComboBox->IsKindOf(RUNTIME_CLASS(CComboBox)))
		{
			CComboBox* pComboBox = DYNAMIC_DOWNCAST(CComboBox, m_pComboBox);
			ASSERT_VALID(pComboBox);

			int nFound = pComboBox->FindStringExact(-1, strFolderPath);
			if (nFound == CB_ERR)
			{
				HTREEITEM hti = m_pTreeCtrl->GetSelectedItem();
				ASSERT(hti);

				pComboBox->InsertString(0, strFolderPath);
				pComboBox->SetCurSel(0);
				pComboBox->SetItemDataPtr(0, (HTREEITEM)hti);
			}
			else
			{
				pComboBox->SetCurSel(nFound);
			}
		}
	}
}

void CXTPShellTreeBase::OnDeleteTreeItem(NMHDR* pNMHDR, LRESULT* pResult)
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

	XTP_TVITEMDATA* lptvid = (XTP_TVITEMDATA*)pNMTreeView->itemOld.lParam;
	if (lptvid != NULL)
	{
		CShellMalloc lpMalloc;

		if (lptvid->lpi)
		{
			lpMalloc.Free(lptvid->lpi);
			lptvid->lpi = NULL;
		}
		if (lptvid->lpsfParent)
		{
			lptvid->lpsfParent->Release();
			lptvid->lpsfParent = NULL;
		}
		if (lptvid->lpifq)
		{
			lpMalloc.Free(lptvid->lpifq);
			lptvid->lpifq = NULL;
		}

		delete lptvid;
	}

	*pResult = 0;
}

void CXTPShellTreeBase::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult)
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

	if (pNMTreeView)
	{
		// get a reference to the tree item struct.
		TVITEM& tvi = pNMTreeView->itemNew;

		// initialize the tree node.
		CString strFolderPath;
		if (!OnFolderSelected(pNMTreeView, strFolderPath))
		{
			// could not determine path from IDL, make sure
			// path is empty.
			strFolderPath.Empty();
		}

		// call virtual change notify method.
		SelectionChanged(tvi.hItem, strFolderPath);

		// mimic modern Windows Explorer behavior.
		if (XTPSystemVersion()->IsWinXPOrGreater())
		{
			// expand selected item.
			if (tvi.hItem && m_pTreeCtrl->ItemHasChildren(tvi.hItem))
			{
				m_pTreeCtrl->Expand(tvi.hItem, TVE_EXPAND);
			}

			// collapse siblings.
			if (tvi.hItem != TVI_ROOT)
			{
				HTREEITEM hItem = m_pTreeCtrl->GetChildItem(
					m_pTreeCtrl->GetParentItem(tvi.hItem));

				while (hItem != NULL)
				{
					if (hItem != tvi.hItem)
					{
						m_pTreeCtrl->Expand(hItem, TVE_COLLAPSE);
					}

					hItem = m_pTreeCtrl->GetNextSiblingItem(hItem);
				}
			}
		}
	}

	*pResult = 0;
}

void CXTPShellTreeBase::OnRclick(NMHDR* /*pNMHDR*/, LRESULT* pResult)
{
	// Display the shell context menu.
	if (m_bContextMenu == TRUE)
	{
		HTREEITEM hItem = GetContextMenu();
		if (hItem != NULL)
		{
			// TODO: Additional error handling.
		}
	}

	*pResult = 0;
}


BOOL CXTPShellTreeBase::InitializeTree(DWORD dwStyle/*= TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT*/)
{
	if (m_pTreeCtrl->GetImageList(TVSIL_NORMAL) == NULL)
	{
		// Initialize the system image list for the tree control.
		VERIFY(InitSystemImageLists());

		// Set the style for the tree control, remove styles that change depending on OS.
		m_pTreeCtrl->ModifyStyle(TVS_HASLINES | TVS_LINESATROOT | TVS_TRACKSELECT, dwStyle);

		// Make sure multi-select mode is disabled.
		EnableMultiSelect(FALSE);

		return TRUE;
	}

	return FALSE;
}


#ifndef TVM_SETEXTENDEDSTYLE
#define TVM_SETEXTENDEDSTYLE      (TV_FIRST + 44)
#endif

#ifndef TVS_EX_DOUBLEBUFFER
#define TVS_EX_DOUBLEBUFFER         0x0004
#define TVS_EX_FADEINOUTEXPANDOS    0x0040
#endif

void CXTPShellTreeBase::SetExplorerStyle()
{
	if (XTPSystemVersion()->IsWinVistaOrGreater())
	{
		m_bExplorerStyle = TRUE;

		CXTPWinThemeWrapper().SetWindowTheme(m_pTreeCtrl->GetSafeHwnd(), L"EXPLORER", NULL);
		m_pTreeCtrl->ModifyStyle(TVS_HASLINES, 0);

		DWORD dwExStyle = TVS_EX_DOUBLEBUFFER | TVS_EX_FADEINOUTEXPANDOS;

		::SendMessage(m_pTreeCtrl->m_hWnd, TVM_SETEXTENDEDSTYLE, (WPARAM)dwExStyle, (LPARAM)dwExStyle);
	}
}

void CXTPShellTreeBase::InitTreeNode(HTREEITEM hItem, XTP_TVITEMDATA* lptvid)
{
	m_pTreeCtrl->SetRedraw(FALSE);
	if (lptvid)
	{
		LPSHELLFOLDER lpsf = NULL;
		if (SUCCEEDED(lptvid->lpsfParent->BindToObject(lptvid->lpi,
			0, IID_IShellFolder, (LPVOID *)&lpsf)))
		{
			InitTreeViewItems(lpsf, lptvid->lpifq, hItem);
		}

		SortChildren(hItem);
	}

	m_pTreeCtrl->SetRedraw(TRUE);
}

HTREEITEM CXTPShellTreeBase::SearchTree(HTREEITEM hItem, LPCITEMIDLIST pABSPidl)
{
	XTP_TVITEMDATA *pItem = NULL;
	HTREEITEM hChildItem = m_pTreeCtrl->GetChildItem(hItem);

	CShellSpecialFolder pShellFolder;
	if (!pShellFolder)
		return NULL;

	while (hChildItem)
	{
		// Get the pidl that is stored in the tree node
		pItem = (XTP_TVITEMDATA *)m_pTreeCtrl->GetItemData(hChildItem);

		// See if it matches the one we're looking for
		if (ComparePidls(pItem->lpifq, pABSPidl, pShellFolder))
		{
			m_pTreeCtrl->Select(hChildItem, TVGN_CARET);

			// Ensure that we are expanded
			UINT uState = m_pTreeCtrl->GetItemState(hChildItem, TVIS_EXPANDEDONCE);
			if (!(uState & TVIS_EXPANDEDONCE))
			{
				InitTreeNode(hChildItem, (XTP_TVITEMDATA*)m_pTreeCtrl->GetItemData(hChildItem));
				m_pTreeCtrl->SetItemState(hChildItem, TVIS_EXPANDEDONCE, TVIS_EXPANDEDONCE);
			}
			break;
		}

		// Didn't compare... try next one
		hChildItem = m_pTreeCtrl->GetNextSiblingItem(hChildItem);
	}

	return hChildItem;
}

BOOL CXTPShellTreeBase::TunnelTree(CString strFindPath)
{
	if (strFindPath.IsEmpty())
		return false;

	m_bTunneling = true;

	BOOL bLock = m_pTreeCtrl->LockWindowUpdate();
	BOOL bFound = false;

	LPITEMIDLIST pidlPath;

	// Attempt to get the folder's item list
	pidlPath = IDLFromPath(strFindPath);

	// If it is NULL then see if it is one of the special folders
	if (pidlPath == NULL)
	{
		// These are the ones we care about.
		const int nCSIDLMax = 0x001b;
		for (int i = 0; i <= nCSIDLMax; i++)
		{
			LPITEMIDLIST pidlSpecialPath = NULL;
			if (::SHGetSpecialFolderLocation(NULL, i, &pidlSpecialPath) != NOERROR)
				continue;

			SHFILEINFO fileInfo;
			::ZeroMemory(&fileInfo, sizeof(fileInfo));
			::SHGetFileInfo((LPCTSTR)pidlSpecialPath, NULL, &fileInfo, sizeof(fileInfo),
				SHGFI_PIDL | SHGFI_DISPLAYNAME);

			CString cs = fileInfo.szDisplayName;
			if (cs.CompareNoCase(strFindPath) == 0)
			{
				// Found the pidl for the special folder
				pidlPath = pidlSpecialPath;
				break;
			}
		}
	}

	if (pidlPath != NULL)
	{
		// Now work through the list and tree nodes until we compare
		int nItems = GetPidlCount(pidlPath);
		LPITEMIDLIST pPartPidl;
		LPITEMIDLIST pABSPidl = NULL;
		HTREEITEM hItem = m_pTreeCtrl->GetRootItem();
		bFound = false;

		// Loop through all the parts and see if we can find a match in the tree.  It should
		// be there unless something got added to the namespace after we built the tree last
		// and we didn't catch it, but that's pretty unlikely.
		for (int i = 0; i < nItems; i++)
		{
			pPartPidl = CopyPidlItem(pidlPath, i);
			pABSPidl = ConcatPidls(pABSPidl, pPartPidl);
			FreePidl(pPartPidl);

			hItem = SearchTree(hItem, pABSPidl);
			if (!hItem)
				break; // Our partial path should still found
		}

		// If it was found the final path should compare to the full path entered
		// and the hItem should be set to the place in the tree where the path ends
		if (hItem && (nItems == 0 || ComparePidls(pidlPath, pABSPidl, NULL)))
		{
			m_pTreeCtrl->Select(hItem, TVGN_CARET);
			// Ensure that we are expanded
			UINT uState = m_pTreeCtrl->GetItemState(hItem, TVIS_EXPANDEDONCE);
			if (!(uState & TVIS_EXPANDEDONCE))
			{
				InitTreeNode(hItem, (XTP_TVITEMDATA*)m_pTreeCtrl->GetItemData(hItem));
				m_pTreeCtrl->SetItemState(hItem, TVIS_EXPANDEDONCE, TVIS_EXPANDEDONCE);
			}
			bFound = true;
		}

		// Clean up
		if (pidlPath)
			FreePidl(pidlPath);

		if (pABSPidl)
			FreePidl(pABSPidl);
	}
	else
	{
		// Must not be a special folder (stand alone) or a path that ParseDisplayName() doesn't recognize so look through tree
		// directly to see if we can find it that way.
		// Start at Desktop ...
		HTREEITEM hItemRoot = m_pTreeCtrl->GetRootItem();
		TCHAR* pszNext = NULL;
		TCHAR szBuff[MAX_PATH];

		STRCPY_S(szBuff, MAX_PATH, strFindPath);

		TCHAR* lpszContext = 0;
		pszNext = STRTOK_S(szBuff, _T("\\/"), &lpszContext);

		bFound = false;

		if (pszNext != NULL)
		{
			CString strItemText(m_pTreeCtrl->GetItemText(hItemRoot));

			// Are we looking from desktop?
			if (strItemText.CompareNoCase(pszNext) == 0)
				pszNext = STRTOK_S(NULL, _T("\\/"), &lpszContext);

			hItemRoot = m_pTreeCtrl->GetChildItem(hItemRoot);

			// Ensure that we are expanded
			UINT uState = m_pTreeCtrl->GetItemState(hItemRoot, TVIS_EXPANDEDONCE);
			if (!(uState & TVIS_EXPANDEDONCE))
			{
				InitTreeNode(hItemRoot, (XTP_TVITEMDATA*)m_pTreeCtrl->GetItemData(hItemRoot));
				m_pTreeCtrl->SetItemState(hItemRoot, TVIS_EXPANDEDONCE, TVIS_EXPANDEDONCE);
			}

			while (pszNext && hItemRoot)
			{
				strItemText = m_pTreeCtrl->GetItemText(hItemRoot);
				if (strItemText.CompareNoCase(pszNext) == 0)
				{
					// Found it
					// We know this was successful - expand at this new root
					m_pTreeCtrl->Select(hItemRoot, TVGN_CARET);

					// Ensure that we are expanded
					uState = m_pTreeCtrl->GetItemState(hItemRoot, TVIS_EXPANDEDONCE);
					if (!(uState & TVIS_EXPANDEDONCE))
					{
						InitTreeNode(hItemRoot, (XTP_TVITEMDATA*)m_pTreeCtrl->GetItemData(hItemRoot));
						m_pTreeCtrl->SetItemState(hItemRoot, TVIS_EXPANDEDONCE, TVIS_EXPANDEDONCE);
					}
					pszNext = STRTOK_S(NULL, _T("\\/"), &lpszContext);
					if (pszNext)
					{
						// Move down a level
						hItemRoot = m_pTreeCtrl->GetChildItem(hItemRoot);
					}
				}
				else
				{
					hItemRoot = m_pTreeCtrl->GetNextSiblingItem(hItemRoot);
				}
			}
		}
	}

	if (bLock)
	{
		m_pTreeCtrl->UnlockWindowUpdate();
	}

	m_bTunneling = false;

	// make sure list gets updated.
	SelectionChanged(m_pTreeCtrl->GetSelectedItem(), strFindPath);
	return bFound;
}

BOOL CXTPShellTreeBase::GetFolderItemPath(HTREEITEM hItem, CString& strFolderPath)
{
	if (hItem == NULL)
		return FALSE;

	XTP_TVITEMDATA* lptvid = (XTP_TVITEMDATA*)m_pTreeCtrl->GetItemData(hItem);
	if (lptvid && lptvid->lpi)
	{
		if (lptvid->lpsfParent)
		{
			// Determine what type of object we have.
			ULONG ulAttrs = SFGAO_FILESYSTEM;
			lptvid->lpsfParent->GetAttributesOf(1, (const struct _ITEMIDLIST **)&lptvid->lpi, &ulAttrs);

			// if the object is a file, folder or root set the path.
			if (ulAttrs & SFGAO_FILESYSTEM)
			{
				TCHAR szFolderPath[_MAX_PATH];
				if (::SHGetPathFromIDList(lptvid->lpifq, szFolderPath))
				{
					strFolderPath = szFolderPath;
					return TRUE;
				}
			}
		}
		else
		{
			// Determine what type of object we have.
			SHFILEINFO sfi;
			::ZeroMemory(&sfi, sizeof(SHFILEINFO));

			::SHGetFileInfo((TCHAR*)lptvid->lpi, 0, &sfi,
				sizeof(SHFILEINFO), SHGFI_PIDL | SHGFI_ATTRIBUTES);

			// if the object is a file, folder or root set the path.
			if (sfi.dwAttributes & SFGAO_FILESYSTEM)
			{
				TCHAR szFolderPath[_MAX_PATH];
				if (::SHGetPathFromIDList(lptvid->lpi, szFolderPath))
				{
					strFolderPath = szFolderPath;
					return TRUE;
				}
			}
		}
	}

	return FALSE;
}

CString CXTPShellTreeBase::PathFindNextComponent(const CString& pszPath)
{
	// Find the path delimiter
	int nIndex = pszPath.Find(_T('\\'));

	if (nIndex == -1)
		return _T("");

	return pszPath.Mid(nIndex + 1);
}

void CXTPShellTreeBase::PopulateTree(LPCTSTR lpszPath)
{
	CString strFolder = lpszPath;
	CString strNextFolder;
	CString strPath;

	CShellMalloc lpMalloc;
	if (!lpMalloc)
		return;

	// Get a pointer to the desktop folder.
	LPSHELLFOLDER lpSF = NULL;
	if (FAILED(::SHGetDesktopFolder(&lpSF)))
		return;

	LPITEMIDLIST lpIDL = NULL;

	// turn off redraw and remove all tree items.
	m_pTreeCtrl->SetRedraw(FALSE);
	m_pTreeCtrl->DeleteAllItems();

	do
	{
		// Get the Next Component
		strNextFolder = PathFindNextComponent(strFolder);
		if (!strNextFolder.IsEmpty())
		{
			strPath = strFolder.Left(strFolder.GetLength() -
				strNextFolder.GetLength());
		}
		else
		{
			strPath = strFolder;
			strNextFolder.Empty();
		}

		// Get ShellFolder Pidl
		ULONG eaten;
		if (FAILED(lpSF->ParseDisplayName(NULL, NULL, (LPOLESTR)XTP_CT2CW(strPath),
			&eaten, &lpIDL, NULL)))
		{
			break;
		}

		LPSHELLFOLDER lpSF2 = NULL;
		if (FAILED(lpSF->BindToObject(lpIDL, 0, IID_IShellFolder, (LPVOID*)&lpSF2)))
		{
			break;
		}

		lpMalloc.Free(lpIDL);

		// Release the Parent Folder pointer.
		lpSF->Release();

		// Change Folder Info
		lpSF = lpSF2;
		strFolder = strNextFolder;
	}
	while (!strNextFolder.IsEmpty());

	// get the base folders item ide list.
	lpIDL = IDLFromPath(lpszPath);

	SHFILEINFO fileInfo;
	::ZeroMemory(&fileInfo, sizeof(fileInfo));

	::SHGetFileInfo((LPCTSTR)lpIDL, NULL, &fileInfo, sizeof(fileInfo),
		SHGFI_PIDL | SHGFI_ATTRIBUTES | SHGFI_DISPLAYNAME);

	TV_ITEM  tvi;
	tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;

	// Allocate memory for ITEMDATA struct
	XTP_TVITEMDATA* lptvid = new XTP_TVITEMDATA;
	if (lptvid != NULL)
	{
		HTREEITEM hItem = TVI_ROOT;

		// get the normal and selected icons for the path.
		GetNormalAndSelectedIcons(lpIDL, &tvi);

		// Now, make a copy of the ITEMIDLIST and store the parent folders SF.
		lptvid->lpi = DuplicateItem(lpMalloc, lpIDL);
		lptvid->lpsfParent = NULL;
		lptvid->lpifq = ConcatPidls(lpMalloc, NULL, lpIDL);

		TCHAR szBuff[MAX_PATH];
		STRCPY_S(szBuff, MAX_PATH, fileInfo.szDisplayName);

		tvi.lParam = (LPARAM)lptvid;
		tvi.pszText = szBuff;
		tvi.cchTextMax = MAX_PATH;

		// Populate the TreeView Insert Struct
		// The item is the one filled above.
		// Insert it after the last item inserted at this level.
		// And indicate this is a root entry.
		TV_INSERTSTRUCT tvins;
		tvins.item = tvi;
		tvins.hInsertAfter = hItem;
		tvins.hParent = hItem;

		// Add the item to the tree
		hItem = m_pTreeCtrl->InsertItem(&tvins);

		// insert child items.
		InitTreeViewItems(lpSF, lpIDL, hItem);

		// Sort the items in the tree view
		SortChildren(TVI_ROOT);

		// expand parent.
		m_pTreeCtrl->Expand(hItem, TVE_EXPAND);
	}

	// turn on redraw and refresh tree.
	m_pTreeCtrl->SetRedraw(TRUE);
	m_pTreeCtrl->RedrawWindow();

	lpMalloc.Free(lpIDL);

	if (lpSF)
	{
		lpSF->Release();
	}
}

void CXTPShellTreeBase::AssociateCombo(CWnd* pWnd)
{
	ASSERT_VALID(pWnd); // must be a valid window.

	if (::IsWindow(pWnd->GetSafeHwnd()))
	{
		m_pComboBox = pWnd;

		if (m_pComboBox->IsKindOf(RUNTIME_CLASS(CComboBoxEx)))
		{
			HIMAGELIST hImageList = GetSystemImageList(SHGFI_SMALLICON);

			if (hImageList != NULL)
			{
				CComboBoxEx* pComboBoxEx = DYNAMIC_DOWNCAST(CComboBoxEx, m_pComboBox);
				ASSERT_VALID(pComboBoxEx);
				pComboBoxEx->SetImageList(CImageList::FromHandle(hImageList));
			}
		}
	}
}

BOOL CXTPShellTreeBase::OnEraseBkgnd(CDC* /*pDC*/)
{
	return TRUE;
}

void CXTPShellTreeBase::OnPaint()
{
	CPaintDC dc(m_pTreeCtrl);
	DoPaint(dc, !m_bExplorerStyle);
}

void CXTPShellTreeBase::BeginDrag(NM_TREEVIEW* pNMTreeView)
{
	// Long pointer to ListView item data
	XTP_TVITEMDATA*  lplvid = (XTP_TVITEMDATA*)m_pTreeCtrl->GetItemData(pNMTreeView->itemNew.hItem);
	ASSERT(lplvid);

	if (lplvid && lplvid->lpsfParent)
	{
		LPDATAOBJECT lpdo;

		HRESULT hResult = lplvid->lpsfParent->GetUIObjectOf(AfxGetMainWnd()->m_hWnd, 1,
			(const struct _ITEMIDLIST**)&lplvid->lpi, IID_IDataObject, 0, (LPVOID*)&lpdo);

		if (SUCCEEDED(hResult))
		{
			LPDROPSOURCE lpds = new CXTPDropSource();
			ASSERT(lpds != NULL);

			DWORD dwEffect;
			::DoDragDrop(lpdo, lpds,
				DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK, &dwEffect);

			lpdo->Release();
			lpds->Release();
		}
	}
}