//
// 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. |
||