Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2183 lines
64 KiB

// 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<CTreeViewObserver&>(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<LONG_PTR>(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<CKeyboardNavDelay>(
(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<PLONG_PTR>(&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; i<length; ++i)
{
if (pIDs[i] == nID)
break;
}
for (++i; i < length; ++i)
{
if (GetChildItem(hti) == NULL)
Expand(hti, TVE_EXPAND);
hti = FindSiblingItem(GetChildItem(hti), pIDs[i]);
if (hti == NULL)
break;
}
if (length == i)
bExactNodeFound = true;
if (hti)
{
// If exact node is to be selected make sure we have walked through the entire path.
if ( (bSelectExactNode) && (! bExactNodeFound) )
return ScFromMMC(IDS_NODE_NOT_FOUND); // do not trace this error.
if (GetSelectedItem() == hti)
ScReselect();
else
SelectItem(hti);
}
return sc;
}
/*+-------------------------------------------------------------------------*
*
* CAMCTreeView::Expand
*
* PURPOSE: Expands a particular tree item. This is just a wrapper around the
* tree control's expand method, which allows items to be expanded
* without changing the visual appearance of the tree.
*
* PARAMETERS:
* HTREEITEM hItem :
* UINT nCode :
* bool bExpandVisually :
*
* RETURNS:
* BOOL
*
*+-------------------------------------------------------------------------*/
BOOL
CAMCTreeView::Expand(HTREEITEM hItem, UINT nCode, bool bExpandVisually)
{
if( (nCode==TVE_EXPAND) && (!bExpandVisually) )
{
bool bExpand = true;
// code repeated here from OnItemExpand - we just mimic the effect of TVN_ITEMEXPANDING.
ExpandNode(hItem);
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);
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; i<length; ++i)
{
if (pIDs[i] == nID)
break;
}
for (++i; i < length; ++i)
{
if (GetChildItem(hti) == NULL)
Expand(hti, TVE_EXPAND, bExpandVisually);
hti = FindSiblingItem(GetChildItem(hti), pIDs[i]);
if (hti == NULL)
break;
}
if (hti)
Expand(hti, bExpand ? TVE_EXPAND : TVE_COLLAPSE, bExpandVisually);
return hti;
}
/*+-------------------------------------------------------------------------*
* CAMCTreeView::OnKeyDown
*
* WM_KEYDOWN handler for CAMCTreeView.
*--------------------------------------------------------------------------*/
void CAMCTreeView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
switch (nChar)
{
case VK_DELETE:
if (m_pAMCView->IsVerbEnabled(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<CWnd*>(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<NMCUSTOMDRAW *>(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<HTREEITEM>(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;
}