/////////////////////////////////////////////////////////////////////////////
/* 
DESCRIPTION:
	CFolderDialog  - Folder Selection Dialog Class
	Copyright(C) Armen Hakobyan, 2002 - 2005
	http://www.codeproject.com/dialog/cfolderdialog.asp

VERSION HISTORY:
	24 Mar 2002 - First release
	30 Mar 2003 - Some minor changes
				- Added missing in old Platform SDK new flag definitions  
				- Added support for both MFC 6.0 and 7.0
				- Added OnIUnknown handler for Windows XP folder filtration
				- Added SetExpanded and SetOKText and GetSelectedFolder functions
	24 May 2003 - Added OnSelChanged implementation
	14 Jul 2003 - Added custom filtration for Windows XP, thanks to Arik Poznanski
	29 Nov 2003 - Added SetRootFolder, thanks to Eckhard Schwabe ( and Jose Insa )
	02 Jan 2004 - Added GetRootFolder, uncomment if needed
	15 Feb 2005 - Small bug fix in DoModal, thanks to WindSeven
*/
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "FolderDlg.h"

/////////////////////////////////////////////////////////////////////////////

#ifndef BFFM_VALIDATEFAILED
	#ifndef UNICODE
		#define BFFM_VALIDATEFAILED		3
	#else
		#define BFFM_VALIDATEFAILED		4	
	#endif
#endif

#ifndef BFFM_IUNKNOWN
	#define BFFM_IUNKNOWN				5
#endif

/////////////////////////////////////////////////////////////////////////////
// CFolderDialog

IMPLEMENT_DYNAMIC( CFolderDialog, CDialog )

CFolderDialog::CFolderDialog( IN LPCTSTR pszTitle	 /*NULL*/,
							  IN LPCTSTR pszSelPath	 /*NULL*/,
							  IN CWnd*	 pWndParent	 /*NULL*/,
							  IN UINT	 uFlags		 /*BIF_RETURNONLYFSDIRS*/ )
			 : CCommonDialog( pWndParent )
			 , m_hWnd( NULL )
{
	::ZeroMemory( &m_bi, sizeof( BROWSEINFO ) );
	::ZeroMemory( m_szFolPath, MAX_PATH );
	::ZeroMemory( m_szSelPath, MAX_PATH );
	
	// Fill data

	m_bi.hwndOwner	= pWndParent->GetSafeHwnd();
	m_bi.pidlRoot	= NULL;
	m_bi.lpszTitle	= pszTitle;
	m_bi.ulFlags	= uFlags;
	m_bi.lpfn		= (BFFCALLBACK)&BrowseCallbackProc;
	m_bi.lParam		= (LPARAM)this;

	// The size of this buffer is assumed to be MAX_PATH bytes:

	m_bi.pszDisplayName = new TCHAR[ MAX_PATH ];
	ASSERT( m_bi.pszDisplayName != NULL );

	SAFE_ZEROMEMORY( 
		m_bi.pszDisplayName, ( MAX_PATH * sizeof( TCHAR ) )
	);

	if( pszSelPath )
		SetSelectedFolder( pszSelPath );
}

CFolderDialog::~CFolderDialog( VOID )
{	
	SAFE_COTASKMEMFREE( m_bi.pidlRoot );
	SAFE_DELETE2( m_bi.pszDisplayName );

	::ZeroMemory( &m_bi, sizeof( BROWSEINFO ) );
}

BEGIN_MESSAGE_MAP( CFolderDialog, CCommonDialog )
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CFolderDialog message handlers

// SetRootFolder By Jose Insa
// Microsoft knowledge Base Article
// ID Q132750: Convert a File Path to an ITEMIDLIST

BOOL CFolderDialog::SetRootFolder( IN LPCTSTR pszPath )
{
	ASSERT_VALID( this );

	if( !pszPath )
	{
		SAFE_COTASKMEMFREE( m_bi.pidlRoot );
		return TRUE;
	}

	ASSERT( AfxIsValidString( pszPath, MAX_PATH ) );

	HRESULT		  hResult	  = S_FALSE;
	IShellFolder* pDeskFolder = NULL;

	hResult = ::SHGetDesktopFolder( &pDeskFolder );
	if ( hResult == S_OK )
	{
		LPITEMIDLIST pidlRoot = NULL;
		LPTSTR       pszRoot  = const_cast< LPTSTR >( pszPath );

		// Convert the path to an ITEMIDLIST:
				
		USES_CONVERSION;

		hResult = pDeskFolder->ParseDisplayName(
			NULL, NULL, T2W( pszRoot ), NULL, &pidlRoot, NULL 
		);

		if( hResult == S_OK )
		{
			SAFE_COTASKMEMFREE( m_bi.pidlRoot );
			m_bi.pidlRoot = pidlRoot;
		}

		SAFE_RELEASE( pDeskFolder );		
	}
	
	return ( hResult == S_OK );
} 

// NOTE: pszPath buffer must be at least
// MAX_PATH characters in size:

BOOL CFolderDialog::GetRootFolder( IN OUT LPTSTR pszPath )
{
	ASSERT_VALID( this );
	ASSERT( AfxIsValidString( pszPath, MAX_PATH ) );

	return ::SHGetPathFromIDList( m_bi.pidlRoot, pszPath );
} 

BOOL CFolderDialog::SetSelectedFolder( IN LPCTSTR pszPath )
{
	ASSERT( AfxIsValidString( pszPath, MAX_PATH ) );
	return (BOOL)::lstrcpy( m_szSelPath, pszPath );
}

/////////////////////////////////////////////////////////////////////////////

#if ( _MFC_VER >= 0x0700 )
	INT_PTR CFolderDialog::DoModal( VOID )
#else
	INT		CFolderDialog::DoModal( VOID )
#endif
{
	ASSERT_VALID( this );	
	ASSERT( m_bi.lpfn != NULL );
	
	INT_PTR nRet = -1;
	m_bi.hwndOwner = PreModal();

	LPITEMIDLIST pItemIDList = ::SHBrowseForFolder( &m_bi );
	if( pItemIDList )
	{
		if( ::SHGetPathFromIDList( pItemIDList, m_szFolPath ) )
			nRet = IDOK;

		SAFE_COTASKMEMFREE( pItemIDList );
	}
	else
		nRet = IDCANCEL;


	PostModal();
	return ( nRet );
}

/////////////////////////////////////////////////////////////////////////////
// Overridables:

VOID CFolderDialog::OnInitialized( VOID )
{
	if( ::lstrlen( m_szSelPath ) > 0 )
		SetSelection( m_szSelPath );
}

VOID CFolderDialog::OnSelChanged( IN LPITEMIDLIST pItemIDList )
{
	if( m_bi.ulFlags & BIF_STATUSTEXT )
	{
		TCHAR szSelFol[ MAX_PATH ] = { 0 };
		if( ::SHGetPathFromIDList( pItemIDList, szSelFol ) )
			SetStatusText( szSelFol );
	}
}

INT CFolderDialog::OnValidateFailed( IN LPCTSTR /*pszPath*/ )
{	
	::MessageBeep( MB_ICONHAND );
	return 1; // Return 1 to leave dialog open, 0 - to end one
}

VOID CFolderDialog::OnIUnknown( IN IUnknown* /*pIUnknown*/ )
{
}

/////////////////////////////////////////////////////////////////////////////
// Callback function used with the SHBrowseForFolder function. 

INT CALLBACK CFolderDialog::BrowseCallbackProc( IN HWND   hWnd, 
												IN UINT   uMsg, 
												IN LPARAM lParam, 
												IN LPARAM lpData )
{
	CFolderDialog* pThis = (CFolderDialog*)lpData;
	ASSERT( pThis != NULL );

	INT nRet = 0;
	pThis->m_hWnd = hWnd;

	switch( uMsg )
	{
	case BFFM_INITIALIZED:
		pThis->OnInitialized();
		break;
	case BFFM_SELCHANGED:
		pThis->OnSelChanged( (LPITEMIDLIST)lParam );
		break;
	case BFFM_VALIDATEFAILED:
		nRet = pThis->OnValidateFailed( (LPCTSTR)lParam );
		break;
	case BFFM_IUNKNOWN:
		pThis->OnIUnknown( (IUnknown*)lParam );
		break;
	default:
		ASSERT( FALSE );
		break;
	}

	pThis->m_hWnd = NULL;
	return nRet;	
}

/////////////////////////////////////////////////////////////////////////////
// Commands, valid to call only from handlers

VOID CFolderDialog::SetExpanded( IN LPCTSTR pszPath )
{
	USES_CONVERSION;

	ASSERT( m_hWnd != NULL );
	ASSERT( AfxIsValidString( pszPath, MAX_PATH ) );

	::SendMessage( 
		m_hWnd, BFFM_SETEXPANDED, 
		(WPARAM)TRUE, (LPARAM)T2CW( pszPath )
	);
}

VOID CFolderDialog::SetOKText( IN LPCTSTR pszText )
{
	USES_CONVERSION;

	ASSERT( m_hWnd != NULL );
	
	::SendMessage( 
		m_hWnd, BFFM_SETOKTEXT, 
		(WPARAM)0, (LPARAM)T2CW( pszText )
	);
}

VOID CFolderDialog::EnableOK( IN BOOL bEnable /*TRUE*/ )
{
	ASSERT( m_hWnd != NULL ); 

	::SendMessage( 
		m_hWnd, BFFM_ENABLEOK, (WPARAM)bEnable, 0L
	);
}

VOID CFolderDialog::SetSelection( IN LPITEMIDLIST pItemIDList )
{
	ASSERT( m_hWnd != NULL );

	::SendMessage( 
		m_hWnd, BFFM_SETSELECTION, 
		(WPARAM)FALSE, (LPARAM)pItemIDList
	);
}

VOID CFolderDialog::SetSelection( IN LPCTSTR pszPath )
{
	ASSERT( m_hWnd != NULL );
	ASSERT( AfxIsValidString( pszPath, MAX_PATH ) );
	
	::SendMessage( 
		m_hWnd, BFFM_SETSELECTION, 
		(WPARAM)TRUE, (LPARAM)pszPath
	);
}

VOID CFolderDialog::SetStatusText( IN LPCTSTR pszText )
{
	ASSERT( m_hWnd != NULL );

	::SendMessage( 
		m_hWnd, BFFM_SETSTATUSTEXT, 
		(WPARAM)0, (LPARAM)pszText
	);
}

// Shell version 5.0 or later:

VOID CFolderDialog::SetExpanded( IN LPITEMIDLIST pItemIDList )
{
	ASSERT( m_hWnd != NULL ); 

	::SendMessage( 
		m_hWnd, BFFM_SETEXPANDED, 
		(WPARAM)FALSE, (LPARAM)pItemIDList
	);
}

/////////////////////////////////////////////////////////////////////////////