// qryitem.cpp - CQueryItem class 

#include "stdafx.h"
#include "scopenode.h"
#include "namemap.h"
#include "qryitem.h"

#include <algorithm>

extern HWND g_hwndMain;

UINT CQueryItem::m_cfDisplayName = RegisterClipboardFormat(TEXT("CCF_DISPLAY_NAME")); 
UINT CQueryItem::m_cfSnapInClsid = RegisterClipboardFormat(TEXT("CCF_SNAPIN_CLSID"));
UINT CQueryItem::m_cfNodeType    = RegisterClipboardFormat(TEXT("CCF_NODETYPE"));
UINT CQueryItem::m_cfszNodeType  = RegisterClipboardFormat(TEXT("CCF_SZNODETYPE"));

// {68D2DFD9-86A7-4964-8263-BA025C358992}
static const GUID GUID_QueryItem = 
{ 0x68d2dfd9, 0x86a7, 0x4964, { 0x82, 0x63, 0xba, 0x2, 0x5c, 0x35, 0x89, 0x92 } };


/////////////////////////////////////////////////////////////////////////////////////////////
// CQueryItem

HRESULT CQueryItem::Initialize(CQueryableNode* pQueryNode, CRowItem* pRowItem)
{
    VALIDATE_POINTER( pQueryNode );
    VALIDATE_POINTER( pRowItem   );

    m_spQueryNode = pQueryNode;
    
    m_pRowItem = new CRowItem(*pRowItem);
    if (m_pRowItem == NULL) return E_OUTOFMEMORY;

    return S_OK;
}


/////////////////////////////////////////////////////////////////////////////////////////////////
// Notification handlers

BEGIN_NOTIFY_MAP(CQueryItem)
    ON_SELECT()
    ON_DBLCLICK()
    ON_NOTIFY(MMCN_CONTEXTHELP, OnHelp)
END_NOTIFY_MAP()

HRESULT CQueryItem::OnHelp(LPCONSOLE2 pConsole, LPARAM /*arg*/, LPARAM /*param*/)
{    
    VALIDATE_POINTER( pConsole );

    tstring strHelpFile  = _T("");
    tstring strHelpTopic = _T("");
    tstring strHelpFull  = _T("");    
        
    strHelpFile = StrLoadString(IDS_HELPFILE);
    if( strHelpFile.empty() ) return E_FAIL;    

    // Special Hack to get a different help topic for the first two nodes.
    int nNodeID = m_spQueryNode->GetNodeID();

    switch( nNodeID )
    {
    case 2:
        {
            // Users Node
            strHelpTopic = StrLoadString(IDS_USERSHELPTOPIC);
            break;
        }

    case 3:
        {
            // Printers Node
            strHelpTopic = StrLoadString(IDS_PRINTERSHELPTOPIC);
            break;
        }
    default:
        {
            strHelpTopic = StrLoadString(IDS_DEFAULTHELPTOPIC);
            break;
        }
    }
    
    if( strHelpTopic.empty() ) return E_FAIL;

    // Build path to %systemroot%\help
    TCHAR szWindowsDir[MAX_PATH+1] = {0};
    UINT nSize = GetSystemWindowsDirectory( szWindowsDir, MAX_PATH );
    if( nSize == 0 || nSize > MAX_PATH )
    {
        return E_FAIL;
    }            

    strHelpFull  = szWindowsDir;
    strHelpFull += _T("\\Help\\");
    strHelpFull += strHelpFile;
    strHelpFull += _T("::/");
    strHelpFull += strHelpTopic;

    // Show the Help topic
    CComQIPtr<IDisplayHelp> spHelp = pConsole;
    if( !spHelp ) return E_NOINTERFACE;
    
    return spHelp->ShowTopic( (LPTSTR)strHelpFull.c_str() );
}

HRESULT CQueryItem::OnSelect(LPCONSOLE2 pConsole, BOOL bSelect, BOOL bScope)
{
    VALIDATE_POINTER( pConsole );
    ASSERT(!bScope);

    if( bSelect ) 
    {
        CComPtr<IConsoleVerb> pConsVerb;
        pConsole->QueryConsoleVerb(&pConsVerb);
        ASSERT(pConsVerb != NULL);

        if (pConsVerb != NULL)
        {    
			// Row item has class display name, so get internal name from class map
			DisplayNameMap* pNameMap = DisplayNames::GetClassMap();
			if (pNameMap == NULL) 
				return E_FAIL;

			ASSERT(m_pRowItem != NULL && m_pRowItem->size() >= ROWITEM_USER_INDEX);
			ASSERT(m_spQueryNode != NULL);
			LPCWSTR pszClass = pNameMap->GetInternalName((*m_pRowItem)[ROWITEM_CLASS_INDEX]);

			// Get menu items for this class from the owning query node
			int iDefault;
			BOOL bPropertyMenu;
			reinterpret_cast<CQueryNode*>(m_pRowItem->GetOwnerParam())->GetClassMenuItems(pszClass, m_vMenus, &iDefault, &bPropertyMenu);

			// if property menu enabled
			if (bPropertyMenu)
			{
				// Enable property button and menu item
				pConsVerb->SetVerbState(MMC_VERB_PROPERTIES, ENABLED, TRUE);
				pConsVerb->SetVerbState(MMC_VERB_PROPERTIES, HIDDEN, FALSE);

				// if no default menu item defined, make properties verb the default
				pConsVerb->SetDefaultVerb( (iDefault >= 0) ? MMC_VERB_NONE : MMC_VERB_PROPERTIES);
			}
		}    
    }

    return S_OK;
}

HRESULT CQueryItem::OnDblClick(LPCONSOLE2 pConsole)
{
    VALIDATE_POINTER(pConsole);

    // Row item has class display name, so get internal name from class map
    DisplayNameMap* pNameMap = DisplayNames::GetClassMap();
    if (pNameMap == NULL) 
        return E_FAIL;

    ASSERT(m_pRowItem != NULL && m_pRowItem->size() >= ROWITEM_USER_INDEX);
    ASSERT(m_spQueryNode != NULL);
    LPCWSTR pszClass = pNameMap->GetInternalName((*m_pRowItem)[ROWITEM_CLASS_INDEX]);

    // Get menu items for this class from the owning query node
    int iDefault;
	BOOL bPropMenu;
    CQueryNode* pQueryNode = reinterpret_cast<CQueryNode*>(m_pRowItem->GetOwnerParam());
    if( !pQueryNode ) return E_FAIL;

    pQueryNode->GetClassMenuItems(pszClass, m_vMenus, &iDefault, &bPropMenu);    

    // if no default menu item, return
    if (iDefault < 0)
        return S_FALSE;

    // if Active directory command, create AD menu extension
    if (m_vMenus[iDefault]->MenuType() == MENUTYPE_ACTDIR) 
    {
        // Create a directory extension object and use it to get the actual menu cmds for the selected object
        // (we might have one already if AddMenuItems was called before)
        if (m_pADExt == NULL) 
            m_pADExt = new CActDirExt();
        
        if( !m_pADExt ) return E_OUTOFMEMORY;

        HRESULT hr = m_pADExt->Initialize(pszClass, m_pRowItem->GetObjPath());
        RETURN_ON_FAILURE(hr);

        menu_vector vADMenus;
        hr = m_pADExt->GetMenuItems(vADMenus);
        RETURN_ON_FAILURE(hr);

        if( m_vMenus.size() <= iDefault ) 
        {
            return E_FAIL;
        }
    
        LPCWSTR pszName      = static_cast<CActDirMenuCmd*>((CMenuCmd*)m_vMenus[iDefault])->ADName();
        LPCWSTR pszNoLocName = static_cast<CActDirMenuCmd*>((CMenuCmd*)m_vMenus[iDefault])->ADNoLocName();

        if( !pszName || !pszNoLocName ) return E_FAIL;

        // if the default command is not provided by the extension, return
        menu_vector::iterator iter;
        for( iter = vADMenus.begin(); iter != vADMenus.end(); iter++ )
        {
            if( _tcslen(pszNoLocName) )
            {
                if( _tcscmp(iter->strNoLoc.c_str(),pszNoLocName) == 0 )
                    break;
            }
            else if( _tcscmp(iter->strPlain.c_str(), pszName) == 0 )
            {
                break;
            }
        }

        if( iter == vADMenus.end() )
        {
            return S_FALSE;
        }        
    }

    // Execute the command as though it had been selected
    return MenuCommand(pConsole, iDefault);
}

HRESULT CQueryItem::AddMenuItems(LPCONTEXTMENUCALLBACK pCallback, long* plAllowed)
{
    VALIDATE_POINTER( pCallback );
    VALIDATE_POINTER( plAllowed );

    if( !m_spQueryNode || !m_pRowItem ) return E_FAIL;

    HRESULT hr = S_OK;

    if (!(*plAllowed & CCM_INSERTIONALLOWED_TOP))
        return S_OK;

    CComQIPtr<IContextMenuCallback2> spContext2 = pCallback;
    if( !spContext2 ) return E_NOINTERFACE;

    ASSERT( m_pRowItem->size() >= ROWITEM_USER_INDEX );    

	//--------------------------- *** HACK ALERT *** -----------------------------------------
	// One or more AD menu extensions allow window message processing while initializing
	// and getting menu items. This causes reentrancy problems because MMC message handlers
	// can execute before this method returns. Specifically, the following can happen:
	//
	//  1. The user right clicks in a taskpad list while the focus is elsewhere.
	//	2. The right button down event causes MMC to call this method to update task buttons.
	//  3. An AD menu extn processes messages allowing the button up event to go to MMC.
	//  4. MMC sees this as a context menu event and calls this method recursively.
	//  5. An AV occurs in nodemgr because a deleted COnContextMenu object is referenced.
	//
	// This can be prevented by not processing menu item requests when the right button is down.
	// The only time this occurs is during the above scenario. The only ill effect is that the
	// task buttons are not enabled until the mouse button up occurs when MMC gets the menu
	// items again.
	//-----------------------------------------------------------------------------------------
	if (GetKeyState(VK_RBUTTON) < 0)
		return S_OK;

    DisplayNameMap* pNameMap = DisplayNames::GetClassMap();
    if (pNameMap == NULL) 
        return E_FAIL;

    LPCWSTR pszClass = pNameMap->GetInternalName((*m_pRowItem)[ROWITEM_CLASS_INDEX]);

    // Get menu items for this class from the owning query node
    int iDefault = 0;
	BOOL bPropertyMenu;
    reinterpret_cast<CQueryNode*>(m_pRowItem->GetOwnerParam())->GetClassMenuItems(pszClass, m_vMenus, &iDefault, &bPropertyMenu);

    // Create a directory extension object and use it to get the actual menu cmds for the selected object
    // (we might have one already if AddMenuItems was called before)
    if (m_pADExt == NULL) 
        m_pADExt = new CActDirExt();

    if( !m_pADExt ) return E_OUTOFMEMORY;
    
    hr = m_pADExt->Initialize(pszClass, m_pRowItem->GetObjPath());
    RETURN_ON_FAILURE(hr);

    menu_vector vADMenus;

    hr = m_pADExt->GetMenuItems(vADMenus);
    RETURN_ON_FAILURE(hr);

    ASSERT(vADMenus.size() > 0);
    ASSERT(vADMenus.begin() != vADMenus.end());

    menucmd_vector::iterator itMenu;
    long lCmdID = 0;
    for (itMenu = m_vMenus.begin(); itMenu != m_vMenus.end(); ++itMenu, ++lCmdID) 
    {
        // if AD menu cmd and not enabled by the selected object, skip it
        if ( (*itMenu)->MenuType() == MENUTYPE_ACTDIR )
        {
            BOOL bFound = FALSE;
            menu_vector::iterator iter = vADMenus.begin();
            while(iter != vADMenus.end())
            {
                LPCWSTR pszName      = static_cast<CActDirMenuCmd*>((CMenuCmd*)(*itMenu))->ADName();
                LPCWSTR pszNoLocName = static_cast<CActDirMenuCmd*>((CMenuCmd*)(*itMenu))->ADNoLocName();
                if( pszNoLocName && wcslen(pszNoLocName) )
                {
                    if( _tcscmp(iter->strNoLoc.c_str(), pszNoLocName) == 0 )
                    {
                        bFound = TRUE;
                        break;
                    }                         
                }
                else if( pszName && _tcscmp(iter->strPlain.c_str(), pszName) == 0 )
                {
                    bFound = TRUE;
                    break;
                }
                iter++;
            }
            if (!bFound)
            {
                continue;
            }
        }        
        
        CONTEXTMENUITEM2 item;
        OLECHAR szGuid[50] = {0};            
        ::StringFromGUID2((*itMenu)->NoLocID(), szGuid, 50);

        item.strName = const_cast<LPWSTR>((*itMenu)->Name());
        item.strStatusBarText = L"";
        item.lCommandID = lCmdID;
        item.lInsertionPointID = CCM_INSERTIONPOINTID_PRIMARY_TOP;
        item.fFlags = 0;
        item.fSpecialFlags = (lCmdID == iDefault) ? CCM_SPECIAL_DEFAULT_ITEM : 0;
        item.strLanguageIndependentName = szGuid;

        hr = spContext2->AddItem(&item);        

        ASSERT(SUCCEEDED(hr));
    }

    return hr;
}


HRESULT
CQueryItem::QueryPagesFor()
{
    ASSERT(m_pRowItem != NULL && m_pRowItem->size() >= ROWITEM_USER_INDEX);
    ASSERT(m_spQueryNode != NULL);

    // Row item has class display name, so get internal name from class map
    DisplayNameMap* pNameMap = DisplayNames::GetClassMap();
    if (pNameMap == NULL) 
        return E_FAIL;

    LPCWSTR pszClass = pNameMap->GetInternalName((*m_pRowItem)[ROWITEM_CLASS_INDEX]);

    // Create a directory extension object    
    // (we might have one already if AddMenuItems was called before)
    if (m_pADExt == NULL) 
        m_pADExt = new CActDirExt();

    ASSERT(m_pADExt != NULL);
    if (m_pADExt == NULL)
        return E_OUTOFMEMORY;

    HRESULT hr = m_pADExt->Initialize(pszClass, m_pRowItem->GetObjPath());
    RETURN_ON_FAILURE(hr);

    hpage_vector vhPages;
    hr = m_pADExt->GetPropertyPages(vhPages);

    if (SUCCEEDED(hr) && vhPages.size() > 0) 
    {
        CPropertySheet sheet;

        // Set title to name of item
        // Can't use SetTitle becuase if wrongly asserts (pszText == NULL)
        sheet.m_psh.pszCaption = (*m_pRowItem)[ROWITEM_NAME_INDEX];
        sheet.m_psh.dwFlags |= PSH_PROPTITLE;
    
        hpage_vector::iterator itPage;
        for (itPage = vhPages.begin(); itPage != vhPages.end(); ++itPage) 
        {
           BOOL bStat = sheet.AddPage(*itPage);
           ASSERT(bStat);
        }
    
        sheet.DoModal(g_hwndMain);
    }

    return S_FALSE;
}


class CRefreshCallback : public CEventCallback
{
public:
    CRefreshCallback(HANDLE hProcess, CQueryableNode* pQueryNode)
        : m_hProcess(hProcess), m_spQueryNode(pQueryNode) {}
 
    virtual void Execute() 
    {
        if( m_spQueryNode )
        {
            m_spQueryNode->OnRefresh(NULL);
        }

        CloseHandle(m_hProcess);
    }

    HANDLE m_hProcess;
    CQueryableNodePtr m_spQueryNode;
};

HRESULT
CQueryItem::MenuCommand(LPCONSOLE2 pConsole, long lCommand)
{
    VALIDATE_POINTER(pConsole);

    ASSERT( lCommand < m_vMenus.size() && lCommand >= 0 );
    if( lCommand >= m_vMenus.size() || lCommand < 0 )
        return E_INVALIDARG;

    HRESULT hr = E_FAIL;
    switch (m_vMenus[lCommand]->MenuType())
    {
    case MENUTYPE_SHELL:
        {
            // Create a query Lookup object to translate the command parameters
            CQueryLookup lookup(m_spQueryNode, m_pRowItem);
    
            HANDLE hProcess = NULL;
            hr = static_cast<CShellMenuCmd*>((CMenuCmd*)m_vMenus[lCommand])->Execute(&lookup, &hProcess);

            // if process started and auto-refresh wanted, setup event-triggered callback
            if (SUCCEEDED(hr) && hProcess != NULL && m_vMenus[lCommand]->IsAutoRefresh()) 
            {
                CallbackOnEvent(hProcess, new CRefreshCallback(hProcess, m_spQueryNode));              
            }

            break;
        }

    case MENUTYPE_ACTDIR:
        {
            ASSERT(m_pADExt != NULL);
            BOMMENU bmMenu;
            bmMenu.strPlain = static_cast<CActDirMenuCmd*>((CMenuCmd*)m_vMenus[lCommand])->ADName();
            bmMenu.strNoLoc = static_cast<CActDirMenuCmd*>((CMenuCmd*)m_vMenus[lCommand])->ADNoLocName();
            hr = m_pADExt->Execute(&bmMenu);

            // if commans should auto-refresh, do it now
            if (SUCCEEDED(hr) && m_vMenus[lCommand]->IsAutoRefresh()) 
            {
                ASSERT(m_spQueryNode != NULL);
                m_spQueryNode->OnRefresh(NULL);
            }
            break;
        }
        
    default:
        ASSERT(0 && L"Unhandled menu command type");
    }

    return hr;
}


HRESULT CQueryItem::GetDataImpl(UINT cf, HGLOBAL* phGlobal)
{
    VALIDATE_POINTER( phGlobal );

    HRESULT hr = DV_E_FORMATETC;

    if (cf == m_cfDisplayName)
    {
        hr = DataToGlobal(phGlobal, (*m_pRowItem)[0], wcslen((*m_pRowItem)[0]) * sizeof(WCHAR));
    }
    else if (cf == m_cfSnapInClsid)
    {
        hr = DataToGlobal(phGlobal, &CLSID_BOMSnapIn, sizeof(GUID));
    }
    else if (cf == m_cfNodeType)
    {
        hr = DataToGlobal(phGlobal, &GUID_QueryItem, sizeof(GUID));
    }
    else if (cf == m_cfszNodeType)
    {
        WCHAR szGuid[GUID_STRING_LEN+1];
        StringFromGUID2(GUID_QueryItem, szGuid, GUID_STRING_LEN+1);

        hr = DataToGlobal(phGlobal, szGuid, GUID_STRING_SIZE);
    }
 
    return hr;
}