MSTreeCtrl.cpp. #1

  • //
  • guest/
  • YourUncleBob/
  • p4win/
  • main/
  • gui/
  • MSTreeCtrl.cpp.
  • View
  • Commits
  • Open Download .zip Download (32 KB)
//
// Copyright 1997 Nicholas J. Irias.  All rights reserved.
//
//

// MSTreeCtrl.cpp : implementation file
//

#include "stdafx.h"
// #define TRACE_HERE
#include "p4win.h"
#include "MSTreeCtrl.h"
#include "P4PaneView.h"
#include "mainfrm.h"

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

/////////////////////////////////////////////////////////////////////////////
// CMultiSelTreeCtrl

IMPLEMENT_DYNCREATE(CMultiSelTreeCtrl, CTreeCtrl)

CMultiSelTreeCtrl::CMultiSelTreeCtrl()
{
	m_Timer=0;
	m_SelectionSet.SetSize(20,5);
	m_SelectFlags=TVIS_SELECTED;
    m_ContextContext= KEYSTROKED;
	m_CtrlDown= m_ShiftDown= m_MultiSelect= FALSE;
	m_PendingKeyedDeselect= FALSE;
	m_ToolTip = NULL;
	m_SortByFilename = m_SortByAction = m_SortByExtension = m_SortByResolveStat = FALSE;
	m_LastLButtonDown = NULL;
	ClearSelection();
}

void CMultiSelTreeCtrl::ClearSelection()
{
	XTRACE(_T("ClearSelection()\n"));
	m_LastSelect = m_LastParent = NULL;
	m_SelectionSet.RemoveAll();
    m_LastMouseOver=NULL;
}

CMultiSelTreeCtrl::~CMultiSelTreeCtrl()
{
}


BEGIN_MESSAGE_MAP(CMultiSelTreeCtrl, CTreeCtrl)
	ON_NOTIFY_REFLECT(TVN_DELETEITEM, OnDeleteitem)
	ON_WM_SETFOCUS()
	ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, OnItemExpanding)
	ON_WM_SETCURSOR()
	ON_WM_KILLFOCUS()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONDOWN()
	ON_WM_KEYDOWN()
	ON_WM_KEYUP()
	ON_NOTIFY_REFLECT(TVN_SELCHANGED, OnSelchanged)
	ON_WM_LBUTTONUP()
	ON_WM_ACTIVATE()
	ON_WM_CHAR()
	ON_WM_TIMER()
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CMultiSelTreeCtrl diagnostics

#ifdef _DEBUG
void CMultiSelTreeCtrl::AssertValid() const
{
	CTreeCtrl::AssertValid();
}

void CMultiSelTreeCtrl::Dump(CDumpContext& dc) const
{
	CTreeCtrl::Dump(dc);
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CMultiSelTreeCtrl message handlers

void CMultiSelTreeCtrl::OnDeleteitem(NMHDR* pNMHDR, LRESULT* pResult) 
{//do a virtual function for depot view that is called by this to delete the memory that was newed.
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
	TV_ITEM ptv=pNMTreeView->itemOld;

	// Get this item out of the selection set, since it was deleted
	SetSelectState(ptv.hItem, FALSE);  

	// Make sure that m_LastParent gets nulled as required
	if(ptv.hItem==m_LastParent || GetCount() == 0)
		m_LastParent=NULL;

	*pResult = 0;
}

BOOL CMultiSelTreeCtrl::DeleteAllItems()
{
	XTRACE(_T("CMultiSelSTreeCtrl::DeleteAllItems()\n"));
	ClearSelection();
	return CTreeCtrl::DeleteAllItems();
}

////////////////////////////////////////////////////////////////////////////////
// Simple utilities for parameter update

void CMultiSelTreeCtrl::SetLParam(HTREEITEM curr_item, LPARAM lParam)
{
	if (curr_item == TVI_ROOT)
		return;

	TV_ITEM item;
	item.hItem = curr_item;
	item.lParam = lParam;
	item.mask = TVIF_PARAM |TVIF_HANDLE;
	SetItem( &item );	
}

DWORD CMultiSelTreeCtrl::GetLParam(HTREEITEM curr_item)
{
	TV_ITEM item;
	item.hItem=curr_item;
	item.mask=TVIF_PARAM | TVIF_HANDLE;
	GetItem(&item );	
	return(item.lParam);
}

void CMultiSelTreeCtrl::SetItemText(HTREEITEM curr_item, LPCTSTR txt)
{
	TV_ITEM item;
	item.hItem = curr_item;
	item.mask = TVIF_TEXT | TVIF_HANDLE;
	item.pszText = ( LPTSTR ) txt;         
    SetItem(&item );	
}

CString CMultiSelTreeCtrl::GetItemText( HTREEITEM curr_item )
{
	TCHAR buf[ LONGPATH + 1 ];

	TV_ITEM item;
	item.hItem = curr_item;
	item.mask = TVIF_TEXT | TVIF_HANDLE;
	item.pszText = buf;         
    item.cchTextMax = LONGPATH ;  
	GetItem( &item );	
	return( CString( item.pszText ) );
}

void CMultiSelTreeCtrl::SetChildCount(HTREEITEM curr_item, int count)
{
	TV_ITEM item;
	item.hItem=curr_item;
	item.cChildren=count ;
	item.mask=TVIF_CHILDREN | TVIF_HANDLE;
	SetItem(&item );	
}

int CMultiSelTreeCtrl::GetChildCount(HTREEITEM curr_item)
{
	TV_ITEM item;
	item.hItem=curr_item;
	item.mask=TVIF_CHILDREN | TVIF_HANDLE;
	GetItem(&item );	
	return( item.cChildren );
}


void CMultiSelTreeCtrl::SetImage(HTREEITEM curr_item, int imageIndex, int selectedImage)
{
	TV_ITEM item;
	item.hItem=curr_item;
	item.iImage=imageIndex;
	if(selectedImage==-1)
		item.iSelectedImage=imageIndex;
	else
		item.iSelectedImage=selectedImage;
	item.mask=TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_HANDLE;
	SetItem(&item );	
}

int CMultiSelTreeCtrl::GetImage(HTREEITEM curr_item)
{
	TV_ITEM item;
	item.hItem=curr_item;
	
	item.mask=TVIF_IMAGE | TVIF_HANDLE;
	GetItem(&item );	

	return item.iImage;
}

BOOL CMultiSelTreeCtrl::HasExpandedChildren(HTREEITEM curr_item)
{
	TV_ITEM item;
	item.hItem=curr_item;
	item.mask=TVIF_CHILDREN | TVIF_STATE | TVIF_HANDLE;
	GetItem(&item );	
	if(item.cChildren > 0 && item.state & TVIS_EXPANDED)
		return TRUE;
	else
		return FALSE;
}

////////////////////////////////////////////////////////////////////////////////
// Selection Set management

// Manage the list of selected items - any item added to the selection set will
// get the current display atts for the set.  Items removed will get normal atts

// delete selection set from tree and selection set
void CMultiSelTreeCtrl::DeleteSelectedItems()	
{
	for(int i= m_SelectionSet.GetSize()-1; i >= 0; i++)
	{
		DeleteItem((HTREEITEM) m_SelectionSet.GetAt(i));	
	}
	ClearSelection();
}

// remove all from selection set and redisplay with normal atts
void CMultiSelTreeCtrl::UnselectAll()
{
	HTREEITEM item;
	for(int i= m_SelectionSet.GetSize()-1; i >= 0; i--)
	{
		item= (HTREEITEM) m_SelectionSet.GetAt(i);	
		// Undo any display atts
		SetItemState(item, 0, TVIS_CUT | TVIS_BOLD | TVIS_SELECTED);
	}
	ClearSelection();

	MainFrame()->SetMessageText(LoadStringResource(IDS_FOR_HELP_PRESS_F1));
}

static int CALLBACK SortTreeCB(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
	if ((lParam1 < 1) || (lParam2 < 1))
		return -1;
	int i = 0;
	CP4FileStats *stats1 = (CP4FileStats *)lParam1;
	CP4FileStats *stats2 = (CP4FileStats *)lParam2;
	CString f1 = stats1->GetFormattedChangeFile(GET_P4REGPTR()->ShowFileType(), GET_P4REGPTR()->ShowOpenAction());
	CString f2 = stats2->GetFormattedChangeFile(GET_P4REGPTR()->ShowFileType(), GET_P4REGPTR()->ShowOpenAction());
	if ((i = f1.ReverseFind(_T('#'))) < 1)
		return f1.CompareNoCase(f2);
	f1 = f1.Left(i);
	CString e1 = ((i = f1.ReverseFind(_T('.'))) == -1) ? _T("") : f1.Right(f1.GetLength() - i - 1);
	if ((i = f1.ReverseFind(_T('/'))) == -1)
		return f1.CompareNoCase(f2);
	CString n1 = f1.Right(f1.GetLength() - i - 1);
	if ((i = f2.ReverseFind(_T('#'))) < 1)
		return f1.CompareNoCase(f2);
	f2 = f2.Left(i);
	CString e2 = ((i = f2.ReverseFind(_T('.'))) == -1) ? _T("") : f2.Right(f2.GetLength() - i - 1);
	if ((i = f2.ReverseFind(_T('/'))) == -1)
		return f1.CompareNoCase(f2);
	CString n2 = f2.Right(f2.GetLength() - i - 1);
	int sortByFilename        = lParamSort & 0x8;
	int sortByAction      = lParamSort & 0x4;
	int sortByResolveStat = lParamSort & 0x2;
	int sortByExtension   = lParamSort & 0x1;
	i = 0;
	if (sortByResolveStat)
	{
		int r1, r2;
		
		if (stats1->IsUnresolved())		r1 = 1;
		else if (stats1->IsResolved())	r1 = 2;
		else							r1 = 3;
		
		if (stats2->IsUnresolved())		r2 = 1;
		else if (stats2->IsResolved())	r2 = 2;
		else							r2 = 3;
		
		i = r1 - r2;
	}
	if (i)
		return i;

	if (sortByAction)
	{
		int a1, a2;
		
		a1 = stats1->IsMyOpen() ? stats1->GetMyOpenAction() : stats1->GetOtherOpenAction();
		a2 = stats2->IsMyOpen() ? stats2->GetMyOpenAction() : stats2->GetOtherOpenAction();
		i = a1 - a2;
	}
	if (i)
		return i;

	if (sortByExtension)
		i = e1.CompareNoCase(e2);
	if (i)
		return i;

	if (sortByFilename)
		i = n1.CompareNoCase(n2);
	if (i)
		return i;

	return f1.CompareNoCase(f2);

}

void CMultiSelTreeCtrl::OnTimer(UINT nIDEvent) 
{
	CTreeCtrl::OnTimer(nIDEvent);
	if (nIDEvent == SORT_TIMER)
	{
		m_Timer = 0;
		::KillTimer(m_hWnd, SORT_TIMER);
		SortTree();
	}
}

// Recursively sort the entire tree
void CMultiSelTreeCtrl::SortTree(HTREEITEM topNode/*=NULL*/, HTREEITEM parentNode/*=NULL*/)
{
	HTREEITEM item;

	// Sort things at the this level
	if (parentNode && (m_SortByExtension || m_SortByResolveStat 
					|| m_SortByAction    || m_SortByFilename))
	{
		TVSORTCB tvsortcb;
		tvsortcb.hParent = topNode;
		tvsortcb.lParam = (m_SortByResolveStat ? 2 : 0) + (m_SortByExtension ? 1 : 0) 
						+ (m_SortByFilename ? 8 : 0)    + (m_SortByAction ? 4 : 0);
		tvsortcb.lpfnCompare = SortTreeCB;
		SortChildrenCB(&tvsortcb);
	}
	else 
		SortChildren(topNode);

	// Get the first item at this level
	if(topNode == NULL)
		item=GetNextItem(TVI_ROOT, TVGN_ROOT);
	else
		item=GetChildItem(topNode);   // Get first child

	// Recurse all items that have children
	while(item != NULL)
	{
		if(ItemHasChildren(item))
			SortTree(item, topNode);
		item=GetNextSiblingItem(item);
	}
}


// add one item to selection set, or remove it	
BOOL CMultiSelTreeCtrl::SetSelectState(HTREEITEM item, BOOL selected)
{
//	ASSERT(item != NULL);	// Can't ASSERT here! When called from context menu click below any changes, there is no 'item'
	if(item==NULL)
		return FALSE;

	HTREEITEM parent=GetParentItem(item);
	int index = SelectionToIndex(item);
	BOOL found = index != -1;
	BOOL success=TRUE;

	if(found && !selected)  
	{
		// Clear the selected appearance for this item
		success=SetItemState(item, 0, TVIS_CUT | TVIS_BOLD | TVIS_SELECTED);

		// and remove item from selection set
		if(success)
		{
			m_SelectionSet.RemoveAt(index);
			if(m_SelectionSet.GetSize()==0)
				SelectItem(NULL);
		}
	}
	else if(!found && selected)
	{
		// Set the appearance for this item
		success=SetItemState(item, m_SelectFlags, TVIS_CUT | TVIS_BOLD | TVIS_SELECTED);

		// and add it to selection set
		if(success)
		{
			m_SelectionSet.Add((DWORD) item);
			if (!m_ShiftDown && !m_MultiSelect)
				SelectItem(item);
		}
	}
	// !found && !selected --> select atts already correct
	// found && selected --> select atts already correct
	
	if(selected)
	{
		m_LastSelect=item;
		m_LastParent=parent;
	}
	else
	{
		if(m_SelectionSet.GetSize()==0)
			m_LastParent=m_LastSelect=NULL;
		else
		{
			if(found)
				// Only change m_LastSelect if we deleted a selected item
				m_LastSelect= (HTREEITEM) m_SelectionSet.GetAt(0);

			// m_LastParent must still be valid
			m_LastParent=GetParentItem(m_LastSelect);
		}
	}

	ShowNbrSelected();
	return success;
}

// add one item to selection set, or remove it	
BOOL CMultiSelTreeCtrl::ToggleSelectState(HTREEITEM item)
{
	ASSERT(item != NULL);
	if(item==NULL)
		return FALSE;

	// All selections required to be under same parent
	HTREEITEM parent=GetParentItem(item);
	if(parent != m_LastParent && m_LastParent != NULL)
		return FALSE;

	int index = SelectionToIndex(item);
	BOOL found = index != -1;
	BOOL success=TRUE;

	if(found)  
	{
		// Clear the selected appearance for this item
		success=SetItemState(item, 0, TVIS_CUT | TVIS_BOLD | TVIS_SELECTED);

		// and remove item from selection set
		if(success)
			m_SelectionSet.RemoveAt(index);
	}
	else 
	{
		// Set the appearance for this item
		success=SetItemState(item, m_SelectFlags, TVIS_CUT | TVIS_BOLD | TVIS_SELECTED);

		// and add it to selection set
		if(success)
			m_SelectionSet.Add((DWORD) item);
	}
	
	m_LastSelect=item;
	if(m_SelectionSet.GetSize()==0)
		m_LastParent=NULL;
	else
		m_LastParent=parent;

	ShowNbrSelected();
	return success;
}

 
// first item is m_LastSelect
BOOL CMultiSelTreeCtrl::RangeSelect(HTREEITEM secondItem)
{
	ASSERT(secondItem != NULL);
	if(secondItem == NULL)
		return FALSE;

	// Special case #1 - anchor point and current point the same
	if(m_LastSelect == secondItem)
		return TRUE;  // nothing to do

	// Special case #2 - no anchor point, just a regular selection
	if(m_LastSelect == NULL)
		return SetSelectState(secondItem, TRUE);

	// Do both items have same parent - an arbitrary restriction to avoid boggling select combos
	// Look up last parent, since m_LastParent will be null if no items currently selected
	HTREEITEM parent=GetParentItem(secondItem);
	HTREEITEM lastParent=GetParentItem(m_LastSelect);
	if(lastParent != parent)
		return FALSE;

	// Allow subclass to veto the operation
	if( !OKToAddSelection( secondItem ) )
		return FALSE;

	BOOL success=TRUE;
	// Find out which one is higher in the tree by comparing item rects - this avoids getting
	// stung by some possible sort order we dont know about
	RECT lastRect, thisRect;
	GetItemRect(m_LastSelect, &lastRect, TRUE);
	GetItemRect(secondItem, &thisRect, TRUE);

	HTREEITEM topItem, bottomItem;
	if(lastRect.top < thisRect.top)   // current selection is below anchor
	{
		topItem=m_LastSelect;
		bottomItem=secondItem;
	}
	else
	{
		topItem=secondItem;
		bottomItem=m_LastSelect;
	}
	
	// Select the items
	while(topItem != bottomItem)
	{
		if(!SetSelectState(topItem, TRUE))
			success=FALSE;
		topItem=GetNextSiblingItem(topItem);
	}

	if(!SetSelectState(topItem, TRUE))
		success=FALSE;

	return success;
}

// Access the selected items
inline int CMultiSelTreeCtrl::SelectionToIndex(HTREEITEM item)
{
	// First, see if item is in list
	for(int index=m_SelectionSet.GetSize()-1; index >= 0; index--)
	{
		if(item == (HTREEITEM) m_SelectionSet.GetAt(index))
		{
			return index;
		}
	}

	return -1;
}

// Utility to support context menues.  Finds the treeitem for a
// mouseclick, and useable screen coords for a shift+f10 key hit
void CMultiSelTreeCtrl::SetItemAndPoint( HTREEITEM &item, CPoint &point )
{
    CString text ;
    if( m_ContextContext == MOUSEHIT )
    {
        ScreenToClient( &point );
        TV_HITTESTINFO ht;
	    ht.pt = point;
	    item = HitTest( &ht );
	    m_ContextContext= KEYSTROKED;
    }
    else
    {
        CRect rect;
        GetClientRect(&rect);   
        point= rect.CenterPoint();
        if(GetSelectedCount() > 0)
            item = GetSelectedItem(0);
        else
            item=NULL;
    }
}

HTREEITEM CMultiSelTreeCtrl::GetSelectedItem(int index)
{
	if (index < m_SelectionSet.GetSize())
		return (HTREEITEM) m_SelectionSet.GetAt(index);
	return NULL;
}

int CMultiSelTreeCtrl::GetSelectedCount()
{
	return m_SelectionSet.GetSize();
}

// Change the appearance of all selected items
void CMultiSelTreeCtrl::SetAppearance(BOOL bold, BOOL selected, BOOL cut)
{
	m_SelectFlags=0;

	if(bold)
		m_SelectFlags= TVIS_BOLD;

	if(selected)
		m_SelectFlags |= TVIS_SELECTED;

	if(cut)
		m_SelectFlags |= TVIS_CUT;

	ApplySelectAtts(m_SelectFlags);
}

void CMultiSelTreeCtrl::ApplySelectAtts(UINT flags)
{
	HTREEITEM item;
	for(int i= m_SelectionSet.GetSize()-1; i >= 0; i--)
	{
		item= (HTREEITEM) m_SelectionSet.GetAt(i);	
		// Undo any display atts
		SetItemState(item, flags, TVIS_CUT | TVIS_BOLD | TVIS_SELECTED);
	}

	ShowNbrSelected();
}

void CMultiSelTreeCtrl::SetItemAtt(HTREEITEM item, UINT flags, BOOL set)
{
    ASSERT( item != NULL );

    if( set )
	    SetItemState(item, flags, flags);
    else
        SetItemState(item, 0, flags);

	ShowNbrSelected();
}

BOOL CMultiSelTreeCtrl::IsBoldAtt()
{
	return ((m_SelectFlags & TVIS_BOLD)==TVIS_BOLD);
}

BOOL CMultiSelTreeCtrl::IsSelectAtt()
{
	return ((m_SelectFlags & TVIS_SELECTED)==TVIS_SELECTED);
}

BOOL CMultiSelTreeCtrl::IsCutAtt()
{
	return ((m_SelectFlags & TVIS_CUT)==TVIS_CUT);
}

void CMultiSelTreeCtrl::OnSetFocus(CWnd* pOldWnd) 
{
	// When activated, CTreeView will highlight first tree item by default, so 
	// undo that highlight and then redisplay all selected items
	CTreeCtrl::OnSetFocus(pOldWnd);

	m_ViewIsActive = TRUE;

	// Update our Shift and Control keys states
	m_CtrlDown = ::GetKeyState(VK_CONTROL) & 0x8000 ? TRUE : FALSE;
	m_ShiftDown= ::GetKeyState(VK_SHIFT) & 0x8000 ? TRUE : FALSE;

	Select(NULL, TVGN_CARET);
	ApplySelectAtts(m_SelectFlags);
	if (GET_P4REGPTR( )->AlwaysShowFocus())
		InvalidateRect(NULL);
}


/*
	_________________________________________________________________
*/

void CMultiSelTreeCtrl::OnItemExpanding(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
	
	HTREEITEM parent=m_LastParent;

	while( 1 )
	{
		//		If expanding/collapsing node is a parent of selected items, 
		//		unselect all.
		//		No need to check pNMTreeView->action, since no items will 
		//		be selected under parent if expanding, and all must be 
		//		cleared while deleting
		//
		if(pNMTreeView->itemNew.hItem == parent)
		{
			UnselectAll();
			break;
		}
		
		if(parent == NULL || parent == TVI_ROOT)
			break;

		parent=GetParentItem(parent);
	}

	//		whenever we expand a folder, we have to call both p4 dirs 
	//		and p4 fstat for that subdirectory. ExpandTree is a 
	//		virtual function in depotview that does this.
	//
	if ( pNMTreeView->action == TVE_EXPAND )
		ExpandTree( pNMTreeView->itemNew.hItem );
	else if ( pNMTreeView->action == TVE_COLLAPSE )
		CollapseTree( pNMTreeView->itemNew.hItem );

	// wait at least 20 secs before autopolling for updates
	MainFrame()->WaitAWhileToPoll();

	*pResult = 0;
}

void CMultiSelTreeCtrl::ScrollToFirstItem( HTREEITEM firstItem )
{
    ASSERT(firstItem != NULL);
    HTREEITEM lastItem= firstItem;

    EnsureVisible( firstItem );
    int c=GetVisibleCount();

    for( int i=1; i < c && lastItem != NULL; i++ )
        lastItem= GetNextVisibleItem( lastItem );
            
    // Make the last item visible, then scroll up if required to make sure the
    // first item is still visible (user may have changed window height while
    // we were running the refresh)
    if( lastItem != NULL )
    {
        EnsureVisible( lastItem );
        EnsureVisible( firstItem );
    }
}


/*
	_________________________________________________________________

	virtual class function just so i can call the proper commands in depotview.cpp
	_________________________________________________________________
*/

BOOL CMultiSelTreeCtrl::ExpandTree( const HTREEITEM item )
{
	return TRUE;
}


BOOL CMultiSelTreeCtrl::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
	if(SERVER_BUSY())
		return SET_BUSYCURSOR();
	else
		return CTreeCtrl::OnSetCursor(pWnd, nHitTest, message);
}


////////////////////////////////////////////////////////////////////
//	
// Functions to support mouse flyover status messages.  The status message
// will remain the default message unless OnSetFlyoverMessage() is over-ridden
// by the subclass.  That over-ride can just set message text, aand can optionally
// call SetItemFocus() to draw a focus rect arount the flyover item text.
//
////////////////////////////////////////////////////////////////////


void CMultiSelTreeCtrl::SetItemFocus(HTREEITEM item) 
{
    if( item == m_LastMouseOver )
        return;

    if( m_LastMouseOver != NULL )
	{
		CRect rect;
		GetItemRect( m_LastMouseOver, &rect, TRUE );
		rect.DeflateRect(0,1,0,1);
	
		CDC *pDC= GetDC();
		CBrush brush;
		if( IsSelected( m_LastMouseOver ) )
		{
			if( GetTextColor() != -1 )
				brush.CreateSolidBrush( GetTextColor() );
			else
				brush.CreateSolidBrush( GetSysColor( COLOR_WINDOWTEXT ) );
		}
		else
			brush.CreateSolidBrush( GetSysColor( COLOR_WINDOW ) );
		pDC->FrameRect( &rect, &brush );

		ReleaseDC( pDC );
	}
    	
    m_LastMouseOver= item;

    if( m_LastMouseOver != NULL )
    {
		CRect rect;
		GetItemRect( item, &rect, TRUE );
		rect.DeflateRect(0,1,0,1);

		CDC *pDC= GetDC();
		pDC->DrawFocusRect(&rect);

		ReleaseDC( pDC );
	}
		
}

void CMultiSelTreeCtrl::OnKillFocus(CWnd* pNewWnd) 
{
	CTreeCtrl::OnKillFocus(pNewWnd);
	
	m_ViewIsActive = FALSE;
	m_ToolState = -1;	// So that we set the colors correctly next time we get a mouse move
    RestoreStatusMessage();
	if (GET_P4REGPTR( )->AlwaysShowFocus())
	{
		InvalidateRect(NULL);
	}
	else
	{
		ApplySelectAtts(0);
		SetItemFocus(NULL);
	}
}

inline void CMultiSelTreeCtrl::RestoreStatusMessage( )
{
    MainFrame()->SetMessageText(LoadStringResource(IDS_FOR_HELP_PRESS_F1));
}

void CMultiSelTreeCtrl::OnSetFlyoverMessage( HTREEITEM item )
{
    RestoreStatusMessage();
    SetItemFocus(NULL);
}

void CMultiSelTreeCtrl::OnMouseMove(UINT nFlags, CPoint point) 
{
	// update shift and control flags
	m_CtrlDown= nFlags & MK_CONTROL ? TRUE : FALSE;
	m_ShiftDown= nFlags & MK_SHIFT  ? TRUE : FALSE;

	if( !m_ViewIsActive )
        return;

    // find out what mouse is over
	TV_HITTESTINFO ht;
	ht.pt=point;
    ht.flags=TVHT_ONITEMLABEL | TVHT_ONITEMICON | TVHT_ONITEMBUTTON;
	HTREEITEM currentItem=HitTest( &ht	);

    if(currentItem != m_LastMouseOver)
    {
        // see if subclassed views want to display something as a status
        // message for the item mouse is over
        if(currentItem == NULL)
            OnSetFlyoverMessage( NULL );
        else
            OnSetFlyoverMessage( currentItem );
    }

	CTreeCtrl::OnMouseMove(nFlags, point);
}

/*
	_________________________________________________________________

	Curious observation:  on a Lbutton click, point is client coords
    on a right button click, its screen coords
	_________________________________________________________________
*/

void CMultiSelTreeCtrl::OnLButtonDown(UINT nFlags, CPoint point) 
{
	// update shift and control flags
	m_CtrlDown= nFlags & MK_CONTROL ? TRUE : FALSE;
	m_ShiftDown= nFlags & MK_SHIFT  ? TRUE : FALSE;

    // find out what was hit
	TV_HITTESTINFO ht;
	ht.pt=point;
	m_LastLButtonDown=HitTest( &ht	);
	if(m_LastLButtonDown==NULL)
		return;
    
    BOOL success;
	m_PendingDeselect=FALSE;

	if(m_LastLButtonDown != TVI_ROOT && (ht.flags & TVHT_ONITEM ))
	{
		// Select nothing so there is no focus rect
		SelectItem(NULL);

		// Add the item to the selection set
		if(nFlags &	MK_CONTROL)
		{
			// Make sure its a valid selection
			if( !OKToAddSelection( m_LastLButtonDown ) )
				return;
			// If not in set, add it
			if(!IsSelected(m_LastLButtonDown))	
				success=SetSelectState(m_LastLButtonDown, TRUE);
			else
				// removing from set, or possibly re-clicking for drag
				success=m_PendingDeselect=TRUE;
		}
		else if(nFlags & MK_SHIFT)
		{
			success=RangeSelect(m_LastLButtonDown);
		}
		else
		{
			if(!IsSelected(m_LastLButtonDown))
			{
				UnselectAll();
				success=SetSelectState(m_LastLButtonDown, TRUE);
				ASSERT(GetSelectedCount());
			}
			else 
			{
				success= TRUE;
				m_PendingDeselect= TRUE;
			}
		}

		// wait at least 20 secs before autopolling for updates
		MainFrame()->WaitAWhileToPoll();

		if(!success)
			return;

		// Store the clicked item
		m_DragFromItem=m_LastLButtonDown;
		
		// Force the stinking item to repaint, because new select atts seem to
		// be lost in commctl32.dll occasionally
		SetAppearance(FALSE, TRUE, FALSE);
		GetItemRect(m_LastLButtonDown, &m_DragSourceRect, TRUE);
		RedrawWindow( m_DragSourceRect, NULL, RDW_UPDATENOW );

		// Then create a suitably small drag rect around the cursor 
		CPoint pt= point;
		ClientToScreen(&pt);
		m_DragSourceRect.SetRect(	max(0, pt.x - 2), max(0, pt.y - 2), 
									max(0, pt.x + 2), max(0, pt.y + 2) );

		// The drag drop attempt will clear m_PendingDeselect if a drag is attempted
		TryDragDrop( m_LastLButtonDown );
			
		if( m_PendingDeselect )
		{
			if( nFlags & MK_CONTROL )
				SetSelectState(m_LastLButtonDown, FALSE);
			else
			{
				UnselectAll();
				success=SetSelectState(m_LastLButtonDown, TRUE);
			}
		}
		
		// Make sure selection set is properly displayed
		SetAppearance(FALSE, TRUE, FALSE);
	}
	else
	{
		if(ht.flags & TVHT_ONITEM && (nFlags &	MK_CONTROL || nFlags & MK_SHIFT) )
			return;
		else 
		{
			if (ht.flags & TVHT_ONITEM && m_ViewIsActive )
			{
				// Select just the one item
				UnselectAll();
				SetSelectState(m_LastLButtonDown, TRUE);
			}
				
			// Clicked on something other than a bimap or item text, so just call
			// the default hanlder and make sure nothing gets selected
			CTreeCtrl::OnLButtonDown(nFlags, point);
			SelectItem( NULL);	
		}
	}
}

void CMultiSelTreeCtrl::OnLButtonUp(UINT nFlags, CPoint point) 
{
	CTreeCtrl::OnLButtonUp(nFlags, point);
	ApplySelectAtts(m_SelectFlags);
}

#define VK_PGUP 33
#define VK_PGDN 34

void CMultiSelTreeCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	MainFrame()->SetGotUserInput( );

	if (nChar == VK_RETURN || nChar == VK_APPS)
		return;

	if (nChar == VK_TAB && m_CtrlDown)	// Ctrl+TAB switches to opposite pane
	{
		BOOL bShift = m_ShiftDown;
		m_CtrlDown= m_ShiftDown= FALSE;	// clear these because we may miss the Up
		m_PendingKeyedDeselect = FALSE;	//	in the other pane
		MainFrame()->SwitchPanes(DYNAMIC_DOWNCAST(CView, GetParent()), bShift);
		return;
	}

	if( nChar == VK_CONTROL )
		m_CtrlDown= TRUE;
	else if( nChar == VK_SHIFT )
	{
		m_ShiftDown= TRUE;
		if( m_SelectionSet.GetSize() > 0 )
		{
			m_AnchorItem= m_LastSelect;
			m_PendingKeyedDeselect= TRUE;
		}
	}
	
	if( m_ShiftDown && m_SelectionSet.GetSize() > 0 )
	{
		// Try to range select, and scroll the tree only as necessary
		// to ensure that the last item selected is visible
		switch( nChar )
		{
		case VK_DOWN:
			ExpandSelection( -1 );
			break;
		case VK_UP:
			ExpandSelection( 1 );
			break;
		case VK_END:
			ExpandSelection( - (int) GetCount() );
			break;
		case VK_HOME:
			ExpandSelection( (int) GetCount() );
			break;
		case VK_PGDN:
			ExpandSelection( - (int) GetVisibleCount() +1 );
			break;
		case VK_PGUP:
			ExpandSelection( (int) GetVisibleCount() -1 );
			break;
		default:
			break;
		}
	}
	else if( m_CtrlDown || m_SelectionSet.GetSize() == 0)
	{
		// Scroll the tree, but do not fool with the selection set
		// or move a focus rect around
		switch( nChar )
		{
		case VK_DOWN:
			ScrollTree( -1 );
			break;
		case VK_UP:
			ScrollTree( 1 );
			break;
		case VK_END:
			ScrollTree( - (int) GetCount() );
			break;
		case VK_HOME:
			ScrollTree( GetCount() );
			break;
		case VK_PGDN:
			ScrollTree( - (int) GetVisibleCount() +1 );
			break;
		case VK_PGUP:
			ScrollTree( GetVisibleCount() -1 );
			break;
		default:
			break;
		}
	}
	else
	{
		// Deselect all, reposition at m_LastSelect + offset, reselect the new
		// item, and ensure visible
		HTREEITEM item= m_LastSelect;
		ASSERT( item != NULL );
		HTREEITEM newItem;
		UnselectAll();
#if 0
		HTREEITEM lastItem;
		int i, count;
#endif
		switch( nChar )
		{
		case VK_END:
			ScrollTree( - (int) GetCount() );
			newItem= GetNextItem(TVI_ROOT, TVGN_LASTVISIBLE);
			break;
		case VK_HOME:
			ScrollTree( GetCount() );
			newItem= GetNextItem(TVI_ROOT, TVGN_ROOT );
			break;
#if 0	// This code was removed to fix job004105 - removing it causes the DepotView to behave like a normal TreeView when page-up and page-down are pressed
		case VK_PGDN:
			count=GetVisibleCount() - 1;
			newItem= GetFirstVisible();

			// First, find the last visible item on the screen
			for( i=0; i< count; i++)
			{
				lastItem= newItem;
				newItem= GetNextVisible(newItem );
				if( newItem == NULL )
				{
					newItem= lastItem;
					break;
				}
			}

			// If current item is the last one on the screen, move down
			// a page beyond the end of current page
			if( newItem == item )
			{
				for( i=0; i< count; i++)
				{
					lastItem= newItem;
					newItem= GetNextVisible( newItem );
					if( newItem==NULL)
					{
						newItem= lastItem;
						break;
					}
				}
			}
			break;
		case VK_PGUP:
			if( item == GetFirstVisible() )
			{
				count=GetVisibleCount() - 1;
				newItem= GetFirstVisible();
				for( i=0; i< count; i++)
				{
					lastItem= newItem;
					newItem= GetPrevVisible( newItem );
					if( newItem==NULL)
					{
						newItem= lastItem;
						break;
					}
				}
			}
			else
				newItem= GetFirstVisible();
			break;
#endif
		default:
			newItem= NULL;
			break;
		}

		// If something went wrong, like hitting top or bottom,
		// use our saved copy of m_LastSelect
		if( newItem == NULL )
			newItem= item;

		if(newItem != NULL )
		{
			SetSelectState( newItem, TRUE );
			EnsureVisible( newItem );

			// Redraw w/out erase to avoid slow video update
			RedrawWindow( NULL, NULL, RDW_UPDATENOW );
		}
	}
	if (!m_CtrlDown && !m_ShiftDown)
		CTreeCtrl::OnKeyDown(nChar, nRepCnt, nFlags);
}

void CMultiSelTreeCtrl::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if (nChar == VK_RETURN)
	{
		HTREEITEM item = GetSelectedItem(0);
		if (item)
			OnLButtonDblClk(item);
		return;
	}
	CTreeCtrl::OnChar(nChar, nRepCnt, nFlags);
}

void CMultiSelTreeCtrl::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if( nChar == VK_CONTROL )
		m_CtrlDown= FALSE;
	else if( nChar == VK_SHIFT )
	{
		m_ShiftDown= FALSE;
		m_PendingKeyedDeselect= FALSE;
	}
	
	CTreeCtrl::OnKeyUp(nChar, nRepCnt, nFlags);
}

// User mouse-keyed a request to expand the current selection.  Note that
// the selection set can actually shrink if the already selected items are
// not contiguous - per Exploder convention
void CMultiSelTreeCtrl::ExpandSelection( int linesToExpand )
{
	ASSERT( m_SelectionSet.GetSize() > 0 );
	if( m_AnchorItem == NULL )
	{
		ASSERT(0);
		return;
	}

	MainFrame()->WaitAWhileToPoll();	// wait at least 20 secs before autopolling for updates

	HTREEITEM currentItem= m_AnchorItem;

	if( linesToExpand > 0 )
	{
		// Scrolling up
		for( int i=0; i < linesToExpand; i++ )
		{
			HTREEITEM lastGoodItem= currentItem;
			currentItem= GetPrevSiblingItem(currentItem);
			if( currentItem == NULL )
			{
				currentItem= lastGoodItem;
				break;
			}
			else
			{
				if( m_PendingKeyedDeselect )
					DoKeyedDeselect( FALSE );
				if( IsSelected( currentItem ) )
					SetSelectState( lastGoodItem, FALSE );  // Selection shrinking
				else if( OKToAddSelection( currentItem ) )
					SetSelectState( currentItem, TRUE );  // Selection growing
				else
					break;
			}
		}
	}
	else
	{
		// Scrolling down
		for( int i=0; i < (-linesToExpand); i++ )
		{
			HTREEITEM lastGoodItem= currentItem;
			currentItem= GetNextSiblingItem(currentItem);
			if( currentItem == NULL )
			{
				currentItem= lastGoodItem;
				break;
			}
			else
			{
				if( m_PendingKeyedDeselect )
					DoKeyedDeselect( TRUE );

				if( IsSelected( currentItem ) )
					SetSelectState( lastGoodItem, FALSE );  // Selection shrinking
				else if( OKToAddSelection( currentItem ) )
					SetSelectState( currentItem, TRUE );  // Selection growing
				else
					break;
			}

		}
	}

	m_AnchorItem= currentItem;
	EnsureVisible( m_AnchorItem );
}

// When starting a mouse-key select, the anchor point, and all contiguous 
// selections that are not in the path of the current item can remain 
// selected.  All other selections are tossed, like Exploder does.
void CMultiSelTreeCtrl::DoKeyedDeselect( BOOL scrollingDown )
{
	CDWordArray keepSet;
	HTREEITEM currentItem= m_AnchorItem;
	
	// Record the contiguous selection's we're keeping
	keepSet.Add( (DWORD) m_AnchorItem );
	while(1)
	{
		if( scrollingDown )
			currentItem= GetPrevSiblingItem( currentItem);
		else
			currentItem= GetNextSiblingItem( currentItem);

		if( currentItem == NULL || !IsSelected( currentItem ) )
			break;

		keepSet.Add( (DWORD) currentItem );
	}

	// Unselect everything
	//
	int i;
	for( i= m_SelectionSet.GetSize()-1; i >= 0; i-- )
	{
		currentItem= (HTREEITEM) m_SelectionSet.GetAt(i);	
		// Undo any display atts
		SetItemState(currentItem, 0, TVIS_CUT | TVIS_BOLD | TVIS_SELECTED);
	}

	// Then select everything in the keepset
	//
	m_SelectionSet.RemoveAll();
	for( i= keepSet.GetSize()-1; i>=0; i-- )
		SetSelectState( (HTREEITEM) keepSet.GetAt(i), TRUE );

	m_PendingKeyedDeselect= FALSE;
	ShowNbrSelected();
}

BOOL CMultiSelTreeCtrl::ScrollTree( int linesToScroll ) 
{
	BOOL moved= FALSE;
	HTREEITEM firstItem;
	HTREEITEM currentItem;

	if( linesToScroll < 0 )
	{
		// Scrolling down
		firstItem=GetFirstVisibleItem();
		long visible=GetVisibleCount();	
		int count=0;

		for(int i=0; i < visible - linesToScroll; i++)
		{
			currentItem= firstItem;
			firstItem= GetNextVisibleItem(currentItem);
			if( firstItem == NULL )
			{
				firstItem= currentItem;
				break;
			}
			else
				count++;
		}
		if( count >= visible )
			moved= TRUE;
	}
	else if( linesToScroll > 0 )
	{
		// Scrolling up
		firstItem=GetFirstVisibleItem();
		
		for(int i=0; i < linesToScroll; i++)
		{
			currentItem= firstItem;
			firstItem= GetPrevVisibleItem(currentItem);
			if( firstItem == NULL )
			{
				firstItem= currentItem;
				break;
			}
			else
				moved= TRUE;
		}
	}
    else
    {
        ASSERT(0);
        return moved;
    }

	// Turn on redraws
	if(moved)
	{
		EnsureVisible(firstItem);

		// Redraw w/out erase to avoid slow video update
		RedrawWindow( NULL, NULL, RDW_UPDATENOW );
	}

	return moved;
}

void CMultiSelTreeCtrl::OnSelchanged(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
	
	if (pNMTreeView->action == TVC_BYKEYBOARD)
	{
		HTREEITEM currentItem = CTreeCtrl::GetSelectedItem();
		if ((currentItem != NULL) && !IsSelected(currentItem))
		{
			UnselectAll();
			SetSelectState(currentItem, TRUE);
			ASSERT(GetSelectedCount());
		}
	}
	*pResult = 0;
}

BOOL CMultiSelTreeCtrl::PreCreateWindow(CREATESTRUCT& cs)
{
	cs.style|=TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS;
	if (GET_P4REGPTR( )->AlwaysShowFocus())
		cs.style|=TVS_SHOWSELALWAYS;
	return CTreeCtrl::PreCreateWindow(cs);
}

void CMultiSelTreeCtrl::ShowNbrSelected()
{
	if (m_MultiSelect)
		return;
	CString msg;
	int n = m_SelectionSet.GetSize();
	if (n < 2)
		msg = LoadStringResource(IDS_FOR_HELP_PRESS_F1);
	else
		msg.FormatMessage(IDS_NBR_n_ITEMSEL, n);
	MainFrame()->SetMessageText(msg);
}

# Change User Description Committed
#1 19924 YourUncleBob Populate -o //guest/perforce_software/p4win/...
//guest/YourUncleBob/p4win/.....
//guest/perforce_software/p4win/main/gui/MSTreeCtrl.cpp
#1 16169 perforce_software Move files to follow new path scheme for branches.
//guest/perforce_software/p4win/gui/MSTreeCtrl.cpp
#1 8562 Matt Attaway These feet never stop running.

Initial commit of the P4Win source code.  To the best of our knowledge this
compiles and runs using the 2013.3 P4 API and VS 2010. Expect a few changes
as we refine the build process. Please post any build issues to the forums.