Click here to Skip to main content
15,885,912 members
Articles / Desktop Programming / MFC

ClassLib, A C++ class library

Rate me:
Please Sign up or sign in to vote.
4.80/5 (32 votes)
25 May 2005CPOL8 min read 399.6K   11.5K   141  
C++ class library.
//
// filedirbrowser.cpp
//
// (C) Copyright 2002-2003 Jan van den Baard.
//     All Rights Reserved.
//

#include "filedirbrowser.h"
#include "filedirtree.h"
#include "../../tools/multimonitor.h"
#include "../../tools/theming.h"
#include "../../gdi/getdc.h"
#include "../../gdi/windowdc.h"
#include "../../coords/rect.h"
#include "../../coords/size.h"

// Just in case...
#ifndef WM_MOUSEWHEEL
#define WM_MOUSEWHEEL 0x020A
#endif

// A popup version of the "ClsFileDirTree" control.
// Handles positioning and destruction itself.
class ClsFileDirTreePopup : public ClsFileDirTree
{
	_NO_COPY( ClsFileDirTreePopup );
public:
	// Constructor. Create and position the control.
	ClsFileDirTreePopup( ClsWindow *pParent, int nMaxLines, LPCTSTR pszLoadingText, LPCTSTR pszPath, LPCTSTR pszFilters, COLORREF crLoadingTextColor, BOOL bShowFiles )
	{
		_ASSERT_VALID( pParent ); // Must be valid.

		// Initialize data.
		m_pParent	   = pParent;
		m_bAbove	   = FALSE;
		m_hTheme	   = NULL;

		// Close any other popups.
		ClosePopups();

		// Create the control.
		ClsRect rc( 0, 0, 0, 0 );
		if ( Create( pParent, rc, 0, WS_POPUP | WS_BORDER | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_DISABLEDRAGDROP | TVS_FULLROWSELECT ))
		{
			// Setup data...
			LoadingTextColor() = crLoadingTextColor;
			LoadingText()	   = pszLoadingText;
			FileFilter()	   = pszFilters;
			ShowFiles( bShowFiles );

			// Get rectangle of the parent or owner control.
			ClsRect parent = pParent->GetWindowRect();

			// Obtain the monitor rectangle which
			// intersects the most with the parent control.
			ClsRect screen;
			ClsMultiMon mon;
			int nMonitor;
			mon.MonitorNumberFromWindow( pParent->GetSafeHWND(), MONITOR_DEFAULTTONEAREST, nMonitor );
			mon.GetMonitorRect( nMonitor, screen, TRUE );

			// Setup the tree defaults.
			SetupTree( pszPath );

			// Select the path in the tree currently in the
			// parent control.
			ClsString pos( *pParent );
			if ( pos.GetStringLength())
				SelectPath( pos );

			// Get selection. If there is none and there is an input path
			// we select and expand the root item.
			HTREEITEM hSel = GetSelection();
			if ( hSel == NULL && pszPath && _tcslen( pszPath ))
			{
				hSel = GetRoot();
				Expand( hSel, TVE_EXPAND );
				SelectItem( hSel );
			}

			// Compute how many lines the popup will show.
			// This will be nMaxLines or the amount
			// expanded items which ever is the smallest value.
			nMaxLines = CountExpandedItems( nMaxLines );

			// Create the rectangle of the popup.
			ClsRect popup;
			popup.Left()   = parent.Left();
			popup.Top()    = parent.Bottom();
			popup.Right()  = parent.Right();
			popup.Bottom() = parent.Bottom() + ( nMaxLines * GetItemHeight()) + ( 2 * ::GetSystemMetrics( SM_CYBORDER ));

			// Store the popup it's minimum size.
			m_szMinSize = popup.Size();

			// Create cursors.
			m_hCursors[ 0 ] = ::LoadCursor( NULL, IDC_ARROW );
			m_hCursors[ 1 ] = ::LoadCursor( NULL, IDC_SIZENWSE );

			// Make sure the popup remains on the screen completely.
			if ( popup.Bottom() > screen.Bottom()) { m_bAbove = TRUE; popup.Offset( 0, -( popup.Height() + parent.Height())); }
			if ( popup.Left() < screen.Left()) popup.Offset( screen.Left() - popup.Left(), 0 );
			else if ( popup.Right() > screen.Right()) popup.Offset( -( popup.Right() - screen.Right()), 0 );

			// Show the popup.
			EnsureVisible( hSel );
			MoveWindow( popup, FALSE );
			ShowWindow( SW_SHOW );
		}
	}

	// Destructor.
	virtual ~ClsFileDirTreePopup()
	{
		// Destroy theme.
		if ( m_hTheme ) ThemingAPI.CloseThemeData( m_hTheme );

		// Destroy the cursors.
		if ( m_hCursors[ 0 ] ) ::DestroyCursor( m_hCursors[ 0 ] );
		if ( m_hCursors[ 1 ] ) ::DestroyCursor( m_hCursors[ 1 ] );
	}

protected:
	// Count the number of expanded item upto nMaxLines.
	int CountExpandedItems( int nMaxLines, HTREEITEM hItem = NULL )
	{
		int nItems = 0;

		// Start at the root item if there
		// is no input item.
		if ( ! hItem ) hItem = GetRoot();

		// Iterate items.
		TVITEM tvi;
		do
		{
			// Get item information.
			tvi.mask      = TVIF_STATE;
			tvi.hItem     = hItem;
			tvi.stateMask = TVIS_EXPANDED;
			if ( GetItem( tvi ))
			{
				// Is this one expanded?
				if ( tvi.state & TVIS_EXPANDED )
					// Recursivly count it's children.
					nItems += CountExpandedItems( nMaxLines, GetChild( hItem ));
				else
					// One more...
					nItems++;

				// Did we reach the limit?
				if ( nItems >= nMaxLines )
					return nMaxLines;
			}
		  // Next...
		} while (( hItem = GetNextSibling( hItem )) != NULL );

		// Return the item count.
		return max( 10, nItems );
	}

	// Adjust creation parameters.
	virtual BOOL PreCreateWindow( LPCREATESTRUCT pCS )
	{
		// Adjust EX flags and pass to the base class.
		pCS->dwExStyle |= WS_EX_TOOLWINDOW;
		return ClsFileDirTree::PreCreateWindow( pCS );
	}

	// Paint the sizegrip.
	void PaintSizeGrip( ClsDC *pDC )
	{
		// Theming?
		if ( ThemingAPI.IsThemingEnabled() && ! m_hTheme )
			m_hTheme = ThemingAPI.OpenThemeData( GetSafeHWND(), L"SCROLLBAR" );

		// Get the clipping rectangle.
		GetClientRect( m_rcSizeGrip );

		// Adjust to fit the gripper.
		m_rcSizeGrip.Top() = m_rcSizeGrip.Bottom() - ::GetSystemMetrics( SM_CYHSCROLL );
		m_rcSizeGrip.Left() = m_rcSizeGrip.Right() - ::GetSystemMetrics( SM_CXVSCROLL );
		m_rcSizeGrip.Offset( -1, -1 );

		// Render the gripper.
		if ( ! m_hTheme )
		{
			// Draw the sizing grip.
			pDC->DrawFrameControl( m_rcSizeGrip, DFC_SCROLL, DFCS_SCROLLSIZEGRIP );

			// Determine the control it's background
			// color.
			COLORREF crBk = GetBkColor();
			if ( crBk == CLR_NONE ) crBk = ::GetSysColor( COLOR_WINDOW );

			// If the top-left pixel of the sizegrip rectangle 
			// differs from the background color we floodfill it with
			// the background color.
			if ( pDC->GetPixel( m_rcSizeGrip.TopLeft()) != crBk )
				pDC->FloodFill( m_rcSizeGrip.Left(), m_rcSizeGrip.Top(), crBk );
		}
		else
			// Themed...
			ThemingAPI.DrawThemeBackground( m_hTheme, *pDC, SBP_SIZEBOX, SZB_RIGHTALIGN, m_rcSizeGrip, NULL );

		// Convert to screen coordinates (for the hittest).
		ClientToScreen( m_rcSizeGrip );
	}

	// WM_SIZE handler. This simply calls the baseclass
	// method and then invalidate the client area so
	// that the control is re-rendered.
	virtual LRESULT OnSize( UINT nSizeType, int nWidth, int nHeight )
	{
		LRESULT rs = ClsFileDirTree::OnSize( nSizeType, nWidth, nHeight );
		Invalidate();
		return rs;
	}

	// WM_PAINT override. Render the control and, when that
	// is done, the sizegrip.
	virtual LRESULT OnPaint( ClsDC *pDC )
	{
		// First the baseclass.
		LRESULT rs = ClsFileDirTree::OnPaint( pDC );

		// We only render the sizegrip when the
		// input DC is not provided.
		if ( ! pDC )
		{
			// Obtain the window DC.
			ClsWindowDC dc( this );
			PaintSizeGrip( &dc );
		}

		// Return the result.
		return rs;
	}

	// Window procedure.
	virtual LRESULT WindowProc( UINT uMsg, WPARAM wParam, LPARAM lParam )
	{
		// What's up?
		switch ( uMsg )
		{
			// A bit expensive but it ensures the Gfx is
			// updated correctly...
			case	WM_MOUSEWHEEL:
			case	WM_HSCROLL:
			case	WM_VSCROLL:
			{
				LRESULT rc;
				rc = ClsFileDirTree::WindowProc( uMsg, wParam, lParam );
				Invalidate();
				return rc;
			}

			// Setup the correct cursor.
			case	WM_SETCURSOR:
			{
				HCURSOR hCursor = NULL;
				switch ( LOWORD( lParam ))
				{
					case	HTCLIENT:
						hCursor = m_hCursors[ 0 ];
						break;

					case	HTBOTTOMRIGHT:
						hCursor = m_hCursors[ 1 ];
						break;
				}

				// If a valid cursor was chosen, set it.
				if ( hCursor )
				{
					SetCursor( hCursor );
					return TRUE;
				}

				// Default handling...
				break;
			}

			case	WM_GETMINMAXINFO:
			{
				// Fill in the minimum size of the control.
				LPMINMAXINFO pmmi = ( LPMINMAXINFO )lParam;
				pmmi->ptMinTrackSize.x = m_szMinSize.CX();
				pmmi->ptMinTrackSize.y = m_szMinSize.CY();
				return 0;
			}

			case	WM_NCHITTEST:
				{
					// Is the cursor inside the size grip?
					if ( m_rcSizeGrip.PtInRect( ClsPoint( LOWORD( lParam ), HIWORD( lParam ))))
						// Set the result to HTBOTTOMRIGHT.
						return HTBOTTOMRIGHT;
				}
				break;

			case	WM_THEMECHANGED:
				// Theming API available?
				if ( ThemingAPI.IsThemingEnabled())
				{
					// Destroy old theme and load the new one.
					if ( m_hTheme ) ThemingAPI.CloseThemeData( m_hTheme );
					m_hTheme = ThemingAPI.OpenThemeData( GetSafeHWND(), L"SCROLLBAR" );
				}
				break;

			case	WM_CREATE:
				m_bIsPopup = TRUE;
				break;

			case	WM_RBUTTONDOWN:
				return 0;

			case	WM_LBUTTONDOWN:
			{
				// Copy mouse position in the hit-test
				// structure.
				TVHITTESTINFO hinf;
				hinf.pt.x = ( int )( SHORT )LOWORD( lParam );
				hinf.pt.y = ( int )( SHORT )HIWORD( lParam );

				// See on what the mouse is located.
				HitTest( hinf );

				// Was the click on the item label?
				if ( hinf.flags & ( TVHT_ONITEMLABEL | TVHT_ONITEMICON ))
				{
					// Are we a file or not?
					BOOL bIsFile = TypeOfSelection() == STYPE_FILE ? TRUE : FALSE;

					// Obtain the full path of the treeview
					// selection.
					ClsString path;
					GetSelectedPath( path );

					// We only accept clicks on the right item type.
					if (( m_bShowFiles && bIsFile ) || ( ! m_bShowFiles && ! bIsFile ))
					{
						// Copy it into the parent.
						m_pParent->SetWindowText( path );

						// Kill the focus which will destroy the popup.
						::SetFocus( NULL );
						return 0;
					}
				}
				break;
			}

			case	WM_SHOWWINDOW:
				// Roll-up or down the popup or hide it whichever is requested.
				if ( AnimateWindow( 100, wParam ? ( m_bAbove ? AW_VER_NEGATIVE : AW_VER_POSITIVE ) : AW_HIDE ))
					return 0;
				break;

			case	WM_MOUSEMOVE:
			{
				// Copy mouse position in the hit-test
				// structure.
				TVHITTESTINFO hinf;
				hinf.pt.x = ( int )( SHORT )LOWORD( lParam );
				hinf.pt.y = ( int )( SHORT )HIWORD( lParam );

				// Select the item under the mouse.
				HTREEITEM hItem = HitTest( hinf );
				if ( hItem ) SelectItem( hItem );
				break;
			}

			case	WM_KILLFOCUS:
				// Delete the object.
				Destroy();
				return 0;

			case	WM_SETFOCUS:
				// Make the parent appear to be active in the
				// title bar.
				GetParent()->SendMessage( WM_NCACTIVATE, TRUE );
				break;
		}
		// Pass to the base class.
		return ClsWindow::WindowProc( uMsg, wParam, lParam );
	}

	void PostNcDestroy()
	{
		// Pass to the base class and delete
		// ourself.
		ClsTreeView::PostNcDestroy();
		delete this;
	}

	// Data.
	BOOL		m_bAbove;	// Open popup above or below the owner.
	ClsWindow      *m_pParent;	// Owner control.

	ClsSize		m_szMinSize;
	ClsRect		m_rcSizeGrip;
	HTHEME		m_hTheme;
	HCURSOR		m_hCursors[ 2 ];
};

// Constructor.
ClsFileDirBrowser::ClsFileDirBrowser()
{
	// Setup data.
	wcscpy( m_szTheme, L"COMBOBOX" );
	m_bShowFiles	     = TRUE;
	m_sLoadingText	     = _T( "Loading..." );
	m_sFilters	     = _T( "*.*" );
	m_crLoadingTextColor = CLR_DEFAULT;
}

void ClsFileDirBrowser::OnBrowserClicked()
{
	// Create/open popup.
	new ClsFileDirTreePopup( this, 15, m_sLoadingText, m_sBasePath,  m_sFilters, m_crLoadingTextColor, m_bShowFiles );
}

void ClsFileDirBrowser::GetButtonSize( ClsDC *pDC, ClsSize& sButtonSize )
{
	// Measure the size of the button.
	ClsRect	rcr;
	pDC->DrawText( _T( " " ), 3, rcr, DT_CALCRECT );
	sButtonSize.CX() = ::GetSystemMetrics( SM_CXVSCROLL );
	sButtonSize.CY() = rcr.Height() + ( 3 * ::GetSystemMetrics( SM_CYFRAME ));
}

void ClsFileDirBrowser::RenderButton( ClsDC *pDC, ClsRect rcClip )
{
	// Theming on?
	if ( ThemingAPI.IsThemingEnabled())
	{
		// Open the theme if it does not exist already.
		if ( ! m_hTheme ) m_hTheme = ThemingAPI.OpenThemeData( GetSafeHWND(), m_szTheme );
		if ( m_hTheme )
		{
			// Determine the rendering flags.
			DWORD dwFlags = CBXS_NORMAL;
			if ( IsWindowEnabled() == FALSE ) dwFlags = CBXS_DISABLED;
			else 
			{
				if ( m_bDown ) dwFlags = CBXS_PRESSED;
				else if ( m_bHasCapture && CursorOnButton()) dwFlags = CBXS_HOT;
			}

			// Clear rectangle and render the button.
			rcClip.Inflate( 1, 1 );
			ThemingAPI.DrawThemeBackground( m_hTheme, *pDC, CP_DROPDOWNBUTTON, dwFlags, rcClip, NULL );
			return;
		}
	}

	// Render the outer button.
	pDC->FillRect( rcClip, ( HBRUSH )( COLOR_BTNFACE + 1 ));
	pDC->DrawEdge( rcClip, BDR_RAISEDOUTER, BF_BOTTOMRIGHT );
	
	// Draw the button frame.
	pDC->DrawFrameControl( rcClip, DFC_SCROLL, ( ! IsWindowEnabled() ? DFCS_INACTIVE : 0 ) | DFCS_SCROLLDOWN | ( m_bDown ? DFCS_FLAT : 0 ));
}

// File dropped...
void ClsFileDirBrowser::OnFileDropped( HDROP hDrop )
{
	// Get the number of files dropped. If there is more than
	// one we use the first.
	if ( UINT nFiles = ::DragQueryFile( hDrop, 0xFFFFFFFF, NULL, 0 ))
	{
		// Get the name of the first file or directory. NOTE: This does
		// not resolve links or shortcuts.
		TCHAR szFile[ MAX_PATH ];
		for ( UINT i = 0; i < nFiles; i++ )
		{
			if ( ::DragQueryFile( hDrop, i, szFile, MAX_PATH ))
			{
				// Is it a directory?
				if (( ::GetFileAttributes( szFile ) & FILE_ATTRIBUTE_DIRECTORY ) == FILE_ATTRIBUTE_DIRECTORY )
				{
					// Are we showing files?
					if ( ! m_bShowFiles )
					{
						// Set the name.
						SetWindowText( szFile );
						return;
					}
				}
				else
				{
					// Are we showing files?
					if ( m_bShowFiles )
					{
						// Set the name.
						SetWindowText( szFile );
						return;
					}
				}
			}
		}
		// Get the name of the first drop and strip it's
		// file component if necessary.
		::DragQueryFile( hDrop, 0, szFile, MAX_PATH );
		
		// Show files?
		if ( ! m_bShowFiles )
		{
			// Strip the file part from the name.
			LPTSTR pFile = ::PathFindFileName( szFile );
			if ( pFile ) *pFile = _T( '\0' );
		}

		// Set the name in the control.
		SetWindowText( szFile );
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Netherlands Netherlands
I have been programming for a hobby since 1985. I have started programming on the C= 64. After that I migrated to the C= Amiga which I traded in for a PC back in 1997 I believe. Back in 2000 I decided to lose a hobby and start developing software for a living.

Currently I am working mainly in developing software for building security and access control systems.

Comments and Discussions