//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Implements a list view for visgroups. Supports drag and drop, and // posts a registered windows message to the list view's parent window // when visgroups are hidden or shown. // //=============================================================================// #include "stdafx.h" #include "hammer.h" #include "GroupList.h" #include "MapDoc.h" #include "MapSolid.h" #include "MapWorld.h" #include "GlobalFunctions.h" #include "VisGroup.h" // memdbgon must be the last include file in a .cpp file!!! #include // // Timer IDs. // enum { TIMER_GROUP_DRAG_SCROLL = 1, }; static const unsigned int g_uToggleStateMsg = ::RegisterWindowMessage(GROUPLIST_MSG_TOGGLE_STATE); static const unsigned int g_uLeftDragDropMsg = ::RegisterWindowMessage(GROUPLIST_MSG_LEFT_DRAG_DROP); static const unsigned int g_uRightDragDropMsg = ::RegisterWindowMessage(GROUPLIST_MSG_RIGHT_DRAG_DROP); static const unsigned int g_uSelChangeMsg = ::RegisterWindowMessage(GROUPLIST_MSG_SEL_CHANGE); BEGIN_MESSAGE_MAP(CGroupList, CTreeCtrl) //{{AFX_MSG_MAP(CGroupList) ON_NOTIFY_REFLECT(TVN_BEGINDRAG, OnBegindrag) ON_NOTIFY_REFLECT(TVN_ENDLABELEDIT, OnEndlabeledit) ON_NOTIFY_REFLECT(TVN_SELCHANGED, OnSelChange) ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() ON_WM_LBUTTONDBLCLK() ON_WM_RBUTTONDOWN() ON_WM_RBUTTONUP() ON_WM_MOUSEMOVE() ON_WM_TIMER() ON_WM_CONTEXTMENU() //}}AFX_MSG_MAP END_MESSAGE_MAP() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CGroupList::CGroupList(void) { m_pDragImageList = NULL; m_hDragItem = NULL; m_bRButtonDown = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CGroupList::~CGroupList(void) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CGroupList::EnableChecks(void) { if (!m_cNormalImageList.GetSafeHandle()) { m_cNormalImageList.Create(IDB_VISGROUPSTATUS, 16, 1, RGB(255, 255, 255)); m_cNormalImageList.SetOverlayImage(1, 1); m_cNormalImageList.SetOverlayImage(2, 2); } CTreeCtrl::SetImageList(&m_cNormalImageList, TVSIL_NORMAL); } //----------------------------------------------------------------------------- // Purpose: // Input : pVisGroup - // hItemParent - //----------------------------------------------------------------------------- void CGroupList::AddVisGroupRecursive(CVisGroup *pVisGroup, HTREEITEM hItemParent) { HTREEITEM hItem = InsertItem(pVisGroup->GetName(), hItemParent, TVI_LAST); if (hItem != NULL) { SetItemData(hItem, (DWORD)pVisGroup); // Add the item to our flattened list. // VisGroupTreeItem_t item; // item.pVisGroup = pVisGroup; // item.hItem = hItem; // m_TreeItems.AddToTail(item); m_VisGroups.AddToTail(pVisGroup); int nCount = pVisGroup->GetChildCount(); for (int i = 0; i < nCount; i++) { CVisGroup *pChild = pVisGroup->GetChild(i); AddVisGroupRecursive(pChild, hItem); } } } //----------------------------------------------------------------------------- // Purpose: // Input : pGroup - //----------------------------------------------------------------------------- void CGroupList::AddVisGroup(CVisGroup *pGroup) { AddVisGroupRecursive(pGroup, TVI_ROOT); } static void UnsetItemData_R( CTreeCtrl *pCtrl, HTREEITEM hItem ) { pCtrl->SetItemData( hItem, 0 ); HTREEITEM hChildItem = pCtrl->GetChildItem( hItem ); while( hChildItem != NULL ) { UnsetItemData_R( pCtrl, hChildItem ); hChildItem = pCtrl->GetNextItem(hChildItem, TVGN_NEXT); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CGroupList::DeleteAllItems(void) { // Un-set all item data because sometimes during a delete it'll trigger selection change notifications // which might crash things later. if ( GetSafeHwnd() && m_VisGroups.Count() > 0 ) { UnsetItemData_R( this, TVI_ROOT ); } DeleteItem(TVI_ROOT); m_VisGroups.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CGroupList::EnsureVisible(CVisGroup *pVisGroup) { //DBG("EnsureVisible: %s\n", pVisGroup->GetName()); HTREEITEM hItem = FindVisGroupItem(pVisGroup); if (hItem) { CTreeCtrl::EnsureVisible(hItem); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CGroupList::ExpandRecursive(HTREEITEM hItem) { if (hItem) { Expand(hItem, TVE_EXPAND); if (ItemHasChildren(hItem)) { HTREEITEM hChildItem = GetChildItem(hItem); while (hChildItem != NULL) { ExpandRecursive(hChildItem); hChildItem = GetNextItem(hChildItem, TVGN_NEXT); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CGroupList::ExpandAll(void) { HTREEITEM hItem = GetRootItem(); while (hItem) { ExpandRecursive(hItem); hItem = GetNextItem(hItem, TVGN_NEXT); } } //----------------------------------------------------------------------------- // Purpose: Returns the tree item in the given subtree associated with the given // visgroup, NULL if none. //----------------------------------------------------------------------------- HTREEITEM CGroupList::FindVisGroupItemRecursive(HTREEITEM hItem, CVisGroup *pVisGroup) { if (hItem) { CVisGroup *pVisGroupCheck = (CVisGroup *)GetItemData(hItem); if (pVisGroupCheck == pVisGroup) { return hItem; } if (ItemHasChildren(hItem)) { HTREEITEM hChildItem = GetChildItem(hItem); while (hChildItem != NULL) { HTREEITEM hFoundItem = FindVisGroupItemRecursive(hChildItem, pVisGroup); if (hFoundItem) { return hFoundItem; } hChildItem = GetNextItem(hChildItem, TVGN_NEXT); } } } return NULL; } //----------------------------------------------------------------------------- // Purpose: Returns the tree item associated with the given visgroup, NULL if none. //----------------------------------------------------------------------------- HTREEITEM CGroupList::FindVisGroupItem(CVisGroup *pVisGroup) { HTREEITEM hItem = GetRootItem(); while (hItem) { HTREEITEM hFound = FindVisGroupItemRecursive(hItem, pVisGroup); if (hFound) { return hFound; } hItem = GetNextItem(hItem, TVGN_NEXT); } return NULL; } //----------------------------------------------------------------------------- // Purpose: Returns the currently selected visgroup in the tree control. //----------------------------------------------------------------------------- CVisGroup *CGroupList::GetSelectedVisGroup(void) { HTREEITEM hItem = GetSelectedItem(); if (hItem) { return (CVisGroup *)GetItemData(hItem); } return NULL; } //----------------------------------------------------------------------------- // Purpose: // Input : nFlags - // point - //----------------------------------------------------------------------------- void CGroupList::OnLButtonDown(UINT nFlags, CPoint point) { unsigned int uFlags; HTREEITEM hItemHit = HitTest(point, &uFlags); if (hItemHit != NULL) { if (uFlags & TVHT_ONITEMICON) { // Don't forward to the base if they clicked on the check box. // This prevents undesired expansion/collapse of tree. return; } } CTreeCtrl::OnLButtonDown(nFlags, point); } //----------------------------------------------------------------------------- // Purpose: // Input : nFlags - // point - //----------------------------------------------------------------------------- void CGroupList::OnLButtonUp(UINT nFlags, CPoint point) { KillTimer(TIMER_GROUP_DRAG_SCROLL); ReleaseCapture(); if (!m_hDragItem) { unsigned int uFlags; HTREEITEM hItemHit = HitTest(point, &uFlags); if (hItemHit != NULL) { if (uFlags & TVHT_ONITEMICON) { // // Notify our parent window that this item's state has changed. // CWnd *pwndParent = GetParent(); if (pwndParent != NULL) { int nCheckState = GetCheck(hItemHit); if (!nCheckState) { nCheckState = 1; } else { nCheckState = 0; } CVisGroup *pGroup = (CVisGroup *)GetItemData(hItemHit); pwndParent->PostMessage(g_uToggleStateMsg, (WPARAM)pGroup, nCheckState); } // Don't forward to the base if they clicked on the check box. // This prevents undesired expansion/collapse of tree. return; } } CTreeCtrl::OnLButtonUp(nFlags, point); return; } Drop(DROP_LEFT, nFlags, point); } //----------------------------------------------------------------------------- // Purpose: // Input : nFlags - // point - //----------------------------------------------------------------------------- void CGroupList::OnLButtonDblClk(UINT nFlags, CPoint point) { unsigned int uFlags; HTREEITEM hItemHit = HitTest(point, &uFlags); if (hItemHit != NULL) { if (uFlags & TVHT_ONITEMICON) { // Don't forward to the base if they clicked on the check box. // This prevents undesired expansion/collapse of tree. return; } } CTreeCtrl::OnLButtonDblClk(nFlags, point); } //----------------------------------------------------------------------------- // Purpose: Forwards selection change notifications to our parent window. // Input : pNMHDR - // pResult - //----------------------------------------------------------------------------- void CGroupList::OnSelChange(NMHDR *pNMHDR, LRESULT *pResult) { CWnd *pwndParent = GetParent(); if (pwndParent != NULL) { pwndParent->PostMessage(g_uSelChangeMsg, 0, 0); } } //----------------------------------------------------------------------------- // Purpose: // Input : pNMHDR - // pResult - //----------------------------------------------------------------------------- void CGroupList::OnEndlabeledit(NMHDR *pNMHDR, LRESULT *pResult) { NMTVDISPINFO *pInfo = (NMTVDISPINFO *)pNMHDR; if (!pInfo->item.pszText) return; CVisGroup *pVisGroup = (CVisGroup *)GetItemData(pInfo->item.hItem); Assert(pVisGroup); if (!pVisGroup) return; pVisGroup->SetName(pInfo->item.pszText); pResult[0] = TRUE; } //----------------------------------------------------------------------------- // Purpose: Begins dragging an item in the visgroup list. The drag image is // created and anchored relative to the mouse cursor. // Input : pNMHDR - // pResult - //----------------------------------------------------------------------------- void CGroupList::OnBegindrag(NMHDR *pNMHDR, LRESULT *pResult) { NMTREEVIEW *ptv = (NMTREEVIEW *)pNMHDR; BeginDrag(ptv->ptDrag, ptv->itemNew.hItem); *pResult = 0; } //----------------------------------------------------------------------------- // Purpose: // Input : pt - // hItem - //----------------------------------------------------------------------------- void CGroupList::BeginDrag(CPoint point, HTREEITEM hItem) { m_hDragItem = hItem; if (m_hDragItem) { m_pDragImageList = CreateDragImage(m_hDragItem); if (m_pDragImageList) { CPoint ptHotSpot(0, 0); m_pDragImageList->BeginDrag(0, ptHotSpot); m_pDragImageList->DragEnter(this, point); SelectDropTarget(NULL); } // Timer handles scrolling the list control when dragging outside the window bounds. SetTimer(TIMER_GROUP_DRAG_SCROLL, 300, NULL); SetCapture(); } } //----------------------------------------------------------------------------- // Purpose: // Input : nFlags - // point - //----------------------------------------------------------------------------- void CGroupList::OnRButtonDown(UINT nFlags, CPoint point) { m_bRButtonDown = true; m_ptRButtonDown = point; m_hDragItem = NULL; SetCapture(); // Chaining to the base class causes us never to receive the button up message // for a right click without drag, so we don't do that. //CTreeCtrl::OnRButtonDown(nFlags, point); } //----------------------------------------------------------------------------- // Purpose: // Input : pWnd - // point - //----------------------------------------------------------------------------- void CGroupList::OnContextMenu(CWnd *pWnd, CPoint point) { KillTimer(TIMER_GROUP_DRAG_SCROLL); ReleaseCapture(); m_bRButtonDown = false; if (!m_hDragItem) { CTreeCtrl::OnContextMenu(pWnd, point); return; } Drop(DROP_RIGHT, 0, point); } //----------------------------------------------------------------------------- // Purpose: // Input : nFlags - // point - //----------------------------------------------------------------------------- void CGroupList::OnRButtonUp(UINT nFlags, CPoint point) { KillTimer(TIMER_GROUP_DRAG_SCROLL); ReleaseCapture(); m_bRButtonDown = false; if (!m_hDragItem) { CTreeCtrl::OnRButtonUp(nFlags, point); return; } Drop(DROP_RIGHT, nFlags, point); } //----------------------------------------------------------------------------- // Purpose: // Input : eDropType - // nFlags - // point - //----------------------------------------------------------------------------- void CGroupList::Drop(DropType_t eDropType, UINT nFlags, CPoint point) { SelectDropTarget(NULL); HTREEITEM hDragItem = m_hDragItem; m_hDragItem = NULL; // // We are dragging. Drop! // if (m_pDragImageList) { m_pDragImageList->DragLeave(this); m_pDragImageList->EndDrag(); delete m_pDragImageList; m_pDragImageList = NULL; } // // Get the group that we were dragging. // CVisGroup *pDragGroup = (CVisGroup *)GetItemData(hDragItem); // // Determine what group was dropped onto. // HTREEITEM hDropItem = HitTest(point); if (hDropItem == hDragItem) { return; } CVisGroup *pDropGroup = NULL; if (hDropItem) { pDropGroup = (CVisGroup *)GetItemData(hDropItem); } if (pDragGroup == pDropGroup) { // Shouldn't happen, but just in case. return; } CWnd *pwndParent = GetParent(); if (pwndParent != NULL) { if (eDropType == DROP_LEFT) { pwndParent->PostMessage(g_uLeftDragDropMsg, (WPARAM)pDragGroup, (LPARAM)pDropGroup); } else { pwndParent->PostMessage(g_uRightDragDropMsg, (WPARAM)pDragGroup, (LPARAM)pDropGroup); } } } //----------------------------------------------------------------------------- // Purpose: // Input : nIDEvent - //----------------------------------------------------------------------------- void CGroupList::OnTimer(UINT nIDEvent) { //DBG("OnTimer\n"); switch (nIDEvent) { case TIMER_GROUP_DRAG_SCROLL: { CPoint point; GetCursorPos(&point); CRect rect; GetWindowRect(&rect); if (!rect.PtInRect(point)) { if (point.y > rect.bottom) { // scroll down int nCount = GetVisibleCount(); HTREEITEM hItem = GetFirstVisibleItem(); for (int i = 1; i < nCount; i++) { hItem = GetNextVisibleItem(hItem); } hItem = GetNextVisibleItem(hItem); if (hItem) { CTreeCtrl::EnsureVisible(hItem); } } else if (point.y < rect.top) { HTREEITEM hItem = GetFirstVisibleItem(); HTREEITEM hPrevVisible = this->GetPrevVisibleItem(hItem); if (hPrevVisible) { // scroll up CTreeCtrl::EnsureVisible(hPrevVisible); } } } break; } default: { CTreeCtrl::OnTimer(nIDEvent); } } } //----------------------------------------------------------------------------- // Purpose: // Input : nFlags - // point - //----------------------------------------------------------------------------- void CGroupList::OnMouseMove(UINT nFlags, CPoint point) { CTreeCtrl::OnMouseMove(nFlags, point); if (m_bRButtonDown && !m_hDragItem && (point.x != m_ptRButtonDown.x) && (point.y != m_ptRButtonDown.y)) { // First mouse move since a right button down. Start dragging. HTREEITEM hItem = HitTest(m_ptRButtonDown); BeginDrag(point, hItem); } if (!m_hDragItem) { return; } if (m_pDragImageList) { m_pDragImageList->DragMove(point); } // // Highlight the item we hit. // HTREEITEM hItem = HitTest(point); if (hItem == GetDropHilightItem()) { return; } // hide image first if (m_pDragImageList) { m_pDragImageList->DragLeave(this); m_pDragImageList->DragShowNolock(FALSE); } SelectDropTarget(hItem); if (m_pDragImageList) { m_pDragImageList->DragEnter(this, point); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CGroupList::SelectVisGroup(CVisGroup *pVisGroup) { //DBG("SelectVisGroup: %s\n", pVisGroup->GetName()); HTREEITEM hItem = FindVisGroupItem(pVisGroup); if (hItem) { Select(hItem, TVGN_CARET); } } //----------------------------------------------------------------------------- // Purpose: Sets the check status for the given group. // Input : pVisGroup - // nCheckState - 0=not checked, 1=checked, -1=gray check (undefined) //----------------------------------------------------------------------------- void CGroupList::SetCheck(CVisGroup *pVisGroup, int nCheckState) { HTREEITEM hItem = FindVisGroupItem(pVisGroup); if (hItem) { UINT uState = INDEXTOOVERLAYMASK(0); if (nCheckState == 1) { uState = INDEXTOOVERLAYMASK(1); } else if (nCheckState != 0) { uState = INDEXTOOVERLAYMASK(2); } SetItemState(hItem, uState, TVIS_OVERLAYMASK); } } //----------------------------------------------------------------------------- // Purpose: Returns the check state for the given visgroup. // Input : pVisGroup - //----------------------------------------------------------------------------- int CGroupList::GetCheck(CVisGroup *pVisGroup) { HTREEITEM hItem = FindVisGroupItem(pVisGroup); if (hItem) { return GetCheck(hItem); } return 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CGroupList::GetCheck(HTREEITEM hItem) { UINT uState = (GetItemState(hItem, TVIS_OVERLAYMASK) & TVIS_OVERLAYMASK); if (uState == INDEXTOOVERLAYMASK(1)) { return 1; } else if (uState == INDEXTOOVERLAYMASK(0)) { return 0; } return -1; } //----------------------------------------------------------------------------- // Purpose: Returns the number of visgroups in the whole tree. //----------------------------------------------------------------------------- int CGroupList::GetVisGroupCount() { return m_VisGroups.Count(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CVisGroup *CGroupList::GetVisGroup(int nIndex) { return m_VisGroups.Element(nIndex); } //----------------------------------------------------------------------------- // Purpose: Updates the tree control item text with the new group name. //----------------------------------------------------------------------------- void CGroupList::UpdateVisGroup(CVisGroup *pVisGroup) { HTREEITEM hItem = FindVisGroupItem(pVisGroup); if (hItem) { SetItemText(hItem, pVisGroup->GetName()); } } int CGroupList::GetGroupPairCount(void) { return m_GroupPairs.Count(); } void CGroupList::SaveVisGroupExpandStates() { for ( int i = 0; i < GetVisGroupCount(); i++ ) { CVisGroup *thisGroup = GetVisGroup(i); GroupListPair newPair; for ( int j = 0; j < GetGroupPairCount(); j++ ) { GroupListPair thisPair = m_GroupPairs.Element( j ); if ( thisGroup == thisPair.pVisGroup ) { m_GroupPairs.Remove( j ); break; } } HTREEITEM thisItem = FindVisGroupItem( thisGroup ); newPair.pVisGroup = thisGroup; newPair.bExpanded = false; if ( thisItem && (GetItemState( thisItem, TVIS_EXPANDED) & TVIS_EXPANDED) ) { newPair.bExpanded = true; } m_GroupPairs.AddToTail( newPair ); } } void CGroupList::RestoreVisGroupExpandStates() { ExpandAll(); for ( int i = 0; i < GetGroupPairCount(); i++ ) { GroupListPair thisPair = m_GroupPairs.Element( i ); HTREEITEM thisItem = FindVisGroupItem( thisPair.pVisGroup ); if ( thisItem ) { if ( thisPair.bExpanded ) { Expand(thisItem, TVE_EXPAND); } else { Expand( thisItem, TVE_COLLAPSE ); } } } }