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.
1541 lines
36 KiB
1541 lines
36 KiB
/*++
|
|
|
|
Copyright (C) Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
componet.cpp
|
|
|
|
Abstract:
|
|
|
|
This module implemets CComponent class
|
|
|
|
Author:
|
|
|
|
William Hsieh (williamh) created
|
|
|
|
Revision History:
|
|
|
|
|
|
--*/
|
|
|
|
#include "devmgr.h"
|
|
#include "factory.h"
|
|
#include <devguid.h>
|
|
|
|
//
|
|
// ctor and dtor
|
|
//
|
|
|
|
CComponent::CComponent(
|
|
CComponentData* pComponentData
|
|
)
|
|
{
|
|
m_pComponentData = pComponentData;
|
|
m_pHeader = NULL;
|
|
m_pConsole = NULL;
|
|
m_pResult = NULL;
|
|
m_pConsoleVerb = NULL;
|
|
m_pCurFolder = NULL;
|
|
m_pPropSheetProvider = NULL;
|
|
m_pDisplayHelp = NULL;
|
|
m_Dirty = FALSE;
|
|
m_pControlbar = NULL;
|
|
m_pToolbar = NULL;
|
|
|
|
//
|
|
// Increment object count(used by CanUnloadNow)
|
|
//
|
|
::InterlockedIncrement(&CClassFactory::s_Objects);
|
|
m_Ref = 1;
|
|
}
|
|
|
|
CComponent::~CComponent()
|
|
{
|
|
//
|
|
// Decrement object count(used by CanUnloadNow)
|
|
//
|
|
ASSERT( 0 != CClassFactory::s_Objects );
|
|
::InterlockedDecrement(&CClassFactory::s_Objects);
|
|
}
|
|
|
|
//
|
|
// IUNKNOWN interface
|
|
//
|
|
ULONG
|
|
CComponent::AddRef()
|
|
{
|
|
return ::InterlockedIncrement(&m_Ref);
|
|
}
|
|
|
|
ULONG
|
|
CComponent::Release()
|
|
{
|
|
ASSERT( 0 != m_Ref );
|
|
ULONG cRef = ::InterlockedDecrement(&m_Ref);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CComponent::QueryInterface(
|
|
REFIID riid,
|
|
void** ppv
|
|
)
|
|
{
|
|
if (!ppv)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (IsEqualIID(riid, IID_IUnknown))
|
|
{
|
|
*ppv = (IUnknown*)(IComponent*)this;
|
|
}
|
|
|
|
else if (IsEqualIID(riid, IID_IComponent))
|
|
{
|
|
*ppv = (IComponent*)this;
|
|
}
|
|
|
|
else if (IsEqualIID(riid, IID_IResultDataCompare))
|
|
{
|
|
*ppv = (IResultDataCompare*)this;
|
|
}
|
|
|
|
else if (IsEqualIID(riid, IID_IExtendContextMenu))
|
|
{
|
|
*ppv = (IExtendContextMenu*)this;
|
|
}
|
|
|
|
else if (IsEqualIID(riid, IID_IExtendControlbar))
|
|
{
|
|
*ppv = (IExtendControlbar*)this;
|
|
}
|
|
|
|
else if (IsEqualIID(riid, IID_IExtendPropertySheet))
|
|
{
|
|
*ppv = (IExtendPropertySheet*)this;
|
|
}
|
|
|
|
else if (IsEqualIID(riid, IID_IPersistStream))
|
|
{
|
|
*ppv = (IPersistStream*)this;
|
|
}
|
|
|
|
else if (IsEqualIID(riid, IID_ISnapinCallback))
|
|
{
|
|
*ppv = (ISnapinCallback*)this;
|
|
}
|
|
|
|
else
|
|
{
|
|
*ppv = NULL;
|
|
hr = E_NOINTERFACE;
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
AddRef();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// IComponent interface implementation
|
|
//
|
|
STDMETHODIMP
|
|
CComponent::GetResultViewType(
|
|
MMC_COOKIE cookie,
|
|
LPOLESTR* ppViewType,
|
|
long* pViewOptions
|
|
)
|
|
{
|
|
if (!ppViewType || !pViewOptions)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
try
|
|
{
|
|
CFolder* pFolder;
|
|
pFolder = FindFolder(cookie);
|
|
|
|
if (pFolder)
|
|
{
|
|
return pFolder->GetResultViewType(ppViewType, pViewOptions);
|
|
}
|
|
|
|
else
|
|
{
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
return S_FALSE;
|
|
}
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CComponent::Initialize(
|
|
LPCONSOLE lpConsole
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (!lpConsole)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
m_pConsole = lpConsole;
|
|
lpConsole->AddRef();
|
|
|
|
hr = lpConsole->QueryInterface(IID_IHeaderCtrl, (void**)&m_pHeader);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
lpConsole->SetHeader(m_pHeader);
|
|
hr = lpConsole->QueryInterface(IID_IResultData, (void**)&m_pResult);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = lpConsole->QueryConsoleVerb(&m_pConsoleVerb);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = lpConsole->QueryInterface(IID_IPropertySheetProvider,
|
|
(void**)&m_pPropSheetProvider);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = lpConsole->QueryInterface(IID_IDisplayHelp, (void**)&m_pDisplayHelp);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
TRACE((TEXT("CComponent::Initialize failed\n")));
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
#if DBG
|
|
TCHAR *mmcNotifyStr[] = {
|
|
TEXT("UNKNOWN"),
|
|
TEXT("ACTIVATE"),
|
|
TEXT("ADD_IMAGES"),
|
|
TEXT("BTN_CLICK"),
|
|
TEXT("CLICK"),
|
|
TEXT("COLUMN_CLICK"),
|
|
TEXT("CONTEXTMENU"),
|
|
TEXT("CUTORMOVE"),
|
|
TEXT("DBLCLICK"),
|
|
TEXT("DELETE"),
|
|
TEXT("DESELECT_ALL"),
|
|
TEXT("EXPAND"),
|
|
TEXT("HELP"),
|
|
TEXT("MENU_BTNCLICK"),
|
|
TEXT("MINIMIZED"),
|
|
TEXT("PASTE"),
|
|
TEXT("PROPERTY_CHANGE"),
|
|
TEXT("QUERY_PASTE"),
|
|
TEXT("REFRESH"),
|
|
TEXT("REMOVE_CHILDREN"),
|
|
TEXT("RENAME"),
|
|
TEXT("SELECT"),
|
|
TEXT("SHOW"),
|
|
TEXT("VIEW_CHANGE"),
|
|
TEXT("SNAPINHELP"),
|
|
TEXT("CONTEXTHELP"),
|
|
TEXT("INITOCX"),
|
|
TEXT("FILTER_CHANGE"),
|
|
TEXT("FILTERBTN_CLICK"),
|
|
TEXT("RESTORE_VIEW"),
|
|
TEXT("PRINT"),
|
|
TEXT("PRELOAD"),
|
|
TEXT("LISTPAD"),
|
|
TEXT("EXPANDSYNC")
|
|
};
|
|
#endif
|
|
|
|
STDMETHODIMP
|
|
CComponent::Notify(
|
|
LPDATAOBJECT lpDataObject,
|
|
MMC_NOTIFY_TYPE event,
|
|
LPARAM arg,
|
|
LPARAM param
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
|
|
INTERNAL_DATA tID;
|
|
|
|
#if DBG
|
|
UINT i = event - MMCN_ACTIVATE + 1;
|
|
if (event > MMCN_EXPANDSYNC || event < MMCN_ACTIVATE)
|
|
{
|
|
i = 0;
|
|
}
|
|
//TRACE((TEXT("Componet:Notify, event = %lx %s\n"), event, mmcNotifyStr[i]));
|
|
#endif
|
|
|
|
try
|
|
{
|
|
if (DOBJ_CUSTOMOCX == lpDataObject)
|
|
{
|
|
return OnOcxNotify(event, arg, param);
|
|
}
|
|
|
|
hr = ExtractData(lpDataObject, CDataObject::m_cfSnapinInternal,
|
|
(PBYTE)&tID, sizeof(tID));
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
switch(event)
|
|
{
|
|
case MMCN_ACTIVATE:
|
|
hr = OnActivate(tID.cookie, arg, param);
|
|
break;
|
|
|
|
case MMCN_VIEW_CHANGE:
|
|
hr = OnViewChange(tID.cookie, arg, param);
|
|
break;
|
|
|
|
case MMCN_SHOW:
|
|
hr = OnShow(tID.cookie, arg, param);
|
|
break;
|
|
|
|
case MMCN_CLICK:
|
|
hr = OnResultItemClick(tID.cookie, arg, param);
|
|
break;
|
|
|
|
case MMCN_DBLCLICK:
|
|
hr = OnResultItemDblClick(tID.cookie, arg, param);
|
|
break;
|
|
|
|
case MMCN_MINIMIZED:
|
|
hr = OnMinimize(tID.cookie, arg, param);
|
|
break;
|
|
|
|
case MMCN_BTN_CLICK:
|
|
hr = OnBtnClick(tID.cookie, arg, param);
|
|
break;
|
|
|
|
case MMCN_SELECT:
|
|
hr = OnSelect(tID.cookie, arg, param);
|
|
break;
|
|
|
|
case MMCN_ADD_IMAGES:
|
|
hr = OnAddImages(tID.cookie, (IImageList*)arg, param);
|
|
break;
|
|
|
|
case MMCN_RESTORE_VIEW:
|
|
hr = OnRestoreView(tID.cookie, arg, param);
|
|
break;
|
|
|
|
case MMCN_CONTEXTHELP:
|
|
hr = OnContextHelp(tID.cookie, arg, param);
|
|
break;
|
|
|
|
default:
|
|
hr = S_OK;
|
|
break;
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
if (MMCN_ADD_IMAGES == event)
|
|
{
|
|
OnAddImages(0, (IImageList*)arg, (HSCOPEITEM)param);
|
|
} else if (MMCN_PROPERTY_CHANGE == event) {
|
|
CNotifyRebootRequest* pNRR = (CNotifyRebootRequest*)param;
|
|
|
|
if (pNRR) {
|
|
PromptForRestart(pNRR->m_hWnd ? pNRR->m_hWnd : m_pComponentData->m_hwndMain,
|
|
pNRR->m_RestartFlags,
|
|
pNRR->m_StringId);
|
|
pNRR->Release();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CComponent::Destroy(
|
|
MMC_COOKIE cookie
|
|
)
|
|
{
|
|
//
|
|
// cookie must point to the static node
|
|
//
|
|
ASSERT(0 == cookie);
|
|
|
|
try
|
|
{
|
|
DetachAllFoldersFromMachine();
|
|
DestroyFolderList(cookie);
|
|
|
|
if (m_pToolbar)
|
|
{
|
|
m_pToolbar->Release();
|
|
}
|
|
|
|
if (m_pControlbar)
|
|
{
|
|
m_pControlbar->Release();
|
|
}
|
|
|
|
//
|
|
// Release the interfaces that we QI'ed
|
|
//
|
|
if (m_pConsole != NULL)
|
|
{
|
|
//
|
|
// Tell the console to release the header control interface
|
|
//
|
|
m_pConsole->SetHeader(NULL);
|
|
m_pHeader->Release();
|
|
|
|
m_pResult->Release();
|
|
|
|
m_pConsoleVerb->Release();
|
|
|
|
m_pDisplayHelp->Release();
|
|
|
|
//
|
|
// Release the IFrame interface last
|
|
//
|
|
m_pConsole->Release();
|
|
}
|
|
|
|
if (m_pPropSheetProvider)
|
|
{
|
|
m_pPropSheetProvider->Release();
|
|
}
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CComponent::QueryDataObject(
|
|
MMC_COOKIE cookie,
|
|
DATA_OBJECT_TYPES type,
|
|
LPDATAOBJECT* ppDataObject
|
|
)
|
|
{
|
|
try
|
|
{
|
|
ASSERT(m_pComponentData);
|
|
|
|
//
|
|
// Delegate to IComponentData
|
|
//
|
|
return m_pComponentData->QueryDataObject(cookie, type, ppDataObject);
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CComponent::GetDisplayInfo(
|
|
LPRESULTDATAITEM pResultDataItem
|
|
)
|
|
{
|
|
try
|
|
{
|
|
CFolder* pFolder = FindFolder(pResultDataItem->lParam);
|
|
|
|
if (pFolder)
|
|
{
|
|
return pFolder->GetDisplayInfo(pResultDataItem);
|
|
}
|
|
|
|
else
|
|
{
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CComponent::CompareObjects(
|
|
LPDATAOBJECT lpDataObjectA,
|
|
LPDATAOBJECT lpDataObjectB
|
|
)
|
|
{
|
|
try
|
|
{
|
|
ASSERT(m_pComponentData);
|
|
|
|
//
|
|
// Delegate to ComponentData
|
|
//
|
|
return m_pComponentData->CompareObjects(lpDataObjectA, lpDataObjectB);
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
/// IResultDataCompare implementation
|
|
///
|
|
|
|
// This compare is used to sort the item's in the listview.
|
|
// lUserParam - user param passed in when IResultData::Sort() was called.
|
|
// cookieA -- first item
|
|
// cookieB -- second item
|
|
// pnResult contains the column on entry. This function has the compared
|
|
// result in the location pointed by this parameter.
|
|
// the valid compare results are:
|
|
// -1 if cookieA "<" cookieB
|
|
// 0 if cookieA "==" cookieB
|
|
// 1 if cookieA ">" cookieB
|
|
//
|
|
//
|
|
|
|
STDMETHODIMP
|
|
CComponent::Compare(
|
|
LPARAM lUserParam,
|
|
MMC_COOKIE cookieA,
|
|
MMC_COOKIE cookieB,
|
|
int* pnResult
|
|
)
|
|
{
|
|
if (!pnResult)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
HRESULT hr;
|
|
|
|
try
|
|
{
|
|
int nCol = *pnResult;
|
|
CFolder* pFolder = (CFolder*)lUserParam;
|
|
|
|
if (pFolder)
|
|
{
|
|
hr = pFolder->Compare(cookieA, cookieB, nCol, pnResult);
|
|
}
|
|
|
|
else
|
|
{
|
|
hr = m_pCurFolder->Compare(cookieA, cookieB, nCol, pnResult);
|
|
}
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
/// Snapin's IExtendContextMenu implementation -- delegate to IComponentData
|
|
////
|
|
// Note that IComponentData also has its own IExtendContextMenu
|
|
// interface implementation. The difference is that
|
|
// IComponentData only deals with scope items while we only
|
|
// deal with result item except for cutomer view menu.
|
|
//
|
|
//
|
|
STDMETHODIMP
|
|
CComponent::AddMenuItems(
|
|
LPDATAOBJECT lpDataObject,
|
|
LPCONTEXTMENUCALLBACK pCallback,
|
|
long* pInsertionAllowed
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
INTERNAL_DATA tID;
|
|
|
|
try
|
|
{
|
|
//
|
|
// If lpDataObject is DOBJ_CUSTOMOCX then the user is viewing
|
|
// the Action menu.
|
|
//
|
|
if (DOBJ_CUSTOMOCX == lpDataObject)
|
|
{
|
|
ASSERT(m_pCurFolder);
|
|
|
|
hr = m_pCurFolder->m_pScopeItem->AddMenuItems(pCallback, pInsertionAllowed);
|
|
}
|
|
|
|
//
|
|
// If we have a valid cookie then the user is using the context menu
|
|
// or the View menu
|
|
//
|
|
else
|
|
{
|
|
hr = ExtractData(lpDataObject, CDataObject::m_cfSnapinInternal,
|
|
reinterpret_cast<BYTE*>(&tID), sizeof(tID)
|
|
);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ASSERT(m_pCurFolder);
|
|
|
|
hr = m_pCurFolder->AddMenuItems(GetActiveCookie(tID.cookie),
|
|
pCallback, pInsertionAllowed
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CComponent::Command(
|
|
long nCommandID,
|
|
LPDATAOBJECT lpDataObject
|
|
)
|
|
{
|
|
INTERNAL_DATA tID;
|
|
|
|
HRESULT hr;
|
|
try
|
|
{
|
|
//
|
|
// Menu item from the Action menu
|
|
//
|
|
if (DOBJ_CUSTOMOCX == lpDataObject)
|
|
{
|
|
ASSERT(m_pCurFolder);
|
|
|
|
hr = m_pCurFolder->m_pScopeItem->MenuCommand(nCommandID);
|
|
}
|
|
|
|
//
|
|
// Context menu item or View menu item
|
|
//
|
|
else
|
|
{
|
|
hr = ExtractData(lpDataObject, CDataObject::m_cfSnapinInternal,
|
|
(PBYTE)&tID, sizeof(tID));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ASSERT(m_pCurFolder);
|
|
|
|
hr = m_pCurFolder->MenuCommand(GetActiveCookie(tID.cookie), nCommandID);
|
|
}
|
|
}
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// IExtendControlbar implementation
|
|
//
|
|
|
|
MMCBUTTON g_SnapinButtons[] =
|
|
{
|
|
{ 0, IDM_REFRESH, TBSTATE_ENABLED, TBSTYLE_BUTTON, (BSTR)IDS_BUTTON_REFRESH, (BSTR)IDS_TOOLTIP_REFRESH },
|
|
{ 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, NULL, NULL },
|
|
{ 4, IDM_UPDATEDRIVER, TBSTATE_ENABLED, TBSTYLE_BUTTON, (BSTR)IDS_BUTTON_UPDATEDRIVER, (BSTR)IDS_TOOLTIP_UPDATEDRIVER },
|
|
{ 2, IDM_REMOVE, TBSTATE_ENABLED, TBSTYLE_BUTTON, (BSTR)IDS_BUTTON_REMOVE, (BSTR)IDS_TOOLTIP_REMOVE },
|
|
{ 1, IDM_ENABLE, TBSTATE_ENABLED, TBSTYLE_BUTTON, (BSTR)IDS_BUTTON_ENABLE, (BSTR)IDS_TOOLTIP_ENABLE },
|
|
{ 3, IDM_DISABLE, TBSTATE_ENABLED, TBSTYLE_BUTTON, (BSTR)IDS_BUTTON_DISABLE, (BSTR)IDS_TOOLTIP_DISABLE },
|
|
};
|
|
|
|
#define CBUTTONS_ARRAY ARRAYLEN(g_SnapinButtons)
|
|
|
|
String* g_astrButtonStrings = NULL; // dynamic array of Strings
|
|
BOOL g_bLoadedStrings = FALSE;
|
|
|
|
|
|
STDMETHODIMP
|
|
CComponent::SetControlbar(
|
|
LPCONTROLBAR pControlbar
|
|
)
|
|
{
|
|
if (pControlbar != NULL)
|
|
{
|
|
//
|
|
// Hold on to the controlbar interface.
|
|
//
|
|
if (m_pControlbar)
|
|
{
|
|
m_pControlbar->Release();
|
|
}
|
|
|
|
m_pControlbar = pControlbar;
|
|
m_pControlbar->AddRef();
|
|
|
|
HRESULT hr = S_FALSE;
|
|
|
|
if (!m_pToolbar)
|
|
{
|
|
//
|
|
// Create the Toolbar
|
|
//
|
|
hr = m_pControlbar->Create(TOOLBAR, this,
|
|
reinterpret_cast<LPUNKNOWN*>(&m_pToolbar));
|
|
ASSERT(SUCCEEDED(hr));
|
|
|
|
//
|
|
// Add the bitmap
|
|
//
|
|
HBITMAP hBitmap = ::LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_TOOLBAR));
|
|
hr = m_pToolbar->AddBitmap(4, hBitmap, 16, 16, RGB(255, 0, 255));
|
|
ASSERT(SUCCEEDED(hr));
|
|
|
|
if (!g_bLoadedStrings)
|
|
{
|
|
//
|
|
// Load strings
|
|
//
|
|
g_astrButtonStrings = new String[2*CBUTTONS_ARRAY];
|
|
|
|
for (UINT i = 0; i < CBUTTONS_ARRAY; i++)
|
|
{
|
|
if (g_astrButtonStrings &&
|
|
g_SnapinButtons[i].lpButtonText &&
|
|
g_astrButtonStrings[i*2].LoadString(g_hInstance,
|
|
(UINT)((ULONG_PTR)g_SnapinButtons[i].lpButtonText))) {
|
|
|
|
g_SnapinButtons[i].lpButtonText =
|
|
const_cast<BSTR>((LPCTSTR)(g_astrButtonStrings[i*2]));
|
|
|
|
} else {
|
|
g_SnapinButtons[i].lpButtonText = NULL;
|
|
}
|
|
|
|
if (g_astrButtonStrings &&
|
|
g_SnapinButtons[i].lpTooltipText &&
|
|
g_astrButtonStrings[(i*2)+1].LoadString(g_hInstance,
|
|
(UINT)((ULONG_PTR)g_SnapinButtons[i].lpTooltipText))) {
|
|
|
|
g_SnapinButtons[i].lpTooltipText =
|
|
const_cast<BSTR>((LPCTSTR)(g_astrButtonStrings[(i*2)+1]));
|
|
|
|
} else {
|
|
g_SnapinButtons[i].lpTooltipText = NULL;
|
|
}
|
|
}
|
|
|
|
g_bLoadedStrings = TRUE;
|
|
}
|
|
|
|
//
|
|
// Add the buttons to the toolbar
|
|
//
|
|
hr = m_pToolbar->AddButtons(CBUTTONS_ARRAY, g_SnapinButtons);
|
|
ASSERT(SUCCEEDED(hr));
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CComponent::ControlbarNotify(
|
|
MMC_NOTIFY_TYPE event,
|
|
LPARAM arg,
|
|
LPARAM param
|
|
)
|
|
{
|
|
switch (event)
|
|
{
|
|
case MMCN_BTN_CLICK:
|
|
//
|
|
// arg - Data object of the currently selected scope or result pane item.
|
|
// param - CmdID of the button.
|
|
//
|
|
switch (param)
|
|
{
|
|
case IDM_REFRESH:
|
|
case IDM_ENABLE:
|
|
case IDM_REMOVE:
|
|
case IDM_DISABLE:
|
|
case IDM_UPDATEDRIVER:
|
|
|
|
//
|
|
// The arg parameter is supposed to be the data object of the
|
|
// currently selected scope or result pane item, but it seems
|
|
// to always passes 0xFFFFFFFF. So the ScopeItem MenuCommand is
|
|
// used because it uses the selected cookie instead.
|
|
//
|
|
// Handle toolbar button requests.
|
|
//
|
|
return m_pCurFolder->m_pScopeItem->MenuCommand((LONG)param);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case MMCN_SELECT:
|
|
//
|
|
// param - Data object of the item being selected.
|
|
// For select, if the cookie has toolbar items attach the toolbar.
|
|
// Otherwise detach the toolbar.
|
|
//
|
|
HRESULT hr;
|
|
|
|
if (LOWORD(arg))
|
|
{
|
|
//
|
|
// LOWORD(arg) being set indicated this is for the scope pane item.
|
|
//
|
|
if (HIWORD(arg))
|
|
{
|
|
//
|
|
// Detach the Controlbar.
|
|
//
|
|
hr = m_pControlbar->Detach(reinterpret_cast<LPUNKNOWN>(m_pToolbar));
|
|
ASSERT(SUCCEEDED(hr));
|
|
}
|
|
|
|
else
|
|
{
|
|
//
|
|
// Attach the Controlbar.
|
|
//
|
|
hr = m_pControlbar->Attach(TOOLBAR,
|
|
reinterpret_cast<LPUNKNOWN>(m_pToolbar));
|
|
ASSERT(SUCCEEDED(hr));
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// This function updates the toolbar buttons based on the selected cookie type.
|
|
//
|
|
HRESULT
|
|
CComponent::UpdateToolbar(
|
|
CCookie* pCookie
|
|
)
|
|
{
|
|
if (!m_pToolbar)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// Everything is hidden by default
|
|
//
|
|
BOOL fRemoveHidden = TRUE;
|
|
BOOL fRefreshHidden = TRUE;
|
|
BOOL fUpdateHidden = TRUE;
|
|
BOOL fDisableHidden = TRUE;
|
|
BOOL fEnableHidden = TRUE;
|
|
|
|
switch (pCookie->GetType())
|
|
{
|
|
case COOKIE_TYPE_RESULTITEM_DEVICE:
|
|
case COOKIE_TYPE_RESULTITEM_RESOURCE_IRQ:
|
|
case COOKIE_TYPE_RESULTITEM_RESOURCE_DMA:
|
|
case COOKIE_TYPE_RESULTITEM_RESOURCE_IO:
|
|
case COOKIE_TYPE_RESULTITEM_RESOURCE_MEMORY:
|
|
if(m_pComponentData->m_pMachine->IsLocal() && g_IsAdmin)
|
|
{
|
|
CDevice* pDevice = NULL;
|
|
CClass* pClass;
|
|
|
|
if (COOKIE_TYPE_RESULTITEM_DEVICE == pCookie->GetType()) {
|
|
pDevice = (CDevice*)pCookie->GetResultItem();
|
|
} else {
|
|
//
|
|
// This is a resource item, get the pointer for the device
|
|
// object from the resource object.
|
|
//
|
|
CResource* pResource = (CResource*) pCookie->GetResultItem();
|
|
if (pResource) {
|
|
pDevice = pResource->GetDevice();
|
|
}
|
|
}
|
|
|
|
if (pDevice)
|
|
{
|
|
pClass = pDevice->GetClass();
|
|
|
|
//
|
|
// Device can be disabled
|
|
//
|
|
if (pDevice->IsDisableable()) {
|
|
|
|
if (pDevice->IsStateDisabled()) {
|
|
|
|
fEnableHidden = FALSE;
|
|
|
|
} else {
|
|
|
|
fDisableHidden = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Device cannot be disabled
|
|
//
|
|
else
|
|
{
|
|
//
|
|
// Hide both the enable and disable buttons in case the
|
|
// previously selected node was a device.
|
|
//
|
|
m_pToolbar->SetButtonState(IDM_ENABLE, HIDDEN, TRUE);
|
|
m_pToolbar->SetButtonState(IDM_DISABLE, HIDDEN, TRUE);
|
|
}
|
|
|
|
//
|
|
// Only show the uninstall button if the device can be uninstalled.
|
|
//
|
|
if (pDevice->IsUninstallable()) {
|
|
|
|
fRemoveHidden = FALSE;
|
|
}
|
|
|
|
//
|
|
// Display Update Driver button for everything except legacy drivers.
|
|
//
|
|
fUpdateHidden = IsEqualGUID(*pClass, GUID_DEVCLASS_LEGACYDRIVER) ? TRUE : FALSE;
|
|
|
|
//
|
|
// Display refresh (Scan...) button.
|
|
//
|
|
fRefreshHidden = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Must be an admin and on the local machine to remove or
|
|
// enable/disable a device.
|
|
//
|
|
//
|
|
// Fall through to hide the remove and enable/disable buttons.
|
|
//
|
|
}
|
|
|
|
case COOKIE_TYPE_RESULTITEM_COMPUTER:
|
|
case COOKIE_TYPE_RESULTITEM_CLASS:
|
|
case COOKIE_TYPE_RESULTITEM_RESTYPE:
|
|
|
|
//
|
|
// Display refresh (enumerate) button if the user is an Administrator
|
|
//
|
|
if (g_IsAdmin) {
|
|
|
|
fRefreshHidden = FALSE;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Hide or show the buttons
|
|
//
|
|
m_pToolbar->SetButtonState(IDM_REMOVE, HIDDEN, fRemoveHidden);
|
|
m_pToolbar->SetButtonState(IDM_REFRESH, HIDDEN, fRefreshHidden);
|
|
m_pToolbar->SetButtonState(IDM_UPDATEDRIVER, HIDDEN, fUpdateHidden);
|
|
m_pToolbar->SetButtonState(IDM_DISABLE, HIDDEN, fDisableHidden);
|
|
m_pToolbar->SetButtonState(IDM_ENABLE, HIDDEN, fEnableHidden);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
//// Snapin's IExtendPropertySheet implementation
|
|
////
|
|
|
|
STDMETHODIMP
|
|
CComponent::QueryPagesFor(
|
|
LPDATAOBJECT lpDataObject
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (!lpDataObject)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
INTERNAL_DATA tID;
|
|
|
|
try
|
|
{
|
|
hr = ExtractData(lpDataObject, CDataObject::m_cfSnapinInternal,
|
|
reinterpret_cast<BYTE*>(&tID), sizeof(tID)
|
|
);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ASSERT(m_pCurFolder);
|
|
hr = m_pCurFolder->QueryPagesFor(GetActiveCookie(tID.cookie));
|
|
}
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
hr = S_FALSE;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CComponent::CreatePropertyPages(
|
|
LPPROPERTYSHEETCALLBACK lpProvider,
|
|
LONG_PTR handle,
|
|
LPDATAOBJECT lpDataObject
|
|
)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (!lpProvider || !lpDataObject)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
INTERNAL_DATA tID;
|
|
|
|
try
|
|
{
|
|
hr = ExtractData(lpDataObject, CDataObject::m_cfSnapinInternal,
|
|
reinterpret_cast<BYTE*>(&tID), sizeof(tID)
|
|
);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ASSERT(m_pCurFolder);
|
|
hr = m_pCurFolder->CreatePropertyPages(GetActiveCookie(tID.cookie),
|
|
lpProvider, handle
|
|
);
|
|
}
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Snapin's IPersistStream implementation
|
|
|
|
STDMETHODIMP
|
|
CComponent::GetClassID(
|
|
CLSID* pClassID
|
|
)
|
|
{
|
|
if(!pClassID)
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
*pClassID = m_pComponentData->GetCoClassID();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CComponent::IsDirty()
|
|
{
|
|
return m_Dirty ? S_OK : S_FALSE;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CComponent::GetSizeMax(
|
|
ULARGE_INTEGER* pcbSize
|
|
)
|
|
{
|
|
if (!pcbSize)
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
// total folders folder signature
|
|
int Size = sizeof(int) + m_listFolder.GetCount() * sizeof(FOLDER_SIGNATURE)
|
|
+ sizeof(CLSID);
|
|
|
|
CFolder* pFolder;
|
|
POSITION pos = m_listFolder.GetHeadPosition();
|
|
|
|
while (NULL != pos)
|
|
{
|
|
pFolder = m_listFolder.GetNext(pos);
|
|
ASSERT(pFolder);
|
|
Size += pFolder->GetPersistDataSize();
|
|
}
|
|
|
|
ULISet32(*pcbSize, Size);
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
// save data format
|
|
|
|
STDMETHODIMP
|
|
CComponent::Save(
|
|
IStream* pStm,
|
|
BOOL fClearDirty
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
SafeInterfacePtr<IStream> StmPtr(pStm);
|
|
|
|
int Count;
|
|
POSITION pos;
|
|
try
|
|
{
|
|
//
|
|
// write out CLSID
|
|
//
|
|
hr = pStm->Write(&CLSID_DEVMGR, sizeof(CLSID), NULL);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
Count = m_listFolder.GetCount();
|
|
CFolder* pFolder;
|
|
|
|
//
|
|
// write out folder count
|
|
//
|
|
hr = pStm->Write(&Count, sizeof(Count), NULL);
|
|
|
|
if (SUCCEEDED(hr) && Count)
|
|
{
|
|
pos = m_listFolder.GetHeadPosition();
|
|
|
|
while (NULL != pos)
|
|
{
|
|
pFolder = m_listFolder.GetNext(pos);
|
|
|
|
//
|
|
// write folder signature
|
|
//
|
|
FOLDER_SIGNATURE Signature = pFolder->GetSignature();
|
|
hr = pStm->Write(&Signature, sizeof(Signature), NULL);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = SaveFolderPersistData(pFolder, pStm, fClearDirty);
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
if (fClearDirty)
|
|
{
|
|
m_Dirty = FALSE;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CComponent::Load(
|
|
IStream* pStm
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
CLSID clsid;
|
|
SafeInterfacePtr<IStream> StmPtr(pStm);
|
|
|
|
ASSERT(pStm);
|
|
|
|
//
|
|
// Read the clsid
|
|
//
|
|
try
|
|
{
|
|
hr = pStm->Read(&clsid, sizeof(clsid), NULL);
|
|
if (SUCCEEDED(hr) && clsid == CLSID_DEVMGR)
|
|
{
|
|
CFolder* pFolder;
|
|
int FolderCount;
|
|
|
|
//
|
|
// Folder list must be create before Load.
|
|
// DO NOT rely on that IComponent::Initialize comes before IStream::Load
|
|
//
|
|
ASSERT(m_listFolder.GetCount());
|
|
|
|
//
|
|
// Load folder count
|
|
//
|
|
hr = pStm->Read(&FolderCount, sizeof(FolderCount), NULL);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ASSERT(m_listFolder.GetCount() == FolderCount);
|
|
|
|
//
|
|
// Get folder signature
|
|
// go through every folder
|
|
//
|
|
for (int i = 0; i < FolderCount; i++)
|
|
{
|
|
FOLDER_SIGNATURE Signature;
|
|
hr = pStm->Read(&Signature, sizeof(Signature), NULL);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
POSITION pos;
|
|
pos = m_listFolder.GetHeadPosition();
|
|
|
|
while (NULL != pos)
|
|
{
|
|
pFolder = m_listFolder.GetNext(pos);
|
|
|
|
if (pFolder->GetSignature() == Signature)
|
|
{
|
|
hr = LoadFolderPersistData(pFolder, pStm);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
m_Dirty = FALSE;
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
CComponent::SaveFolderPersistData(
|
|
CFolder* pFolder,
|
|
IStream* pStm,
|
|
BOOL fClearDirty
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
int Size;
|
|
SafeInterfacePtr<IStream> StmPtr(pStm);
|
|
|
|
UNREFERENCED_PARAMETER(fClearDirty);
|
|
|
|
try
|
|
{
|
|
Size = pFolder->GetPersistDataSize();
|
|
|
|
//
|
|
// Always write the length even though it can be 0.
|
|
//
|
|
hr = pStm->Write(&Size, sizeof(Size), NULL);
|
|
|
|
if (SUCCEEDED(hr) && Size)
|
|
{
|
|
BufferPtr<BYTE> Buffer(Size);
|
|
pFolder->GetPersistData(Buffer, Size);
|
|
hr = pStm->Write(Buffer, Size, NULL);
|
|
}
|
|
}
|
|
|
|
catch (CMemoryException* e)
|
|
{
|
|
e->Delete();
|
|
MsgBoxParam(m_pComponentData->m_hwndMain, 0, 0, 0);
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT
|
|
CComponent::LoadFolderPersistData(
|
|
CFolder* pFolder,
|
|
IStream* pStm
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
SafeInterfacePtr<IStream> StmPtr(pStm);
|
|
|
|
int Size = 0;
|
|
hr = pStm->Read(&Size, sizeof(Size), NULL);
|
|
|
|
if (SUCCEEDED(hr) && Size)
|
|
{
|
|
BufferPtr<BYTE> Buffer(Size);
|
|
hr = pStm->Read(Buffer, Size, NULL);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pFolder->SetPersistData(Buffer, Size);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// This function attaches the given folder the the machine created
|
|
// by the component data. The machine notifies every attached folder
|
|
// when there are state changes in the machine.
|
|
//
|
|
// INPUT:
|
|
// pFolder -- the folder to be attached
|
|
// ppMachind -- to receive a pointer to the machine
|
|
// OUTPUT:
|
|
// TRUE if the folder is attached successfully.
|
|
// FALSE if the attachment failed.
|
|
//
|
|
//
|
|
BOOL
|
|
CComponent::AttachFolderToMachine(
|
|
CFolder* pFolder,
|
|
CMachine** ppMachine
|
|
)
|
|
{
|
|
if (!pFolder)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
// Initialize the machine.
|
|
if (m_pComponentData->InitializeMachine())
|
|
{
|
|
*ppMachine = m_pComponentData->m_pMachine;
|
|
(*ppMachine)->AttachFolder(pFolder);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// This function detaches all the component's folders from the machine
|
|
//
|
|
void
|
|
CComponent::DetachAllFoldersFromMachine()
|
|
{
|
|
if (m_pComponentData->m_pMachine)
|
|
{
|
|
CMachine* pMachine = m_pComponentData->m_pMachine;
|
|
|
|
CFolder* pFolder;
|
|
POSITION pos = m_listFolder.GetHeadPosition();
|
|
|
|
while (NULL != pos)
|
|
{
|
|
pFolder = m_listFolder.GetNext(pos);
|
|
pMachine->DetachFolder(pFolder);
|
|
}
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
CComponent::CreateFolderList(
|
|
CCookie* pCookie
|
|
)
|
|
{
|
|
CCookie* pCookieChild;
|
|
CScopeItem* pScopeItem;
|
|
CFolder* pFolder;
|
|
|
|
ASSERT(pCookie);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
do
|
|
{
|
|
pScopeItem = pCookie->GetScopeItem();
|
|
ASSERT(pScopeItem);
|
|
pFolder = pScopeItem->CreateFolder(this);
|
|
|
|
if (pFolder)
|
|
{
|
|
m_listFolder.AddTail(pFolder);
|
|
pFolder->AddRef();
|
|
pCookieChild = pCookie->GetChild();
|
|
|
|
if (pCookieChild)
|
|
{
|
|
hr = CreateFolderList(pCookieChild);
|
|
}
|
|
|
|
pCookie = pCookie->GetSibling();
|
|
}
|
|
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
} while (SUCCEEDED(hr) && pCookie);
|
|
|
|
return hr;
|
|
}
|
|
|
|
BOOL
|
|
CComponent::DestroyFolderList(
|
|
MMC_COOKIE cookie
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(cookie);
|
|
|
|
if (!m_listFolder.IsEmpty())
|
|
{
|
|
POSITION pos = m_listFolder.GetHeadPosition();
|
|
|
|
while (NULL != pos)
|
|
{
|
|
CFolder* pFolder = m_listFolder.GetNext(pos);
|
|
|
|
//
|
|
// DONOT delete it!!!!!!!
|
|
//
|
|
pFolder->Release();
|
|
}
|
|
|
|
m_listFolder.RemoveAll();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
CFolder*
|
|
CComponent::FindFolder(
|
|
MMC_COOKIE cookie
|
|
)
|
|
{
|
|
CCookie* pCookie = GetActiveCookie(cookie);
|
|
CFolder* pFolder;
|
|
POSITION pos = m_listFolder.GetHeadPosition();
|
|
|
|
while (NULL != pos)
|
|
{
|
|
pFolder = m_listFolder.GetNext(pos);
|
|
|
|
if (pCookie->GetScopeItem() == pFolder->m_pScopeItem)
|
|
{
|
|
return pFolder;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
CComponent::MessageBox(
|
|
LPCTSTR Msg,
|
|
LPCTSTR Caption,
|
|
DWORD Flags
|
|
)
|
|
{
|
|
int Result;
|
|
ASSERT(m_pConsole);
|
|
|
|
if (SUCCEEDED(m_pConsole->MessageBox(Msg, Caption, Flags, &Result)))
|
|
{
|
|
return Result;
|
|
}
|
|
|
|
else
|
|
{
|
|
return IDCANCEL;
|
|
}
|
|
}
|