// // 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); }