//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1999 - 1999 // // File: cmenu.cpp // //-------------------------------------------------------------------------- // cmenu.cpp : Implementation of IContextMenuProvider and DLL registration. #include "stdafx.h" #include "oncmenu.h" #include "menuitem.h" #include "constatbar.h" #include "regutil.h" #include "moreutil.h" #include "multisel.h" #include "cmenuinfo.h" #include "conview.h" #include "scopndcb.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /*+-------------------------------------------------------------------------* * class CNativeExtendContextMenu * * * PURPOSE: implements IExtendContextMenu by forwarding calls to CContextMenu * but does not affect lifetime of CContextMenu * *+-------------------------------------------------------------------------*/ class CNativeExtendContextMenu : public CTiedComObject, public CComObjectRoot, public IExtendContextMenu // this is used so that menu items can be executed uniformly. { protected: typedef CNativeExtendContextMenu ThisClass; typedef CContextMenu CMyTiedObject; public: // com entry points BEGIN_COM_MAP(ThisClass) COM_INTERFACE_ENTRY(IExtendContextMenu) END_COM_MAP() DECLARE_NOT_AGGREGATABLE(ThisClass) // IExtendContexMenu methods MMC_METHOD3( AddMenuItems, LPDATAOBJECT, LPCONTEXTMENUCALLBACK, long * ); MMC_METHOD2( Command, long, LPDATAOBJECT ); }; //############################################################################ //############################################################################ // // Implementation of methods on CNodeInitObject that // forward to CContextMenu // //############################################################################ //############################################################################ CContextMenu * CNodeInitObject::GetContextMenu() { DECLARE_SC(sc, TEXT("CNodeInitObject::GetContextMenu")); if(m_spContextMenu == NULL) { // check internal pointers sc = ScCheckPointers(m_spScopeTree, E_UNEXPECTED); if (sc) return NULL; // get scopetree and call back pointers CScopeTree* const pScopeTree = dynamic_cast(m_spScopeTree.GetInterfacePtr()); // if the menu is created by component data, it does not have the node. // in that case menu is created by passing NULL pointers to some parameters. // Menu should never need those pointers in the mentioned case CNodeCallback* pNodeCallback = NULL; if ( m_pNode != NULL ) { // check other required pointers sc = ScCheckPointers(m_pNode->GetViewData(), E_UNEXPECTED); if (sc) return NULL; pNodeCallback = dynamic_cast(m_pNode->GetViewData()->GetNodeCallback()); } // create context menu CContextMenu *pContextMenu = NULL; sc = CContextMenu::ScCreateContextMenuForScopeNode(m_pNode, pNodeCallback, pScopeTree, &m_spContextMenu, pContextMenu); if (sc) return NULL; sc = ScCheckPointers(pContextMenu, E_UNEXPECTED); if (sc) return NULL; return pContextMenu; } return dynamic_cast(m_spContextMenu.GetInterfacePtr()); } STDMETHODIMP CNodeInitObject::AddItem(CONTEXTMENUITEM * pItem) { DECLARE_SC(sc, TEXT("CNodeInitObject::AddItem")); CContextMenu *pContextMenu = GetContextMenu(); sc = ScCheckPointers(pContextMenu, E_UNEXPECTED); if(sc) return sc.ToHr(); sc = pContextMenu->ScAddItem(pItem, true/*bPassCommandBackToSnapin*/); return sc.ToHr(); } STDMETHODIMP CNodeInitObject::EmptyMenuList () { DECLARE_SC(sc, TEXT("CNodeInitObject::EmptyMenuList")); if (m_spContextMenu == NULL) return S_OK; CContextMenu *pContextMenu = GetContextMenu(); sc = ScCheckPointers(pContextMenu, E_UNEXPECTED); if(sc) return sc.ToHr(); sc = pContextMenu->EmptyMenuList(); return sc.ToHr(); } STDMETHODIMP CNodeInitObject::AddThirdPartyExtensionItems(IDataObject* piDataObject ) { DECLARE_SC(sc, TEXT("CNodeInitObject::AddThirdPartyExtensionItems")); CContextMenu *pContextMenu = GetContextMenu(); sc = ScCheckPointers(pContextMenu, E_UNEXPECTED); if(sc) return sc.ToHr(); sc = pContextMenu->AddThirdPartyExtensionItems(piDataObject); return sc.ToHr(); } STDMETHODIMP CNodeInitObject::AddPrimaryExtensionItems(IUnknown* piCallback, IDataObject* piDataObject ) { DECLARE_SC(sc, TEXT("CNodeInitObject::AddPrimaryExtensionItems")); CContextMenu *pContextMenu = GetContextMenu(); sc = ScCheckPointers(pContextMenu, E_UNEXPECTED); if(sc) return sc.ToHr(); sc = pContextMenu->AddPrimaryExtensionItems(piCallback, piDataObject); return sc.ToHr(); } STDMETHODIMP CNodeInitObject::ShowContextMenu(HWND hwndParent, LONG xPos, LONG yPos, LONG* plSelected) { DECLARE_SC(sc, TEXT("CNodeInitObject::ShowContextMenu")); CContextMenu *pContextMenu = GetContextMenu(); sc = ScCheckPointers(pContextMenu, E_UNEXPECTED); if(sc) return sc.ToHr(); pContextMenu->SetStatusBar(GetStatusBar()); // wire up the status bar. sc = pContextMenu->ShowContextMenu(hwndParent, xPos, yPos, plSelected); return sc.ToHr(); } //############################################################################ //############################################################################ // // Implementation of CCommandSink // //############################################################################ //############################################################################ /*+-------------------------------------------------------------------------* * class CCommandSink * * * PURPOSE: * *+-------------------------------------------------------------------------*/ class CCommandSink : public CWindowImpl { // Construction public: CCommandSink( CContextMenu& nodemgr, WTL::CMenu& menu, CConsoleStatusBar * pStatusbar); virtual ~CCommandSink(); BOOL Init(); LRESULT OnMenuSelect(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); BEGIN_MSG_MAP(CCommandSink) MESSAGE_HANDLER(WM_MENUSELECT, OnMenuSelect) END_MSG_MAP() private: CContextMenu& m_nodemgr; const WTL::CMenu& m_menu; CConsoleStatusBar * m_pStatusBar; }; CCommandSink::CCommandSink( CContextMenu& nodemgr, WTL::CMenu& menu, CConsoleStatusBar * pStatusbar) : m_nodemgr( nodemgr ), m_menu( menu ), m_pStatusBar(pStatusbar) { } CCommandSink::~CCommandSink() { /* * clear the status bar text, if there's any there. */ if (m_pStatusBar != NULL) m_pStatusBar->ScSetStatusText (NULL); } BOOL CCommandSink::Init() { RECT rcPos = {0,0,0,0}; Create(NULL, rcPos, _T("ACFx:CxtMenuSink"), WS_POPUP); return TRUE; } LRESULT CCommandSink::OnMenuSelect(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { UINT nItemID = (UINT) LOWORD(wParam); // menu item or submenu index UINT nFlags = (UINT) HIWORD(wParam); // menu flags HMENU hSysMenu = (HMENU) lParam; // handle of menu clicked TRACE(_T("CCommandSink::OnMenuSelect: nItemID=%d, nFlags=0x%X, hSysMenu=0x%X\n"), nItemID, nFlags, hSysMenu); if ( 0xFFFF == nFlags && NULL == hSysMenu ) return 0; // as per Win32 ProgRef if ( 0 == nItemID && !(nFlags & MF_POPUP) ) return 0; // no item selected CMenuItem* pmenuitem = NULL; if (nFlags & MF_POPUP) { if ( hSysMenu == m_menu.m_hMenu ) { // We assume menu's cannot be longer than 256 chars TCHAR szMenu[256]; MENUITEMINFO menuItemInfo; menuItemInfo.cbSize = sizeof(MENUITEMINFO); menuItemInfo.fMask = MIIM_TYPE; menuItemInfo.fType = MFT_STRING; menuItemInfo.cch = 256; menuItemInfo.dwTypeData = szMenu; ::GetMenuItemInfo(hSysMenu, nItemID, TRUE, &menuItemInfo); ASSERT(256 >= (menuItemInfo.cch+1)); pmenuitem = m_nodemgr.FindNthItemInSubmenu( NULL, nItemID, szMenu ); } else pmenuitem = m_nodemgr.FindNthItemInSubmenu( hSysMenu, nItemID, NULL ); } else pmenuitem = m_nodemgr.FindMenuItem( nItemID ); if ( NULL == pmenuitem ) { ASSERT( FALSE ); return 0; } if(m_pStatusBar) m_pStatusBar->ScSetStatusText( pmenuitem->GetMenuItemStatusBarText() ); return 0; } //############################################################################ //############################################################################ // // CContextMenu methods - continued from oncmenu.cpp // These methods were originally in this file and I dont want to move // them and break history - vivekj // //############################################################################ //############################################################################ //+------------------------------------------------------------------- // // Member: CContextMenu::EmptyMenuList // // Synopsis: Clear the context menu. // // Arguments: // // Returns: HRESULT // //-------------------------------------------------------------------- STDMETHODIMP CContextMenu::EmptyMenuList () { DECLARE_SC(sc, _T("IContextMenuProvider::EmptyMenuList")); START_CRITSEC_BOTH delete m_pmenuitemRoot; m_pmenuitemRoot = NULL; m_nNextMenuItemID = MENUITEM_BASE_ID; ReleaseSnapinList(); m_fAddedThirdPartyExtensions = FALSE; m_MaxPrimaryOwnerID = OWNERID_PRIMARY_MIN; m_MaxThirdPartyOwnerID = OWNERID_THIRD_PARTY_MIN; m_CurrentExtensionOwnerID = OWNERID_NATIVE; m_fPrimaryInsertionFlags = 0; m_fThirdPartyInsertionFlags = 0; END_CRITSEC_BOTH return sc.ToHr(); } /*+-------------------------------------------------------------------------* * * CContextMenu::RemoveAccelerators * * PURPOSE: Removes the accelerators from a context menu item name * * PARAMETERS: * CStr & str : * * RETURNS: * void * *+-------------------------------------------------------------------------*/ void RemoveAccelerators(tstring &str) { // in some locales , the accelerators appear at the end eg: Start (&s). Therefore, remove anything after (& int i = str.find(TEXT( "(&" )); if (i != tstring::npos) str.erase (i); // remove the waste left over after and including the string "(&" tstring::iterator itToTrim = std::remove (str.begin(), str.end(), _T('&')); // remove the waste left over after removing accelerator markers str.erase (itToTrim, str.end()); } //+------------------------------------------------------------------- // // Member: CContextMenu::AddItem // // Synopsis: Add a menu item to context menu. // // Arguments: CONTEXTMENUITEM* // // Returns: HRESULT // //-------------------------------------------------------------------- STDMETHODIMP CContextMenu::AddItem( CONTEXTMENUITEM* pItem ) { DECLARE_SC(sc, _T("IContextMenuCallback::AddItem")); return ( sc = ScAddItem( pItem ) ).ToHr(); } //+------------------------------------------------------------------- // // Member: CContextMenu::ScAddItem // // Synopsis: Add a menu item to context menu. // // Arguments: CONTEXTMENUITEM* // // Returns: SC // //-------------------------------------------------------------------- SC CContextMenu::ScAddItem( CONTEXTMENUITEM* pItem, bool bPassCommandBackToSnapin /*= false*/ ) { DECLARE_SC(sc, _T("IContextMenuCallback::ScAddItem")); if (NULL == pItem) { sc = E_INVALIDARG; TraceSnapinError(_T("NULL CONTEXTMENUITEM ptr"), sc); return sc; } // added a non-langugage independent context menu item. Cook up a language independent ID. // get the menu text and strip out accelerator markers tstring strLanguageIndependentName; if(pItem->strName) { USES_CONVERSION; strLanguageIndependentName = OLE2CT(pItem->strName); RemoveAccelerators(strLanguageIndependentName); } #ifdef DBG TRACE(_T("CContextMenu::AddItem name \"%ls\" statusbartext \"%ls\" commandID %ld submenuID %ld flags %ld special %ld\n"), SAFEDBGBSTR(pItem->strName), SAFEDBGBSTR(pItem->strStatusBarText), pItem->lCommandID, pItem->lInsertionPointID, pItem->fFlags, pItem->fSpecialFlags); #endif // leaves critsec claim for DoAddMenuItem USES_CONVERSION; sc = DoAddMenuItem( OLE2CT(pItem->strName), OLE2CT(pItem->strStatusBarText), strLanguageIndependentName.data(), pItem->lCommandID, pItem->lInsertionPointID, pItem->fFlags, pItem->fSpecialFlags, m_CurrentExtensionOwnerID, NULL, bPassCommandBackToSnapin ); return sc; } //+------------------------------------------------------------------- // // Member: CContextMenu::AddItem // // Synopsis: Add a menu item to context menu. // // Arguments: CONTEXTMENUITEM2* - includes a language independent name // // Returns: HRESULT // //-------------------------------------------------------------------- STDMETHODIMP CContextMenu::AddItem( CONTEXTMENUITEM2* pItem ) { DECLARE_SC(sc, _T("IContextMenuCallback::AddItem")); if (NULL == pItem) { sc = E_INVALIDARG; TraceSnapinError(_T("NULL CONTEXTMENUITEM ptr"), sc); return sc.ToHr(); } // No language-independent-id ? if ( (pItem->strLanguageIndependentName == NULL) || (wcscmp(pItem->strLanguageIndependentName, L"") == 0) ) { // and it is neither a separator nor insertion point. if ( !(MF_SEPARATOR & pItem->fFlags) && !(CCM_SPECIAL_INSERTION_POINT & pItem->fSpecialFlags) ) { sc = E_INVALIDARG; TraceSnapinError(_T("NULL language-indexpendent-id passed"), sc); return sc.ToHr(); } } #ifdef DBG TRACE(_T("CContextMenu::AddItem name \"%ls\" statusbartext \"%ls\" languageIndependentName \"%ls\" commandID %ld submenuID %ld flags %ld special %ld\n"), SAFEDBGBSTR(pItem->strName), SAFEDBGBSTR(pItem->strStatusBarText), SAFEDBGBSTR(pItem->strLanguageIndependentName), pItem->lCommandID, pItem->lInsertionPointID, pItem->fFlags, pItem->fSpecialFlags ); #endif // leaves critsec claim for DoAddMenuItem USES_CONVERSION; sc = DoAddMenuItem( OLE2CT(pItem->strName), OLE2CT(pItem->strStatusBarText), OLE2CT(pItem->strLanguageIndependentName), pItem->lCommandID, pItem->lInsertionPointID, pItem->fFlags, pItem->fSpecialFlags, m_CurrentExtensionOwnerID ); return sc.ToHr(); } //+------------------------------------------------------------------- // // Member: CContextMenu::AddPrimaryExtensionItems // // Synopsis: Ask primary snapin to add menu items. // // Arguments: [piExtension] // [piDataobject] // // Note: claims critsec // // Returns: HRESULT // //-------------------------------------------------------------------- STDMETHODIMP CContextMenu::AddPrimaryExtensionItems ( IUnknown* piExtension, IDataObject* piDataObject ) { DECLARE_SC(sc, _T("IContextMenuProvider::AddPrimaryExtensionItems")); if (NULL == piExtension) { sc = E_INVALIDARG; TraceSnapinError(_T("NULL IUnknown ptr"), sc); return sc.ToHr(); } if (NULL == piDataObject) { sc = E_INVALIDARG; TraceSnapinError(_T("NULL IDataObject ptr"), sc); return sc.ToHr(); } // control reentrant access to this if (!m_fAddingPrimaryExtensionItems) { m_fAddingPrimaryExtensionItems = true; //HRESULT hr = ExtractObjectTypeCStr( piDataObject, &m_strObjectGUID ); //ASSERT( SUCCEEDED(hr) ); START_CRITSEC_SNAPIN; sc = ScAddSnapinToList_IUnknown( piExtension, piDataObject, m_MaxPrimaryOwnerID++ ); END_CRITSEC_SNAPIN; m_fAddingPrimaryExtensionItems = false; // Clear view menu allowed flag // A second call may be made to AddPrimaryExtensionItems to handle the other item // types only, so the view items must be disabled after the first call. m_fPrimaryInsertionFlags &= ~CCM_INSERTIONALLOWED_VIEW; if (sc) return sc.ToHr(); } return sc.ToHr(); } //+------------------------------------------------------------------- // // Member: CContextMenu::AddThirdPartyExtensionItems // // Synopsis: Ask extensions to add comtext menu items. // // Arguments: IDataObject* // // Note: claims critsec, potentially for a considerable period of time // // Returns: HRESULT // //-------------------------------------------------------------------- STDMETHODIMP CContextMenu::AddThirdPartyExtensionItems ( IDataObject* piDataObject ) { DECLARE_SC(sc, _T("IContextMenuProvider::AddThirdPartyExtensionItems")); if (NULL == piDataObject) { sc = E_INVALIDARG; TraceSnapinError(_T("NULL piDataObject"), sc); return sc.ToHr(); } START_CRITSEC_SNAPIN; // Extensions may only be added once, otherwise return S_FALSE if (m_fAddedThirdPartyExtensions == TRUE) { sc = S_FALSE; TraceNodeMgrLegacy(_T("CContextMenu::AddThirdPartyExtensionItems>> Menu already extended"), sc); return sc.ToHr(); } m_fAddedThirdPartyExtensions = TRUE; do // not a loop { CExtensionsIterator it; sc = it.ScInitialize(piDataObject, g_szContextMenu); if (sc) { sc = S_FALSE; break; } BOOL fProblem = FALSE; for (; it.IsEnd() == FALSE; it.Advance()) { sc = ScAddSnapinToList_GUID(it.GetCLSID(), piDataObject, m_MaxThirdPartyOwnerID++); if (sc) fProblem = TRUE; // Continue even on error. } if (fProblem == TRUE) sc = S_FALSE; } while (0); END_CRITSEC_SNAPIN; return sc.ToHr(); } // claims critsec, potentially for a considerable period of time STDMETHODIMP CContextMenu::AddMultiSelectExtensionItems ( LONG_PTR lMultiSelection) { MMC_TRY if (lMultiSelection == 0) return E_INVALIDARG; CMultiSelection* pMS = reinterpret_cast(lMultiSelection); ASSERT(pMS != NULL); TRACE_METHOD(CContextMenu,AddThirdPartyExtensionItems); TRACE(_T("CContextMenu::AddThirdPartyExtensionItems")); START_CRITSEC_SNAPIN; // Extensions may only be added once, otherwise return S_FALSE if (m_fAddedThirdPartyExtensions == TRUE) { TRACE(_T("CContextMenu::AddThirdPartyExtensionItems>> Menu already extended")); return S_FALSE; } m_fAddedThirdPartyExtensions = TRUE; do // not a loop { CList snapinClsidList; HRESULT hr = pMS->GetExtensionSnapins(g_szContextMenu, snapinClsidList); BREAK_ON_FAIL(hr); POSITION pos = snapinClsidList.GetHeadPosition(); if (pos == NULL) break; CLSID clsid; IDataObjectPtr spDataObject; hr = pMS->GetMultiSelDataObject(&spDataObject); ASSERT(SUCCEEDED(hr)); BREAK_ON_FAIL(hr); BOOL fProblem = FALSE; while (pos) { clsid = snapinClsidList.GetNext(pos); hr = ScAddSnapinToList_GUID(clsid, spDataObject, m_MaxThirdPartyOwnerID++).ToHr(); CHECK_HRESULT(hr); if (FAILED(hr)) fProblem = TRUE; // Continue even on error. } if (fProblem == TRUE) hr = S_FALSE; } while (0); END_CRITSEC_SNAPIN; return S_OK; MMC_CATCH } // Worker function, called recursively by FindMenuItem // critsec should already be claimed // If fFindSubmenu, then nMenuItemID is actually an HMENU CMenuItem* FindWorker( MenuItemList& list, LONG_PTR nMenuItemID, BOOL fFindSubmenu ) { POSITION pos = list.GetHeadPosition(); while(pos) { CMenuItem* pItem = list.GetNext(pos); if ( !fFindSubmenu && pItem->GetMenuItemID() == nMenuItemID ) { // Found a match return pItem; } else if ( pItem->HasChildList() ) { if ( fFindSubmenu && pItem->GetPopupMenuHandle() == (HMENU)nMenuItemID && !pItem->IsSpecialInsertionPoint() ) // "insertion point" is not real menu return pItem; pItem = FindWorker( pItem->GetMenuItemSubmenu(), nMenuItemID, fFindSubmenu ); if (NULL != pItem) return pItem; } } return NULL; } MenuItemList* CContextMenu::GetMenuItemList() { if (NULL == m_pmenuitemRoot) m_pmenuitemRoot = new CRootMenuItem; if (m_pmenuitemRoot == NULL) { return NULL; } return &m_pmenuitemRoot->GetMenuItemSubmenu(); } // critsec should already be claimed CMenuItem* CContextMenu::FindMenuItem( LONG_PTR nMenuItemID, BOOL fFindSubmenu ) { DECLARE_SC(sc, TEXT("CContextMenu::FindMenuItem")); if (0 == nMenuItemID || CCM_INSERTIONPOINTID_ROOT_MENU == nMenuItemID) return m_pmenuitemRoot; else { MenuItemList* plist = GetMenuItemList(); sc = ScCheckPointers( plist ); if (sc) return NULL; return FindWorker( *plist, nMenuItemID, fFindSubmenu ); } } /*+-------------------------------------------------------------------------* * * ReverseFindWorker * * PURPOSE: Worker function, called recursively by ReverseFindMenuItem * critsec should already be claimed * * PARAMETERS: * MenuItemList& list : * long nCommandID : * MENU_OWNER_ID ownerID : * CStr & strPath : * * RETURNS: * CMenuItem* * *+-------------------------------------------------------------------------*/ CMenuItem* ReverseFindWorker( MenuItemList& list, long nCommandID, MENU_OWNER_ID ownerID, CStr &strPath, CStr &strLanguageIndependentPath ) { POSITION pos = list.GetHeadPosition(); while(pos) { CMenuItem* pItem = list.GetNext(pos); if ( pItem->GetCommandID() == nCommandID && ( (pItem->GetMenuItemOwner() == ownerID) || IsSharedInsertionPointID(nCommandID) ) ) { // Found a match - add it to the path and return strPath = pItem->GetPath(); strLanguageIndependentPath = pItem->GetLanguageIndependentPath(); return pItem; } else if ( pItem->HasChildList() ) { pItem = ReverseFindWorker( pItem->GetMenuItemSubmenu(), nCommandID, ownerID, strPath, strLanguageIndependentPath ); if (NULL != pItem) { return pItem; } } } return NULL; } /*+-------------------------------------------------------------------------* * * CContextMenu::ReverseFindMenuItem * * PURPOSE: Searches for the specified menu item. Also builds up the * path to the menu item in strPath. * * NOTE: critsec should already be claimed * * PARAMETERS: * long nCommandID : * MENU_OWNER_ID ownerID : * CStr & strPath : * * RETURNS: * CMenuItem* * *+-------------------------------------------------------------------------*/ CMenuItem* CContextMenu::ReverseFindMenuItem( long nCommandID, MENU_OWNER_ID ownerID, CStr &strPath, CStr &strLanguageIndependentPath) { DECLARE_SC(sc, TEXT("CContextMenu::ReverseFindMenuItem")); strPath = TEXT(""); // initialize if (CCM_INSERTIONPOINTID_ROOT_MENU == nCommandID) return m_pmenuitemRoot; else { MenuItemList* plist = GetMenuItemList(); sc = ScCheckPointers( plist ); if (sc) return NULL; return ReverseFindWorker( *plist, nCommandID, ownerID, strPath, strLanguageIndependentPath); } } // // Find Nth item in specified menu/submenu // CMenuItem* CContextMenu::FindNthItemInSubmenu( HMENU hmenuParent, UINT iPosition, LPTSTR lpszMenuName ) { // locate menu/submenu MenuItemList* plist = GetMenuItemList(); if ( NULL != hmenuParent ) { CMenuItem* pParent = FindMenuItem( (LONG_PTR)hmenuParent, TRUE ); if ( NULL == pParent ) { ASSERT( FALSE ); return NULL; } plist = &pParent->GetMenuItemSubmenu(); } if ( NULL == plist ) { ASSERT( FALSE ); return NULL; } // find the Nth item POSITION pos = plist->GetHeadPosition(); if (NULL != lpszMenuName) { while(pos) { CMenuItem* pItem = plist->GetNext(pos); if (! _tcscmp(lpszMenuName, pItem->GetMenuItemName() )) { // Found the match return pItem; } } } else { while(pos) { CMenuItem* pItem = plist->GetNext(pos); if ( 0 == iPosition-- ) { // Found a match return pItem; } } } ASSERT( FALSE ); return NULL; } // claims critsec STDMETHODIMP CContextMenu::DoAddMenuItem(LPCTSTR lpszName, LPCTSTR lpszStatusBarText, LPCTSTR lpszLanguageIndependentName, LONG lCommandID, LONG lInsertionPointID, LONG fFlags, LONG fSpecialFlags, MENU_OWNER_ID ownerID, CMenuItem** ppMenuItem /* = NULL */, bool bPassCommandBackToSnapin /*= false*/ ) { DECLARE_SC(sc, TEXT("CContextMenu::DoAddMenuItem")); MMC_TRY // init out param if (ppMenuItem) *ppMenuItem = NULL; // Save test flag now because special flags are modified below BOOL bTestOnly = fSpecialFlags & CCM_SPECIAL_TESTONLY; if ( OWNERID_INVALID == ownerID ) { TRACE(_T("CContextMenu::DoAddMenuItem(): invalid ownerid")); ASSERT(FALSE); return E_INVALIDARG; } if ( (CCM_SPECIAL_SEPARATOR & fSpecialFlags)?0:1 + ((CCM_SPECIAL_SUBMENU|CCM_SPECIAL_DEFAULT_ITEM) & fSpecialFlags)?0:1 + (CCM_SPECIAL_INSERTION_POINT & fSpecialFlags)?0:1 > 1 ) { TRACE(_T("CContextMenu::DoAddMenuItem(): invalid combination of special flags")); ASSERT(FALSE); return E_INVALIDARG; } if (CCM_SPECIAL_SEPARATOR & fSpecialFlags) { lpszName = NULL; lpszStatusBarText = NULL; lCommandID = 0; fFlags = MF_SEPARATOR | MF_GRAYED | MF_DISABLED; } if ( CCM_SPECIAL_INSERTION_POINT & fSpecialFlags ) { fFlags = NULL; // be sure to clear MF_POPUP fSpecialFlags = CCM_SPECIAL_INSERTION_POINT; } if ( (CCM_SPECIAL_SUBMENU & fSpecialFlags) && !(MF_POPUP & fFlags) ) { TRACE(_T("CContextMenu::DoAddMenuItem(): CCM_SPECIAL_SUBMENU requires MF_POPUP")); ASSERT(FALSE); return E_INVALIDARG; } if ( (MF_OWNERDRAW|MF_BITMAP) & fFlags ) { TRACE(_T("CContextMenu::DoAddMenuItem(): MF_OWNERDRAW and MF_BITMAP are invalid")); ASSERT(FALSE); return E_INVALIDARG; } else if ( !(MF_SEPARATOR & fFlags) && !(CCM_SPECIAL_INSERTION_POINT & fSpecialFlags) && NULL == lpszName ) { TRACE(_T("CContextMenu::DoAddMenuItem(): invalid menuitem text\n")); ASSERT(FALSE); return E_INVALIDARG; } // note that NULL==lpszStatusBarText is permitted START_CRITSEC_MENU; // // An insertion point of 0 is interpreted the same as CCM_INSERTIONPOINTID_ROOT_MENU // if (0 == lInsertionPointID) lInsertionPointID = CCM_INSERTIONPOINTID_ROOT_MENU; // // Check that the insertion point ID specified is legal for this customer // do // false loop { if ( !IsSpecialInsertionPointID(lInsertionPointID) ) break; if ( IsReservedInsertionPointID(lInsertionPointID) ) { TRACE(_T("CContextMenu::DoAddMenuItem(): using reserved insertion point ID\n")); return E_INVALIDARG; } if ( !IsSharedInsertionPointID(lInsertionPointID) ) break; if ( !IsAddPrimaryInsertionPointID(lInsertionPointID) ) { if ( IsPrimaryOwnerID(ownerID) ) { TRACE(_T("CContextMenu::DoAddMenuItem(): not addprimary insertion point ID\n")); return E_INVALIDARG; } } if ( !IsAdd3rdPartyInsertionPointID(lInsertionPointID) ) { if ( IsThirdPartyOwnerID(ownerID) ) { TRACE(_T("CContextMenu::DoAddMenuItem(): not add3rdpartyinsertion point ID\n")); return E_INVALIDARG; } } } while (FALSE); // false loop // // Check that the command ID specified is legal for this customer // if ( (MF_POPUP & fFlags) || (CCM_SPECIAL_INSERTION_POINT & fSpecialFlags) ) { do // false loop { if ( !IsSpecialInsertionPointID(lCommandID) ) break; if ( IsReservedInsertionPointID(lCommandID) ) { TRACE(_T("CContextMenu::DoAddMenuItem(): adding reserved insertion point ID\n")); ASSERT(FALSE); return E_INVALIDARG; } if ( !IsSharedInsertionPointID(lCommandID) ) break; if ( IsThirdPartyOwnerID(ownerID) ) { TRACE(_T("CContextMenu::DoAddMenuItem(): 3rdparty cannot add shared insertion point")); ASSERT(FALSE); return E_INVALIDARG; } else if ( IsPrimaryOwnerID(ownerID) ) { if ( !IsCreatePrimaryInsertionPointID(lCommandID) ) { TRACE(_T("CContextMenu::DoAddMenuItem(): only system for new !PRIMARYCREATE submenu")); ASSERT(FALSE); return E_INVALIDARG; } } else if ( IsSystemOwnerID(ownerID) ) { if ( IsCreatePrimaryInsertionPointID(lCommandID) ) { TRACE(_T("CContextMenu::DoAddMenuItem(): only primary extension for new PRIMARYCREATE submenu")); ASSERT(FALSE); return E_INVALIDARG; } } } while (FALSE); // false loop } else if ( !(CCM_SPECIAL_SEPARATOR & fSpecialFlags) ) { if ( IsReservedCommandID(lCommandID) ) { TRACE(_T("CContextMenu::DoAddMenuItem(): no new RESERVED menu items")); ASSERT(FALSE); return E_INVALIDARG; } } if (NULL == m_pmenuitemRoot) m_pmenuitemRoot = new CRootMenuItem; CStr strPath, strLanguageIndependentPath; // this builds up the path of the menu item. CMenuItem* pParent = ReverseFindMenuItem( lInsertionPointID, ownerID, strPath, strLanguageIndependentPath); if (NULL == pParent) { TRACE(_T("CContextMenu::DoAddMenuItem(): submenu with command ID %ld owner %ld does not exist"), lInsertionPointID, ownerID ); ASSERT(FALSE); return E_INVALIDARG; } MenuItemList& rMenuList = pParent->GetMenuItemSubmenu(); // If this is only a test add, return with success now if (bTestOnly) return S_OK; // get the data object and IExtendContextMenu pointer to set in the item. IExtendContextMenuPtr spExtendContextMenu; IDataObject* pDataObject = NULL; // This is used JUST to hold on to the object until Command completes. // locate the IExtendContextMenu of the snapin. { // The selected item was added by an extension SnapinStruct* psnapin = FindSnapin( ownerID ); if(psnapin != NULL) { pDataObject = psnapin->m_pIDataObject; spExtendContextMenu = psnapin->pIExtendContextMenu; } else { CTiedComObjectCreator:: ScCreateAndConnect(*this, spExtendContextMenu); // built in items are handled by CContextMenu itself. } } // compute the language independent and language dependent paths for the context menu item. CStr strLanguageIndependentName = lpszLanguageIndependentName; tstring tstrName = lpszName ? lpszName : TEXT(""); RemoveAccelerators(tstrName); CStr strName; strName = tstrName.data(); // got to standardise on either tstring or CStr // add a "->" separator to the path if needed if(!strPath.IsEmpty() && !strName.IsEmpty()) strPath += _T("->"); strPath += strName; // add a "->" separator to the language independent path if needed if(!strLanguageIndependentPath.IsEmpty() && !strLanguageIndependentName.IsEmpty()) strLanguageIndependentPath += _T("->"); strLanguageIndependentPath += strLanguageIndependentName; CMenuItem* pItem = new CMenuItem( lpszName, lpszStatusBarText, lpszLanguageIndependentName, (LPCTSTR)strPath, (LPCTSTR)strLanguageIndependentPath, lCommandID, m_nNextMenuItemID++, fFlags, ownerID, spExtendContextMenu, pDataObject, fSpecialFlags, bPassCommandBackToSnapin); ASSERT( pItem ); if (pItem == NULL) return E_OUTOFMEMORY; rMenuList.AddTail(pItem); // If this is a system defined insertion point, update the insertion flags if (IsSharedInsertionPointID(lCommandID) && !IsCreatePrimaryInsertionPointID(lCommandID)) { long fFlag = ( 1L << (lCommandID & CCM_INSERTIONPOINTID_MASK_FLAGINDEX)); if (IsAddPrimaryInsertionPointID(lCommandID)) m_fPrimaryInsertionFlags |= fFlag; if (IsAdd3rdPartyInsertionPointID(lCommandID)) m_fThirdPartyInsertionFlags |= fFlag; } // return the item if required if (ppMenuItem) *ppMenuItem = pItem; END_CRITSEC_MENU; return S_OK; MMC_CATCH } // APP HACK. Workarounding dependency on older FP where they were QI'ing for IConsole from // IContextMenuCallback, which was working in MMC 1.2, but cannot work in mmc 2.0 // See bug 200621 (Windows bugs (ntbug9) 11/15/2000) #define WORKAROUND_FOR_FP_REQUIRED #if defined (WORKAROUND_FOR_FP_REQUIRED) /***************************************************************************\ * * CLASS: CWorkaroundWrapperForFrontPageMenu * * PURPOSE: Used from subclassed MMC's IExtendContextMenu interface for FrontPage. * Contains (in com sense) IContextMenuCallback2 and IContextMenuCallback by forwarding * them to original interface, but in addition supports QI for IConsole. * This is a requirement for older FrontPage to work * \***************************************************************************/ class CWorkaroundWrapperForFrontPageMenu : public IContextMenuCallback, public IContextMenuCallback2, public IConsole2, // workaround for bug 200621. This is a dummy implementation of IConsole2 public CComObjectRoot { friend class CWorkaroundMMCWrapperForFrontPageMenu; // pointer to context menu object IContextMenuCallbackPtr m_spIContextMenuCallback; IContextMenuCallback2Ptr m_spIContextMenuCallback2; public: typedef CWorkaroundWrapperForFrontPageMenu ThisClass; // com entry points BEGIN_COM_MAP(ThisClass) COM_INTERFACE_ENTRY(IContextMenuCallback) // the IContextMenuProvider and IContextMenu COM_INTERFACE_ENTRY(IContextMenuCallback2) COM_INTERFACE_ENTRY(IConsole) COM_INTERFACE_ENTRY(IConsole2) END_COM_MAP() // just forward... STDMETHOD(AddItem) ( CONTEXTMENUITEM* pItem ) { if ( m_spIContextMenuCallback == NULL ) return E_UNEXPECTED; return m_spIContextMenuCallback->AddItem( pItem ); } // just forward... STDMETHOD(AddItem) ( CONTEXTMENUITEM2* pItem ) { if ( m_spIContextMenuCallback2 == NULL ) return E_UNEXPECTED; return m_spIContextMenuCallback2->AddItem( pItem ); } // IConsole2 methods - DUMMY - workaround for bug 200621 STDMETHOD(SetHeader)( LPHEADERCTRL pHeader) {return E_NOTIMPL;} STDMETHOD(SetToolbar)( LPTOOLBAR pToolbar) {return E_NOTIMPL;} STDMETHOD(QueryResultView)( LPUNKNOWN* pUnknown) {return E_NOTIMPL;} STDMETHOD(QueryScopeImageList)( LPIMAGELIST* ppImageList) {return E_NOTIMPL;} STDMETHOD(QueryResultImageList)( LPIMAGELIST* ppImageList) {return E_NOTIMPL;} STDMETHOD(UpdateAllViews)( LPDATAOBJECT lpDataObject,LPARAM data,LONG_PTR hint) {return E_NOTIMPL;} STDMETHOD(MessageBox)( LPCWSTR lpszText, LPCWSTR lpszTitle,UINT fuStyle, int* piRetval) {return E_NOTIMPL;} STDMETHOD(QueryConsoleVerb)( LPCONSOLEVERB * ppConsoleVerb) {return E_NOTIMPL;} STDMETHOD(SelectScopeItem)( HSCOPEITEM hScopeItem) {return E_NOTIMPL;} STDMETHOD(GetMainWindow)( HWND* phwnd) { if (!phwnd) return E_INVALIDARG; *phwnd = (CScopeTree::GetScopeTree() ? CScopeTree::GetScopeTree()->GetMainWindow() : NULL); return S_OK; } STDMETHOD(NewWindow)( HSCOPEITEM hScopeItem, unsigned long lOptions) {return E_NOTIMPL;} STDMETHOD(Expand)( HSCOPEITEM hItem, BOOL bExpand) {return E_NOTIMPL;} STDMETHOD(IsTaskpadViewPreferred)() {return E_NOTIMPL;} STDMETHOD(SetStatusText )( LPOLESTR pszStatusText) {return E_NOTIMPL;} }; /***************************************************************************\ * * CLASS: CWorkaroundMMCWrapperForFrontPageMenu * * PURPOSE: Subclasses MMC's IExtendContextMenu interface for FrontPage. * Contains ( in com sense) IExtendContextMenu; Forwards calls to default MMC implementation, * but for AddMenuItems gives itself as a callback interface. * [ main purpose to have this object is to avoid changing main MMC functions ] * [ to implement this workaround ] * \***************************************************************************/ class CWorkaroundMMCWrapperForFrontPageMenu : public IExtendContextMenu, public CComObjectRoot { // pointer to context menu object IExtendContextMenuPtr m_spExtendContextMenu; CNode *m_pNode; public: typedef CWorkaroundMMCWrapperForFrontPageMenu ThisClass; // this method is null for all snapins, but FrontPage // for FrontPage it wraps and replaces spIUnknown paramter static SC ScSubclassFP(const CLSID& clsid,IUnknownPtr &spIUnknown) { DECLARE_SC(sc, TEXT("CWorkaroundMMCWrapperForFrontPageMenu::ScSubclassFP")); static const CLSID CLSID_Fpsrvmmc = { 0xFF5903A8, 0x78D6, 0x11D1, { 0x92, 0xF6, 0x00, 0x60, 0x97, 0xB0, 0x10, 0x56 } }; // only required intercept one clsid if ( clsid != CLSID_Fpsrvmmc ) return sc; // create self typedef CComObject ThisComObj_t; ThisComObj_t *pObj = NULL; sc = ThisComObj_t::CreateInstance(&pObj); if (sc) return sc; // cast to avoid member access problems (workarounding compiler) ThisClass *pThis = pObj; sc = ScCheckPointers( pThis, E_UNEXPECTED ); if (sc) return sc; // maintain the lifetime in case of accident IUnknownPtr spThis = pThis->GetUnknown(); // grab on snapin's interface pThis->m_spExtendContextMenu = spIUnknown; sc = ScCheckPointers( pThis->m_spExtendContextMenu, E_UNEXPECTED ); if (sc) return sc; // substitute the snapin (in-out parameter) spIUnknown = spThis; return sc; } // com entry points BEGIN_COM_MAP(ThisClass) COM_INTERFACE_ENTRY(IExtendContextMenu) END_COM_MAP() // AddMenuItems is the method this object exists for. // If we got here, mmc is about to ask FrontPage to add its items to context menu. // We'll wrap the callback interface given by MMC with the object implementing // phony IConsole - this is required for older FP to work STDMETHOD(AddMenuItems)( LPDATAOBJECT piDataObject, LPCONTEXTMENUCALLBACK piCallback, long * pInsertionAllowed ) { DECLARE_SC(sc, TEXT("CWorkaroundMMCWrapperForFrontPageMenu::AddMenuItems")); IContextMenuCallbackPtr spIContextMenuCallback = piCallback; IContextMenuCallback2Ptr spIContextMenuCallback2 = piCallback; if ( m_spExtendContextMenu == NULL || spIContextMenuCallback == NULL || spIContextMenuCallback2 == NULL ) return E_UNEXPECTED; // create a wrapper for FP typedef CComObject WrapperComObj_t; WrapperComObj_t *pObj = NULL; sc = WrapperComObj_t::CreateInstance(&pObj); if (sc) return sc.ToHr(); // cast to avoid member access problems (workarounding compiler) CWorkaroundWrapperForFrontPageMenu *pWrapper = pObj; sc = ScCheckPointers( pWrapper, E_UNEXPECTED ); if (sc) return sc.ToHr(); // maintain the lifetime in case of accident IUnknownPtr spWrapper = pWrapper->GetUnknown(); // grab on snapin's interface pWrapper->m_spIContextMenuCallback = spIContextMenuCallback; pWrapper->m_spIContextMenuCallback2 = spIContextMenuCallback2; // call snapin on behave on mmc, but pass itself as callback sc = m_spExtendContextMenu->AddMenuItems( piDataObject, pWrapper, pInsertionAllowed ); // fall thru even on error - need to release interfaces // reset callback interfaces - not valid after the call anyway... // this will let context menu go, and prevent FP from suicide (AV); // Following this all calls to IContextMenuCallback would fail, // but that's ok, since it is not legal to call them after AddMenuItems. pWrapper->m_spIContextMenuCallback = NULL; pWrapper->m_spIContextMenuCallback2 = NULL; return sc.ToHr(); } // simply forward.... STDMETHOD(Command)(long lCommandID, LPDATAOBJECT piDataObject) { ASSERT( m_spExtendContextMenu != NULL ); if ( m_spExtendContextMenu == NULL ) return E_UNEXPECTED; return m_spExtendContextMenu->Command(lCommandID, piDataObject); } }; #endif // defined (WORKAROUND_FOR_FP_REQUIRED) // critsec should already be claimed SC CContextMenu::ScAddSnapinToList_GUID( const CLSID& clsid, IDataObject* piDataObject, MENU_OWNER_ID ownerID ) { DECLARE_SC(sc, TEXT("CContextMenu::ScAddSnapinToList_GUID")); // cocreate extension IUnknownPtr spIUnknown; sc = ::CoCreateInstance(clsid, NULL, MMC_CLSCTX_INPROC, IID_IUnknown, (LPVOID*)&spIUnknown); if (sc) return sc; #if defined (WORKAROUND_FOR_FP_REQUIRED) sc = CWorkaroundMMCWrapperForFrontPageMenu::ScSubclassFP(clsid, spIUnknown); #endif // defined (WORKAROUND_FOR_FP_REQUIRED) // get IExtendContextMenu interface IExtendContextMenuPtr spIExtendContextMenu = spIUnknown; sc = ScCheckPointers(spIExtendContextMenu, E_NOINTERFACE); if (sc) return sc; // add menu items sc = ScAddSnapinToList_IExtendContextMenu(spIExtendContextMenu, piDataObject, ownerID ); if (sc) return sc; return sc; } // does not AddRef() or Release() interface pointer // critsec should already be claimed SC CContextMenu::ScAddSnapinToList_IUnknown( IUnknown* piExtension, IDataObject* piDataObject, MENU_OWNER_ID ownerID ) { DECLARE_SC(sc, TEXT("CContextMenu::AddSnapinToList_IUnknown")); // parameter check sc = ScCheckPointers(piExtension); if (sc) return sc; IExtendContextMenuPtr spIExtendContextMenu = piExtension; if (spIExtendContextMenu == NULL) return sc; // snapin does not extend context menus // add menu items sc = ScAddSnapinToList_IExtendContextMenu( spIExtendContextMenu, piDataObject, ownerID ); if (sc) return sc; return sc; } // Interface pointer is Release()d when menu list is emptied // critsec should already be claimed SC CContextMenu::ScAddSnapinToList_IExtendContextMenu( IExtendContextMenu* pIExtendContextMenu, IDataObject* piDataObject, MENU_OWNER_ID ownerID ) { DECLARE_SC(sc, TEXT("CContextMenu::ScAddSnapinToList_IExtendContextMenu")); // parameter check sc = ScCheckPointers(pIExtendContextMenu); if (sc) return sc; SnapinStruct* psnapstruct = new SnapinStruct( pIExtendContextMenu, piDataObject, ownerID ); sc = ScCheckPointers(psnapstruct, E_OUTOFMEMORY); if (sc) return sc; m_SnapinList->AddTail(psnapstruct); m_CurrentExtensionOwnerID = ownerID; long fInsertionFlags = IsPrimaryOwnerID(ownerID) ? m_fPrimaryInsertionFlags : m_fThirdPartyInsertionFlags; // if view items are requested, then allow only view items // view item requests go to the IComponent. If other item types are allowed there // will be a second pass through this code directed to the IComponentData. long lTempFlags = fInsertionFlags; if ( fInsertionFlags & CCM_INSERTIONALLOWED_VIEW ) lTempFlags = CCM_INSERTIONALLOWED_VIEW; try { sc = pIExtendContextMenu->AddMenuItems( piDataObject, this, &lTempFlags ); #ifdef DBG if (sc) TraceSnapinError(_T("IExtendContextMenu::AddMenuItems failed"), sc); #endif } catch (...) { if (DOBJ_CUSTOMOCX == piDataObject) { ASSERT( FALSE && "IExtendContextMenu::AddMenuItem of IComponent is called with DOBJ_CUSTOMOCX and snapin derefed this custom data object. Please handle special dataobjects in your snapin."); sc = E_UNEXPECTED; } else if (DOBJ_CUSTOMWEB == piDataObject) { ASSERT( FALSE && "IExtendContextMenu::AddMenuItem of IComponent is called with DOBJ_CUSTOMWEB and snapin derefed this custom data object. Please handle special dataobjects in your snapin."); sc = E_UNEXPECTED; } else { ASSERT( FALSE && "IExtendContextMenu::AddMenuItem implemented by snapin has thrown an exception."); sc = E_UNEXPECTED; } } m_CurrentExtensionOwnerID = OWNERID_NATIVE; if (sc) return sc; // Primary snapin is allowed to clear extension snapin insertion flags if ( IsPrimaryOwnerID(ownerID) ) m_fThirdPartyInsertionFlags &= fInsertionFlags; return sc; } // All snapin interface pointers are Release()d // critsec should already be claimed void CContextMenu::ReleaseSnapinList() { ASSERT(m_SnapinList != NULL); if (m_SnapinList != NULL && m_SnapinList->GetCount() != 0) { POSITION pos = m_SnapinList->GetHeadPosition(); while(pos) { SnapinStruct* pItem = (SnapinStruct*)m_SnapinList->GetNext(pos); ASSERT_OBJECTPTR( pItem ); delete pItem; } m_SnapinList->RemoveAll(); } } // critsec should already be claimed SnapinStruct* CContextMenu::FindSnapin( MENU_OWNER_ID ownerID ) { ASSERT(m_SnapinList != NULL); if (m_SnapinList != NULL && m_SnapinList->GetCount() != 0) { POSITION pos = m_SnapinList->GetHeadPosition(); while(pos) { SnapinStruct* pItem = (SnapinStruct*)m_SnapinList->GetNext(pos); ASSERT( NULL != pItem ); if ( ownerID == pItem->m_OwnerID ) return pItem; } } return NULL; } // Worker function, called recursively by ShowContextMenu // critsec should already be claimed HRESULT CollapseInsertionPoints( CMenuItem* pmenuitemParent ) { ASSERT( NULL != pmenuitemParent && !pmenuitemParent->IsSpecialInsertionPoint() ); MenuItemList& rMenuList = pmenuitemParent->GetMenuItemSubmenu(); POSITION pos = rMenuList.GetHeadPosition(); while(pos) { POSITION posThisItem = pos; CMenuItem* pItem = (rMenuList.GetNext(pos)); ASSERT( pItem != NULL ); if ( pItem->IsPopupMenu() ) { ASSERT( !pItem->IsSpecialInsertionPoint() ); HRESULT hr = CollapseInsertionPoints( pItem ); if ( FAILED(hr) ) { ASSERT( FALSE ); return hr; } continue; } if ( !pItem->IsSpecialInsertionPoint() ) continue; // we found an insertion point, move its items into this list MenuItemList& rInsertedList = pItem->GetMenuItemSubmenu(); POSITION posInsertAfterThis = posThisItem; while ( !rInsertedList.IsEmpty() ) { CMenuItem* pInsertedItem = rInsertedList.RemoveHead(); posInsertAfterThis = rMenuList.InsertAfter( posInsertAfterThis, pInsertedItem ); } // delete the insertion point item rMenuList.RemoveAt(posThisItem); delete pItem; // restart at head of list, in case of recursive insertion points pos = rMenuList.GetHeadPosition(); } return S_OK; } // Worker function, called recursively by ShowContextMenu // critsec should already be claimed and CollapseInsertionPoints should have been called HRESULT CollapseSpecialSeparators( CMenuItem* pmenuitemParent ) { ASSERT( NULL != pmenuitemParent && !pmenuitemParent->IsSpecialInsertionPoint() ); MenuItemList& rMenuList = pmenuitemParent->GetMenuItemSubmenu(); CMenuItem* pItem = NULL; BOOL fLastItemWasReal = FALSE; POSITION pos = rMenuList.GetHeadPosition(); POSITION posThisItem = pos; while(pos) { posThisItem = pos; pItem = (rMenuList.GetNext(pos)); ASSERT( pItem != NULL ); ASSERT( !pItem->IsSpecialInsertionPoint() ); if ( pItem->IsPopupMenu() ) { ASSERT( !pItem->IsSpecialSeparator() ); HRESULT hr = CollapseSpecialSeparators( pItem ); if ( FAILED(hr) ) { ASSERT( FALSE ); return hr; } fLastItemWasReal = TRUE; continue; } if ( !pItem->IsSpecialSeparator() ) { fLastItemWasReal = TRUE; continue; } if ( fLastItemWasReal ) { fLastItemWasReal = FALSE; continue; } // Found two consecutive special separators, or special seperator as first item // delete the insertion point item rMenuList.RemoveAt(posThisItem); delete pItem; } if ( !fLastItemWasReal && !rMenuList.IsEmpty() ) { // Found special separator as last item delete rMenuList.RemoveTail(); } return S_OK; } // Worker function, called recursively by ShowContextMenu // critsec should already be claimed HRESULT BuildContextMenu( WTL::CMenu& menu, CMenuItem* pmenuitemParent ) { MenuItemList& rMenuList = pmenuitemParent->GetMenuItemSubmenu(); int nCount = 0; bool fInsertedItemSinceLastSeparator = false; POSITION pos = rMenuList.GetHeadPosition(); while(pos) { CMenuItem* pItem = (rMenuList.GetNext(pos)); ASSERT( pItem != NULL ); ASSERT( !pItem->IsSpecialInsertionPoint() ); UINT_PTR nCommandID = pItem->GetMenuItemID(); long nFlags = pItem->GetMenuItemFlags(); /* * special processing for submenus */ if ( pItem->IsPopupMenu() ) { // add items to a submenu WTL::CMenu submenu; VERIFY( submenu.CreatePopupMenu() ); HRESULT hr = BuildContextMenu( submenu, pItem ); if ( FAILED(hr) ) return hr; HMENU hSubmenu = submenu.Detach(); ASSERT( NULL != hSubmenu ); nCommandID = (UINT_PTR)hSubmenu; pItem->SetPopupMenuHandle( hSubmenu ); if ( pItem->IsSpecialSubmenu() ) { MenuItemList& rChildMenuList = pItem->GetMenuItemSubmenu(); if ( rChildMenuList.IsEmpty() ) { // Bug 151435: remove instead of disabling unused submenus // pItem->SetMenuItemFlags(nFlags | (MF_GRAYED|MF_DISABLED)); ::DestroyMenu(hSubmenu); continue; } } fInsertedItemSinceLastSeparator = true; } /* * special processing for separators */ else if (nFlags & MF_SEPARATOR) { /* * if we haven't inserted an item since the last separator, * we don't want to insert this one or we'll have consecutive * separators, or an unnecessary separator at the top of the menu */ if (!fInsertedItemSinceLastSeparator) continue; /* * if there aren't any more items after this separator, * we don't want to insert this one or we'll have an * unnecessary separator at the bottom of the menu */ if (pos == NULL) continue; fInsertedItemSinceLastSeparator = false; } /* * just a normal menu item */ else { fInsertedItemSinceLastSeparator = true; } if (!menu.AppendMenu(nFlags, nCommandID, pItem->GetMenuItemName())) { #ifdef DBG TRACE(_T("BuildContextMenu: AppendMenu(%ld, %ld, \"%s\") reports error\n"), nFlags, nCommandID, SAFEDBGTCHAR(pItem->GetMenuItemName()) ); #endif ASSERT( FALSE ); return E_UNEXPECTED; } if (pItem->IsSpecialItemDefault()) { VERIFY( ::SetMenuDefaultItem(menu, nCount, TRUE) ); } ++nCount; } return S_OK; } /*+-------------------------------------------------------------------------* * CContextMenu::BuildContextMenu * * PURPOSE: * * PARAMETERS: * WTL::CMenu & menu: * * RETURNS: * HRESULT /*+-------------------------------------------------------------------------*/ HRESULT CContextMenu::BuildContextMenu(WTL::CMenu &menu) { HRESULT hr = S_OK; hr = ::CollapseInsertionPoints( m_pmenuitemRoot ); if ( FAILED(hr) ) return hr; hr = ::CollapseSpecialSeparators( m_pmenuitemRoot ); if ( FAILED(hr) ) return hr; hr = ::BuildContextMenu( menu, m_pmenuitemRoot ); if ( FAILED(hr) ) return hr; UINT iItems = menu.GetMenuItemCount(); if ((UINT)-1 == iItems) { TRACE(_T("CContextMenu::BuildContextMenu(): itemcount error")); ASSERT( FALSE ); return E_UNEXPECTED; } else if (0 >= iItems) { TRACE(_T("CContextMenu::BuildContextMenu(): no items added")); return S_OK; } return hr; } /*+-------------------------------------------------------------------------* * CContextMenu::ShowContextMenu * * PURPOSE: * * PARAMETERS: * WND hwndParent: * LONG xPos: * LONG yPos: * LONG* plSelected: * * RETURNS: * HRESULT /*+-------------------------------------------------------------------------*/ STDMETHODIMP CContextMenu::ShowContextMenu( HWND hwndParent, LONG xPos, LONG yPos, LONG* plSelected) { return (ShowContextMenuEx (hwndParent, xPos, yPos, NULL/*prcExclude*/, true/*bAllowDefaultMenuItem*/, plSelected)); } STDMETHODIMP CContextMenu::ShowContextMenuEx(HWND hwndParent, LONG xPos, LONG yPos, LPCRECT prcExclude, bool bAllowDefaultMenuItem, LONG* plSelected) { DECLARE_SC(sc, _T("IContextMenuProvider::ShowContextMenuEx")); if (NULL == plSelected) { sc = E_INVALIDARG; TraceSnapinError(_T("NULL selected ptr"), sc); return sc.ToHr(); } *plSelected = 0; WTL::CMenu menu; VERIFY( menu.CreatePopupMenu() ); START_CRITSEC_BOTH; if (NULL == m_pmenuitemRoot) return sc.ToHr(); sc = BuildContextMenu(menu); // build the context menu if (sc) return sc.ToHr(); CMenuItem* pItem = NULL; LONG lSelected = 0; CConsoleStatusBar *pStatusBar = GetStatusBar(); // At this point, pStatusBar should be non-NULL, either because // 1) This function was called by CNodeInitObject, which calls SetStatusBar() first, // or 2) by the object model, where m_pNode is always non-NULL. ASSERT(pStatusBar); // set up the menu command sink and hook up the status bar. CCommandSink comsink( *this, menu, pStatusBar); if ( !comsink.Init() ) { sc = E_UNEXPECTED; TraceNodeMgrLegacy(_T("CContextMenu::ShowContextMenuEx(): comsink error\n"), sc); return sc.ToHr(); } /* * if we got an exclusion rectangle, set up a TPMPARAMS to specify it */ TPMPARAMS* ptpm = NULL; TPMPARAMS tpm; if (prcExclude != NULL) { tpm.cbSize = sizeof(tpm); tpm.rcExclude = *prcExclude; ptpm = &tpm; } /* * Bug 139708: menu bar popups shouldn't have default menu items. If * we can't have one on this popup, remove any default item now. */ if (!bAllowDefaultMenuItem) SetMenuDefaultItem (menu, -1, false); lSelected = menu.TrackPopupMenuEx( TPM_RETURNCMD | TPM_NONOTIFY | TPM_RIGHTBUTTON | TPM_LEFTBUTTON | TPM_VERTICAL, xPos, yPos, comsink.m_hWnd, // CODEWORK can we eliminate this? ptpm ); comsink.DestroyWindow(); pItem = (0 == lSelected) ? NULL : FindMenuItem( lSelected ); if ( pItem != NULL ) { // execute the menu item sc = ExecuteMenuItem(pItem); if(sc) return sc.ToHr(); // in some cases we'll need to pass command to the sanpin if ( pItem->NeedsToPassCommandBackToSnapin() ) *plSelected = pItem->GetCommandID(); } else ASSERT( 0 == lSelected ); // no items selected. END_CRITSEC_BOTH; return sc.ToHr(); } HRESULT CContextMenu::ExecuteMenuItem(CMenuItem *pItem) { DECLARE_SC(sc, TEXT("CContextMenu::ExecuteMenuItem")); sc = ScCheckPointers(pItem); if(sc) return sc.ToHr(); // execute it; sc = pItem->ScExecute(); if(sc) return sc.ToHr(); return sc.ToHr(); }