1039 lines
27 KiB
C++
1039 lines
27 KiB
C++
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Microsoft Windows
|
||
|
//
|
||
|
// Copyright (C) Microsoft Corporation, 1998 - 1999
|
||
|
//
|
||
|
// File: treedat_.cpp
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// Miscellanea
|
||
|
LPCWSTR g_lpszNullString = L"\0";
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////
|
||
|
// Global Helper functions
|
||
|
|
||
|
BOOL LoadContextMenuResources(MENUMAP* pMenuMap)
|
||
|
{
|
||
|
HINSTANCE hInstance = _Module.GetModuleInstance();
|
||
|
for (int i = 0; pMenuMap->ctxMenu[i].strName; i++)
|
||
|
{
|
||
|
if (0 == ::LoadString(hInstance, pMenuMap->dataRes[i].uResID, pMenuMap->dataRes[i].szBuffer, MAX_CONTEXT_MENU_STRLEN*2))
|
||
|
return FALSE;
|
||
|
pMenuMap->ctxMenu[i].strName = pMenuMap->dataRes[i].szBuffer;
|
||
|
for (WCHAR* pCh = pMenuMap->dataRes[i].szBuffer; (*pCh) != NULL; pCh++)
|
||
|
{
|
||
|
if ( (*pCh) == L'\n')
|
||
|
{
|
||
|
pMenuMap->ctxMenu[i].strStatusBarText = (pCh+1);
|
||
|
(*pCh) = NULL;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
BOOL LoadResultHeaderResources(RESULT_HEADERMAP* pHeaderMap, int nCols)
|
||
|
{
|
||
|
HINSTANCE hInstance = _Module.GetModuleInstance();
|
||
|
for ( int i = 0; i < nCols ; i++)
|
||
|
{
|
||
|
if ( 0 == ::LoadString(hInstance, pHeaderMap[i].uResID, pHeaderMap[i].szBuffer, MAX_RESULT_HEADER_STRLEN))
|
||
|
return TRUE;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// CTreeNode
|
||
|
|
||
|
BEGIN_TOOLBAR_MAP(CTreeNode)
|
||
|
END_TOOLBAR_MAP()
|
||
|
|
||
|
BOOL CTreeNode::HasContainer(CContainerNode* pContainerNode)
|
||
|
{
|
||
|
if (m_pContainer == NULL)
|
||
|
return FALSE; // root
|
||
|
if (m_pContainer == pContainerNode)
|
||
|
return TRUE; // got it
|
||
|
return m_pContainer->HasContainer(pContainerNode);
|
||
|
}
|
||
|
|
||
|
HRESULT CTreeNode::GetResultViewType(CComponentDataObject* pComponentData,
|
||
|
LPOLESTR* ppViewType,
|
||
|
long* pViewOptions)
|
||
|
{
|
||
|
if (pComponentData->IsMultiSelect())
|
||
|
{
|
||
|
*pViewOptions = MMC_VIEW_OPTIONS_MULTISELECT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*pViewOptions = MMC_VIEW_OPTIONS_NONE;
|
||
|
}
|
||
|
*ppViewType = NULL;
|
||
|
return S_FALSE;
|
||
|
}
|
||
|
|
||
|
void CTreeNode::Show(BOOL bShow, CComponentDataObject* pComponentData)
|
||
|
{
|
||
|
if (bShow)
|
||
|
{
|
||
|
ASSERT(m_dwNodeFlags & TN_FLAG_HIDDEN); // must be currently hidden
|
||
|
SetFlagsDown(TN_FLAG_HIDDEN,FALSE); // mark it visible
|
||
|
VERIFY(SUCCEEDED(pComponentData->AddNode(this)));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ASSERT(!(m_dwNodeFlags & TN_FLAG_HIDDEN)); // must be currently visible
|
||
|
SetFlagsDown(TN_FLAG_HIDDEN,TRUE); // mark it hidden
|
||
|
VERIFY(SUCCEEDED(pComponentData->DeleteNode(this)));
|
||
|
if (IsContainer())
|
||
|
{
|
||
|
((CContainerNode*)this)->RemoveAllChildrenFromList();
|
||
|
((CContainerNode*)this)->MarkEnumerated(FALSE);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CTreeNode::SetFlagsDown(DWORD dwNodeFlags, BOOL bSet)
|
||
|
{
|
||
|
if (bSet)
|
||
|
m_dwNodeFlags |= dwNodeFlags;
|
||
|
else
|
||
|
m_dwNodeFlags &= ~dwNodeFlags;
|
||
|
}
|
||
|
|
||
|
void CTreeNode::SetFlagsUp(DWORD dwNodeFlags, BOOL bSet)
|
||
|
{
|
||
|
if (bSet)
|
||
|
m_dwNodeFlags |= dwNodeFlags;
|
||
|
else
|
||
|
m_dwNodeFlags &= ~dwNodeFlags;
|
||
|
if (m_pContainer != NULL)
|
||
|
{
|
||
|
ASSERT(m_pContainer != this);
|
||
|
m_pContainer->SetFlagsUp(dwNodeFlags, bSet);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Property Page methods
|
||
|
//
|
||
|
void CTreeNode::ShowPageForNode(CComponentDataObject* pComponentDataObject)
|
||
|
{
|
||
|
ASSERT(pComponentDataObject != NULL);
|
||
|
pComponentDataObject->GetPropertyPageHolderTable()->BroadcastSelectPage(this, -1);
|
||
|
}
|
||
|
|
||
|
BOOL CTreeNode::HasPropertyPages(DATA_OBJECT_TYPES,
|
||
|
BOOL* pbHideVerb,
|
||
|
CNodeList*)
|
||
|
{
|
||
|
*pbHideVerb = TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Menu Item methods
|
||
|
//
|
||
|
HRESULT CTreeNode::OnAddMenuItems(IContextMenuCallback2* pContextMenuCallback2,
|
||
|
DATA_OBJECT_TYPES type,
|
||
|
long *pInsertionAllowed,
|
||
|
CNodeList* pNodeList)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
LPCONTEXTMENUITEM2 pContextMenuItem = NULL;
|
||
|
|
||
|
if (pNodeList->GetCount() == 1) // single selection
|
||
|
{
|
||
|
pContextMenuItem = OnGetContextMenuItemTable();
|
||
|
if (pContextMenuItem == NULL)
|
||
|
return hr;
|
||
|
|
||
|
//
|
||
|
// Loop through and add each of the menu items
|
||
|
//
|
||
|
for (LPCONTEXTMENUITEM2 m = pContextMenuItem; m->strName; m++)
|
||
|
{
|
||
|
if (
|
||
|
( (*pInsertionAllowed & CCM_INSERTIONALLOWED_NEW) &&
|
||
|
(m->lInsertionPointID == CCM_INSERTIONPOINTID_PRIMARY_NEW) ) ||
|
||
|
( (*pInsertionAllowed & CCM_INSERTIONALLOWED_TASK) &&
|
||
|
(m->lInsertionPointID == CCM_INSERTIONPOINTID_PRIMARY_TASK) ) ||
|
||
|
( (*pInsertionAllowed & CCM_INSERTIONALLOWED_VIEW) &&
|
||
|
(m->lInsertionPointID == CCM_INSERTIONPOINTID_PRIMARY_VIEW) ) ||
|
||
|
( (*pInsertionAllowed & CCM_INSERTIONALLOWED_TOP) &&
|
||
|
(m->lInsertionPointID == CCM_INSERTIONPOINTID_PRIMARY_TOP) )
|
||
|
)
|
||
|
{
|
||
|
// make a temporary copy that can be modified
|
||
|
CONTEXTMENUITEM2 tempItem;
|
||
|
::memcpy(&tempItem, m, sizeof(CONTEXTMENUITEM2));
|
||
|
if (OnAddMenuItem(&tempItem, pInsertionAllowed))
|
||
|
{
|
||
|
hr = pContextMenuCallback2->AddItem(&tempItem);
|
||
|
if (FAILED(hr))
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (pNodeList->GetCount() > 1) // multiple selection
|
||
|
{
|
||
|
hr = OnAddMenuItemsMultipleSelect(pContextMenuCallback2,
|
||
|
type,
|
||
|
pInsertionAllowed,
|
||
|
pNodeList);
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
BOOL CTreeNode::OnSetRenameVerbState(DATA_OBJECT_TYPES,
|
||
|
BOOL* pbHide,
|
||
|
CNodeList*)
|
||
|
{
|
||
|
*pbHide = TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
BOOL CTreeNode::OnSetDeleteVerbState(DATA_OBJECT_TYPES,
|
||
|
BOOL* pbHide,
|
||
|
CNodeList*)
|
||
|
{
|
||
|
*pbHide = TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
BOOL CTreeNode::OnSetRefreshVerbState(DATA_OBJECT_TYPES,
|
||
|
BOOL* pbHide,
|
||
|
CNodeList*)
|
||
|
{
|
||
|
*pbHide = TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
BOOL CTreeNode::OnSetCutVerbState(DATA_OBJECT_TYPES,
|
||
|
BOOL* pbHide,
|
||
|
CNodeList*)
|
||
|
{
|
||
|
*pbHide = TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
BOOL CTreeNode::OnSetCopyVerbState(DATA_OBJECT_TYPES,
|
||
|
BOOL* pbHide,
|
||
|
CNodeList*)
|
||
|
{
|
||
|
*pbHide = TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
BOOL CTreeNode::OnSetPasteVerbState(DATA_OBJECT_TYPES,
|
||
|
BOOL* pbHide,
|
||
|
CNodeList*)
|
||
|
{
|
||
|
*pbHide = TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
BOOL CTreeNode::OnSetPrintVerbState(DATA_OBJECT_TYPES,
|
||
|
BOOL* pbHide,
|
||
|
CNodeList*)
|
||
|
{
|
||
|
*pbHide = TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
MMC_CONSOLE_VERB CTreeNode::GetDefaultVerb(DATA_OBJECT_TYPES type,
|
||
|
CNodeList* pNodeList)
|
||
|
{
|
||
|
ASSERT((type == CCT_SCOPE) || (type == CCT_RESULT));
|
||
|
if (type == CCT_SCOPE)
|
||
|
return MMC_VERB_OPEN;
|
||
|
BOOL bHideVerbDummy;
|
||
|
if (HasPropertyPages(type, &bHideVerbDummy, pNodeList))
|
||
|
return MMC_VERB_PROPERTIES;
|
||
|
return MMC_VERB_NONE;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CTreeNode::OnSetVerbState(LPCONSOLEVERB pConsoleVerb,
|
||
|
DATA_OBJECT_TYPES type,
|
||
|
CNodeList* pNodeList)
|
||
|
{
|
||
|
//
|
||
|
// Use the virtual functions to get the verb state
|
||
|
//
|
||
|
BOOL bHideCut;
|
||
|
BOOL bCanCut = OnSetCutVerbState(type, &bHideCut, pNodeList);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_CUT, HIDDEN, bHideCut);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_CUT, ENABLED, bCanCut);
|
||
|
|
||
|
|
||
|
BOOL bHideCopy;
|
||
|
BOOL bCanCopy = OnSetCopyVerbState(type, &bHideCopy, pNodeList);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_COPY, HIDDEN, bHideCopy);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_COPY, ENABLED, bCanCopy);
|
||
|
|
||
|
|
||
|
BOOL bHidePaste;
|
||
|
BOOL bCanPaste = OnSetPasteVerbState(type, &bHidePaste, pNodeList);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_PASTE, HIDDEN, bHidePaste);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_PASTE, ENABLED, bCanPaste);
|
||
|
|
||
|
|
||
|
BOOL bHidePrint;
|
||
|
BOOL bCanPrint = OnSetPrintVerbState(type, &bHidePrint, pNodeList);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_PRINT, HIDDEN, bHidePrint);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_PRINT, ENABLED, bCanPrint);
|
||
|
|
||
|
BOOL bHideRename;
|
||
|
BOOL bCanRename = OnSetRenameVerbState(type, &bHideRename, pNodeList);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_RENAME, HIDDEN, bHideRename);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_RENAME, ENABLED, bCanRename);
|
||
|
|
||
|
// MMC_VERB_PROPERTIES
|
||
|
BOOL bHideProperties;
|
||
|
BOOL bHasProperties = HasPropertyPages(type, &bHideProperties, pNodeList);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_PROPERTIES, ENABLED, bHasProperties);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_PROPERTIES, HIDDEN, bHideProperties);
|
||
|
|
||
|
// MMC_VERB_DELETE
|
||
|
BOOL bHideDelete;
|
||
|
BOOL bCanDelete = OnSetDeleteVerbState(type, &bHideDelete, pNodeList);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_DELETE, ENABLED, bCanDelete);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_DELETE, HIDDEN, bHideDelete);
|
||
|
|
||
|
// MMC_VERB_REFRESH
|
||
|
BOOL bHideRefresh;
|
||
|
BOOL bCanRefresh = OnSetRefreshVerbState(type, &bHideRefresh, pNodeList);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, ENABLED, bCanRefresh);
|
||
|
pConsoleVerb->SetVerbState(MMC_VERB_REFRESH, HIDDEN, bHideRefresh);
|
||
|
}
|
||
|
|
||
|
HRESULT CTreeNode::OnSetToolbarVerbState(IToolbar*,
|
||
|
CNodeList*)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
|
||
|
//
|
||
|
// Set the button state for each button on the toolbar using
|
||
|
// hr = pToolbar->SetButtonState(event, MMC_BUTTON_STATE, bState);
|
||
|
//
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
void CTreeNode::DeleteHelper(CComponentDataObject* pComponentData)
|
||
|
{
|
||
|
ASSERT(pComponentData != NULL);
|
||
|
ASSERT(m_pContainer != NULL);
|
||
|
ASSERT((CTreeNode*)m_pContainer != this);
|
||
|
CContainerNode* pCont = m_pContainer;
|
||
|
VERIFY(m_pContainer->RemoveChildFromList(this));
|
||
|
ASSERT(m_pContainer == NULL);
|
||
|
m_pContainer = pCont; // not in the container's list of children, but still needed
|
||
|
|
||
|
// remove from UI only if the container is visible
|
||
|
if (pCont->IsVisible())
|
||
|
VERIFY(SUCCEEDED(pComponentData->DeleteNode(this))); // remove from the UI
|
||
|
}
|
||
|
|
||
|
void CTreeNode::IncrementSheetLockCount()
|
||
|
{
|
||
|
++m_nSheetLockCount;
|
||
|
if (m_pContainer != NULL)
|
||
|
m_pContainer->IncrementSheetLockCount();
|
||
|
}
|
||
|
|
||
|
void CTreeNode::DecrementSheetLockCount()
|
||
|
{
|
||
|
--m_nSheetLockCount;
|
||
|
if (m_pContainer != NULL)
|
||
|
m_pContainer->DecrementSheetLockCount();
|
||
|
}
|
||
|
|
||
|
void CTreeNode::OnPropertyChange(CComponentDataObject* pComponentData,
|
||
|
BOOL, long changeMask)
|
||
|
{
|
||
|
// function called when the PPHolder successfully updated the node
|
||
|
ASSERT(pComponentData != NULL);
|
||
|
VERIFY(SUCCEEDED(pComponentData->ChangeNode(this, changeMask)));
|
||
|
}
|
||
|
|
||
|
void CTreeNode::OnCreateSheet()
|
||
|
{
|
||
|
++m_nSheetCount;
|
||
|
IncrementSheetLockCount();
|
||
|
SetFlagsUp(TN_FLAG_HAS_SHEET, TRUE);
|
||
|
}
|
||
|
|
||
|
void CTreeNode::OnDeleteSheet()
|
||
|
{
|
||
|
DecrementSheetLockCount();
|
||
|
--m_nSheetCount;
|
||
|
SetFlagsUp(TN_FLAG_HAS_SHEET,FALSE);
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// CContainerNode
|
||
|
|
||
|
void CContainerNode::IncrementThreadLockCount()
|
||
|
{
|
||
|
++m_nThreadLockCount;
|
||
|
if (m_pContainer != NULL)
|
||
|
m_pContainer->IncrementThreadLockCount();
|
||
|
}
|
||
|
|
||
|
void CContainerNode::DecrementThreadLockCount()
|
||
|
{
|
||
|
--m_nThreadLockCount;
|
||
|
if (m_pContainer != NULL)
|
||
|
m_pContainer->DecrementThreadLockCount();
|
||
|
}
|
||
|
|
||
|
BOOL CContainerNode::OnRefresh(CComponentDataObject* pComponentData,
|
||
|
CNodeList* pNodeList)
|
||
|
{
|
||
|
BOOL bRet = TRUE;
|
||
|
if (pNodeList->GetCount() == 1) // single selection
|
||
|
{
|
||
|
if (IsSheetLocked())
|
||
|
{
|
||
|
if (!CanCloseSheets())
|
||
|
return FALSE;
|
||
|
pComponentData->GetPropertyPageHolderTable()->DeleteSheetsOfNode(this);
|
||
|
}
|
||
|
ASSERT(!IsSheetLocked());
|
||
|
|
||
|
RemoveAllChildrenHelper(pComponentData);
|
||
|
ASSERT(!HasChildren());
|
||
|
OnEnumerate(pComponentData);
|
||
|
AddCurrentChildrenToUI(pComponentData);
|
||
|
MarkEnumerated();
|
||
|
}
|
||
|
else // multiple selection
|
||
|
{
|
||
|
POSITION pos = pNodeList->GetHeadPosition();
|
||
|
while (pos != NULL)
|
||
|
{
|
||
|
CTreeNode* pNode = pNodeList->GetNext(pos);
|
||
|
ASSERT(pNode != NULL);
|
||
|
|
||
|
//
|
||
|
// Have each node refresh itself
|
||
|
//
|
||
|
CNodeList nodeList;
|
||
|
nodeList.AddTail(pNode);
|
||
|
|
||
|
if (!pNode->OnRefresh(pComponentData, &nodeList))
|
||
|
{
|
||
|
bRet = FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return bRet;
|
||
|
}
|
||
|
|
||
|
BOOL CContainerNode::RemoveChildFromList(CTreeNode* p)
|
||
|
{
|
||
|
if (p->IsContainer())
|
||
|
{
|
||
|
if (m_containerChildList.RemoveNode(p))
|
||
|
{
|
||
|
p->m_pContainer = NULL;
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (m_leafChildList.RemoveNode(p))
|
||
|
{
|
||
|
p->m_pContainer = NULL;
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
void CContainerNode::RemoveAllChildrenHelper(CComponentDataObject* pComponentData)
|
||
|
{
|
||
|
ASSERT(pComponentData != NULL);
|
||
|
// remove from the UI
|
||
|
VERIFY(SUCCEEDED(pComponentData->RemoveAllChildren(this)));
|
||
|
// remove from memory, recursively from the bottom
|
||
|
RemoveAllChildrenFromList();
|
||
|
}
|
||
|
|
||
|
void CContainerNode::AddCurrentChildrenToUI(CComponentDataObject* pComponentData)
|
||
|
{
|
||
|
POSITION pos;
|
||
|
|
||
|
//
|
||
|
// Add leaves
|
||
|
//
|
||
|
for( pos = m_leafChildList.GetHeadPosition(); pos != NULL; )
|
||
|
{
|
||
|
CTreeNode* pCurrentChild = m_leafChildList.GetNext(pos);
|
||
|
VERIFY(SUCCEEDED(pComponentData->AddNode(pCurrentChild)));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Add Containers
|
||
|
//
|
||
|
for( pos = m_containerChildList.GetHeadPosition(); pos != NULL; )
|
||
|
{
|
||
|
CTreeNode* pCurrentChild = m_containerChildList.GetNext(pos);
|
||
|
VERIFY(SUCCEEDED(pComponentData->AddNode(pCurrentChild)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CContainerNode::SetFlagsDown(DWORD dwNodeFlags, BOOL bSet)
|
||
|
{
|
||
|
CTreeNode::SetFlagsDown(dwNodeFlags,bSet);
|
||
|
// scan the list of children
|
||
|
POSITION pos;
|
||
|
for( pos = m_containerChildList.GetHeadPosition(); pos != NULL; )
|
||
|
{
|
||
|
CTreeNode* pCurrentChild = m_containerChildList.GetNext(pos);
|
||
|
pCurrentChild->SetFlagsDown(dwNodeFlags,bSet);
|
||
|
}
|
||
|
for( pos = m_leafChildList.GetHeadPosition(); pos != NULL; )
|
||
|
{
|
||
|
CTreeNode* pCurrentChild = m_leafChildList.GetNext(pos);
|
||
|
pCurrentChild->SetFlagsDown(dwNodeFlags,bSet);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CContainerNode::SetFlagsOnNonContainers(DWORD dwNodeFlags, BOOL bSet)
|
||
|
{
|
||
|
// do not set on urselves, we are a container
|
||
|
// scan the list of children
|
||
|
POSITION pos;
|
||
|
for( pos = m_leafChildList.GetHeadPosition(); pos != NULL; )
|
||
|
{
|
||
|
CTreeNode* pCurrentChild = m_leafChildList.GetNext(pos);
|
||
|
pCurrentChild->SetFlagsDown(dwNodeFlags,bSet);
|
||
|
}
|
||
|
|
||
|
for (pos = m_containerChildList.GetHeadPosition(); pos != NULL; )
|
||
|
{
|
||
|
CTreeNode* pCurrentChild = m_containerChildList.GetNext(pos);
|
||
|
((CContainerNode*)pCurrentChild)->SetFlagsOnNonContainers(dwNodeFlags,bSet);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BOOL CContainerNode::AddChildToList(CTreeNode* p)
|
||
|
{
|
||
|
BOOL bRet = FALSE;
|
||
|
p->m_pContainer = this;
|
||
|
if (p->IsContainer())
|
||
|
{
|
||
|
bRet = NULL != m_containerChildList.AddTail(p);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bRet = NULL != m_leafChildList.AddTail(p);
|
||
|
}
|
||
|
return bRet;
|
||
|
}
|
||
|
|
||
|
BOOL CContainerNode::FindChild(CTreeNode* pNode, CTreeNode** ppContainer)
|
||
|
{
|
||
|
*ppContainer = NULL;
|
||
|
if (pNode == NULL)
|
||
|
return FALSE; // no sense in continuing
|
||
|
if (pNode == this)
|
||
|
{
|
||
|
*ppContainer = m_pContainer;
|
||
|
return TRUE; // the node is ourselves
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If we are looking for a leaf node search the list of leaves first
|
||
|
//
|
||
|
if (!pNode->IsContainer())
|
||
|
{
|
||
|
POSITION pos;
|
||
|
for (pos = m_leafChildList.GetHeadPosition(); pos != NULL; )
|
||
|
{
|
||
|
CLeafNode* pCurrentLeafNode = (CLeafNode*)m_leafChildList.GetNext(pos);
|
||
|
ASSERT(pCurrentLeafNode != NULL);
|
||
|
|
||
|
if (pCurrentLeafNode == pNode)
|
||
|
{
|
||
|
*ppContainer = this;
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// scan and recurse the containers if necessary
|
||
|
//
|
||
|
POSITION contPos;
|
||
|
for( contPos = m_containerChildList.GetHeadPosition(); contPos != NULL; )
|
||
|
{
|
||
|
CContainerNode* pCurrentChild = (CContainerNode*)m_containerChildList.GetNext(contPos);
|
||
|
ASSERT(pCurrentChild != NULL);
|
||
|
|
||
|
if (pCurrentChild == pNode)
|
||
|
{
|
||
|
*ppContainer = this;
|
||
|
return TRUE; // we directly contain the node
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// if the current node is a container, look inside it
|
||
|
//
|
||
|
if (pCurrentChild->FindChild(pNode,ppContainer))
|
||
|
{
|
||
|
return TRUE; // got it in the recursion
|
||
|
}
|
||
|
}
|
||
|
return FALSE; // not found
|
||
|
}
|
||
|
|
||
|
BOOL CContainerNode::AddChildToListAndUI(CTreeNode* pChildToAdd, CComponentDataObject* pComponentData)
|
||
|
{
|
||
|
ASSERT(pComponentData != NULL);
|
||
|
VERIFY(AddChildToList(pChildToAdd)); // at the end of the list of children
|
||
|
ASSERT(pChildToAdd->GetContainer() == this); // inserted underneath
|
||
|
|
||
|
// add to UI only if currently visible and already expanded
|
||
|
if (!IsVisible() || !IsExpanded())
|
||
|
return TRUE;
|
||
|
return SUCCEEDED(pComponentData->AddNode(pChildToAdd)); // add to the UI
|
||
|
}
|
||
|
|
||
|
BOOL CContainerNode::AddChildToListAndUISorted(CTreeNode* pChildToAdd, CComponentDataObject* pComponentData)
|
||
|
{
|
||
|
ASSERT(pComponentData != NULL);
|
||
|
VERIFY(AddChildToListSorted(pChildToAdd, pComponentData));
|
||
|
ASSERT(pChildToAdd->GetContainer() == this); // inserted underneath
|
||
|
|
||
|
// add to UI only if currently visible and already expanded
|
||
|
if (!IsVisible() || !IsExpanded())
|
||
|
return TRUE;
|
||
|
return SUCCEEDED(pComponentData->AddNodeSorted(pChildToAdd)); // add to the UI
|
||
|
}
|
||
|
|
||
|
BOOL CContainerNode::AddChildToListSorted(CTreeNode* p, CComponentDataObject*)
|
||
|
{
|
||
|
//
|
||
|
// Containers will be sorted with respect to containers and leaves will be
|
||
|
// sorted with respect to leaves but they won't be intermingled.
|
||
|
//
|
||
|
p->m_pContainer = this;
|
||
|
|
||
|
CNodeList* pChildNodeList = NULL;
|
||
|
if (p->IsContainer())
|
||
|
{
|
||
|
pChildNodeList = &m_containerChildList;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pChildNodeList = &m_leafChildList;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Find the position to insert the node in the list in sorted order
|
||
|
//
|
||
|
POSITION pos = pChildNodeList->GetHeadPosition();
|
||
|
while (pos != NULL)
|
||
|
{
|
||
|
CTreeNode* pNodeInList = pChildNodeList->GetAt(pos);
|
||
|
if (_wcsicoll(p->GetDisplayName(), pNodeInList->GetDisplayName()) < 0)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
pChildNodeList->GetNext(pos);
|
||
|
}
|
||
|
if (pos == NULL)
|
||
|
{
|
||
|
return NULL != pChildNodeList->AddTail(p);
|
||
|
}
|
||
|
return NULL != pChildNodeList->InsertBefore(pos, p);
|
||
|
}
|
||
|
|
||
|
void CContainerNode::RemoveAllChildrenFromList()
|
||
|
{
|
||
|
RemoveAllContainersFromList();
|
||
|
RemoveAllLeavesFromList();
|
||
|
}
|
||
|
|
||
|
int CContainerNode::Compare(CTreeNode* pNodeA, CTreeNode* pNodeB, int nCol, LPARAM)
|
||
|
{
|
||
|
// default sorting behavior
|
||
|
LPCTSTR lpszA = pNodeA->GetString(nCol);
|
||
|
LPCTSTR lpszB = pNodeB->GetString(nCol);
|
||
|
// cannot process NULL strings, have to use ""
|
||
|
ASSERT(lpszA != NULL);
|
||
|
ASSERT(lpszB != NULL);
|
||
|
return _tcsicoll( (lpszA != NULL) ? lpszA : g_lpszNullString, (lpszB != NULL) ? lpszB : g_lpszNullString);
|
||
|
}
|
||
|
|
||
|
void CContainerNode::ForceEnumeration(CComponentDataObject* pComponentData)
|
||
|
{
|
||
|
if (IsEnumerated())
|
||
|
return;
|
||
|
OnEnumerate(pComponentData);
|
||
|
MarkEnumerated();
|
||
|
}
|
||
|
|
||
|
void CContainerNode::MarkEnumerated(BOOL bEnum)
|
||
|
{
|
||
|
ASSERT(IsContainer());
|
||
|
if (bEnum)
|
||
|
m_dwNodeFlags |= TN_FLAG_CONTAINER_ENUM;
|
||
|
else
|
||
|
m_dwNodeFlags &= ~TN_FLAG_CONTAINER_ENUM;
|
||
|
}
|
||
|
|
||
|
void CContainerNode::MarkEnumeratedAndLoaded(CComponentDataObject* pComponentData)
|
||
|
{
|
||
|
MarkEnumerated();
|
||
|
OnChangeState(pComponentData); // move to loading
|
||
|
OnChangeState(pComponentData); // move to loaded
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
// CBackgroundThread
|
||
|
|
||
|
CBackgroundThread::CBackgroundThread()
|
||
|
{
|
||
|
m_pQueryObj = NULL;
|
||
|
m_bAutoDelete = FALSE;
|
||
|
m_bAbandoned = FALSE;
|
||
|
m_pContNode = NULL;
|
||
|
m_hEventHandle = NULL;
|
||
|
ExceptionPropagatingInitializeCriticalSection(&m_cs);
|
||
|
m_nQueueCountMax = 10;
|
||
|
}
|
||
|
|
||
|
CBackgroundThread::~CBackgroundThread()
|
||
|
{
|
||
|
TRACE(_T("CBackgroundThread::~CBackgroundThread()\n"));
|
||
|
ASSERT(IsAbandoned() || IsQueueEmpty());
|
||
|
::DeleteCriticalSection(&m_cs);
|
||
|
if (m_hEventHandle != NULL)
|
||
|
{
|
||
|
VERIFY(::CloseHandle(m_hEventHandle));
|
||
|
m_hEventHandle = NULL;
|
||
|
}
|
||
|
if (m_pQueryObj != NULL)
|
||
|
{
|
||
|
delete m_pQueryObj;
|
||
|
m_pQueryObj = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CBackgroundThread::SetQueryObj(CQueryObj* pQueryObj)
|
||
|
{
|
||
|
ASSERT(pQueryObj != NULL);
|
||
|
m_pQueryObj = pQueryObj;
|
||
|
m_pQueryObj->SetThread(this);
|
||
|
}
|
||
|
|
||
|
BOOL CBackgroundThread::Start(CMTContainerNode* pNode, CComponentDataObject* pComponentData)
|
||
|
{
|
||
|
AFX_MANAGE_STATE(AfxGetStaticModuleState());
|
||
|
ASSERT(m_pContNode == NULL);
|
||
|
m_pContNode = pNode;
|
||
|
|
||
|
m_hHiddenWnd = pComponentData->GetHiddenWindow();
|
||
|
|
||
|
ASSERT(m_hEventHandle == NULL); // cannot call start twice or reuse the same C++ object
|
||
|
m_hEventHandle = ::CreateEvent(NULL,TRUE /*bManualReset*/,FALSE /*signalled*/, NULL);
|
||
|
if (m_hEventHandle == NULL)
|
||
|
return FALSE;
|
||
|
return CreateThread();
|
||
|
}
|
||
|
|
||
|
int CBackgroundThread::Run()
|
||
|
{
|
||
|
ASSERT(m_pContNode != NULL);
|
||
|
ASSERT(m_pQueryObj != NULL);
|
||
|
TRACE(_T("CBackgroundThread::Run() started\n"));
|
||
|
|
||
|
while (m_pQueryObj->Enumerate());
|
||
|
|
||
|
// before exiting, have to make sure there are no items in the queue
|
||
|
if (!IsQueueEmpty())
|
||
|
VERIFY(PostHaveData());
|
||
|
|
||
|
VERIFY(PostExiting());
|
||
|
|
||
|
// wait for the main thread to acknowledge the exiting message
|
||
|
WaitForExitAcknowledge();
|
||
|
|
||
|
ASSERT(IsAbandoned() || IsQueueEmpty()); // we cannot lose items in the queue
|
||
|
TRACE(_T("CBackgroundThread::Run() terminated\n"));
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CBackgroundThread::Abandon()
|
||
|
{
|
||
|
Lock();
|
||
|
TRACE(_T("CBackgroundThread::Abandon()\n"));
|
||
|
m_bAutoDelete = TRUE;
|
||
|
m_bAbandoned = TRUE;
|
||
|
Unlock();
|
||
|
VERIFY(0 != ::SetEvent(m_hEventHandle));
|
||
|
}
|
||
|
|
||
|
BOOL CBackgroundThread::IsAbandoned()
|
||
|
{
|
||
|
Lock();
|
||
|
BOOL b = m_bAbandoned;
|
||
|
Unlock();
|
||
|
return b;
|
||
|
}
|
||
|
|
||
|
BOOL CBackgroundThread::OnAddToQueue(INT_PTR nCount)
|
||
|
{
|
||
|
BOOL bPostedMessage = FALSE;
|
||
|
if (nCount >= m_nQueueCountMax)
|
||
|
{
|
||
|
VERIFY(PostHaveData());
|
||
|
bPostedMessage = TRUE;
|
||
|
}
|
||
|
return bPostedMessage;
|
||
|
}
|
||
|
|
||
|
|
||
|
CObjBase* CBackgroundThread::RemoveFromQueue()
|
||
|
{
|
||
|
Lock();
|
||
|
ASSERT(m_pQueryObj != NULL);
|
||
|
CObjBaseList* pQueue = m_pQueryObj->GetQueue();
|
||
|
CObjBase* p = pQueue->IsEmpty() ? NULL : pQueue->RemoveHead();
|
||
|
Unlock();
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
BOOL CBackgroundThread::IsQueueEmpty()
|
||
|
{
|
||
|
Lock();
|
||
|
ASSERT(m_pQueryObj != NULL);
|
||
|
CObjBaseList* pQueue = m_pQueryObj->GetQueue();
|
||
|
BOOL bRes = pQueue->IsEmpty();
|
||
|
Unlock();
|
||
|
return bRes;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CBackgroundThread::PostHaveData()
|
||
|
{
|
||
|
return PostMessageToComponentDataRaw(CHiddenWnd::s_NodeThreadHaveDataNotificationMessage,
|
||
|
(WPARAM)m_pContNode, (LPARAM)0);
|
||
|
}
|
||
|
|
||
|
BOOL CBackgroundThread::PostError(DWORD dwErr)
|
||
|
{
|
||
|
return PostMessageToComponentDataRaw(CHiddenWnd::s_NodeThreadErrorNotificationMessage,
|
||
|
(WPARAM)m_pContNode, (LPARAM)dwErr);
|
||
|
}
|
||
|
|
||
|
BOOL CBackgroundThread::PostExiting()
|
||
|
{
|
||
|
return PostMessageToComponentDataRaw(CHiddenWnd::s_NodeThreadExitingNotificationMessage,
|
||
|
(WPARAM)m_pContNode, (LPARAM)0);
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CBackgroundThread::PostMessageToComponentDataRaw(UINT Msg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
BOOL b = IsAbandoned();
|
||
|
if (b)
|
||
|
{
|
||
|
return TRUE; // no need to post
|
||
|
}
|
||
|
|
||
|
ASSERT(m_pContNode != NULL);
|
||
|
|
||
|
ASSERT(m_hHiddenWnd != NULL);
|
||
|
ASSERT(::IsWindow(m_hHiddenWnd));
|
||
|
return ::PostMessage(m_hHiddenWnd, Msg, wParam, lParam);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CBackgroundThread::WaitForExitAcknowledge()
|
||
|
{
|
||
|
VERIFY(WAIT_OBJECT_0 == ::WaitForSingleObject(m_hEventHandle,INFINITE));
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// CMTContainerNode
|
||
|
|
||
|
|
||
|
CMTContainerNode::~CMTContainerNode()
|
||
|
{
|
||
|
ASSERT(m_pThread == NULL);
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CMTContainerNode::OnEnumerate(CComponentDataObject* pComponentData, BOOL bAsync)
|
||
|
{
|
||
|
OnChangeState(pComponentData);
|
||
|
VERIFY(StartBackgroundThread(pComponentData, bAsync));
|
||
|
return FALSE; // children not added, the thread will add them later
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CMTContainerNode::OnRefresh(CComponentDataObject* pComponentData,
|
||
|
CNodeList* pNodeList)
|
||
|
{
|
||
|
BOOL bRet = TRUE;
|
||
|
|
||
|
if (pNodeList->GetCount() == 1) // single selection
|
||
|
{
|
||
|
BOOL bLocked = IsThreadLocked();
|
||
|
ASSERT(!bLocked); // cannot do refresh on locked node, the UI should prevent this
|
||
|
if (bLocked)
|
||
|
return FALSE;
|
||
|
if (IsSheetLocked())
|
||
|
{
|
||
|
if (!CanCloseSheets())
|
||
|
{
|
||
|
pComponentData->GetPropertyPageHolderTable()->BroadcastSelectPage(this, -1);
|
||
|
return FALSE;
|
||
|
}
|
||
|
pComponentData->GetPropertyPageHolderTable()->DeleteSheetsOfNode(this);
|
||
|
}
|
||
|
ASSERT(!IsSheetLocked());
|
||
|
|
||
|
RemoveAllChildrenHelper(pComponentData);
|
||
|
ASSERT(!HasChildren());
|
||
|
OnEnumerate(pComponentData); // will spawn a thread to do enumeration
|
||
|
MarkEnumerated();
|
||
|
}
|
||
|
else // multiple selection
|
||
|
{
|
||
|
POSITION pos = pNodeList->GetHeadPosition();
|
||
|
while (pos != NULL)
|
||
|
{
|
||
|
CTreeNode* pNode = pNodeList->GetNext(pos);
|
||
|
ASSERT(pNode != NULL);
|
||
|
|
||
|
CNodeList nodeList;
|
||
|
nodeList.AddTail(pNode);
|
||
|
|
||
|
if (!pNode->OnRefresh(pComponentData, &nodeList))
|
||
|
{
|
||
|
bRet = FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
void CMTContainerNode::AbandonThread(CComponentDataObject* pComponentData)
|
||
|
{
|
||
|
if(m_pThread == NULL) // nothing running
|
||
|
return;
|
||
|
m_pThread->Abandon();
|
||
|
m_pThread = NULL;
|
||
|
pComponentData->GetRunningThreadTable()->Remove(this);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
BOOL CMTContainerNode::StartBackgroundThread(CComponentDataObject* pComponentData, BOOL bAsync)
|
||
|
{
|
||
|
ASSERT(m_pThread == NULL); // nothing running
|
||
|
|
||
|
// notify the UI to change icon, if needed
|
||
|
VERIFY(SUCCEEDED(pComponentData->ChangeNode(this, CHANGE_RESULT_ITEM_ICON)));
|
||
|
m_pThread = CreateThreadObject();
|
||
|
ASSERT(m_pThread != NULL);
|
||
|
m_pThread->SetQueryObj(OnCreateQuery());
|
||
|
BOOL bRes = m_pThread->Start(this, pComponentData);
|
||
|
if (bRes)
|
||
|
{
|
||
|
pComponentData->GetRunningThreadTable()->Add(this);
|
||
|
// we need to call UpdateVerbState() because the lock count changed
|
||
|
// by adding the node from the running thread table
|
||
|
VERIFY(SUCCEEDED(pComponentData->UpdateVerbState(this)));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If we don't want this call to be asynchronous then we have to wait for
|
||
|
// the thread to finish
|
||
|
//
|
||
|
if (!bAsync)
|
||
|
{
|
||
|
pComponentData->WaitForThreadExitMessage(this);
|
||
|
}
|
||
|
return bRes;
|
||
|
}
|
||
|
|
||
|
void CMTContainerNode::OnThreadHaveDataNotification(CComponentDataObject* pComponentDataObject)
|
||
|
{
|
||
|
ASSERT(m_pThread != NULL);
|
||
|
ASSERT(IsThreadLocked());
|
||
|
// do data transfer from thread queue
|
||
|
CObjBase* p = m_pThread->RemoveFromQueue();
|
||
|
while (p)
|
||
|
{
|
||
|
// add new node to the list of children and propagate to the UI
|
||
|
OnHaveData(p,pComponentDataObject);
|
||
|
p = m_pThread->RemoveFromQueue();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CMTContainerNode::OnThreadErrorNotification(DWORD dwErr, CComponentDataObject*)
|
||
|
{
|
||
|
ASSERT(m_pThread != NULL);
|
||
|
ASSERT(IsThreadLocked());
|
||
|
OnError(dwErr);
|
||
|
}
|
||
|
|
||
|
void CMTContainerNode::OnThreadExitingNotification(CComponentDataObject* pComponentDataObject)
|
||
|
{
|
||
|
ASSERT(m_pThread != NULL);
|
||
|
ASSERT(IsThreadLocked());
|
||
|
#if (TRUE)
|
||
|
// let the thread know it can shut down
|
||
|
m_pThread->AcknowledgeExiting();
|
||
|
VERIFY(WAIT_OBJECT_0 == ::WaitForSingleObject(m_pThread->m_hThread,INFINITE));
|
||
|
OnChangeState(pComponentDataObject);
|
||
|
delete m_pThread;
|
||
|
m_pThread = NULL;
|
||
|
pComponentDataObject->GetRunningThreadTable()->Remove(this);
|
||
|
// we need to call UpdateVerbState() because the lock count changed
|
||
|
// by removing the node from the running thread table
|
||
|
VERIFY(SUCCEEDED(pComponentDataObject->UpdateVerbState(this)));
|
||
|
|
||
|
TRACE(_T("OnThreadExitingNotification()\n"));
|
||
|
|
||
|
#else // maybe better way of doing it???
|
||
|
// we are going to detach from the thread, so make copies of variables
|
||
|
HANDLE hThread = m_pThread->m_hThread;
|
||
|
CBackgroundThread* pThread = m_pThread;
|
||
|
AbandonThread(pComponentDataObject); // sets m_pThread = NULL
|
||
|
// acknowledge to thread
|
||
|
pThread->AcknowledgeExiting();
|
||
|
VERIFY(WAIT_OBJECT_0 == ::WaitForSingleObject(hThread,INFINITE));
|
||
|
OnChangeState(pComponentDataObject);
|
||
|
#endif
|
||
|
|
||
|
VERIFY(SUCCEEDED(pComponentDataObject->SortResultPane(this)));
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|