// TreeCtrl.cpp : implementation file // //+------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1992 - 1999 // // File: amctreectrl.cpp // // Contents: AMC Tree control implementation // // History: 16-Jul-96 WayneSc Created // // //-------------------------------------------------------------------------- #include "stdafx.h" #include "AMCDoc.h" // AMC Console Document #include "amcview.h" #include "childfrm.h" #include "macros.h" #include "AMCPriv.h" #include "AMC.h" #include "mainfrm.h" #include "TreeCtrl.h" #include "resource.h" #include "guidhelp.h" // LoadRootDisplayName #include "histlist.h" #include "websnk.h" #include "webctrl.h" #include "..\inc\mmcutil.h" #include "amcmsgid.h" #include "resultview.h" #include "eventlock.h" extern "C" UINT dbg_count; //############################################################################ //############################################################################ // // Traces // //############################################################################ //############################################################################ #ifdef DBG CTraceTag tagTree(TEXT("Tree View"), TEXT("Tree View")); #endif //DBG //############################################################################ //############################################################################ // // Implementation of class CTreeViewMap // //############################################################################ //############################################################################ /*+-------------------------------------------------------------------------* * * CTreeViewMap::ScOnItemAdded * * PURPOSE: Called when an item is added. Indexes the item. * * PARAMETERS: * TVINSERTSTRUCT * pTVInsertStruct : * HTREEITEM hti : * HMTNODE hMTNode : * * RETURNS: * SC * *+-------------------------------------------------------------------------*/ SC CTreeViewMap::ScOnItemAdded (TVINSERTSTRUCT *pTVInsertStruct, HTREEITEM hti, HMTNODE hMTNode) { DECLARE_SC(sc, TEXT("CTreeViewMap::ScOnItemAdded")); // validate parameters sc = ScCheckPointers(pTVInsertStruct); if(sc) return sc; if(!hti || !hMTNode) return (sc = E_INVALIDARG); // create a new map info structure. TreeViewMapInfo *pMapInfo = new TreeViewMapInfo; if(!pMapInfo) return (sc = E_OUTOFMEMORY); // fill in the values pMapInfo->hNode = CAMCTreeView::NodeFromLParam (pTVInsertStruct->item.lParam); pMapInfo->hti = hti; pMapInfo->hMTNode = hMTNode; // set up the indexes ASSERT(m_hMTNodeMap.find(pMapInfo->hMTNode) == m_hMTNodeMap.end()); ASSERT(m_hNodeMap.find(pMapInfo->hNode) == m_hNodeMap.end()); m_hMTNodeMap [pMapInfo->hMTNode] = pMapInfo; m_hNodeMap [pMapInfo->hNode] = pMapInfo; return sc; } /*+-------------------------------------------------------------------------* * * CTreeViewMap::ScOnItemDeleted * * PURPOSE: Called when a tree item is deleted. Removes the item from the * indexes. * * PARAMETERS: * HNODE hNode : * HTREEITEM hti : * * RETURNS: * SC * *+-------------------------------------------------------------------------*/ SC CTreeViewMap::ScOnItemDeleted (HNODE hNode, HTREEITEM hti) { DECLARE_SC(sc, TEXT("CTreeViewMap::ScOnItemDeleted")); // validate parameters sc = ScCheckPointers((LPVOID) hNode, (LPVOID) hti); if(sc) return sc; // remove the TreeViewMapInfo pointer from all the maps HNodeLookupMap::iterator iter = m_hNodeMap.find(hNode); if(iter == m_hNodeMap.end()) return (sc = E_UNEXPECTED); TreeViewMapInfo *pMapInfo = iter->second; // find the map info structure. if(!pMapInfo) return (sc = E_UNEXPECTED); HMTNODE hMTNode = pMapInfo->hMTNode; #ifdef DBG // verify that the same structure is pointed to by the other maps. ASSERT(m_hMTNodeMap.find(hMTNode)->second == pMapInfo); #endif m_hMTNodeMap.erase(hMTNode); m_hNodeMap.erase(hNode); // finally delete the TreeViewMapInfo structure delete pMapInfo; return sc; } // Fast lookup methods /*+-------------------------------------------------------------------------* * * CTreeViewMap::ScGetHNodeFromHMTNode * * PURPOSE: Quickly (log n time) retrieves the HNODE for an HMTNODE. * * PARAMETERS: * HMTNODE hMTNode : * ou t : * * RETURNS: * SC * *+-------------------------------------------------------------------------*/ SC CTreeViewMap::ScGetHNodeFromHMTNode (HMTNODE hMTNode, /*out*/ HNODE* phNode) // fast conversion from hNode to hMTNode. { DECLARE_SC(sc, TEXT("CTreeViewMap::ScGetHNode")); // validate parameters sc = ScCheckPointers((LPVOID) hMTNode, phNode); if(sc) return sc; // find the mapinfo structure. HMTNodeLookupMap::iterator iter = m_hMTNodeMap.find(hMTNode); if(iter == m_hMTNodeMap.end()) return (sc = ScFromMMC(IDS_NODE_NOT_FOUND)); TreeViewMapInfo *pMapInfo = iter->second; // find the map info structure. if(!pMapInfo) return (sc = E_UNEXPECTED); *phNode = pMapInfo->hNode; return sc; } /*+-------------------------------------------------------------------------* * * CTreeViewMap::ScGetHTreeItemFromHNode * * PURPOSE: Quickly (log n time) retrieves the HTREEITEM for an HNODE. * * PARAMETERS: * HNODE hNode : * ou t : * * RETURNS: * SC * *+-------------------------------------------------------------------------*/ SC CTreeViewMap::ScGetHTreeItemFromHNode(HNODE hNode, /*out*/ HTREEITEM* phti) // fast conversion from HTREEITEM to HNODE { DECLARE_SC(sc, TEXT("CTreeViewMap::ScGetHTreeItem")); // validate parameters sc = ScCheckPointers((LPVOID) hNode, phti); if(sc) return sc; // find the mapinfo structure. HNodeLookupMap::iterator iter = m_hNodeMap.find(hNode); if(iter == m_hNodeMap.end()) return (sc = E_UNEXPECTED); TreeViewMapInfo *pMapInfo = iter->second; // find the map info structure. if(!pMapInfo) return (sc = E_UNEXPECTED); *phti = pMapInfo->hti; return sc; } /*+-------------------------------------------------------------------------* * * CTreeViewMap::ScGetHTreeItemFromHMTNode * * PURPOSE: Quickly (log n time) retrieves the HTREEITEM for an HMTNODE. * * PARAMETERS: * HMTNODE hMTNode : * ou t : * * RETURNS: * SC * *+-------------------------------------------------------------------------*/ SC CTreeViewMap::ScGetHTreeItemFromHMTNode(HMTNODE hMTNode, /*out*/ HTREEITEM* phti) // fast conversion from HMTNode to HTREEITEM. { DECLARE_SC(sc, TEXT("CTreeViewMap::ScGetHTreeItem")); // validate parameters //sc = ScCheckPointers(hMTNode, phti); if(sc) return sc; // find the mapinfo structure. HMTNodeLookupMap::iterator iter = m_hMTNodeMap.find(hMTNode); if(iter == m_hMTNodeMap.end()) return (sc = E_UNEXPECTED); TreeViewMapInfo *pMapInfo = iter->second; // find the map info structure. if(!pMapInfo) return (sc = E_UNEXPECTED); *phti = pMapInfo->hti; return sc; } //############################################################################ //############################################################################ // // Implementation of class CAMCTreeView // //############################################################################ //############################################################################ ///////////////////////////////////////////////////////////////////////////// // CAMCTreeView DEBUG_DECLARE_INSTANCE_COUNTER(CAMCTreeView); CAMCTreeView::CAMCTreeView() : m_FontLinker (this) { DEBUG_INCREMENT_INSTANCE_COUNTER(CAMCTreeView); m_fInCleanUp = FALSE; m_fInExpanding = FALSE; m_pAMCView = NULL; SetHasList(TRUE); SetTempSelectedItem (NULL); ASSERT (!IsTempSelectionActive()); AddObserver(static_cast(m_treeMap)); // add an observer to this control. } CAMCTreeView::~CAMCTreeView() { DEBUG_DECREMENT_INSTANCE_COUNTER(CAMCTreeView); // Smart pointer are released during their destructor } IMPLEMENT_DYNCREATE(CAMCTreeView, CTreeView) BEGIN_MESSAGE_MAP(CAMCTreeView, CTreeView) //{{AFX_MSG_MAP(CAMCTreeView) ON_WM_CREATE() ON_NOTIFY_REFLECT(TVN_SELCHANGED, OnSelChanged) ON_NOTIFY_REFLECT(TVN_SELCHANGING, OnSelChanging) ON_NOTIFY_REFLECT(TVN_GETDISPINFO, OnGetDispInfo) ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, OnItemExpanding) ON_NOTIFY_REFLECT(TVN_ITEMEXPANDED, OnItemExpanded) ON_WM_DESTROY() ON_WM_KEYDOWN() ON_WM_SYSKEYDOWN() ON_WM_SYSCHAR() ON_WM_MOUSEACTIVATE() ON_WM_SETFOCUS() ON_NOTIFY_REFLECT(TVN_BEGINDRAG, OnBeginDrag) ON_NOTIFY_REFLECT(TVN_BEGINRDRAG, OnBeginRDrag) ON_WM_KILLFOCUS() //}}AFX_MSG_MAP ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw) END_MESSAGE_MAP() /*+-------------------------------------------------------------------------* * CAMCTreeView::ScSetTempSelection * * Applies temporary selection to the specified HTREEITEM. *--------------------------------------------------------------------------*/ SC CAMCTreeView::ScSetTempSelection (HTREEITEM htiTempSelect) { AFX_MANAGE_STATE (AfxGetAppModuleState()); DECLARE_SC (sc, _T("CAMCTreeView::ScSetTempSelection")); /* * Don't use ScSetTempSelection(NULL) to remove temporary selection; * use ScRemoveTempSelection instead. */ ASSERT (htiTempSelect != NULL); if (htiTempSelect == NULL) return (sc = E_FAIL); /* * If this fails, you must first call ScRemoveTempSelection to remove * the temporary selection state (TVIS_SELECTED) from the current * temporary selection. */ ASSERT (!IsTempSelectionActive()); SetTempSelectedItem (htiTempSelect); ASSERT (GetTempSelectedItem() == htiTempSelect); HTREEITEM htiSelected = GetSelectedItem(); if (htiSelected != htiTempSelect) { SetItemState (htiSelected, 0, TVIS_SELECTED); SetItemState (htiTempSelect, TVIS_SELECTED, TVIS_SELECTED); } ASSERT (IsTempSelectionActive()); return (sc); } /*+-------------------------------------------------------------------------* * CAMCTreeView::ScRemoveTempSelection * * Removes the temporary selection from the current temporarily selected * item, if there is one, and restores it to the item that was selected * when the temp selection was applied. *--------------------------------------------------------------------------*/ SC CAMCTreeView::ScRemoveTempSelection () { AFX_MANAGE_STATE (AfxGetAppModuleState()); DECLARE_SC (sc, _T("CAMCTreeView::ScRemoveTempSelection")); if (!IsTempSelectionActive()) return (sc = S_FALSE); HTREEITEM htiTempSelect = GetTempSelectedItem(); HTREEITEM htiSelected = GetSelectedItem(); if (htiTempSelect != htiSelected) { SetItemState (htiTempSelect, 0, TVIS_SELECTED); SetItemState (htiSelected, TVIS_SELECTED, TVIS_SELECTED); } SetTempSelectedItem (NULL); ASSERT (!IsTempSelectionActive()); return (sc); } /*+-------------------------------------------------------------------------* * CAMCTreeView::ScReselect * * *--------------------------------------------------------------------------*/ SC CAMCTreeView::ScReselect () { AFX_MANAGE_STATE (AfxGetAppModuleState()); NM_TREEVIEW nmtv; nmtv.itemOld.hItem = nmtv.itemNew.hItem = GetSelectedItem(); if (nmtv.itemOld.hItem) { nmtv.itemOld.lParam = nmtv.itemNew.lParam = GetItemData(nmtv.itemOld.hItem); LRESULT lUnused; OnSelChangingWorker (&nmtv, &lUnused); OnSelChangedWorker (&nmtv, &lUnused); } return (S_OK); } ///////////////////////////////////////////////////////////////////////////// // CAMCTreeView message handlers BOOL CAMCTreeView::PreCreateWindow(CREATESTRUCT& cs) { cs.style |= TVS_EDITLABELS | TVS_HASBUTTONS | TVS_HASLINES | TVS_SHOWSELALWAYS; cs.dwExStyle |= WS_EX_CLIENTEDGE; // do not paint over the children cs.style |= WS_CLIPCHILDREN; return CTreeView::PreCreateWindow(cs); } INodeCallback* CAMCTreeView::GetNodeCallback() { return m_pAMCView->GetNodeCallback(); } inline IScopeTreeIter* CAMCTreeView::GetScopeIterator() { return m_pAMCView->GetScopeIterator(); } inline IScopeTree* CAMCTreeView::GetScopeTree() { return m_pAMCView->GetScopeTree(); } void CAMCTreeView::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult) { HRESULT hr; TV_DISPINFO* ptvdi = (TV_DISPINFO*)pNMHDR; HNODE hNode = NodeFromLParam (ptvdi->item.lParam); ASSERT(m_pAMCView != NULL); INodeCallback* spCallback = GetNodeCallback(); ASSERT(spCallback != NULL); if (hNode) { if (ptvdi->item.mask & TVIF_TEXT) { tstring strName; hr = spCallback->GetDisplayName(hNode, strName); if (hr != S_OK) { ptvdi->item.pszText[0] = _T('\0'); ASSERT(FALSE); } else { // copy the text, but not too much ASSERT (!IsBadWritePtr (ptvdi->item.pszText, ptvdi->item.cchTextMax)); _tcsncpy (ptvdi->item.pszText, strName.data(), ptvdi->item.cchTextMax); /* * _tcsncpy won't terminate the destination if the * source is bigger than the buffer; make sure the * string is NULL-terminated */ ptvdi->item.pszText[ptvdi->item.cchTextMax-1] = _T('\0'); /* * If this is the selected item and it's text has changed, * fire an event so observers can know. */ if ((m_strSelectedItemText != strName.data()) && (GetSelectedItem() == ptvdi->item.hItem)) { m_strSelectedItemText = strName.data(); SC sc = ScFireEvent (CTreeViewObserver::ScOnSelectedItemTextChanged, (LPCTSTR) m_strSelectedItemText); if (sc) sc.TraceAndClear(); } } } int nImage, nSelectedImage; hr = spCallback->GetImages(hNode, &nImage, &nSelectedImage); #ifdef DBG if (hr != S_OK) { ASSERT(nImage == 0 && nSelectedImage == 0); } #endif if (ptvdi->item.mask & TVIF_IMAGE) ptvdi->item.iImage = nImage; if (ptvdi->item.mask & TVIF_SELECTEDIMAGE) ptvdi->item.iSelectedImage = nSelectedImage; // We will get this request once, the first time the scope item comes into view if (ptvdi->item.mask & TVIF_CHILDREN) { ptvdi->item.cChildren = (spCallback->IsExpandable(hNode) != S_FALSE); // set children to fixed value, to avoid any more callbacks SetCountOfChildren(ptvdi->item.hItem, ptvdi->item.cChildren); } } else { ASSERT(0 && "OnGetDispInfo(HNODE is NULL)"); } *pResult = 0; } // // Description: This method will set the folders Button(+/-) on or // of depending on the value of bState // // Parameters: // hItem: the tree item affected // bState: TRUE = Enable for folder to show it has children // FALSE = Disable for folder to show it has NO children // void CAMCTreeView::SetButton(HTREEITEM hItem, BOOL bState) { ASSERT(hItem != NULL); TV_ITEM item; ZeroMemory(&item, sizeof(item)); item.hItem = hItem; item.mask = TVIF_HANDLE | TVIF_CHILDREN; item.cChildren = bState; SetItem(&item); } //+------------------------------------------------------------------- // // Member: CAMCTreeView::ExpandNode // // Synopsis: This method populates hItem's(parent folder) children into // the tree control. // In the first phase, Expand notifications are sent to the snapin // which causes all children to be inserted to the master scope // tree. // In the second phase, we step through these children and insert // them to the tree controls. // For performance reasons, we walk the child list in reverse order // (last child to first child) inserting the current item at the // first position. // If we were to walk the child list in normal order (first to last // child) inserting the current item at the last position -- the // underlying tree control takes time that grows exponentially in // the number of children (as opposed to linearly). // // Arguments: hItem: the parent // // Returns: TRUE on success and FALSE on failure // //-------------------------------------------------------------------- BOOL CAMCTreeView::ExpandNode(HTREEITEM hItem) { TRACE_METHOD(CAMCTreeView, ExpandNode); // not frequently, but... snap-in will display the dialog, dismissing that will // activate the frame again. Tree item will be automatically selected if there // is none selected yet. Following will prevent the recursion. if (m_fInExpanding) return FALSE; HRESULT hr; // Get the HNODE from the tree node HNODE hNode = GetItemNode(hItem); ASSERT(hNode != NULL); ASSERT(m_pAMCView != NULL); HMTNODE hMTNode; INodeCallback* spCallback = GetNodeCallback(); ASSERT(spCallback != NULL); hr = spCallback->GetMTNode(hNode, &hMTNode); ASSERT(hr == S_OK); if (hr == S_OK) { // The notify will return S_FALSE to indicate already expanded // or E_xxxx to indicate an error. hr = spCallback->Notify(hNode, NCLBK_EXPAND, FALSE, 0); if (hr == S_FALSE) { __try { m_fInExpanding = TRUE; hr = spCallback->Notify(hNode, NCLBK_EXPAND, TRUE, 0); } __finally { m_fInExpanding = FALSE; } if (SUCCEEDED(hr)) { IScopeTreeIter* spIterator = m_pAMCView->GetScopeIterator(); hr = spIterator->SetCurrent(hMTNode); HMTNODE hMTChildNode; // Get the last child for the current iterator node: // (Starting node for walking the child list in reverse order) if ((spIterator->LastChild(&hMTChildNode) == S_OK) && (hMTChildNode)) // Do nothing if last child is NULL { IScopeTree* spScopeTree = m_pAMCView->GetScopeTree(); HNODE hNewNode; // Set the last child as the "current" iterator node if (spIterator->SetCurrent(hMTChildNode) == S_OK) { HMTNODE hCurrentChildNode = hMTChildNode; do { // Insert current node into the tree control spScopeTree->CreateNode(hCurrentChildNode, reinterpret_cast(m_pAMCView->GetViewData()), FALSE, &hNewNode); #include "pushwarn.h" #pragma warning(disable: 4552) // "!=" operator has no effect // Insert at the first position VERIFY(InsertNode(hItem, hNewNode, TVI_FIRST) != NULL); #include "popwarn.h" // give 'em a chance to do the "preload" thing, if applicable spCallback->PreLoad (hNewNode); // Traverse the child list in reverse order: // Set current iterator node to the previous sibling hr = spIterator->Prev(&hCurrentChildNode); if(FAILED(hr)) return FALSE; // child list completely traversed. if (!hCurrentChildNode) break; } while (1); } } spCallback->Notify(hNode, NCLBK_EXPANDED, 0, 0); } } } return SUCCEEDED(hr); } HTREEITEM CAMCTreeView::InsertNode(HTREEITEM hParent, HNODE hNode, HTREEITEM hInsertAfter) { DECLARE_SC(sc, TEXT("CAMCTreeView::InsertNode")); ASSERT(hParent != NULL); ASSERT(hNode != NULL); HRESULT hr; TV_INSERTSTRUCT tvInsertStruct; TV_ITEM& item = tvInsertStruct.item; ZeroMemory(&tvInsertStruct, sizeof(tvInsertStruct)); // Insert item at the end of the hItem chain tvInsertStruct.hParent = hParent; tvInsertStruct.hInsertAfter = hInsertAfter; item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN | TVIF_PARAM | TVIF_TEXT; item.pszText = LPSTR_TEXTCALLBACK; item.lParam = LParamFromNode (hNode); INodeCallback* spCallback = GetNodeCallback(); ASSERT(spCallback != NULL); // Set callback mode for children, so we don't have to determine this // until the scope item becomes visible (it can be expensive). item.cChildren = I_CHILDRENCALLBACK; spCallback->GetImages(hNode, &item.iImage, &item.iSelectedImage); HTREEITEM hti = InsertItem(&tvInsertStruct); HMTNODE hMTNode = NULL; sc = spCallback->GetMTNode(hNode, &hMTNode); if(sc) sc.TraceAndClear(); // send an event to all interested observers sc = ScFireEvent(CTreeViewObserver::ScOnItemAdded, &tvInsertStruct, hti, hMTNode); if(sc) sc.TraceAndClear(); if (hParent != TVI_ROOT && hti != NULL) SetCountOfChildren(hParent, 1); return hti; } void CAMCTreeView::ResetNode(HTREEITEM hItem) { if (hItem == NULL) return; TV_ITEM item; ZeroMemory(&item, sizeof(item)); item.hItem = hItem; item.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_PARAM | TVIF_SELECTEDIMAGE | TVIF_STATE | TVIF_TEXT | TVIF_CHILDREN; item.pszText = LPSTR_TEXTCALLBACK; item.iImage = I_IMAGECALLBACK; item.iSelectedImage = I_IMAGECALLBACK; item.cChildren = I_CHILDRENCALLBACK; item.lParam = GetItemData(hItem); SetItem(&item); } void CAMCTreeView::OnItemExpanding(NMHDR* pNMHDR, LRESULT* pResult) { TRACE_METHOD(CAMCTreeView, OnItemExpanding); HRESULT hr; NM_TREEVIEW* pNotify = (NM_TREEVIEW*)pNMHDR; ASSERT(pNotify != NULL); HTREEITEM &hItem = pNotify->itemNew.hItem; ASSERT(hItem != NULL); BOOL bExpand = FALSE; // Iteratate the folders below this item if (pNotify->action == TVE_EXPAND) { /* * Bug 333971: Node expansion might take awhile. Supply a wait cursor * for all of the UI-challenged snap-ins out there. */ SetCursor (LoadCursor (NULL, IDC_WAIT)); ExpandNode(hItem); bExpand = TRUE; /* * return the arrow */ SetCursor (LoadCursor (NULL, IDC_ARROW)); } INodeCallback* pCallback = GetNodeCallback(); ASSERT(pCallback != NULL); HNODE hNode = GetItemNode (hItem); pCallback->Notify(hNode, NCLBK_SETEXPANDEDVISUALLY, bExpand, 0); // If item has no children remove the + sign if (GetChildItem(hItem) == NULL) SetButton(hItem, FALSE); *pResult = 0; } /*+-------------------------------------------------------------------------* * CAMCTreeView::OnItemExpanded * * TVN_ITEMEXPANDED handler for CAMCTreeView. *--------------------------------------------------------------------------*/ void CAMCTreeView::OnItemExpanded(NMHDR* pNMHDR, LRESULT* pResult) { DECLARE_SC (sc, _T("CAMCTreeView::OnItemExpanded")); NM_TREEVIEW* pnmtv = (NM_TREEVIEW*)pNMHDR; sc = ScCheckPointers (pnmtv); if (sc) return; /* * Bug 23153: when collapsing, totally collapse the tree beneath the * collapsing item. We do this in OnItemExpanded rather than * OnItemExpanding so we won't see the collapse happen. */ if (pnmtv->action == TVE_COLLAPSE) { CWaitCursor wait; CollapseChildren (pnmtv->itemNew.hItem); } } /*+-------------------------------------------------------------------------* * CAMCTreeView::CollapseChildren * * Collapses each descendent node of htiParent. *--------------------------------------------------------------------------*/ void CAMCTreeView::CollapseChildren (HTREEITEM htiParent) { HTREEITEM htiChild; for (htiChild = GetChildItem (htiParent); htiChild != NULL; htiChild = GetNextItem (htiChild, TVGN_NEXT)) { Expand (htiChild, TVE_COLLAPSE); CollapseChildren (htiChild); } } void CAMCTreeView::OnDeSelectNode(HNODE hNode) { DECLARE_SC(sc, TEXT("CAMCTreeView::OnDeSelectNode")); { // tell all interested observers about the deselection. // NOTE: the order is important - legacy snapins believe they can access the // result pane at this point and have the items still there. // But this is intermediate state, so Com events are locked out until the // results are cleared. // see windows bug (ntbug09) bug# 198660. (10/11/00) LockComEventInterface(AppEvents); sc = ScFireEvent(CTreeViewObserver::ScOnItemDeselected, hNode); if(sc) return; // Ensure the result view is clean. if (HasList()) { // First findout if the result view is properly // set in the nodemgr by asking IFramePrivate. IFramePrivatePtr spFrame = m_spResultData; if (NULL != spFrame) { BOOL bIsResultViewSet = FALSE; sc = spFrame->IsResultViewSet(&bIsResultViewSet); // The result view is set, clean it up. if (bIsResultViewSet) { m_spResultData->DeleteAllRsltItems(); m_spResultData->ResetResultData(); } } } } // don't have a valid result pane type anymore. SetHasList(false); } // Note that OnSelectNode will return S_FALSE if the snap-in changes // the selection during the process of selecting the requested node. // A caller that gets an S_FALSE should assume that a different node // is selected and continue accordingly. HRESULT CAMCTreeView::OnSelectNode(HTREEITEM hItem, HNODE hNode) { DECLARE_SC(sc, _T("CAMCTreeView::OnSelectNode")); if (!hItem) { TraceError(_T("Null hItem ptr\n"), sc); sc = S_FALSE; return sc.ToHr(); } if (!hNode) { TraceError(_T("Null hNode ptr\n"), sc); sc = S_FALSE; return sc.ToHr(); } // First ensure that the node has been enumerated by calling expand node. ExpandNode(hItem); // set up the AMCView correctly. BOOL bAddSubFolders = FALSE; sc = m_pAMCView->ScOnSelectNode(hNode, bAddSubFolders); if(sc) return sc.ToHr(); SetHasList(m_pAMCView->HasList()); // add subfolders if necessary. if(bAddSubFolders) { sc = AddSubFolders(hItem, m_spResultData); if (sc) return sc.ToHr(); } if (HasList()) m_spResultData->SetLoadMode(FALSE); // SetLoadMode(FALSE) was called by CAMCView::OnSelectNode. // Need to change so that both calls are from the same function. // get the node callback INodeCallback* spNodeCallBack = GetNodeCallback(); sc = ScCheckPointers(spNodeCallBack, E_UNEXPECTED); if (sc) return sc.ToHr(); // send preload notify to children HTREEITEM hti = GetChildItem (hItem); while (hti != NULL) { HNODE hNode = GetItemNode (hti); if (hNode != 0) spNodeCallBack->PreLoad (hNode); hti = GetNextItem(hti, TVGN_NEXT); } return S_OK; } /*+-------------------------------------------------------------------------* * CAMCTreeView::SetNavigatingWithKeyboard * * *--------------------------------------------------------------------------*/ void CAMCTreeView::SetNavigatingWithKeyboard (bool fKeyboardNav) { /* * if the requested state doesn't match the current state, * change the current state to match the request */ if (fKeyboardNav != IsNavigatingWithKeyboard()) { m_spKbdNavDelay = std::auto_ptr( (fKeyboardNav) ? new CKeyboardNavDelay (this) : NULL /*assigning NULL deletes*/); } } /*+-------------------------------------------------------------------------* * CAMCTreeView::OnSelChanging * * TVN_SELCHANGING handler for CAMCTreeView. *--------------------------------------------------------------------------*/ void CAMCTreeView::OnSelChanging(NMHDR* pNMHDR, LRESULT* pResult) { *pResult = 0; if (!IsNavigatingWithKeyboard()) OnSelChangingWorker ((NM_TREEVIEW*) pNMHDR, pResult); } /*+-------------------------------------------------------------------------* * CAMCTreeView::OnSelChanged * * TVN_SELCHANGED handler for CAMCTreeView. *--------------------------------------------------------------------------*/ void CAMCTreeView::OnSelChanged(NMHDR* pNMHDR, LRESULT* pResult) { NM_TREEVIEW* pnmtv = (NM_TREEVIEW*) pNMHDR; *pResult = 0; if (IsNavigatingWithKeyboard()) m_spKbdNavDelay->ScStopTimer(); SetNavigatingWithKeyboard (pnmtv->action == TVC_BYKEYBOARD); bool fDelayedSelection = IsNavigatingWithKeyboard() && !m_spKbdNavDelay->ScStartTimer(pnmtv).IsError(); if (!fDelayedSelection) OnSelChangedWorker (pnmtv, pResult); } /*+-------------------------------------------------------------------------* * CAMCTreeView::CKeyboardNavDelay::CKeyboardNavDelay * * *--------------------------------------------------------------------------*/ CAMCTreeView::CKeyboardNavDelay::CKeyboardNavDelay (CAMCTreeView* pTreeView) : m_pTreeView (pTreeView) { ZeroMemory (&m_nmtvSelChanged, sizeof (m_nmtvSelChanged)); } /*+-------------------------------------------------------------------------* * CAMCTreeView::CKeyboardNavDelay::OnTimer * * Called when the keyboard navigation delay timer fires. When that happens, * we need to perform the selection *--------------------------------------------------------------------------*/ void CAMCTreeView::CKeyboardNavDelay::OnTimer() { /* * we don't need any more ticks from this timer (ignoring errors) */ ScStopTimer(); Trace (tagKeyboardNavDelay, _T("Applying delayed scope selection change")); LRESULT lUnused = 0; m_pTreeView->OnSelChangedWorker (&m_nmtvSelChanged, &lUnused); m_pTreeView->SetNavigatingWithKeyboard (false); /* * HANDS OFF! CAMCTreeView::SetNavigatingWithKeyboard deleted this object! */ } /*+-------------------------------------------------------------------------* * CAMCTreeView::CKeyboardNavDelay::ScStartTimer * * *--------------------------------------------------------------------------*/ SC CAMCTreeView::CKeyboardNavDelay::ScStartTimer(NMTREEVIEW* pnmtv) { DECLARE_SC (sc, _T("CAMCTreeView:CKeyboardNavDelay::ScStartTimer")); /* * let the base class start the timer */ sc = BaseClass::ScStartTimer(); if (sc) return (sc); /* * copy the notification struct so we can send it when our timer ticks */ m_nmtvSelChanged = *pnmtv; return (sc); } void CAMCTreeView::OnSelChangedWorker(NM_TREEVIEW* pnmtv, LRESULT* pResult) { TRACE_METHOD(CAMCTreeView, OnSelChangedWorker); if (m_fInCleanUp == TRUE) return; // See which pane has focus. Some snapins/ocx may steal the focus // so we restore the focus after selecting the node. ASSERT (m_pAMCView != NULL); const CConsoleView::ViewPane ePane = m_pAMCView->GetFocusedPane(); // // Select the new node // // Disable drawing to avoid seeing intermediate tree states. UpdateWindow(); HRESULT hr = OnSelectNode(pnmtv->itemNew.hItem, (HNODE)pnmtv->itemNew.lParam); if (hr == S_OK) { CStandardToolbar* pStdToolbar = m_pAMCView->GetStdToolbar(); ASSERT(NULL != pStdToolbar); if (NULL != pStdToolbar) { pStdToolbar->ScEnableUpOneLevel(GetRootItem() != pnmtv->itemNew.hItem); pStdToolbar->ScEnableExportList(m_pAMCView->HasListOrListPad()); } *pResult = 0; } else if (hr == S_FALSE) { // snap-in changed the selection on us, so don't continue with this node. return; } else { // something wrong with the node we are trying to select, reselect the old one // SelectItem(pnmtv->itemOld.hItem); MMCMessageBox(IDS_SNAPIN_FAILED_INIT); *pResult = hr; } /* * Even if the active view hasn't changed always restore the active view. * Reason being, for OCX's even though they have the focus, they require * MMC to inform that OCX being selected. (see bug: 180964) */ switch (ePane) { case CConsoleView::ePane_ScopeTree: { // if another view was made active, switch it back. // View could still be active, but have focus stolen by // a snap-in or ocx, so ensure view has focus too. CFrameWnd* pFrame = GetParentFrame(); if (pFrame->GetActiveView() != this) pFrame->SetActiveView(this); else if (::GetFocus() != m_hWnd) SetFocus(); break; } case CConsoleView::ePane_Results: // If the result pane has the focus before and after // the node was selected, then the last event snapin // receives is scope selected which is incorrect. // So we first set scope pane as active view but do // not send notifications. Then we set result pane // as active view which sends scope de-select and // result pane select. // Set Scope pane as active view and we also want to // be notified about this active view so that our // view activation observers will know who is the // active view. GetParentFrame()->SetActiveView(this, true); // Now set result pane as active view and ask for notifications. m_pAMCView->ScDeferSettingFocusToResultPane(); break; case CConsoleView::ePane_None: // no pane is active, do nothing break; default: m_pAMCView->ScSetFocusToPane (ePane); break; } /* * Bug 345402: Make sure the focus rect is on the list control (if it * actually has the focus) to wake up any accessibility tools that might * be watching for input and focus changes. */ m_pAMCView->ScJiggleListViewFocus (); } void CAMCTreeView::OnSelChangingWorker (NM_TREEVIEW* pnmtv, LRESULT* pResult) { TRACE_METHOD(CAMCTreeView, OnSelChangingWorker); if (m_fInCleanUp == TRUE) return; // // De-select the current node // OnDeSelectNode ((HNODE)pnmtv->itemOld.lParam); *pResult = 0; } HRESULT CAMCTreeView::AddSubFolders(MTNODEID* pIDs, int length) { ASSERT(pIDs != NULL && length != 0); HRESULT hr = E_FAIL; // first make sure the specified node is expanded in the tree ctrl HTREEITEM hti = ExpandNode(pIDs, length, TRUE, false /*bExpandVisually*/); ASSERT(hti != NULL); // if successful, add the node's subfolders to the list view if (hti != NULL) { hr = AddSubFolders(hti, m_spResultData); ASSERT(SUCCEEDED(hr)); } return hr; } HRESULT CAMCTreeView::AddSubFolders(HTREEITEM hti, LPRESULTDATA pResultData) { HRESULT hr; RESULTDATAITEM tRDI; ::ZeroMemory(&tRDI, sizeof(tRDI)); tRDI.mask = RDI_STR | RDI_IMAGE | RDI_PARAM; tRDI.nCol = 0; tRDI.str = MMC_TEXTCALLBACK; tRDI.nImage = MMC_IMAGECALLBACK; tRDI.nIndex = -1; hti = GetChildItem(hti); ASSERT(m_pAMCView != NULL); INodeCallback* spCallback = GetNodeCallback(); ASSERT(spCallback != NULL); while (hti != NULL) { HNODE hNode = GetItemNode (hti); if (hNode != 0) { tRDI.lParam = LParamFromNode (hNode); hr = pResultData->InsertItem(&tRDI); CHECK_HRESULT(hr); if (SUCCEEDED(hr)) hr = spCallback->SetResultItem(hNode, tRDI.itemID); // add custom image if any spCallback->AddCustomFolderImage (hNode, m_spRsltImageList); } hti = GetNextItem(hti, TVGN_NEXT); } return S_OK; } int CAMCTreeView::OnCreate(LPCREATESTRUCT lpCreateStruct) { DECLARE_SC (sc, _T("CAMCTreeView::OnCreate")); TRACE_METHOD(CAMCTreeView, OnCreate); if (CTreeView::OnCreate(lpCreateStruct) == -1) return -1; m_pAMCView = ::GetAMCView (this); if (NULL == m_pAMCView) { ASSERT(m_pAMCView != NULL); sc = E_UNEXPECTED; return (-1); } IScopeTree* spScopeTree = m_pAMCView->GetScopeTree(); ASSERT(spScopeTree != NULL); HIMAGELIST hImageList; spScopeTree->GetImageList(reinterpret_cast(&hImageList)); CBitmap bmp; bmp.LoadBitmap(MAKEINTRESOURCE(IDB_AMC_NODES16)); int i = ImageList_AddMasked(hImageList, (HBITMAP) bmp.GetSafeHandle(), RGB(255,0,255)); ASSERT(i != -1 && "ImageList_Add failed."); TreeView_SetImageList( *this, hImageList, TVSIL_NORMAL ); sc = ScRegisterAsDropTarget(m_hWnd); if (sc) return (-1); sc = CreateNodeManager(); if (sc) return (-1); return 0; } BOOL CAMCTreeView::DestroyWindow() { TRACE_METHOD(CAMCTreeView, DestroyWindow); CleanUp(); return CTreeView::DestroyWindow(); } void CAMCTreeView::DeleteNode( HTREEITEM htiToDelete, BOOL fDeleteThis) { // Ensure curr sel is not a child of the item to be deleted. for (HTREEITEM hti = GetSelectedItem(); hti != NULL; hti = GetParentItem(hti)) { if (htiToDelete == hti) { if (fDeleteThis == TRUE) { hti = GetParentItem(hti); if (hti) SelectItem(hti); } break; } } // There are two paths to this function. Path 1, the view is deleted and there is no // longer a root node. Path 2. When a node is manually deleted, the selection is updated // in CAMCView::OnUpdateSelectionForDelete, therefore, the above code traverses to the root node ASSERT(hti == NULL || fDeleteThis == FALSE); // Collapse the node for performance reasons: // For each node deleted, the underlying tree control re-adjusts // the scroll bar dimensions. This involves walking the succeeding // visible nodes; collapsing reduces the number of such nodes. Expand(htiToDelete, TVE_COLLAPSE); SDeleteNodeInfo dniLocal = {htiToDelete, hti, fDeleteThis}; _DeleteNode(dniLocal); } void CAMCTreeView::_DeleteNode(SDeleteNodeInfo& dni) { ASSERT(&dni != NULL); ASSERT(dni.htiToDelete != NULL); if (dni.htiToDelete == NULL) return; SDeleteNodeInfo dniLocal = {GetChildItem(dni.htiToDelete), dni.htiSelected, TRUE}; // delete all the child nodes of the node being deleted while (dniLocal.htiToDelete != NULL) { _DeleteNode(dniLocal); dniLocal.htiToDelete = GetChildItem(dni.htiToDelete); } if (dni.fDeleteThis == TRUE) { // Reset the temp selection cache. // This deals with items that are right click selected (temporary) on the context // menu if (IsTempSelectionActive() && (GetTempSelectedItem() == dni.htiToDelete)) { SC sc = ScRemoveTempSelection (); if (sc) sc.TraceAndClear(); } HNODE hNode = (HNODE)GetItemData(dni.htiToDelete); HTREEITEM htiParentOfItemToDelete = GetParentItem(dni.htiToDelete); // If the item is in list view remove it. We do not want to do this // if it is virtual list or if selected item is "Console Root" // in which case then parent is NULL. if (HasList() && !m_pAMCView->IsVirtualList() && (NULL != htiParentOfItemToDelete) && (htiParentOfItemToDelete == dni.htiSelected) ) { HRESULTITEM itemID; HRESULT hr; hr = m_spResultData->FindItemByLParam(LParamFromNode(hNode), &itemID); if (SUCCEEDED(hr)) { hr = m_spResultData->DeleteItem(itemID, 0); ASSERT(SUCCEEDED(hr)); } } // tell the tree control to nuke it DeleteItem(dni.htiToDelete); // send an event to all interested observers SC sc = ScFireEvent(CTreeViewObserver::ScOnItemDeleted, hNode, dni.htiToDelete); if(sc) sc.TraceAndClear(); // tell the master tree to nuke it. m_pAMCView->GetScopeTree()->DestroyNode(hNode); // maintain history m_pAMCView->GetHistoryList()->DeleteEntry (hNode); } } void CAMCTreeView::DeleteScopeTree() { DECLARE_SC(sc, _T("CAMCTreeView::DeleteScopeTree")); m_fInCleanUp = TRUE; // Release the ResultView from the IFrame in the primary snapin in // the selected node. // This is necessary to release the result view if the selected node // is a snap-in node. // Free all the nodes HTREEITEM htiRoot = GetRootItem(); if (htiRoot != NULL) DeleteNode(htiRoot, TRUE); // First findout if the result view is properly // set in the nodemgr by asking IFramePrivate. IFramePrivatePtr spFrame = m_spResultData; if (NULL != spFrame) { BOOL bIsResultViewSet = FALSE; sc = spFrame->IsResultViewSet(&bIsResultViewSet); // The result view is set, clean it up. if (bIsResultViewSet) sc = m_spResultData->DeleteAllRsltItems(); } m_fInCleanUp = FALSE; } void CAMCTreeView::CleanUp() { TRACE_METHOD(CAMCTreeView, CleanUp); m_fInCleanUp = TRUE; m_spNodeManager = NULL; m_spHeaderCtrl = NULL; m_spResultData = NULL; m_spRsltImageList = NULL; m_spScopeData = NULL; m_fInCleanUp = FALSE; } void CAMCTreeView::OnDestroy() { TRACE_METHOD(CAMCTreeView, OnDestroy); //CleanUp(); CTreeView::OnDestroy(); CleanUp(); } HRESULT CAMCTreeView::CreateNodeManager(void) { TRACE_METHOD(CAMCTreeView, CreateNodeManager); if (m_spScopeData) return S_OK; #if _MSC_VER >= 1100 IFramePrivatePtr pIFrame(CLSID_NodeInit, NULL, MMC_CLSCTX_INPROC); #else IFramePrivatePtr pIFrame(CLSID_NodeInit, MMC_CLSCTX_INPROC); #endif ASSERT(pIFrame != NULL); if (pIFrame == NULL) return E_FAIL; m_spScopeData = pIFrame; m_spHeaderCtrl = pIFrame; if (m_spHeaderCtrl) pIFrame->SetHeader(m_spHeaderCtrl); m_spResultData = pIFrame; m_spRsltImageList = pIFrame; m_spNodeManager = pIFrame; pIFrame->SetComponentID(TVOWNED_MAGICWORD); return S_OK; } HTREEITEM CAMCTreeView::GetClickedNode() { TV_HITTESTINFO tvhi; tvhi.pt = (POINT)GetCaretPos(); tvhi.flags = TVHT_ONITEMLABEL; tvhi.hItem = 0; HTREEITEM htiClicked = HitTest(&tvhi); return htiClicked; } void CAMCTreeView::GetCountOfChildren(HTREEITEM hItem, LONG* pcChildren) { TV_ITEM tvi; tvi.hItem = hItem; tvi.mask = TVIF_CHILDREN; tvi.cChildren = 0; GetItem(&tvi); *pcChildren = tvi.cChildren; } void CAMCTreeView::SetCountOfChildren(HTREEITEM hItem, int cChildren) { TV_ITEM tvi; tvi.hItem = hItem; tvi.mask = TVIF_HANDLE | TVIF_CHILDREN; tvi.cChildren = cChildren; SetItem(&tvi); } HTREEITEM CAMCTreeView::FindNode(HTREEITEM hti, MTNODEID id) { INodeCallback* pCallback = GetNodeCallback(); static MTNODEID nID = -1; static HRESULT hr = S_OK; hr = pCallback->GetMTNodeID(GetItemNode(hti), &nID); ASSERT(SUCCEEDED(hr)); if (FAILED(hr)) return NULL; if (nID == id) return hti; HTREEITEM htiTemp = GetChildItem(hti); if (htiTemp != NULL) htiTemp = FindNode(htiTemp, id); if (htiTemp == NULL) { htiTemp = GetNextSiblingItem(hti); if (htiTemp != NULL) htiTemp = FindNode(htiTemp, id); } return htiTemp; } HTREEITEM CAMCTreeView::FindSiblingItem(HTREEITEM hti, MTNODEID id) { INodeCallback* pCallback = GetNodeCallback(); if (!pCallback) return NULL; static MTNODEID nID = -1; static HRESULT hr = S_OK; while (hti != NULL) { hr = pCallback->GetMTNodeID(GetItemNode(hti), &nID); if (FAILED(hr)) return NULL; if (nID == id) return hti; hti = GetNextSiblingItem(hti); } return NULL; } //+------------------------------------------------------------------- // // Member: CAMCTreeView::SelectNode // // Synopsis: Given path to a node, select the node. If bSelectExactNode // is false then walk the path as much as possible and select // the last node in the best matched path. If bSelectExactNode // is true then select the node if available else do nothing. // // Arguments: [pIDs] - [in] Array of node-id's (the path) // [length] - [in] length of the above array // [bSelectExactNode] - [in] select the exact node or not? // // Returns: SC, return ScFromMMC(IDS_NODE_NOT_FOUND) if select exact node is specified // and it cannot be selected // //-------------------------------------------------------------------- SC CAMCTreeView::ScSelectNode(MTNODEID* pIDs, int length, bool bSelectExactNode /*= false*/) { DECLARE_SC(sc, TEXT("CAMCTreeView::ScSelectNode")); sc = ScCheckPointers(pIDs); if (sc) return sc; if (m_fInExpanding) return (sc); HTREEITEM hti = GetRootItem(); sc = ScCheckPointers( (void*)hti, E_UNEXPECTED); if (sc) return sc; if (pIDs[0] != ROOTNODEID) return (sc = E_INVALIDARG); INodeCallback* pCallback = GetNodeCallback(); sc = ScCheckPointers(pCallback, E_UNEXPECTED); if (sc) return sc; MTNODEID nID = 0; sc = pCallback->GetMTNodeID(GetItemNode(hti), &nID); if (sc) return sc; bool bExactNodeFound = false; for (int i=0; iNotify(hNode, NCLBK_SETEXPANDEDVISUALLY, bExpand, 0); // If item has no children remove the + sign if (GetChildItem(hItem) == NULL) SetButton(hItem, FALSE); return true; } else return Expand(hItem, nCode); } /*+-------------------------------------------------------------------------* * * CAMCTreeView::ExpandNode * * PURPOSE: Expands a particular node in the tree. * * PARAMETERS: * MTNODEID* pIDs : * int length : * bool bExpand : * bool bExpandVisually : valid only if bExpand is true. If bExpandVisually * is true, the items appear in the tree. If false, * the tree appears unchanged, although items have been * added. * * RETURNS: * HTREEITEM * *+-------------------------------------------------------------------------*/ HTREEITEM CAMCTreeView::ExpandNode(MTNODEID* pIDs, int length, bool bExpand, bool bExpandVisually) { HTREEITEM hti = GetRootItem(); ASSERT(hti != NULL); ASSERT(pIDs[0] == ROOTNODEID); INodeCallback* pCallback = GetNodeCallback(); if (!pCallback) return NULL; MTNODEID nID = 0; HRESULT hr = pCallback->GetMTNodeID(GetItemNode(hti), &nID); if (FAILED(hr)) return NULL; for (int i=0; iIsVerbEnabled(MMC_VERB_DELETE)) { HTREEITEM hti = GetSelectedItem(); if (hti != NULL) { HNODE hNodeSel = GetItemNode(hti); ASSERT(hNodeSel != NULL); INodeCallback* pNC = GetNodeCallback(); ASSERT(pNC != NULL); pNC->Notify(hNodeSel, NCLBK_DELETE, TRUE, 0); } return; } break; } CTreeView::OnKeyDown(nChar, nRepCnt, nFlags); } #ifdef DBG void CAMCTreeView::DbgDisplayNodeName(HNODE hNode) { ASSERT(hNode != NULL); INodeCallback* spCallback = GetNodeCallback(); ASSERT(spCallback != NULL); tstring strName; HRESULT hr = spCallback->GetDisplayName(hNode, strName); ::MMCMessageBox( strName.data() ); } void CAMCTreeView::DbgDisplayNodeName(HTREEITEM hti) { DbgDisplayNodeName((HNODE)GetItemData(hti)); } #endif /*+-------------------------------------------------------------------------* * * CAMCTreeView::OnSysKeyDown and CAMCTreeView::OnSysChar * * PURPOSE: Handle the WM_SYSKEYDOWN and WM_SYSCHAR messages. Note: * VK_RETURN causes a beep if handled in WM_SYSKEYDOWN. And VK_LEFT and * VK_RIGHT don't cause a WM_SYSCHAR. Thats why we need to handle these * differently. * * PARAMETERS: * UINT nChar : * UINT nRepCnt : * UINT nFlags : * * RETURNS: * void * *+-------------------------------------------------------------------------*/ void CAMCTreeView::OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { switch (nChar) { case VK_LEFT: case VK_RIGHT: { CWnd* pwndParent = GetParent(); ASSERT(pwndParent != NULL); if (pwndParent != NULL) pwndParent->SendMessage (WM_SYSKEYDOWN, nChar, MAKELPARAM (nRepCnt, nFlags)); return; } default: break; } CTreeView::OnSysKeyDown(nChar, nRepCnt, nFlags); } void CAMCTreeView::OnSysChar(UINT nChar, UINT nRepCnt, UINT nFlags) { DECLARE_SC(sc, TEXT("CAMCTreeView::OnSysChar")); switch (nChar) { case VK_RETURN: { INodeCallback* pCallback = GetNodeCallback(); CAMCView* pAMCView = GetAMCView(); sc = ScCheckPointers(pAMCView, pCallback, E_UNEXPECTED); if (sc) return; if (! pAMCView->IsVerbEnabled(MMC_VERB_PROPERTIES)) return; HTREEITEM hti = GetSelectedItem(); if (!hti) break; HNODE hNode = (HNODE)GetItemData(hti); if (hNode != 0) pCallback->Notify(hNode, NCLBK_PROPERTIES, TRUE, 0); return; } default: break; } CTreeView::OnSysChar(nChar, nRepCnt, nFlags); } BOOL CAMCTreeView::OnCmdMsg( UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo ) { // Do normal command routing if (CTreeView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // if view didn't handle it, give parent view a chance if (m_pAMCView != NULL) return static_cast(m_pAMCView)->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); else return FALSE; } int CAMCTreeView::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message) { /*------------------------------------------------------------------------*/ /* Short out the WM_MOUSEACTIVATE here to prevent default processing, */ /* which is to send the message on to succeeding parent windows until */ /* one answers the message. In our case, it goes all the way up to */ /* the main frame, which invariably decides to activate. This is a */ /* problem for two reasons: */ /* */ /* 1. On the way back down from the main frame, the message passes */ /* through CAMCView, which lets CView::OnMouseActivate do the */ /* work. CView::OnMouseActivate will set itself (CAMCView) as */ /* the active view, which in turn causes focus to be set to */ /* the view. CAMCView never wants the focus, since it is just */ /* a frame for the scope and result panes, so it will deflect */ /* the activation to the scope pane (CAMCTreeView) in */ /* CAMCView::OnSetFocus, which is where we want it to be. If */ /* we short out the processing here, we avoid excessive focus */ /* churn. It is essential that CAMCTreeView::OnSetFocus set */ /* itself as the active view to keep the bookkeeping straight. */ /* */ /* 2. If we don't short out here and avoid excessive focus churn, */ /* we have a problem with sometimes erroneously entering rename */ /* mode when the tree isn't active and the user clicks (once) on */ /* the selected item. An ordinary activation sequence goes like */ /* this: WM_MOUSEACTIVATE, WM_xBUTTONDOWN, WM_SETFOUS -- all to */ /* the tree view. The tree's button down processing doesn't enter */ /* the label edit (i.e. rename) sequence because it recognizes */ /* that it doesn't have the focus when the click happens. When */ /* the tree view is a CView, as in this case, CView::OnMouseActivate */ /* sets the focus to the tree view, causing the activation sequence */ /* to look like this: WM_MOUSEACTIVATE, WM_SETFOCUS, WM_xBUTTONDOWN. */ /* Now the tree's button down processing sees that the tree has */ /* the focus, so it enters label edit mode. BUG! Shorting out */ /* here (and relying on CAMCTreeView::OnSetFocus to properly activate */ /* the view) fixes all that. */ /*------------------------------------------------------------------------*/ return (MA_ACTIVATE); } /*+-------------------------------------------------------------------------* * CAMCTreeView::OnSetFocus * * WM_SETFOCUS handler for CAMCTreeView. *--------------------------------------------------------------------------*/ void CAMCTreeView::OnSetFocus(CWnd* pOldWnd) { Trace(tagTree, TEXT("OnSetFocus")); /* * if this view has the focus, it should be the active view */ GetParentFrame()->SetActiveView (this); CTreeView::OnSetFocus(pOldWnd); } /*+-------------------------------------------------------------------------* * CAMCTreeView::OnKillFocus * * WM_KILLFOCUS handler for CAMCTreeView. *--------------------------------------------------------------------------*/ void CAMCTreeView::OnKillFocus(CWnd* pNewWnd) { Trace(tagTree, TEXT("OnKillFocus")); CTreeView::OnKillFocus(pNewWnd); /* * Bug 114948 (from the "Windows NT Bugs" database, aka "the overlapping * rectangle problem"): The tree control has code to invalidate the * selected item when focus is lost. If we have a temp selection, we've * made a temporary item appear selected by fiddling with TVIS_SELECTED * states (see ScSet/RemoveTempSelection). We need to do it that way * instead of sending TVM_SELECTITEM so we don't get unwanted * TVN_SELCHANGED notifications, but it has the side effect of fooling * the tree control's WM_KILLFOCUS handler into invalidating the non-temp * selected item instead of the item that is really showing selection, the * temp item. * * This bug was originally fixed with a sledgehammer, specifically by * forcing the entire main frame and all of its children to be totally * redrawn after displaying any context menu. This caused bug 139541 * (in the "Windows Bugs" database). * * A much more surgical fix to 114948, which also avoids 139541, is to * manually invalidate the temporarily selected item. It's important * that we do this after calling the base class so it will be redrawn * in the "we don't have the focus" color (usually gray), rather than * the standard selection color. */ if (IsTempSelectionActive()) { CRect rectItem; GetItemRect (GetTempSelectedItem(), rectItem, false); RedrawWindow (rectItem); } } void CAMCTreeView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView) { DECLARE_SC(sc, TEXT("CAMCTreeView::OnActivateView")); #ifdef DBG Trace(tagTree, _T("TreeView::OnActivateView (%s, pAct=0x%08x, pDeact=0x%08x))\n"), (bActivate) ? _T("true") : _T("false"), pActivateView, pDeactiveView); #endif if ( (pActivateView != pDeactiveView) && (bActivate) ) { sc = ScFireEvent(CTreeViewObserver::ScOnTreeViewActivated); if (sc) sc.TraceAndClear(); } CTreeView::OnActivateView(bActivate, pActivateView, pDeactiveView); } /*+-------------------------------------------------------------------------* * CAMCTreeView::OnCustomDraw * * NM_CUSTOMDRAW handler for CAMCTreeView. *--------------------------------------------------------------------------*/ void CAMCTreeView::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult) { NMCUSTOMDRAW* pnmcd = reinterpret_cast(pNMHDR); ASSERT (CWnd::FromHandle (pnmcd->hdr.hwndFrom) == this); *pResult = m_FontLinker.OnCustomDraw (pnmcd); } /*+-------------------------------------------------------------------------* * CTreeFontLinker::GetItemText * * *--------------------------------------------------------------------------*/ std::wstring CTreeFontLinker::GetItemText (NMCUSTOMDRAW* pnmcd) const { USES_CONVERSION; HTREEITEM hItem = reinterpret_cast(pnmcd->dwItemSpec); CTreeCtrl& tc = m_pTreeView->GetTreeCtrl(); return (std::wstring (T2CW (tc.GetItemText (hItem)))); } //+------------------------------------------------------------------- // // Member: CAMCTreeView::ScGetTreeItemIconInfo // // Synopsis: Get the given node's small icon. // // Arguments: [hNode] - for which info is needed. // [phIcon] - [out], ptr to HICON. // // Note: Caller calls DestroyIcon on the HICON returned. // // Returns: SC // //-------------------------------------------------------------------- SC CAMCTreeView::ScGetTreeItemIconInfo(HNODE hNode, HICON *phIcon) { DECLARE_SC(sc, TEXT("CAMCTreeView::ScGetTreeItemIconInfo")); sc = ScCheckPointers(hNode, phIcon); if (sc) return sc; INodeCallback* spNodeCallBack = GetNodeCallback(); sc = ScCheckPointers(spNodeCallBack, m_pAMCView, E_UNEXPECTED); if (sc) return sc; // Get the index. int nImage = -1; int nSelectedImage = -1; sc = spNodeCallBack->GetImages(hNode, &nImage, &nSelectedImage); if (sc) return sc; // Get the imagelist. HIMAGELIST hImageList = NULL; hImageList = TreeView_GetImageList(GetSafeHwnd(), TVSIL_NORMAL); if (! hImageList) return (sc = E_FAIL); *phIcon = ImageList_GetIcon(hImageList, nImage, ILD_TRANSPARENT); if (!*phIcon) return (sc = E_FAIL); return sc; } /*+-------------------------------------------------------------------------* * * CAMCTreeView::ScRenameScopeNode * * PURPOSE: put the specified scope node into rename mode. * * PARAMETERS: * HMTNODE hMTNode : * * RETURNS: * SC * *+-------------------------------------------------------------------------*/ SC CAMCTreeView::ScRenameScopeNode(HMTNODE hMTNode) { DECLARE_SC(sc, TEXT("CAMCTreeView::ScRenameScopeNode")); if(!IsWindowVisible()) return (sc = E_FAIL); HTREEITEM hti = NULL; sc = m_treeMap.ScGetHTreeItemFromHMTNode(hMTNode, &hti); if(sc) return sc; // must have the focus to rename if (::GetFocus() != m_hWnd) SetFocus(); if(NULL==EditLabel(hti)) return (sc = E_FAIL); // if for any reason the operation failed, return an error return sc; }