windows-nt/Source/XPSP1/NT/net/mmc/tapi/tapihand.cpp
2020-09-26 16:20:57 +08:00

887 lines
23 KiB
C++

/**********************************************************************/
/** Microsoft Windows/NT **/
/** Copyright(c) Microsoft Corporation, 1997 - 1999 **/
/**********************************************************************/
/*
tapihand.cpp
TAPI specifc handler base classes
FILE HISTORY:
*/
#include "stdafx.h"
#include "tapihand.h"
#include "snaputil.h" // For CGUIDArray
#include "extract.h" // For ExtractInternalFormat
const TCHAR g_szDefaultHelpTopic[] = _T("\\help\\tapiconcepts.chm::/sag_TAPItopnode.htm");
/*---------------------------------------------------------------------------
CMTTapiHandler::OnChangeState
Description
Author: EricDav
---------------------------------------------------------------------------*/
void CMTTapiHandler::OnChangeState
(
ITFSNode * pNode
)
{
// Increment the state to the next position
switch (m_nState)
{
case notLoaded:
case loaded:
case unableToLoad:
{
m_nState = loading;
m_dwErr = 0;
}
break;
case loading:
{
m_nState = (m_dwErr != 0) ? unableToLoad : loaded;
if (m_dwErr)
{
CString strPrefix;
GetErrorPrefix(pNode, &strPrefix);
if (!strPrefix.IsEmpty())
::TapiMessageBoxEx(m_dwErr, strPrefix);
}
}
break;
default:
ASSERT(FALSE);
}
// check to make sure we are still the visible node in the UI
if (m_bSelected)
{
UpdateStandardVerbs(pNode, pNode->GetData(TFS_DATA_TYPE));
}
// Now check and see if there is a new image for this state for this handler
int nImage, nOpenImage;
nImage = GetImageIndex(FALSE);
nOpenImage = GetImageIndex(TRUE);
if (nImage >= 0)
pNode->SetData(TFS_DATA_IMAGEINDEX, nImage);
if (nOpenImage >= 0)
pNode->SetData(TFS_DATA_OPENIMAGEINDEX, nOpenImage);
VERIFY(SUCCEEDED(pNode->ChangeNode(SCOPE_PANE_CHANGE_ITEM_ICON)));
}
/*!--------------------------------------------------------------------------
CMTTapiHandler::UpdateStandardVerbs
Tells the IComponent to update the verbs for this node
Author: EricDav
---------------------------------------------------------------------------*/
void
CMTTapiHandler::UpdateStandardVerbs
(
ITFSNode * pNode,
LONG_PTR dwNodeType
)
{
HRESULT hr = hrOK;
SPIComponentData spCompData;
SPIConsole spConsole;
IDataObject* pDataObject;
m_spNodeMgr->GetComponentData(&spCompData);
CORg ( spCompData->QueryDataObject(NULL, CCT_RESULT, &pDataObject) );
CORg ( m_spNodeMgr->GetConsole(&spConsole) );
CORg ( spConsole->UpdateAllViews(pDataObject,
reinterpret_cast<MMC_COOKIE>(pNode),
RESULT_PANE_UPDATE_VERBS) );
pDataObject->Release();
Error:
return;
}
/*!--------------------------------------------------------------------------
CMTTapiHandler::OnCreateDataObject
-
Author: EricDav
---------------------------------------------------------------------------*/
STDMETHODIMP
CMTTapiHandler::OnCreateDataObject
(
ITFSComponent * pComponent,
MMC_COOKIE cookie,
DATA_OBJECT_TYPES type,
IDataObject ** ppDataObject
)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
Assert(ppDataObject != NULL);
CDataObject * pObject = NULL;
SPIDataObject spDataObject;
pObject = new CDataObject;
spDataObject = pObject; // do this so that it gets released correctly
Assert(pObject != NULL);
if (cookie == MMC_MULTI_SELECT_COOKIE)
{
CreateMultiSelectData(pComponent, pObject);
}
// Save cookie and type for delayed rendering
pObject->SetType(type);
pObject->SetCookie(cookie);
// Store the coclass with the data object
pObject->SetClsid(*(m_spTFSComponentData->GetCoClassID()));
pObject->SetTFSComponentData(m_spTFSComponentData);
return pObject->QueryInterface(IID_IDataObject,
reinterpret_cast<void**>(ppDataObject));
}
HRESULT
CMTTapiHandler::CreateMultiSelectData(ITFSComponent * pComponent, CDataObject * pObject)
{
HRESULT hr = hrOK;
// build the list of selected nodes
CTFSNodeList listSelectedNodes;
CGUIDArray rgGuids;
UINT cb;
GUID* pGuid;
COM_PROTECT_TRY
{
CORg (BuildSelectedItemList(pComponent, &listSelectedNodes));
// collect all of the unique guids
while (listSelectedNodes.GetCount() > 0)
{
SPITFSNode spCurNode;
const GUID * pGuid;
spCurNode = listSelectedNodes.RemoveHead();
pGuid = spCurNode->GetNodeType();
rgGuids.AddUnique(*pGuid);
}
// now put the information in the data object
pObject->SetMultiSelDobj();
cb = (UINT)rgGuids.GetSize() * sizeof(GUID);
pGuid = new GUID[(size_t)rgGuids.GetSize()];
CopyMemory(pGuid, rgGuids.GetData(), cb);
pObject->SetMultiSelData((BYTE*)pGuid, cb);
COM_PROTECT_ERROR_LABEL;
}
COM_PROTECT_CATCH
return hr;
}
/*!--------------------------------------------------------------------------
CMTTapiHandler::SaveColumns
-
Author: EricDav
---------------------------------------------------------------------------*/
HRESULT
CMTTapiHandler::SaveColumns
(
ITFSComponent * pComponent,
MMC_COOKIE cookie,
LPARAM arg,
LPARAM lParam
)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
HRESULT hr = hrOK;
LONG_PTR dwNodeType;
int nCol = 0;
int nColWidth;
SPITFSNode spNode, spRootNode;
SPIHeaderCtrl spHeaderCtrl;
BOOL bDirty = FALSE;
CORg (m_spNodeMgr->FindNode(cookie, &spNode));
CORg (pComponent->GetHeaderCtrl(&spHeaderCtrl));
dwNodeType = spNode->GetData(TFS_DATA_TYPE);
while (aColumns[dwNodeType][nCol] != 0)
{
if ( (SUCCEEDED(spHeaderCtrl->GetColumnWidth(nCol, &nColWidth))) &&
(aColumnWidths[dwNodeType][nCol] != nColWidth) )
{
aColumnWidths[dwNodeType][nCol] = nColWidth;
bDirty = TRUE;
}
nCol++;
}
if (bDirty)
{
CORg (m_spNodeMgr->GetRootNode(&spRootNode));
spRootNode->SetData(TFS_DATA_DIRTY, TRUE);
}
Error:
return hr;
}
/*!--------------------------------------------------------------------------
CMTTapiHandler::OnResultSelect
Handles the MMCN_SELECT notifcation
Author: EricDav
---------------------------------------------------------------------------*/
HRESULT
CMTTapiHandler::OnResultSelect
(
ITFSComponent * pComponent,
LPDATAOBJECT pDataObject,
MMC_COOKIE cookie,
LPARAM arg,
LPARAM lParam
)
{
SPIConsoleVerb spConsoleVerb;
SPITFSNode spNode;
HRESULT hr = hrOK;
SPINTERNAL spInternal;
BOOL bMultiSelect = FALSE;
BOOL bScope = (BOOL) LOWORD(arg);
BOOL bSelect = (BOOL) HIWORD(arg);
m_bSelected = bSelect;
Trace1("CMTTapiHandler::OnResultSelect select = %d\n", bSelect);
CORg (pComponent->GetConsoleVerb(&spConsoleVerb));
spInternal = ::ExtractInternalFormat(pDataObject);
if (spInternal &&
spInternal->m_cookie == MMC_MULTI_SELECT_COOKIE)
{
CORg (pComponent->GetSelectedNode(&spNode));
bMultiSelect = TRUE;
}
else
{
CORg (m_spNodeMgr->FindNode(cookie, &spNode));
}
UpdateConsoleVerbs(spConsoleVerb, spNode->GetData(TFS_DATA_TYPE), bMultiSelect);
Error:
return hr;
}
/*!--------------------------------------------------------------------------
CMTTapiHandler::OnResultUpdateView
Implementation of ITFSResultHandler::OnResultUpdateView
Author: EricDav
---------------------------------------------------------------------------*/
HRESULT CMTTapiHandler::OnResultUpdateView
(
ITFSComponent *pComponent,
LPDATAOBJECT pDataObject,
LPARAM data,
LPARAM hint
)
{
HRESULT hr = hrOK;
if (hint == RESULT_PANE_UPDATE_VERBS)
{
SPIConsoleVerb spConsoleVerb;
SPITFSNode spNode;
CORg (pComponent->GetConsoleVerb(&spConsoleVerb));
spNode.Set(reinterpret_cast<ITFSNode *>(data));
UpdateConsoleVerbs(spConsoleVerb, spNode->GetData(TFS_DATA_TYPE));
}
else
{
return CBaseResultHandler::OnResultUpdateView(pComponent, pDataObject, data, hint);
}
Error:
return hr;
}
/*!--------------------------------------------------------------------------
CMTTapiHandler::OnResultContextHelp
Implementation of ITFSResultHandler::OnResultContextHelp
Author: EricDav
---------------------------------------------------------------------------*/
HRESULT
CMTTapiHandler::OnResultContextHelp
(
ITFSComponent * pComponent,
LPDATAOBJECT pDataObject,
MMC_COOKIE cookie,
LPARAM arg,
LPARAM lParam
)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
HRESULT hr = hrOK;
SPIDisplayHelp spDisplayHelp;
SPIConsole spConsole;
pComponent->GetConsole(&spConsole);
hr = spConsole->QueryInterface (IID_IDisplayHelp, (LPVOID*) &spDisplayHelp);
ASSERT (SUCCEEDED (hr));
if ( SUCCEEDED (hr) )
{
LPCTSTR pszHelpFile = m_spTFSCompData->GetHTMLHelpFileName();
if (pszHelpFile == NULL)
goto Error;
CString szHelpFilePath;
UINT nLen = ::GetWindowsDirectory (szHelpFilePath.GetBufferSetLength(2 * MAX_PATH), 2 * MAX_PATH);
if (nLen == 0)
{
hr = E_FAIL;
goto Error;
}
szHelpFilePath.ReleaseBuffer();
szHelpFilePath += g_szDefaultHelpTopic;
hr = spDisplayHelp->ShowTopic (T2OLE ((LPTSTR)(LPCTSTR) szHelpFilePath));
ASSERT (SUCCEEDED (hr));
}
Error:
return hr;
}
/*!--------------------------------------------------------------------------
CMTTapiHandler::UpdateStandardVerbs
Updates the standard verbs depending upon the state of the node
Author: EricDav
---------------------------------------------------------------------------*/
void
CMTTapiHandler::UpdateConsoleVerbs
(
IConsoleVerb * pConsoleVerb,
LONG_PTR dwNodeType,
BOOL bMultiSelect
)
{
BOOL bStates[ARRAYLEN(g_ConsoleVerbs)];
MMC_BUTTON_STATE * ButtonState;
int i;
if (bMultiSelect)
{
ButtonState = g_ConsoleVerbStatesMultiSel[dwNodeType];
for (i = 0; i < ARRAYLEN(g_ConsoleVerbs); bStates[i++] = TRUE);
}
else
{
ButtonState = g_ConsoleVerbStates[dwNodeType];
switch (m_nState)
{
case loaded:
for (i = 0; i < ARRAYLEN(g_ConsoleVerbs); bStates[i++] = TRUE);
break;
case notLoaded:
case loading:
for (i = 0; i < ARRAYLEN(g_ConsoleVerbs); bStates[i++] = FALSE);
break;
case unableToLoad:
for (i = 0; i < ARRAYLEN(g_ConsoleVerbs); bStates[i++] = FALSE);
bStates[MMC_VERB_REFRESH & 0x000F] = TRUE;
bStates[MMC_VERB_DELETE & 0x000F] = TRUE;
break;
}
}
EnableVerbs(pConsoleVerb, ButtonState, bStates);
}
/*!--------------------------------------------------------------------------
CMTTapiHandler::EnableVerbs
Enables the verb buttons
Author: EricDav
---------------------------------------------------------------------------*/
void
CMTTapiHandler::EnableVerbs
(
IConsoleVerb * pConsoleVerb,
MMC_BUTTON_STATE ButtonState[],
BOOL bState[]
)
{
if (pConsoleVerb == NULL)
{
Assert(FALSE);
return;
}
for (int i=0; i < ARRAYLEN(g_ConsoleVerbs); ++i)
{
if (ButtonState[i] == ENABLED)
{
// unhide this button before enabling
pConsoleVerb->SetVerbState(g_ConsoleVerbs[i],
HIDDEN,
FALSE);
pConsoleVerb->SetVerbState(g_ConsoleVerbs[i],
ButtonState[i],
bState[i]);
}
else
{
// hide this button
pConsoleVerb->SetVerbState(g_ConsoleVerbs[i],
HIDDEN,
TRUE);
}
}
}
/*!--------------------------------------------------------------------------
CMTTapiHandler::OnResultRefresh
Call into the MTHandler to do a refresh
Author: EricDav
---------------------------------------------------------------------------*/
HRESULT
CMTTapiHandler::OnResultRefresh
(
ITFSComponent * pComponent,
LPDATAOBJECT pDataObject,
MMC_COOKIE cookie,
LPARAM arg,
LPARAM lParam
)
{
HRESULT hr = hrOK;
SPITFSNode spNode;
CORg (m_spNodeMgr->FindNode(cookie, &spNode));
OnRefresh(spNode, pDataObject, 0, arg, lParam);
Error:
return hr;
}
/*!--------------------------------------------------------------------------
CMTTapiHandler::ExpandNode
Expands/compresses this node
Author: EricDav
---------------------------------------------------------------------------*/
void
CMTTapiHandler::ExpandNode
(
ITFSNode * pNode,
BOOL fExpand
)
{
SPIComponentData spCompData;
SPIDataObject spDataObject;
LPDATAOBJECT pDataObject;
SPIConsole spConsole;
HRESULT hr = hrOK;
// don't expand the node if we are handling the EXPAND_SYNC message,
// this screws up the insertion of item, getting duplicates.
if (!m_fExpandSync)
{
m_spNodeMgr->GetComponentData(&spCompData);
CORg ( spCompData->QueryDataObject((MMC_COOKIE) pNode, CCT_SCOPE, &pDataObject) );
spDataObject = pDataObject;
CORg ( m_spNodeMgr->GetConsole(&spConsole) );
CORg ( spConsole->UpdateAllViews(pDataObject, TRUE, RESULT_PANE_EXPAND) );
}
Error:
return;
}
/*!--------------------------------------------------------------------------
CMTTapiHandler::OnExpandSync
Handles the MMCN_EXPANDSYNC notifcation
We need to do syncronous enumeration. We'll fire off the background
thread like before, but we'll wait for it to exit before we return.
Author: EricDav
---------------------------------------------------------------------------*/
HRESULT
CMTTapiHandler::OnExpandSync
(
ITFSNode * pNode,
LPDATAOBJECT pDataObject,
LPARAM arg,
LPARAM lParam
)
{
HRESULT hr = hrOK;
MSG msg;
m_fExpandSync = TRUE;
hr = OnExpand(pNode, pDataObject, CCT_SCOPE, arg, lParam);
// wait for the background thread to exit
WaitForSingleObject(m_hThread, INFINITE);
// The background thread posts messages to a hidden window to
// pass data back to the main thread. The messages won't go through since we are
// blocking the main thread. The data goes on a queue in the query object
// which the handler has a pointer to so we can just fake the notification.
if (m_spQuery.p)
OnNotifyHaveData((LPARAM) m_spQuery.p);
// Tell MMC we handled this message
MMC_EXPANDSYNC_STRUCT * pES = reinterpret_cast<MMC_EXPANDSYNC_STRUCT *>(lParam);
if (pES)
pES->bHandled = TRUE;
m_fExpandSync = FALSE;
return hr;
}
/*---------------------------------------------------------------------------
Class: CTapiHandler
---------------------------------------------------------------------------*/
/*!--------------------------------------------------------------------------
CTapiHandler::SaveColumns
-
Author: EricDav
---------------------------------------------------------------------------*/
HRESULT
CTapiHandler::SaveColumns
(
ITFSComponent * pComponent,
MMC_COOKIE cookie,
LPARAM arg,
LPARAM lParam
)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
HRESULT hr = hrOK;
LONG_PTR dwNodeType;
int nCol = 0;
int nColWidth = 0;
SPITFSNode spNode, spRootNode;
SPIHeaderCtrl spHeaderCtrl;
BOOL bDirty = FALSE;
CORg (m_spNodeMgr->FindNode(cookie, &spNode));
CORg (pComponent->GetHeaderCtrl(&spHeaderCtrl));
dwNodeType = spNode->GetData(TFS_DATA_TYPE);
while (aColumns[dwNodeType][nCol] != 0)
{
if ( (SUCCEEDED(spHeaderCtrl->GetColumnWidth(nCol, &nColWidth))) &&
(aColumnWidths[dwNodeType][nCol] != nColWidth) )
{
aColumnWidths[dwNodeType][nCol] = nColWidth;
bDirty = TRUE;
}
nCol++;
}
if (bDirty)
{
CORg (m_spNodeMgr->GetRootNode(&spRootNode));
spRootNode->SetData(TFS_DATA_DIRTY, TRUE);
}
Error:
return hr;
}
/*!--------------------------------------------------------------------------
CTapiHandler::OnCreateDataObject
-
Author: EricDav
---------------------------------------------------------------------------*/
STDMETHODIMP
CTapiHandler::OnCreateDataObject
(
ITFSComponent * pComponent,
MMC_COOKIE cookie,
DATA_OBJECT_TYPES type,
IDataObject ** ppDataObject
)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
Assert(ppDataObject != NULL);
CDataObject * pObject = NULL;
SPIDataObject spDataObject;
pObject = new CDataObject;
spDataObject = pObject; // do this so that it gets released correctly
Assert(pObject != NULL);
if (cookie == MMC_MULTI_SELECT_COOKIE)
{
CreateMultiSelectData(pComponent, pObject);
}
// Save cookie and type for delayed rendering
pObject->SetType(type);
pObject->SetCookie(cookie);
// Store the coclass with the data object
pObject->SetClsid(*(m_spTFSComponentData->GetCoClassID()));
pObject->SetTFSComponentData(m_spTFSComponentData);
return pObject->QueryInterface(IID_IDataObject,
reinterpret_cast<void**>(ppDataObject));
}
HRESULT
CTapiHandler::CreateMultiSelectData(ITFSComponent * pComponent, CDataObject * pObject)
{
HRESULT hr = hrOK;
// build the list of selected nodes
CTFSNodeList listSelectedNodes;
CGUIDArray rgGuids;
UINT cb;
GUID* pGuid;
COM_PROTECT_TRY
{
CORg (BuildSelectedItemList(pComponent, &listSelectedNodes));
// collect all of the unique guids
while (listSelectedNodes.GetCount() > 0)
{
SPITFSNode spCurNode;
const GUID * pGuid;
spCurNode = listSelectedNodes.RemoveHead();
pGuid = spCurNode->GetNodeType();
rgGuids.AddUnique(*pGuid);
}
// now put the information in the data object
pObject->SetMultiSelDobj();
cb = (UINT)rgGuids.GetSize() * sizeof(GUID);
pGuid = new GUID[(size_t)rgGuids.GetSize()];
CopyMemory(pGuid, rgGuids.GetData(), cb);
pObject->SetMultiSelData((BYTE*)pGuid, cb);
COM_PROTECT_ERROR_LABEL;
}
COM_PROTECT_CATCH
return hr;
}
/*---------------------------------------------------------------------------
CTapiHandler::OnResultDelete
Description
Author: EricDav
---------------------------------------------------------------------------*/
HRESULT
CTapiHandler::OnResultDelete
(
ITFSComponent * pComponent,
LPDATAOBJECT pDataObject,
MMC_COOKIE cookie,
LPARAM arg,
LPARAM lParam
)
{
HRESULT hr = hrOK;
Trace0("CTapiHandler::OnResultDelete received\n");
// translate this call to the parent and let it handle deletion
// of result pane items
SPITFSNode spNode, spParent;
SPITFSResultHandler spParentRH;
CORg (m_spNodeMgr->FindNode(cookie, &spNode));
CORg (spNode->GetParent(&spParent));
if (spParent == NULL)
return hr;
CORg (spParent->GetResultHandler(&spParentRH));
CORg (spParentRH->Notify(pComponent, spParent->GetData(TFS_DATA_COOKIE), pDataObject, MMCN_DELETE, arg, lParam));
Error:
return hr;
}
/*!--------------------------------------------------------------------------
CTapiHandler::OnResultContextHelp
Implementation of ITFSResultHandler::OnResultContextHelp
Author: EricDav
---------------------------------------------------------------------------*/
HRESULT
CTapiHandler::OnResultContextHelp
(
ITFSComponent * pComponent,
LPDATAOBJECT pDataObject,
MMC_COOKIE cookie,
LPARAM arg,
LPARAM lParam
)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
HRESULT hr = hrOK;
SPIDisplayHelp spDisplayHelp;
SPIConsole spConsole;
pComponent->GetConsole(&spConsole);
hr = spConsole->QueryInterface (IID_IDisplayHelp, (LPVOID*) &spDisplayHelp);
ASSERT (SUCCEEDED (hr));
if ( SUCCEEDED (hr) )
{
LPCTSTR pszHelpFile = m_spTFSCompData->GetHTMLHelpFileName();
if (pszHelpFile == NULL)
goto Error;
CString szHelpFilePath;
UINT nLen = ::GetWindowsDirectory (szHelpFilePath.GetBufferSetLength(2 * MAX_PATH), 2 * MAX_PATH);
if (nLen == 0)
{
hr = E_FAIL;
goto Error;
}
szHelpFilePath.ReleaseBuffer();
szHelpFilePath += g_szDefaultHelpTopic;
hr = spDisplayHelp->ShowTopic (T2OLE ((LPTSTR)(LPCTSTR) szHelpFilePath));
ASSERT (SUCCEEDED (hr));
}
Error:
return hr;
}
/*!--------------------------------------------------------------------------
CTapiHandler::OnResultSelect
Handles the MMCN_SELECT notifcation
Author: EricDav
---------------------------------------------------------------------------*/
HRESULT
CTapiHandler::OnResultSelect
(
ITFSComponent * pComponent,
LPDATAOBJECT pDataObject,
MMC_COOKIE cookie,
LPARAM arg,
LPARAM lParam
)
{
SPIConsoleVerb spConsoleVerb;
SPITFSNode spNode;
HRESULT hr = hrOK;
BOOL bStates[ARRAYLEN(g_ConsoleVerbs)];
int i;
BOOL bScope = (BOOL) LOWORD(arg);
BOOL bSelect = (BOOL) HIWORD(arg);
Trace1("CTapiHandler::OnResultSelect select = %d\n", bSelect);
//m_bSelected = bSelect;
CORg (pComponent->GetConsoleVerb(&spConsoleVerb));
CORg (m_spNodeMgr->FindNode(cookie, &spNode));
for (i = 0; i < ARRAYLEN(g_ConsoleVerbs); bStates[i++] = TRUE);
EnableVerbs(spConsoleVerb, g_ConsoleVerbStates[spNode->GetData(TFS_DATA_TYPE)], bStates);
Error:
return hr;
}
/*!--------------------------------------------------------------------------
CMTTapiHandler::EnableVerbs
Enables the verb buttons
Author: EricDav
---------------------------------------------------------------------------*/
void
CTapiHandler::EnableVerbs
(
IConsoleVerb * pConsoleVerb,
MMC_BUTTON_STATE ButtonState[],
BOOL bState[]
)
{
if (pConsoleVerb == NULL)
{
Assert(FALSE);
return;
}
for (int i=0; i < ARRAYLEN(g_ConsoleVerbs); ++i)
{
if (ButtonState[i] == ENABLED)
{
// unhide this button before enabling
pConsoleVerb->SetVerbState(g_ConsoleVerbs[i],
HIDDEN,
FALSE);
pConsoleVerb->SetVerbState(g_ConsoleVerbs[i],
ButtonState[i],
bState[i]);
}
else
{
// hide this button
pConsoleVerb->SetVerbState(g_ConsoleVerbs[i],
HIDDEN,
TRUE);
}
}
}