// PNDirTree.cpp : implementation file
//

#include "stdafx.h"
#include "PNDirTree.h"

#include <stdlib.h>

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

/////////////////////////////////////////////////////////////////////////////
// CPNDirTree
CImageList CPNDirTree::m_ImageList;

int CPNDirTree::m_ObjCount;

CPNDirTree::CPNDirTree()
{
	m_ShowFiles = false;
	m_AutoRefresh = true;
	m_ObjCount++;
}

CPNDirTree::~CPNDirTree()
{
}


BEGIN_MESSAGE_MAP(CPNDirTree, CTreeCtrl)
	//{{AFX_MSG_MAP(CPNDirTree)
	ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, OnItemexpanding)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CPNDirTree message handlers

void CPNDirTree::StartPath( const char* Start )
{
	if( !Start )
		LoadDriveLetters();
	else
	{
		DeleteAllItems();
		m_StartPath = Start;
		AddPath( Start );
	}
}

void CPNDirTree::LoadDriveLetters( const char* OneDrive )
{
	// Initialize control with list of valid drive letters
	if( IsWindowVisible() )
		SetRedraw( FALSE );
	DeleteAllItems();
	char  Drives[128], *CurDrive;
	CWaitCursor Wait;

	if( OneDrive )
	{
		AddPath( OneDrive );
		return;
	}

	::GetLogicalDriveStrings( sizeof(Drives), Drives );

	CurDrive = Drives;
	while( *CurDrive )
	{
		AddPath( CurDrive );
		CurDrive += strlen( CurDrive ) + 1;
	}
	if( IsWindowVisible() )
		SetRedraw( TRUE );
}

BOOL CPNDirTree::HasSubItem(const char *Folder )
{
	// Determine if a folder has sub-items (files or folders)
	CFileFind Find;
	CString   Tmp = Folder;
	BOOL      Found;

	SlashPath( Tmp, true );
	Tmp += "*.*";
	
	Found = Find.FindFile( Tmp );

	while ( Found )
	{
		Found = Find.FindNextFile();

		if( Find.IsHidden() )
			continue;
		
		if ( Find.IsDirectory() )
		{
			if( !Find.IsDots() )
				return TRUE;
		}

		if ( !Find.IsDirectory() && m_ShowFiles )
			return TRUE;
		
	}
	return FALSE;
}

void CPNDirTree::PreSubclassWindow() 
{
	// Initialize the control
	SHFILEINFO ShFileInfo;
	HIMAGELIST hImageList = NULL;

	if( !m_ImageList.GetSafeHandle() )
	{
		hImageList = (HIMAGELIST)SHGetFileInfo( "C:\\", 0, &ShFileInfo, sizeof( ShFileInfo ),
			 SHGFI_SYSICONINDEX | SHGFI_SMALLICON );
		CImageList Tmp;
		m_ImageList.Attach( ImageList_Duplicate( hImageList ) );
	}	
   	SetImageList( &m_ImageList, TVSIL_NORMAL );

	m_HasCheck = (::GetWindowLong( m_hWnd, GWL_STYLE ) &TVS_CHECKBOXES ) ? true : false; 
	
	LoadDriveLetters();
	CTreeCtrl::PreSubclassWindow();
}

HTREEITEM CPNDirTree::AddPath(const char *Path, HTREEITEM hNode, HTREEITEM hInsertAfter )
{
	// Adds path to tree, and gets the icon for the file specified.
	SHFILEINFO shFileInfo;
	int iIcon, iIconSel;
    CString Tmp = Path;
	HTREEITEM Ret;
	HIMAGELIST hImageList;
    
	SlashPath( Tmp, true );

	// Get Icons
	hImageList = (HIMAGELIST)SHGetFileInfo( Tmp, 0, &shFileInfo, sizeof( shFileInfo ), 
		SHGFI_SYSICONINDEX | SHGFI_ICON | SHGFI_SMALLICON );
	iIcon = shFileInfo.iIcon;
	SHGetFileInfo( Tmp, 0, &shFileInfo, sizeof( shFileInfo ), 
		SHGFI_SYSICONINDEX | SHGFI_OPENICON | SHGFI_SMALLICON );
	iIconSel = shFileInfo.iIcon;

	m_ImageList.DeleteImageList();
	m_ImageList.Attach( ImageList_Duplicate(hImageList) );

	// Perform insertion
	if ( hNode == TVI_ROOT )
		Ret =  InsertItem( Path, iIcon, iIconSel, hNode, hInsertAfter );
	else
		Ret = InsertItem( GetSubPath( Path ), iIcon, iIconSel, hNode, hInsertAfter );

	if( HasSubItem( Path ) )
		InsertItem( "", 0, 0, Ret);

	return( Ret );
}


LPCSTR CPNDirTree::GetSubPath(const char *Str)
{
	static CString Path;
	int i;

	Path = Str;
	SlashPath( Path, false );
	i = Path.ReverseFind( '\\' );
	if ( i != -1 )
	    Path = Path.Mid( i + 1);

	return (LPCTSTR)Path;
}

void CPNDirTree::OnItemexpanding(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;

	// When an item is expanding, make sure its sub-items are loaded:
	ExpandItem( pNMTreeView->itemNew.hItem, pNMTreeView->itemNew.state & TVIS_EXPANDED?false:true );

	*pResult = 0;
}


void CPNDirTree::RefreshItem(HTREEITEM hNode)
{
	// Known Bug: If you:
	// 1. Expand a folder
	// 2. Collapse the folder
	// 3. Delete a File or folder in that folder
	// 4. Recreate a Folder of file for the deleted File or Folder (respectively)
	// 5. Expand the folder
	// Then, the icon for the file is no longer correct (But, it will still have a '+' for subitems)

	CStringList CurFiles;
	CStringArray NewFiles;
	CString S, S2;
	BOOL Found, IsFolder, IsCurFolder;
	HTREEITEM hInsert, hOld, hChild = GetChildItem( hNode );


	BuildFileListEx( NewFiles, GetFullPath( hNode ), false );

	// Test current items in tree, and see if file is still there
	while ( hChild )
	{
		S = GetItemText( hChild );
		Found = FALSE;
		for( int i=0; !Found && i < NewFiles.GetSize(); i++ )
		{
			S2 = NewFiles[i];
			IsFolder = (S2[0] == 'A' );
			S2.Delete( 0, 1 );

			if( S.CompareNoCase( S2 ) == 0 )
				Found = TRUE;
		}

		hOld = hChild;
		hChild = GetNextSiblingItem( hChild );
		if( !Found )
			DeleteItem( hOld );
		else
			CurFiles.AddTail( GetItemText( hOld ) );
	}

	//Now, test to see for newly added items
	for( int i=0; i < NewFiles.GetSize(); i++ )
	{
		S = NewFiles[i];
		IsFolder = (S[0] == 'A' );
		S.Delete( 0, 1 );
		hChild = GetChildItem( hNode );
		Found = FALSE;
		hInsert = hChild;
		while ( hChild && !Found )
		{
			S2 = GetItemText( hChild );
			IsCurFolder = IsFileFolder( GetFullPath( hChild ) );
			// What if its the first file, after the folders?
			if( S2.CompareNoCase( S ) < 0 || !IsFolder && IsCurFolder )
			{	
				if( IsFolder ==  IsCurFolder || !IsFolder && IsCurFolder ) 
					hInsert = hChild;
			}

			if( S.CompareNoCase( S2 ) == 0 )
			{
				Found = TRUE;
				S = GetFullPath( hChild );
				if( HasSubItem( S ) )
				{
					// Make sure children are there
					if( !GetChildItem( hChild ) )
						InsertItem( "", hChild ); // New sub items
				}
				else
				{
					// Make sure children no longer there
					HTREEITEM hTmp = GetChildItem( hChild );
					POSITION Pos;
					while( hTmp ) 
					{
						DeleteItem( hTmp );
						Pos = m_Expanded.Find( hTmp );
						if( Pos )
							m_Expanded.RemoveAt( Pos );
						hTmp = GetChildItem( hChild );
					}
				}
			}
			else
				hChild = GetNextSiblingItem( hChild );
		}
		if( !Found ) // It's New
		{
			S2 = GetFullPath( hNode );
			SlashPath( S2, true );
			S = S2+S;
			hInsert = AddPath( S, hNode, hInsert );
		}
	}


}

void CPNDirTree::ExpandAll(HTREEITEM hNode )
{
	// Expands (and populates if needed) all child nodes of a node
	static int Running;

	if( !hNode )
		return;

	if( !Running++ )
	{
		ExpandItem( hNode, true, true );
		Expand( hNode, TVE_EXPAND );
	}

	HTREEITEM hChild = GetChildItem( hNode );
	while( hChild )
	{
		Expand( hChild, TVE_EXPAND );
		ExpandAll( hChild );
		hChild = GetNextSiblingItem( hChild );
	}
	
	Running--;
}

void CPNDirTree::ExpandItem(HTREEITEM hNode, bool ExpandIt, bool Recursive )
{
	CString Path;
	
	// Expands an item.  Can be called to do so recursively
	if ( ExpandIt )
	{
		if( m_HasCheck )
		{
			if( m_Expanded.Find( hNode ) ) // Already populated
			{
				if( m_AutoRefresh ) // Refresh anyway?
					RefreshItem( hNode );
				return;
			}
			m_Expanded.AddTail( hNode );
		}
		
		HTREEITEM hChild = GetChildItem( hNode );
		while ( hChild )
		{

			DeleteItem( hChild );
			hChild = GetChildItem( hChild );
		}
        
		Path = GetFullPath( hNode );
		LoadPath( hNode, Path );
		if( Recursive )
		{
			HTREEITEM hChild = GetChildItem( hNode );
			while( hChild )
			{
				ExpandItem( hChild, ExpandIt, Recursive );
				hChild = GetNextSiblingItem( hChild );
			}

		}
	}
}

LPCSTR CPNDirTree::GetFullPath(HTREEITEM hNode)
{
	// Gets the full path for a Node
	static CString Path;
	CString Tmp;
	
	Path.Empty();
	while ( hNode )
	{
		Tmp  = GetItemText( hNode );
		SlashPath( Tmp, true );
		Path = Tmp + Path;
		hNode = GetParentItem( hNode );
	}
	SlashPath( Path, false );
    return( Path );

}

int CStringCmp( const void* A, const void* B )
{
	// Comparison for two CStrings, for qsort call below
	return( ((CString*)A)->CompareNoCase( *(CString*)B ) );
}

int CPNDirTree::BuildFileList( CStringArray &Dest, const char *Path, bool FullPath )
{
	// Builds a list of file names for a path.
	int Ret = BuildFileListEx( Dest, Path, FullPath );
	for( int i=0; i < Ret; i++ )
		Dest[i].Delete( 0, 1 );
	return( Ret );
}

int CPNDirTree::BuildFileListEx( CStringArray &Dest, const char *Path, bool FullPath )
{
	// Builds a list of file names for a path.  This also sorts the list
	// and preceeds folder names with 'A' and file names with a 'B'.

	CFileFind Find;
	CString MyPath = Path;
	BOOL Found;
	CWaitCursor Wait;
	
	SlashPath( MyPath, true );
	MyPath += "*.*";

	Found = Find.FindFile( MyPath );
	while ( Found )
	{
		Found = Find.FindNextFile();
		if ( Find.IsDirectory() && !Find.IsDots() )
			Dest.Add( "A" + (FullPath?Find.GetFilePath():Find.GetFileName()));
		if ( m_ShowFiles && !Find.IsDirectory() )
			Dest.Add( "B" + (FullPath?Find.GetFilePath():Find.GetFileName()));
	}

	qsort( (void*)Dest.GetData(), Dest.GetSize(), sizeof(CString), CStringCmp );
	return( Dest.GetSize() );
}

void CPNDirTree::LoadPath(HTREEITEM hNode, const char *Path)
{
	int i;
	CString Tmp;
	BOOL IsFolder;
	CStringArray Files;
	CWaitCursor Wait;
	
	// Popupates a single node with its file-list information
	SetRedraw( FALSE );
	BuildFileListEx( Files, Path );
	
	for( i = 0; i < Files.GetSize(); i++ )
	{
		Tmp = Files[i];
		IsFolder = (Tmp[0] == 'A' );
		Tmp.Delete( 0, 1 );
		AddPath( Tmp, hNode );
	}

	SetRedraw( TRUE );

}

void CPNDirTree::SlashPath(CString &Dest, bool MustHave)
{
	// Insures that a path either has, or does not have, a slash at the end
	char Now='\0';

	if( !Dest.IsEmpty() )
		Now = Dest[Dest.GetLength()-1];
	if( MustHave && Now != '\\' )
		Dest += '\\';
	else if( !MustHave && Now == '\\' )
		Dest.TrimRight( '\\' );
}


void CPNDirTree::CheckSearch(CStringArray &Dest, HTREEITEM hNode)
{
	HTREEITEM hCurNode;
	
	// Builds a list of Checked items and stores then in a CStringArray
	// Search can start from a specific node
	if( hNode )
		do 
		{
			if( GetCheck( hNode ) )
				Dest.Add( GetFullPath( hNode  ) );
			if( (hCurNode = GetChildItem( hNode )) )
				CheckSearch( Dest, hCurNode );
			hNode = GetNextSiblingItem( hNode );
		} while ( hNode ); 

}


int CPNDirTree::GetAllChecked( CStringArray& Dest )
{
	// Builds a list of Checked items and stores then in a CStringArray
	Dest.RemoveAll();
	CheckSearch( Dest, GetChildItem(TVI_ROOT) );
	return( Dest.GetSize() );
}

int CPNDirTree::GetAllChecked( CListBox& List )
{
	// Builds a list of Checked items and stores then in a listbox

	CStringArray Dest;
	CheckSearch( Dest, GetChildItem(TVI_ROOT) );
	List.ResetContent();
	for( int i=0; i < Dest.GetSize(); i++ )
		List.AddString( Dest[i] );
	return( Dest.GetSize() );
}


BOOL CPNDirTree::IsFileFolder(const char *Path)
{
	// Determines if 'Path' is a file or folder.
	CFileFind Find;
	if( Find.FindFile( Path ) )
	{
		Find.FindNextFile();
		return( Find.IsDirectory()? TRUE : FALSE );
	}
	return( FALSE );	
}

BOOL CPNDirTree::IsFileFolder( HTREEITEM hItem)
{
	// Determines if path for a node is a file or a folder
	return( IsFileFolder( GetFullPath( hItem ) ) );
}

bool CPNDirTree::SetShowFiles(bool NewMode)
{
	// Sets the ShowFiles property to true or false.
	bool Ret = m_ShowFiles;
	m_ShowFiles = NewMode;
	LoadDriveLetters();
	return( Ret );
}

void CPNDirTree::CheckAll(BOOL Checked, HTREEITEM hNode)
{
	HTREEITEM hCurNode;
	
	// Checks all child nodes from a node.  Populates node items
	// as needed
	if( !hNode )
	{
		hNode = GetSelectedItem();
		if( !hNode )
			return;
		SetCheck( hNode, Checked );
		ExpandItem( hNode, true, true );
		hNode = GetChildItem( hNode );
	}
	if( hNode )
	{
		do 
		{
			SetCheck( hNode, Checked );
			if( (hCurNode = GetChildItem( hNode )) )
				CheckAll( Checked, hCurNode );
			hNode = GetNextSiblingItem( hNode );
		} while ( hNode ); 
	}
}

void CPNDirTree::Reset()
{
	if( m_StartPath.IsEmpty() )
		LoadDriveLetters();
	else
		StartPath( m_StartPath );
}

void CPNDirTree::SetCheckStyle(bool Mode)
{
	// Bummer: You can't just change the Checkbox style
	// for a tree, you need to destroy and re-create it.

	CWnd* pParent = GetParent();
	int ID = GetDlgCtrlID();
	int ExStyle = ::GetWindowLong( m_hWnd, GWL_EXSTYLE );

	int Style = ::GetWindowLong( m_hWnd, GWL_STYLE );
	if( Mode )
		Style = Style | TVS_CHECKBOXES;
	else
		Style = Style & (~TVS_CHECKBOXES);

	m_HasCheck = Mode;
	CRect Rect;
	GetWindowRect( &Rect );
	GetParent()->ScreenToClient( &Rect );
	DestroyWindow();
	CreateEx( ExStyle, "systreeview32", "TreeView", Style, Rect, pParent, ID );
   	SetImageList( &m_ImageList, TVSIL_NORMAL );
	ShowWindow( SW_NORMAL );
	Reset();
}
