// CJTreeCtrl.cpp : implementation file
// Copyright © 1998-1999 CodeJock.com, All Rights Reserved.
// See ReadMe.txt for TERMS OF USE.
//
// Based on the CTreeView articles from http://www.codeguru.com/treeview
//
/////////////////////////////////////////////////////////////////////////////
/****************************************************************************
 *
 * $Date: 1999/11/30 21:45:49 $
 * $Revision: 1.1.1.3 $
 * $Archive: /CodeJock/CJLibrary/CJTreeCtrl.cpp $
 *
 * $History: CJTreeCtrl.cpp $
 * 
 * *****************  Version 6  *****************
 * User: Kirk Stowell Date: 10/30/99   Time: 1:54p
 * Updated in $/CodeJock/CJLibrary
 * Overrode OnEraseBkgnd(...) to handle flicker free drawing.
 * 
 * *****************  Version 5  *****************
 * User: Kirk Stowell Date: 10/25/99   Time: 10:52p
 * Updated in $/CodeJock/CJLibrary
 * 21. Fixed bug with CCJTreeCtrl where searching for a string in the tree
 * and this string is currently selected, the method will return NULL.
 * Fixed bug where tree control item font was changed to a smaller font
 * than the previous font, part of the old font would still show.
 * 
 * *****************  Version 4  *****************
 * User: Kirk Stowell Date: 10/24/99   Time: 12:01a
 * Updated in $/CodeJock/CJLibrary
 * Fixed potential resource and memory leak problems.
 * 
 * *****************  Version 3  *****************
 * User: Kirk Stowell Date: 7/25/99    Time: 12:30a
 * Updated in $/CodeJockey/CJLibrary
 * 
 * *****************  Version 2  *****************
 * User: Kirk Stowell Date: 6/23/99    Time: 12:06a
 * Updated in $/CodeJockey/CJLibrary
 * 
 * *****************  Version 1  *****************
 * User: Kirk Stowell Date: 6/23/99    Time: 12:04a
 * Created in $/CodeJockey/CJLibrary
 * Initial creation.
 * 
 ***************************************************************************/
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "CJResource.h"
#include "CJTreeCtrl.h"

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

/////////////////////////////////////////////////////////////////////////////
// CCJTreeCtrl

CCJTreeCtrl::CCJTreeCtrl()
{
	m_hItemFirstSel = NULL;
	m_dwDragStart   = NULL;
	m_bMultiSelect = FALSE;
}

CCJTreeCtrl::~CCJTreeCtrl()
{
}

IMPLEMENT_DYNAMIC(CCJTreeCtrl, CTreeCtrl)

BEGIN_MESSAGE_MAP(CCJTreeCtrl, CTreeCtrl)
	//{{AFX_MSG_MAP(CCJTreeCtrl)
	ON_WM_PAINT()
	ON_WM_LBUTTONDOWN()
	ON_WM_ERASEBKGND()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CCJTreeCtrl message handlers

// Taken from: Get next item in outline by Zafir Anjum.
// http://www.codeguru.com/treeview/get_next.shtml

// GetNextItem  - Get next item as if outline was completely expanded
// Returns      - The item immediately below the reference item
// hItem        - The reference item
HTREEITEM CCJTreeCtrl::GetNextItem( HTREEITEM hItem )
{
	HTREEITEM hti = NULL;
	
	if (ItemHasChildren(hItem))
		hti = GetChildItem(hItem);
	
	if (hti == NULL) {
		while ((hti = GetNextSiblingItem(hItem)) == NULL) {
			if ((hItem = GetParentItem(hItem)) == NULL)
				return NULL;
		}
	}
	return hti;
}

// Taken from: Get previous item in outline by Zafir Anjum.
// http://www.codeguru.com/treeview/get_prev.shtml

// GetPrevItem  - Get previous item as if outline was completely expanded
// Returns      - The item immediately above the reference item
// hItem        - The reference item
HTREEITEM CCJTreeCtrl::GetPrevItem( HTREEITEM hItem )
{
	HTREEITEM       hti;
	
	hti = GetPrevSiblingItem(hItem);
	if( hti == NULL )
		hti = GetParentItem(hItem);
	else
		hti = GetLastItem(hti);
	return hti;
}

// Taken from: Get last item in branch by Zafir Anjum.
// http://www.codeguru.com/treeview/get_last.shtml

// GetLastItem  - Gets last item in the branch
// Returns      - Last item
// hItem        - Node identifying the branch. NULL will 
//                return the last item in outine
HTREEITEM CCJTreeCtrl::GetLastItem( HTREEITEM hItem )
{
	//	Temporary used variable
	HTREEITEM	htiNext;
	
	if( hItem == NULL ) {
		// Get the last item at the top level
		hItem = GetRootItem();
	}
	
	while( ItemHasChildren( hItem ) != NULL ) {
		//	Find the last child of hItem
		htiNext = GetChildItem( hItem );
		while( htiNext != NULL ) {
			hItem = htiNext;
			htiNext = GetNextSiblingItem( htiNext );
		}
	}
	
	return hItem;
	
}

// Taken from: Finding an item by Zafir Anjum.
// http://www.codeguru.com/treeview/find_item.shtml

// FindItem		- Finds an item that contains the search string
// Returns		- Handle to the item or NULL
// str			- String to search for
// bCaseSensitive	- Should the search be case sensitive
// bDownDir		- Search direction - TRUE for down
// bWholeWord		- True if search should match whole words
// hItem		- Item to start searching from. NULL for
//			- currently selected item
HTREEITEM CCJTreeCtrl::FindItem(LPCTSTR lpszSearch, 
				BOOL bCaseSensitive /*= FALSE*/, 
				BOOL bDownDir /*= TRUE*/, 
				BOOL bWholeWord /*= FALSE*/, 
				HTREEITEM hItem /*= NULL*/)
{
	CString str = lpszSearch;

	int lenSearchStr = str.GetLength();
	if( lenSearchStr == 0 ) return NULL;

	// For the first pass, set the current item equal to the selection
	HTREEITEM htiSel = hItem ? hItem : GetSelectedItem();
	HTREEITEM htiCur = htiSel;
	CString sSearch = str;

	// make sure it ends if we started with no selection
	if(( htiCur == NULL ) && ( htiSel != NULL ))
	{
		if( bDownDir )
			htiCur = GetRootItem();
		else
			htiCur = GetLastItem( NULL );
	}

	if( !bCaseSensitive )
		sSearch.MakeLower();

	// For the first pass only, we check to see if it 
	// is the item we're looking for.
	BOOL bFirstPass = TRUE;

	while( htiCur && (htiCur != htiSel || bFirstPass) )
	{
		bFirstPass = FALSE;

		CString sItemText = GetItemText( htiCur );
		if( !bCaseSensitive )
			sItemText.MakeLower();

		int n;
		while( (n = sItemText.Find( sSearch )) != -1 )
		{
			// Search string found
			if( bWholeWord )
			{
				// Check preceding char
				if( n != 0 )
				{
					if( isalpha(sItemText[n-1]) || 
					    	sItemText[n-1] == '_' ){
						// Not whole word
						sItemText = sItemText.Right(
							sItemText.GetLength() - n - 
							lenSearchStr );
						continue;
					}
				}

				// Check succeeding char
				if( sItemText.GetLength() > n + lenSearchStr
					&& ( isalpha(sItemText[n+lenSearchStr]) ||
					sItemText[n+lenSearchStr] == '_' ) )
				{
					// Not whole word
					sItemText = sItemText.Right( sItemText.GetLength() 
							- n - sSearch.GetLength() );
					continue;
				}
			}
			
			if( IsFindValid( htiCur ) )
				return htiCur;
			else break;
		}


		htiCur = bDownDir ? GetNextItem( htiCur ) : GetPrevItem( htiCur );
		if( htiCur == NULL )
		{
			if( bDownDir )  htiCur = GetRootItem();
			else htiCur = GetLastItem( NULL );
		}
	}
	return NULL;
}

// Taken from: Finding an item by Zafir Anjum.
// http://www.codeguru.com/treeview/find_item.shtml

// IsFindValid	- Virtual function used by FindItem to allow this
//		  function to filter the result of FindItem
// Returns	- True if item matches the criteria
// Arg		- Handle of the item
BOOL CCJTreeCtrl::IsFindValid( HTREEITEM )
{
	return TRUE;
}

BOOL CCJTreeCtrl::PreCreateWindow(CREATESTRUCT& cs) 
{
	if( !CTreeCtrl::PreCreateWindow(cs))
		return FALSE;

	cs.style |= TVS_HASLINES|TVS_LINESATROOT|TVS_HASBUTTONS|TVS_SHOWSELALWAYS;

	return TRUE;
}

// Taken from: Setting color and font attribute for individual items by Zafir Anjum.
// http://www.codeguru.com/treeview/item_color_font.shtml

void CCJTreeCtrl::SetItemFont(HTREEITEM hItem, LOGFONT& logfont)
{
	Color_Font cf;
	if( !m_mapColorFont.Lookup( hItem, cf ) )
		cf.color = (COLORREF)-1;
	cf.logfont = logfont;
	m_mapColorFont[hItem] = cf;
}

void CCJTreeCtrl::SetItemBold(HTREEITEM hItem, BOOL bBold)
{
	SetItemState( hItem, bBold ? TVIS_BOLD: 0, TVIS_BOLD );
}

void CCJTreeCtrl::SetItemColor(HTREEITEM hItem, COLORREF color)
{
	Color_Font cf;
	if( !m_mapColorFont.Lookup( hItem, cf ) )
		cf.logfont.lfFaceName[0] = '\0';
	cf.color = color;
	m_mapColorFont[hItem] = cf;
}

BOOL CCJTreeCtrl::GetItemFont(HTREEITEM hItem, LOGFONT * plogfont)
{
	Color_Font cf;
	if( !m_mapColorFont.Lookup( hItem, cf ) )
		return FALSE;
	if( cf.logfont.lfFaceName[0] == '\0' ) 
		return FALSE;
	*plogfont = cf.logfont;
	return TRUE;

}

BOOL CCJTreeCtrl::GetItemBold(HTREEITEM hItem)
{
	return GetItemState( hItem, TVIS_BOLD ) & TVIS_BOLD;
}

COLORREF CCJTreeCtrl::GetItemColor(HTREEITEM hItem)
{
	// Returns (COLORREF)-1 if color was not set
	Color_Font cf;
	if( !m_mapColorFont.Lookup( hItem, cf ) )
		return (COLORREF)-1;
	return cf.color;

}

void CCJTreeCtrl::OnPaint() 
{
	CPaintDC dc(this);
	
	// Create a memory DC compatible with the paint DC
	CDC memDC;
	memDC.CreateCompatibleDC( &dc );
	
	CRect rcClip, rcClient;
	dc.GetClipBox( &rcClip );
	GetClientRect(&rcClient);
	
	// Select a compatible bitmap into the memory DC
	CBitmap bitmap;
	bitmap.CreateCompatibleBitmap( &dc, rcClient.Width(), rcClient.Height() );
	CBitmap* pOldBitmap = memDC.SelectObject( &bitmap );
	
	// Set clip region to be same as that in paint DC
	CRgn rgn;
	rgn.CreateRectRgnIndirect( &rcClip );
	memDC.SelectClipRgn(&rgn);
	rgn.DeleteObject();
	
	// First let the control do its default drawing.
	CWnd::DefWindowProc( WM_PAINT, (WPARAM)memDC.m_hDC, 0 );
	
	HTREEITEM hItem = GetFirstVisibleItem();
	
	int n = GetVisibleCount()+1;
	while( hItem && n--)
	{
		CRect rect;
		
		// Do not meddle with selected items or drop highlighted items
		UINT selflag = TVIS_DROPHILITED | TVIS_SELECTED;
		Color_Font cf;
		
		if ( !(GetItemState( hItem, selflag ) & selflag ) 
			&& m_mapColorFont.Lookup( hItem, cf ))
		{
			CFont *pFontDC;
			CFont fontDC;
			LOGFONT logfont;
			
			if( cf.logfont.lfFaceName[0] != '\0' ) 
			{
				logfont = cf.logfont;
			}
			else
			{
				// No font specified, so use window font
				CFont *pFont = GetFont();
				pFont->GetLogFont( &logfont );
			}

			CString sItem = GetItemText( hItem );
			GetItemRect( hItem, &rect, TRUE );

			// cover up over old font.
			CFont* pOldFont = memDC.SelectObject(GetFont());
			memDC.SetTextColor(::GetSysColor( COLOR_WINDOW ));
			memDC.SetBkColor( GetSysColor( COLOR_WINDOW ) );
			memDC.TextOut( rect.left+2, rect.top+1, sItem );
			memDC.SelectObject(pOldFont);

			if( GetItemBold( hItem ) )
				logfont.lfWeight = 700;
			fontDC.CreateFontIndirect( &logfont );
			pFontDC = memDC.SelectObject( &fontDC );
			
			if( cf.color != (COLORREF)-1 )
				memDC.SetTextColor( cf.color );
			
			memDC.SetBkColor( GetSysColor( COLOR_WINDOW ) );
			memDC.TextOut( rect.left+2, rect.top+1, sItem );
			
			memDC.SelectObject( pFontDC );

			// fix potential resource leak - KStowell - 10-22-99.
			fontDC.DeleteObject();
		}
		hItem = GetNextVisibleItem( hItem );
	}
	
	
	dc.BitBlt( rcClip.left, rcClip.top, rcClip.Width(), rcClip.Height(), &memDC, 
		rcClip.left, rcClip.top, SRCCOPY );

	// fix potential resource leak - KStowell - 10-21-99
	memDC.SelectObject( pOldBitmap );
	memDC.DeleteDC();
	bitmap.DeleteObject();
}

// Taken from: Allowing multiple selection by Zafir Anjum.
// http://www.codeguru.com/treeview/multiple_sel.shtml

void CCJTreeCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{
	if(m_bMultiSelect)
	{
		// Set focus to control if key strokes are needed.
		// Focus is not automatically given to control on lbuttondown
		
		m_dwDragStart = GetTickCount();
		
		if(nFlags & MK_CONTROL ) 
		{
			// Control key is down
			UINT flag;
			HTREEITEM hItem = HitTest( point, &flag );
			if( hItem )
			{
				// Toggle selection state
				UINT uNewSelState = 
					GetItemState(hItem, TVIS_SELECTED) & TVIS_SELECTED ? 
					0 : TVIS_SELECTED;
            
				// Get old selected (focus) item and state
				HTREEITEM hItemOld = GetSelectedItem();
				UINT uOldSelState  = hItemOld ? 
					GetItemState(hItemOld, TVIS_SELECTED) : 0;
            
				// Select new item
				if( GetSelectedItem() == hItem )
					SelectItem( NULL );		// to prevent edit
				CTreeCtrl::OnLButtonDown(nFlags, point);
				
				// Set proper selection (highlight) state for new item
				SetItemState(hItem, uNewSelState,  TVIS_SELECTED);
				
				// Restore state of old selected item
				if (hItemOld && hItemOld != hItem)
					SetItemState(hItemOld, uOldSelState, TVIS_SELECTED);
				
				m_hItemFirstSel = NULL;
				
				return;
			}
		} 
		else if(nFlags & MK_SHIFT)
		{
			// Shift key is down
			UINT flag;
			HTREEITEM hItem = HitTest( point, &flag );
			
			// Initialize the reference item if this is the first shift selection
			if( !m_hItemFirstSel )
				m_hItemFirstSel = GetSelectedItem();
			
			// Select new item
			if( GetSelectedItem() == hItem )
				SelectItem( NULL );			// to prevent edit
			CTreeCtrl::OnLButtonDown(nFlags, point);
			
			if( m_hItemFirstSel )
			{
				SelectItems( m_hItemFirstSel, hItem );
				return;
			}
		}
		else
		{
			// Normal - remove all selection and let default 
			// handler do the rest
			ClearSelection();
			m_hItemFirstSel = NULL;
		}
	}
	
	CTreeCtrl::OnLButtonDown(nFlags, point);
}

void CCJTreeCtrl::ClearSelection()
{
	if(m_bMultiSelect)
	{
		// This can be time consuming for very large trees 
		// and is called every time the user does a normal selection
		// If performance is an issue, it may be better to maintain 
		// a list of selected items
		for ( HTREEITEM hItem=GetRootItem(); hItem!=NULL; hItem=GetNextItem( hItem ) )
			if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
				SetItemState( hItem, 0, TVIS_SELECTED );
	}
}

// SelectItems	- Selects items from hItemFrom to hItemTo. Does not
//		- select child item if parent is collapsed. Removes
//		- selection from all other items
// hItemFrom	- item to start selecting from
// hItemTo	- item to end selection at.
BOOL CCJTreeCtrl::SelectItems(HTREEITEM hItemFrom, HTREEITEM hItemTo)
{
	if(m_bMultiSelect)
	{
		HTREEITEM hItem = GetRootItem();

		// Clear selection upto the first item
		while ( hItem && hItem!=hItemFrom && hItem!=hItemTo )
		{
			hItem = GetNextVisibleItem( hItem );
			SetItemState( hItem, 0, TVIS_SELECTED );
		}

		if ( !hItem )
			return FALSE;	// Item is not visible

		SelectItem( hItemTo );

		// Rearrange hItemFrom and hItemTo so that hItemFirst is at top
		if( hItem == hItemTo )
		{
			hItemTo = hItemFrom;
			hItemFrom = hItem;
		}


		// Go through remaining visible items
		BOOL bSelect = TRUE;
		while ( hItem )
		{
			// Select or remove selection depending on whether item
			// is still within the range.
			SetItemState( hItem, bSelect ? TVIS_SELECTED : 0, TVIS_SELECTED );

			// Do we need to start removing items from selection
			if( hItem == hItemTo ) 
				bSelect = FALSE;

			hItem = GetNextVisibleItem( hItem );
		}

		return TRUE;
	}

	return FALSE;
}

HTREEITEM CCJTreeCtrl::GetFirstSelectedItem()
{
	if(m_bMultiSelect)
	{
		for ( HTREEITEM hItem = GetRootItem(); hItem!=NULL; hItem = GetNextItem( hItem ) )
			if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
				return hItem;
	}

	return NULL;
}

HTREEITEM CCJTreeCtrl::GetNextSelectedItem(HTREEITEM hItem)
{
	if(m_bMultiSelect)
	{
		for ( hItem = GetNextItem( hItem ); hItem!=NULL; hItem = GetNextItem( hItem ) )
			if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
				return hItem;
	}

	return NULL;
}

HTREEITEM CCJTreeCtrl::GetPrevSelectedItem(HTREEITEM hItem)
{
	if(m_bMultiSelect)
	{
		for ( hItem = GetPrevItem( hItem ); hItem!=NULL; hItem = GetPrevItem( hItem ) )
			if ( GetItemState( hItem, TVIS_SELECTED ) & TVIS_SELECTED )
				return hItem;
	}

	return NULL;
}

BOOL CCJTreeCtrl::OnEraseBkgnd(CDC* pDC) 
{
	// KStowell - overridden for flicker-free drawing.
	UNUSED_ALWAYS(pDC);
	return TRUE;
}
