#include "shellprv.h" #include "mmhelper.h" #include "ids.h" #include "pidl.h" #include "fstreex.h" #include "views.h" #include "shlwapip.h" #include "ole2dup.h" #include "datautil.h" #include "undo.h" #include "defview.h" #define DEF_FOLDERMENU_MAXHKEYS 16 // used with static defcm elements (pointer from mii.dwItemData) // and find extensions typedef struct { WCHAR wszMenuText[MAX_PATH]; WCHAR wszHelpText[MAX_PATH]; int iIcon; } SEARCHEXTDATA; typedef struct { SEARCHEXTDATA* psed; UINT idCmd; } SEARCHINFO; //Defined in fsmenu.obj BOOL _MenuCharMatch(LPCTSTR lpsz, TCHAR ch, BOOL fIgnoreAmpersand); // static verbs, added by getting const struct { LPCTSTR pszCmd; WPARAM idDFMCmd; UINT idDefCmd; } c_sDFMCmdInfo[] = { { c_szDelete, DFM_CMD_DELETE, DCMIDM_DELETE }, { c_szCut, DFM_CMD_MOVE, DCMIDM_CUT }, { c_szCopy, DFM_CMD_COPY, DCMIDM_COPY }, { c_szPaste, DFM_CMD_PASTE, DCMIDM_PASTE }, { c_szLink, DFM_CMD_LINK, DCMIDM_LINK }, { c_szProperties, DFM_CMD_PROPERTIES, DCMIDM_PROPERTIES }, { c_szPaste, DFM_CMD_PASTE, 0 }, { c_szPasteLink, DFM_CMD_PASTELINK, 0 }, { c_szRename, DFM_CMD_RENAME, DCMIDM_RENAME }, }; // CDefFolderMenu class class CDefFolderMenu : public IContextMenu3, public IObjectWithSite, public IServiceProvider, public ISearchProvider { friend HRESULT CDefFolderMenu_CreateHKeyMenu(HWND hwnd, HKEY hkey, IContextMenu **ppcm); friend HRESULT CDefFolderMenu_Create2Ex(LPCITEMIDLIST pidlFolder, HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl, IShellFolder *psf, IContextMenuCB *pcmcb, UINT nKeys, const HKEY *ahkeyClsKeys, IContextMenu **ppcm); public: // IUnknown STDMETHOD(QueryInterface)(REFIID riid, void **ppv); STDMETHOD_(ULONG,AddRef)(); STDMETHOD_(ULONG,Release)(); // IContextMenu STDMETHOD(QueryContextMenu)(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags); STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpici); STDMETHOD(GetCommandString)(UINT_PTR idCmd, UINT uType, UINT *pwRes, LPSTR pszName, UINT cchMax); // IContextMenu2 STDMETHOD(HandleMenuMsg)(UINT uMsg, WPARAM wParam, LPARAM lParam); // IContextMenu3 STDMETHOD(HandleMenuMsg2)(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* plResult); // IObjectWithSite STDMETHOD(SetSite)(IUnknown *pUnkSite); STDMETHOD(GetSite)(REFIID riid, void **ppvSite); // IServiceProvider STDMETHOD(QueryService)(REFGUID guidService, REFIID riid, void ** ppvObj); // ISearchProvider STDMETHOD(GetSearchGUID)(GUID *pGuid); private: CDefFolderMenu(UINT nKeys); ~CDefFolderMenu(); ULONG _GetAttributes(ULONG dwAttrMask); UINT _Static_Add(HMENU hmenu, UINT idCmd, UINT idCmdLast, HKEY hkey); void _Static_InvokeCommand(UINT iCmd); HRESULT _InitDropTarget(DWORD *pdwAttr); HRESULT _ProcessEditPaste(BOOL fPasteLink); HRESULT _ProcessRename(); void _DrawItem(DRAWITEMSTRUCT *pdi); LRESULT _MeasureItem(MEASUREITEMSTRUCT *pmi); private: IDropTarget *_pdtgt; // Drop target of selected item IContextMenuCB *_pcmcb; // Callback object IDataObject *_pdtobj; // Data object IShellFolder *_psf; // Shell folder LONG _cRef; // Reference count HWND _hwnd; // Owner window UINT _idCmdFirst; // base id UINT _idStdMax; // standard commands (cut/copy/delete/properties) ID MAX UINT _idFolderMax; // Folder command ID MAX UINT _idVerbMax; // Add-in command (verbs) ID MAX UINT _idDelayInvokeMax;// extensiosn loaded at invoke time UINT _idFld2Max; // 2nd range of Folder command ID MAX HDSA _hdsaStatics; // For static menu items. HDXA _hdxa; // Dynamic menu array HDSA _hdsaCustomInfo; // array of SEARCHINFO's LPITEMIDLIST _pidlFolder; IUnknown* _pSite; BOOL _bUnderKeys; // Data is directly under key, not // shellex\ContextMenuHandlers UINT _nKeys; // Number of class keys HKEY _hkeyClsKeys[DEF_FOLDERMENU_MAXHKEYS]; // Class keys HMENU _hmenu; BITBOOL _bFindHack : 1; BITBOOL _bInitMenuPopup : 1; // true if we received WM_INITMENUPOPUP and _bFindHack = TRUE INT _iStaticInvoked; // index of the invoked static item }; #define GetFldFirst(this) (_idStdMax + _idCmdFirst) HRESULT HDXA_FindByCommand(HDXA hdxa, UINT idCmd, REFIID riid, void **ppv); typedef struct { CLSID clsid; UINT idCmd; UINT idMenu; // used in cleanup GUID guidSearch; //used with search extensions only } STATICITEMINFO, *PSTATICITEMINFO; STDAPI CDefFolderMenu_CreateHKeyMenu(HWND hwnd, HKEY hkey, IContextMenu **ppcm) { HRESULT hres = E_OUTOFMEMORY; CDefFolderMenu *pmenu = new CDefFolderMenu(1); IDLData_InitializeClipboardFormats(); if (pmenu) { pmenu->_hwnd = hwnd; pmenu->_hdxa = HDXA_Create(); ASSERT(pmenu->_pidlFolder == NULL); ASSERT(pmenu->_pSite == NULL); if (pmenu->_hdxa) { if (hkey) { RegOpenKeyEx(hkey, NULL, 0L, MAXIMUM_ALLOWED, &pmenu->_hkeyClsKeys[0]); pmenu->_nKeys = 1; pmenu->_bUnderKeys = TRUE; } *ppcm = (IContextMenu *)pmenu; } hres = NOERROR; } return hres; } // CDefFolderMenu : Members STDMETHODIMP CDefFolderMenu::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENTMULTI(CDefFolderMenu, IContextMenu, IContextMenu3), QITABENTMULTI(CDefFolderMenu, IContextMenu2, IContextMenu3), QITABENT(CDefFolderMenu, IContextMenu3), QITABENT(CDefFolderMenu, IObjectWithSite), QITABENT(CDefFolderMenu, IServiceProvider), QITABENT(CDefFolderMenu,ISearchProvider), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } STDMETHODIMP_(ULONG) CDefFolderMenu::AddRef() { return InterlockedIncrement(&_cRef); } CDefFolderMenu::CDefFolderMenu(UINT nKeys) { _cRef = 1; _iStaticInvoked = -1; TraceMsg(TF_DOCFIND, "CDefFolderMenu ctor"); ASSERT(nKeys <= DEF_FOLDERMENU_MAXHKEYS); } CDefFolderMenu::~CDefFolderMenu() { UINT nKeys; TraceMsg(TF_DOCFIND, "CDefFolderMenu dtor"); if (_hdxa) HDXA_Destroy(_hdxa); ATOMICRELEASE(_psf); ATOMICRELEASE(_pSite); ATOMICRELEASE(_pdtgt); ATOMICRELEASE(_pdtobj); if (_pcmcb) { IUnknown_SetSite(_pcmcb, NULL); _pcmcb->Release(); } for (nKeys=_nKeys-1; (int)nKeys>=0; --nKeys) { RegCloseKey(_hkeyClsKeys[nKeys]); } // if _bInitMenuPopup = true then we changed the dwItemData of the non static items // so we have to free them. otherwise don't touch them if (_hdsaCustomInfo) { // remove the custom data structures hanging off mii.dwItemData of static menu items // or all items if _bFindHack is true int cItems = DSA_GetItemCount(_hdsaCustomInfo); for (int i = 0; i < cItems; i++) { SEARCHINFO* psinfo = (SEARCHINFO*)DSA_GetItemPtr(_hdsaCustomInfo, i); ASSERT(psinfo); SEARCHEXTDATA* psed = psinfo->psed; if (psed) LocalFree(psed); } DSA_Destroy(_hdsaCustomInfo); } if (_hdsaStatics) DSA_Destroy(_hdsaStatics); ILFree(_pidlFolder); } STDMETHODIMP_(ULONG) CDefFolderMenu::Release() { if (InterlockedDecrement(&(_cRef))) return _cRef; if (_pcmcb) _pcmcb->CallBack(_psf, _hwnd, NULL, DFM_RELEASE, _idStdMax, 0); delete this; return 0; } int _SHMergePopupMenus(HMENU hmMain, HMENU hmMerge, int idCmdFirst, int idCmdLast) { int i, idMax = idCmdFirst; for (i = GetMenuItemCount(hmMerge) - 1; i >= 0; --i) { MENUITEMINFO mii; mii.cbSize = SIZEOF(mii); mii.fMask = MIIM_ID|MIIM_SUBMENU; mii.cch = 0; // just in case if (GetMenuItemInfo(hmMerge, i, TRUE, &mii)) { int idTemp = Shell_MergeMenus(_GetMenuFromID(hmMain, mii.wID), mii.hSubMenu, (UINT)0, idCmdFirst, idCmdLast, MM_ADDSEPARATOR | MM_SUBMENUSHAVEIDS); if (idMax < idTemp) idMax = idTemp; } } return idMax; } void CDefFolderMenu_MergeMenu(HINSTANCE hinst, UINT idMainMerge, UINT idPopupMerge, LPQCMINFO pqcm) { UINT idMax = pqcm->idCmdFirst; if (idMainMerge) { HMENU hmMerge = SHLoadPopupMenu(hinst, idMainMerge); if (hmMerge) { idMax = Shell_MergeMenus( pqcm->hmenu, hmMerge, pqcm->indexMenu, pqcm->idCmdFirst, pqcm->idCmdLast, MM_ADDSEPARATOR | MM_SUBMENUSHAVEIDS | MM_DONTREMOVESEPS); DestroyMenu(hmMerge); } } if (idPopupMerge) { HMENU hmMerge = LoadMenu(hinst, MAKEINTRESOURCE(idPopupMerge)); if (hmMerge) { UINT idTemp = _SHMergePopupMenus(pqcm->hmenu, hmMerge, pqcm->idCmdFirst, pqcm->idCmdLast); if (idMax < idTemp) idMax = idTemp; DestroyMenu(hmMerge); } } pqcm->idCmdFirst = idMax; } ULONG CDefFolderMenu::_GetAttributes(ULONG dwAttrMask) { STGMEDIUM medium; LPIDA pida = DataObj_GetHIDA(_pdtobj, &medium); if (pida) { LPITEMIDLIST *ppidl = (LPITEMIDLIST *)LocalAlloc(LPTR, pida->cidl * SIZEOF(LPCITEMIDLIST)); if (ppidl) { int i; BOOL fAllocated; for (i = pida->cidl - 1; i >= 0; --i) { ppidl[i] = (LPITEMIDLIST)IDA_GetRelativeIDListPtr(pida, i, &fAllocated); } _psf->GetAttributesOf(pida->cidl, (const _ITEMIDLIST **)ppidl, &dwAttrMask); if (fAllocated) { for (i = pida->cidl - 1; i >= 0; --i) { ILFree(ppidl[i]); } } LocalFree((HLOCAL)ppidl); } else { dwAttrMask = 0; } HIDA_ReleaseStgMedium(pida, &medium); } return dwAttrMask; } void _DisableRemoveMenuItem(HMENU hmInit, UINT uID, BOOL bAvail, BOOL bRemoveUnavail) { if (bAvail) { EnableMenuItem(hmInit, uID, MF_ENABLED|MF_BYCOMMAND); } else if (bRemoveUnavail) { DeleteMenu(hmInit, uID, MF_BYCOMMAND); } else { EnableMenuItem(hmInit, uID, MF_GRAYED|MF_BYCOMMAND); } } // Enable/disable menuitems in the "File" pulldown. void Def_InitFileCommands(ULONG dwAttr, HMENU hmInit, UINT idCmdFirst, BOOL bContext) { idCmdFirst -= SFVIDM_FIRST; _DisableRemoveMenuItem(hmInit, SFVIDM_FILE_RENAME+idCmdFirst, dwAttr & SFGAO_CANRENAME, bContext); _DisableRemoveMenuItem(hmInit, SFVIDM_FILE_DELETE+idCmdFirst, dwAttr & SFGAO_CANDELETE, bContext); _DisableRemoveMenuItem(hmInit, SFVIDM_FILE_LINK+idCmdFirst, dwAttr & SFGAO_CANLINK, bContext); // Check to see if the folder supports properties on objects, if it is // the context menu then we are allowed to remove the item, otherwise just // gray it. _DisableRemoveMenuItem( hmInit, SFVIDM_FILE_PROPERTIES + idCmdFirst, dwAttr & SFGAO_HASPROPSHEET, bContext); } STDAPI_(BOOL) Def_IsPasteAvailable(IDropTarget *pdtgt, DWORD *pdwEffect) { BOOL fRet = FALSE; *pdwEffect = 0; // assume none // Count the number of clipboard formats available, if there are none then there // is no point making the clipboard available. if (CountClipboardFormats() > 0) { if (pdtgt) { DECLAREWAITCURSOR; SetWaitCursor(); IDataObject * pdtobj; if (SUCCEEDED(OleGetClipboard(&pdtobj))) { POINTL pt = {0, 0}; DWORD dwEffectOffered = DataObj_GetDWORD(pdtobj, g_cfPreferredDropEffect, DROPEFFECT_COPY | DROPEFFECT_LINK); // Check if we can paste. DWORD dwEffect = (dwEffectOffered & (DROPEFFECT_MOVE | DROPEFFECT_COPY)); if (dwEffect) { if (SUCCEEDED(pdtgt->DragEnter(pdtobj, MK_RBUTTON, pt, &dwEffect))) { pdtgt->DragLeave(); } else { dwEffect = 0; } } // Check if we can past-link. DWORD dwEffectLink = (dwEffectOffered & DROPEFFECT_LINK); if (dwEffectLink) { if (SUCCEEDED(pdtgt->DragEnter(pdtobj, MK_RBUTTON, pt, &dwEffectLink))) { pdtgt->DragLeave(); dwEffect |= dwEffectLink; } } fRet = (dwEffect & (DROPEFFECT_MOVE | DROPEFFECT_COPY)); *pdwEffect = dwEffect; pdtobj->Release(); } ResetWaitCursor(); } } return fRet; } void Def_InitEditCommands(ULONG dwAttr, HMENU hmInit, UINT idCmdFirst, IDropTarget *pdtgt, UINT fContext) { DWORD dwEffect = 0; BOOL bEnableUndo; TCHAR szMenuText[80]; idCmdFirst -= SFVIDM_FIRST; // enable undo if there's an undo history bEnableUndo = IsUndoAvailable(); if (bEnableUndo) { GetUndoText(PeekUndoAtom(), szMenuText, ARRAYSIZE(szMenuText), UNDO_MENUTEXT); } else { LoadString(HINST_THISDLL, IDS_UNDOMENU, szMenuText, ARRAYSIZE(szMenuText)); } // BUGBUG - raymondc - RIPs if the menu doesn't have an Undo command if (szMenuText[0]) { ModifyMenu(hmInit, SFVIDM_EDIT_UNDO + idCmdFirst, MF_BYCOMMAND | MF_STRING, SFVIDM_EDIT_UNDO + idCmdFirst, szMenuText); } _DisableRemoveMenuItem(hmInit, SFVIDM_EDIT_UNDO + idCmdFirst, bEnableUndo, fContext); _DisableRemoveMenuItem(hmInit, SFVIDM_EDIT_CUT+idCmdFirst, dwAttr & SFGAO_CANMOVE, fContext); _DisableRemoveMenuItem(hmInit, SFVIDM_EDIT_COPY+idCmdFirst, dwAttr & SFGAO_CANCOPY, fContext); // Enable the "Paste" menuitem if the background drop target can // handle what's in the clipboard // Never remove the "Paste" command _DisableRemoveMenuItem(hmInit, SFVIDM_EDIT_PASTE+idCmdFirst, Def_IsPasteAvailable(pdtgt, &dwEffect), fContext & DIEC_SELECTIONCONTEXT); _DisableRemoveMenuItem(hmInit, SFVIDM_EDIT_PASTELINK+idCmdFirst, dwEffect & DROPEFFECT_LINK, fContext & DIEC_SELECTIONCONTEXT); // _DisableRemoveMenuItem(hmInit, SFVIDM_EDIT_PASTESPECIAL+idCmdFirst, // dwEffect & (DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK), fContext & DIEC_SELECTIONCONTEXT); // enable disable move to/copy to items _DisableRemoveMenuItem(hmInit, SFVIDM_EDIT_MOVETO+idCmdFirst, dwAttr & SFGAO_CANMOVE, fContext); _DisableRemoveMenuItem(hmInit, SFVIDM_EDIT_COPYTO+idCmdFirst, dwAttr & SFGAO_CANCOPY, fContext); } const TCHAR c_szStatic[] = TEXT("Static"); int Static_ExtractIcon(HKEY hkeyMenuItem) { HKEY hkeyDefIcon; int iImage = -1; if (RegOpenKey(hkeyMenuItem, c_szDefaultIcon, &hkeyDefIcon) == ERROR_SUCCESS) { TCHAR szDefIcon[MAX_PATH]; DWORD dwType, cb = SIZEOF(szDefIcon); if (SHQueryValueEx(hkeyDefIcon, NULL, NULL, &dwType, (BYTE*)szDefIcon, &cb) == ERROR_SUCCESS) { iImage = Shell_GetCachedImageIndex(szDefIcon, PathParseIconLocation(szDefIcon), 0); } RegCloseKey(hkeyDefIcon); } return iImage; } #define SEARCH_GUID TEXT("SearchGUID") #define HELP_TEXT TEXT("HelpText") #define LAST_ITEM (int)0x7FFFFFFF UINT CDefFolderMenu::_Static_Add(HMENU hmenu, UINT idCmd, UINT idCmdLast, HKEY hkey) { HDSA hdsaStatics; HDSA hdsaCustomInfo; if (idCmd > idCmdLast) { DebugMsg(DM_ERROR, TEXT("si_a: Out of command ids!")); return idCmd; } ASSERT(!_hdsaStatics); ASSERT(!_hdsaCustomInfo); hdsaCustomInfo = DSA_Create(SIZEOF(SEARCHINFO), 1); // Create a hdsaStatics. hdsaStatics = DSA_Create(SIZEOF(STATICITEMINFO), 1); if (hdsaStatics && hdsaCustomInfo) { HKEY hkeyStatic; // Try to open the "Static" subkey. if (RegOpenKey(hkey, c_szStatic, &hkeyStatic) == ERROR_SUCCESS) { TCHAR szClass[MAX_PATH]; int i; BOOL bFindFilesInserted = FALSE; // For each subkey of static. for (i = 0; RegEnumKey(hkeyStatic, i, szClass, ARRAYSIZE(szClass)) == ERROR_SUCCESS; i++) { HKEY hkeyClass; // Record the GUID. if (RegOpenKey(hkeyStatic, szClass, &hkeyClass) == ERROR_SUCCESS) { TCHAR szCLSID[MAX_PATH]; DWORD dwType, cb = SIZEOF(szCLSID); // HACKHACK: (together with bWebSearchInserted above // we need to have On the Internet as the first menu item // and Find Files or Folders as second BOOL bWebSearch = lstrcmp(szClass, TEXT("WebSearch")) == 0; BOOL bFindFiles = FALSE; if (SHQueryValueEx(hkeyClass, NULL, NULL, &dwType, (BYTE*)szCLSID, &cb) == ERROR_SUCCESS) { HKEY hkeyMenuItem; TCHAR szSubKey[32]; int iMenuItem; // enum the sub keys 0..N for (iMenuItem = 0; wsprintf(szSubKey, TEXT("%d"), iMenuItem), RegOpenKey(hkeyClass, szSubKey, &hkeyMenuItem) == ERROR_SUCCESS; iMenuItem++) { TCHAR szMenuText[MAX_PATH]; TCHAR szSearchGUID[MAX_PATH]; // Get all the command text. cb = SIZEOF(szMenuText); // dwType = REG_SZ; if (SHQueryValueEx(hkeyMenuItem, NULL, NULL, &dwType, (BYTE*)szMenuText, &cb) == ERROR_SUCCESS) { STATICITEMINFO sii; SEARCHINFO sinfo; int iIcon = Static_ExtractIcon(hkeyMenuItem); TCHAR szHelpText[MAX_PATH]; cb = SIZEOF(szHelpText); if (SHGetValue(hkeyMenuItem, HELP_TEXT, NULL, &dwType, (BYTE*)szHelpText, &cb) != ERROR_SUCCESS) szHelpText[0] = TEXT('\0'); SHCLSIDFromString(szCLSID, &sii.clsid); // store it sii.idCmd = iMenuItem; sii.idMenu = idCmd; // get the search guid if any... cb = SIZEOF(szSearchGUID); if (SHGetValue(hkeyMenuItem, SEARCH_GUID, NULL, &dwType, (BYTE*)szSearchGUID, &cb) == ERROR_SUCCESS) SHCLSIDFromString(szSearchGUID, &sii.guidSearch); else sii.guidSearch = GUID_NULL; // HACKHACK: this is a hack. TODO: cleanup -- allow non-static // find extensions to specify a search guid and then we can // remove this static "Find Computer" business... // if this is FindComputer item and the restriction is not set // don't add it to the menu if (IsEqualGUID(sii.guidSearch, SRCID_SFindComputer) && !SHRestricted(REST_HASFINDCOMPUTERS)) continue; bFindFiles = IsEqualGUID(sii.guidSearch, SRCID_SFileSearch); if (bFindFiles && SHRestricted(REST_NOFIND)) continue; if (IsEqualGUID(sii.guidSearch, SRCID_SFindPrinter)) continue; DSA_AppendItem(hdsaStatics, &sii); // Create the menu. //AppendMenu(hmenu, MF_STRING, idCmd, szMenuText); // Add the icon if there is one. { MENUITEMINFO mii; SEARCHEXTDATA *psed = (SEARCHEXTDATA *)LocalAlloc(LPTR, sizeof(SEARCHEXTDATA)); if (psed) { psed->iIcon = iIcon; SHTCharToUnicode(szHelpText, psed->wszHelpText, ARRAYSIZE(psed->wszHelpText)); SHTCharToUnicode(szMenuText, psed->wszMenuText, ARRAYSIZE(psed->wszMenuText)); } mii.cbSize = SIZEOF(MENUITEMINFO); mii.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID; mii.fType = MFT_OWNERDRAW; mii.wID = idCmd; mii.dwItemData = (DWORD_PTR)psed; //iIcon; sinfo.psed = psed; sinfo.idCmd = idCmd; if (DSA_AppendItem(hdsaCustomInfo, &sinfo) != -1) { // insert Files or Folders in the first place (see HACKHACK above) if (!bFindFilesInserted && bFindFiles) bFindFilesInserted = InsertMenuItem(hmenu, 0, TRUE, &mii); else { UINT uiPos = LAST_ITEM; // if this is Find Files or Folders insert it after // On the Internet or in the first place if OtI is // not inserted yet if (bWebSearch) uiPos = bFindFilesInserted ? 1 : 0; // we don't free psed if Insert fails because it's // in dsa and it's going to be freed on destroy InsertMenuItem(hmenu, uiPos, TRUE, &mii); } } } // Next command. idCmd++; if (idCmd > idCmdLast) { DebugMsg(DM_ERROR, TEXT("si_a: Out of command ids!")); break; } } RegCloseKey(hkeyMenuItem); } } RegCloseKey(hkeyClass); } } RegCloseKey(hkeyStatic); } _hdsaStatics = hdsaStatics; _hdsaCustomInfo = hdsaCustomInfo; } return idCmd; } #define CMD_ID_FIRST 1 #define CMD_ID_LAST 0x7fff void CDefFolderMenu::_Static_InvokeCommand(UINT iCmd) { if (_hdsaStatics) { PSTATICITEMINFO psii = (PSTATICITEMINFO)DSA_GetItemPtr(_hdsaStatics, iCmd); if (psii) { IContextMenu *pcm; if (SUCCEEDED(SHExtCoCreateInstance(NULL, &psii->clsid, NULL, IID_PPV_ARG(IContextMenu, &pcm)))) { HMENU hmenu = CreatePopupMenu(); if (hmenu) { CMINVOKECOMMANDINFO ici; CHAR szSearchGUID[GUIDSTR_MAX]; LPSTR psz = NULL; _iStaticInvoked = iCmd; IUnknown_SetSite(pcm, SAFECAST(this, IContextMenu3*)); pcm->QueryContextMenu(hmenu, 0, CMD_ID_FIRST, CMD_ID_LAST, CMF_NORMAL); ici.cbSize = SIZEOF(CMINVOKECOMMANDINFO); ici.fMask = 0; ici.hwnd = NULL; ici.lpVerb = (LPSTR)MAKEINTRESOURCE(psii->idCmd); if (!IsEqualGUID(psii->guidSearch, GUID_NULL)) { SHStringFromGUIDA(psii->guidSearch, szSearchGUID, ARRAYSIZE(szSearchGUID)); psz = szSearchGUID; } ici.lpParameters = psz; ici.lpDirectory = NULL; ici.nShow = SW_NORMAL; pcm->InvokeCommand(&ici); DestroyMenu(hmenu); IUnknown_SetSite(pcm, NULL); } pcm->Release(); } } } } HRESULT CDefFolderMenu::_InitDropTarget(DWORD *pdwAttr) { HRESULT hres = E_FAIL; if (_pdtgt) // already have one? return NOERROR; if (_pdtobj) { STGMEDIUM medium; DWORD dwAttr = _GetAttributes( SFGAO_CANRENAME | SFGAO_CANDELETE | SFGAO_CANLINK | SFGAO_HASPROPSHEET | SFGAO_CANCOPY | SFGAO_CANMOVE | SFGAO_DROPTARGET); LPIDA pida = DataObj_GetHIDA(_pdtobj, &medium); if (pida) { if (dwAttr & SFGAO_DROPTARGET) { BOOL fAllocated; LPCITEMIDLIST pidl = IDA_GetRelativeIDListPtr(pida, 0, &fAllocated); // ok if it fails... initeditcommands will grey out paste option hres = _psf->GetUIObjectOf(_hwnd, 1, &pidl, IID_IDropTarget, 0, (void **)&_pdtgt); if (fAllocated) ILFree ((LPITEMIDLIST)pidl); } HIDA_ReleaseStgMedium(pida, &medium); } ASSERT(pdwAttr); *pdwAttr = dwAttr; } else { hres = _psf->CreateViewObject(_hwnd, IID_PPV_ARG(IDropTarget, &_pdtgt)); } return hres; } STDMETHODIMP CDefFolderMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { QCMINFO qcm = { hmenu, indexMenu, idCmdFirst, idCmdLast }; DECLAREWAITCURSOR; HRESULT hres; BOOL fUseDefExt; SetWaitCursor(); _idCmdFirst = idCmdFirst; _hmenu = hmenu; _bFindHack = BOOLIFY(uFlags & CMF_FINDHACK); _bInitMenuPopup = FALSE; // first add in the folder commands like cut/copy/paste if (_pdtobj && !(uFlags & (CMF_VERBSONLY | CMF_DVFILE))) { ULONG dwAttr; CDefFolderMenu_MergeMenu(HINST_THISDLL, POPUP_DCM_ITEM, 0, &qcm); // If there is previously got drop target, release it. if (_pdtgt) { _pdtgt->Release(); _pdtgt = NULL; } _InitDropTarget(&dwAttr); if (!(uFlags & CMF_CANRENAME)) dwAttr &= ~(SFGAO_CANRENAME); Def_InitFileCommands(dwAttr, hmenu, idCmdFirst, TRUE); Def_InitEditCommands(dwAttr, hmenu, idCmdFirst, _pdtgt, DIEC_SELECTIONCONTEXT); } _idStdMax = qcm.idCmdFirst - idCmdFirst; // DFM_MERGECONTEXTMENU returns (S_FALSE) if we should not // add any verbs. if (_pcmcb) { hres = _pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_MERGECONTEXTMENU, uFlags, (LPARAM)&qcm); fUseDefExt = (hres == NOERROR); if (_pdtobj && (hres == ResultFromShort(-1)) && !(uFlags & CMF_NODEFAULT) && (GetMenuDefaultItem(hmenu, MF_BYCOMMAND, 0) == -1)) { SetMenuDefaultItem(hmenu, (idCmdFirst - SFVIDM_FIRST) + SFVIDM_FILE_PROPERTIES, MF_BYCOMMAND); } } else { fUseDefExt = FALSE; } _idFolderMax = qcm.idCmdFirst - idCmdFirst; // add registry verbs if ((!(uFlags & CMF_NOVERBS)) || (!_pdtobj && !_psf && _nKeys)) // this second case is for find extensions { // Put the separator between container menuitems and object menuitems // only if we don't have the separator at the insertion point. MENUITEMINFO mii; HDCA hdca; //HACK: Default Extenstions EXPECT a selection, Let's hope all don't if(!_pdtobj) fUseDefExt = FALSE; mii.cbSize = SIZEOF(mii); mii.fMask = MIIM_TYPE; mii.cch = 0; // WARNING: We must put 0 here!!!! mii.dwTypeData = NULL; mii.fType = MFT_SEPARATOR; // to avoid ramdom result. if (GetMenuItemInfo(hmenu, indexMenu, TRUE, &mii) && !(mii.fType & MFT_SEPARATOR)) { InsertMenu(hmenu, indexMenu, MF_BYPOSITION | MF_SEPARATOR, (UINT)-1, NULL); } hdca = DCA_Create(); if (hdca) { UINT nKeys; // Add default extensions, only if the folder callback returned // NOERROR. The Printer and Control folder returns S_FALSE // indicating that they don't need any default extension. if (fUseDefExt) { // Always add this default extention at the top. DCA_AddItem(hdca, CLSID_ShellFileDefExt); } // Append menus for all extensions for (nKeys = 0; nKeys < _nKeys; ++nKeys) { DCA_AddItemsFromKey(hdca, _hkeyClsKeys[nKeys], _bUnderKeys ? NULL : STRREG_SHEX_MENUHANDLER); } // BUGBUG: // first time we call this _hdxa is empty // after that it has the same items as before but will not add any new ones // if user keeps right clicking we will eventually run out of menu item ids // read comment in HDXA_AppendMenuItems2. to prevent it we empty _hdxa HDXA_DeleteAll(_hdxa); // BUGBUG (lamadio) For background context menu handlers, the pidlFolder // should be a valid pidl, but, for backwards compatilility, this // parameter should be NULL, if the Dataobject is NOT NULL. // We need to rewrite the background CMs to unpack the dataobject to get the // Folder Pidl. qcm.idCmdFirst = HDXA_AppendMenuItems2(_hdxa, _pdtobj, _nKeys, _hkeyClsKeys, (!_pdtobj)?(_pidlFolder):NULL, &qcm, uFlags, hdca, _pSite); // if no default menu got set, choose the first one. if (_pdtobj && !(uFlags & CMF_NODEFAULT) && GetMenuDefaultItem(hmenu, MF_BYPOSITION, 0) == -1) { UINT idStatic; // we are about to set the default menu id, give the callback a chance // to override and set one of the static entries instead of the // first entry in the menu. if (_pcmcb && SUCCEEDED(_pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_GETDEFSTATICID, 0, (LPARAM)&idStatic))) { for (int i = 0; i < ARRAYSIZE(c_sDFMCmdInfo); i++) { if (idStatic == c_sDFMCmdInfo[i].idDFMCmd) { SetMenuDefaultItem(hmenu, idCmdFirst+c_sDFMCmdInfo[i].idDefCmd, MF_BYCOMMAND); break; } } } if ( GetMenuDefaultItem(hmenu, MF_BYPOSITION, 0) == -1 ) SetMenuDefaultItem(hmenu, 0, MF_BYPOSITION); } DCA_Destroy(hdca); } _idVerbMax = qcm.idCmdFirst - idCmdFirst; // menu extensions that are loaded at invoke time if (uFlags & CMF_INCLUDESTATIC) { qcm.idCmdFirst = _Static_Add(hmenu, qcm.idCmdFirst, idCmdLast, _hkeyClsKeys[0]); } _idDelayInvokeMax = qcm.idCmdFirst - idCmdFirst; // Remove the separator if we did not add any. if ((_idDelayInvokeMax == _idFolderMax)) { if(GetMenuState(hmenu, 0, MF_BYPOSITION) & MF_SEPARATOR) DeleteMenu(hmenu, 0, MF_BYPOSITION); } } // And now we give the callback the option to put (more) commands on top // of everything else if (_pcmcb) _pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_MERGECONTEXTMENU_TOP, uFlags, (LPARAM)&qcm); _idFld2Max = qcm.idCmdFirst - idCmdFirst; _SHPrettyMenu(hmenu); ResetWaitCursor(); return ResultFromShort(_idFld2Max); } HRESULT CDefFolderMenu::_ProcessEditPaste(BOOL fPasteLink) { DWORD dwAttr; DECLAREWAITCURSOR; SetWaitCursor(); HRESULT hr = _InitDropTarget(&dwAttr); if (SUCCEEDED(hr)) { IDataObject *pdtobj; hr = OleGetClipboard(&pdtobj); if (SUCCEEDED(hr)) { DWORD grfKeyState, dwEffect; DWORD dwEffectIn = DataObj_GetDWORD(pdtobj, g_cfPreferredDropEffect, DROPEFFECT_COPY | DROPEFFECT_LINK); if (fPasteLink) { // MK_FAKEDROP to avoid drag/drop pop up menu grfKeyState = MK_LBUTTON | MK_CONTROL | MK_SHIFT | MK_FAKEDROP; dwEffectIn &= DROPEFFECT_LINK; } else { grfKeyState = MK_LBUTTON; dwEffectIn &= ~DROPEFFECT_LINK; } IUnknown_SetSite(_pdtgt, _pSite); // simulate the drag drop protocol dwEffect = dwEffectIn; hr = SHSimulateDrop(_pdtgt, pdtobj, grfKeyState, NULL, &dwEffect); IUnknown_SetSite(_pdtgt, NULL); if (SUCCEEDED(hr)) { // these formats are put into the data object by the drop target code. this // requires the data object support ::SetData() for arbitrary data formats // g_cfPerformedDropEffect effect is the reliable version of dwEffect (some targets // return dwEffect == DROPEFFECT_MOVE always) // g_cfLogicalPerformedDropEffect indicates the logical action so we can tell the // difference between optmized and non optimized move DWORD dwPerformedEffect = DataObj_GetDWORD(pdtobj, g_cfPerformedDropEffect, DROPEFFECT_NONE); DWORD dwLogicalPerformedEffect = DataObj_GetDWORD(pdtobj, g_cfLogicalPerformedDropEffect, DROPEFFECT_NONE); if ((DROPEFFECT_MOVE == dwLogicalPerformedEffect) || (DROPEFFECT_MOVE == dwEffect && DROPEFFECT_MOVE == dwPerformedEffect)) { // communicate back the source data object // so they can complete the "move" if necessary DataObj_SetDWORD(pdtobj, g_cfPasteSucceeded, dwEffect); // if we just did a paste and we moved the files we cant paste // them again (because they moved!) so empty the clipboard OleSetClipboard(NULL); } } pdtobj->Release(); } } ResetWaitCursor(); if (FAILED(hr)) MessageBeep(0); return hr; } // BUGBUG:: This is complete trash, the view added the Rename command (sortof), but we have // no way to actually try to execute it. HRESULT CDefFolderMenu::_ProcessRename() { IContextMenuSite *pcms; HRESULT hr = IUnknown_QueryService(_pSite, SID_SContextMenuSite, IID_PPV_ARG(IContextMenuSite, &pcms)); if (SUCCEEDED(hr)) { hr = pcms->DoRename(); pcms->Release(); } return hr; } // deal with the versioning of this structure... void CopyInvokeInfo(CMINVOKECOMMANDINFOEX *pici, const CMINVOKECOMMANDINFO *piciIn) { ASSERT(piciIn->cbSize >= SIZEOF(CMINVOKECOMMANDINFO)); ZeroMemory(pici, SIZEOF(CMINVOKECOMMANDINFOEX)); memcpy(pici, piciIn, min(SIZEOF(CMINVOKECOMMANDINFOEX), piciIn->cbSize)); pici->cbSize = SIZEOF(CMINVOKECOMMANDINFOEX); } #ifdef UNICODE #define IS_UNICODE_ICI(pici) ((pici->cbSize >= CMICEXSIZE_NT4) && ((pici->fMask & CMIC_MASK_UNICODE) == CMIC_MASK_UNICODE)) #else #define IS_UNICODE_ICI(pici) (FALSE) #endif STDMETHODIMP CDefFolderMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) { HRESULT hres = NOERROR; WPARAM idCmd = (WPARAM)-1; WPARAM idCmdLocal; // this is used within each if block for the local idCmd value LPCMINVOKECOMMANDINFOEX picix = (LPCMINVOKECOMMANDINFOEX)pici; // This value is only usable when fCmdInfoEx is true BOOL fUnicode = IS_UNICODE_ICI(pici); if (pici->cbSize < SIZEOF(CMINVOKECOMMANDINFO)) return E_INVALIDARG; if (!IS_INTRESOURCE(pici->lpVerb)) { LPCTSTR pszVerb; #ifdef UNICODE WCHAR szVerb[MAX_PATH]; if (!fUnicode || picix->lpVerbW == NULL) { SHAnsiToUnicode(picix->lpVerb, szVerb, ARRAYSIZE(szVerb)); pszVerb = szVerb; } else pszVerb = picix->lpVerbW; #else pszVerb = pici->lpVerb; #endif idCmdLocal = idCmd; for (int i = 0; i < ARRAYSIZE(c_sDFMCmdInfo) ; i++) { if (lstrcmpi(pszVerb, c_sDFMCmdInfo[i].pszCmd)==0) { idCmdLocal = c_sDFMCmdInfo[i].idDFMCmd; // We need to use goto because idFolderMax might not be initialized // yet (QueryContextMenu might have not been called). goto ProcessCommand; } } // see if this is a command provided by name by the callback if (*pszVerb && SUCCEEDED(_pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_MAPCOMMANDNAME, (WPARAM)&idCmdLocal, (LPARAM)pszVerb))) { goto ProcessCommand; } // we need to give the verbs a chance in case they asked for it by string goto ProcessVerb; } else { idCmd = LOWORD((UINT_PTR)pici->lpVerb); } if (idCmd < _idStdMax) { idCmdLocal = idCmd; for (int i = 0; i < ARRAYSIZE(c_sDFMCmdInfo); i++) { if (idCmdLocal == c_sDFMCmdInfo[i].idDefCmd) { idCmdLocal = c_sDFMCmdInfo[i].idDFMCmd; goto ProcessCommand; } } hres = E_INVALIDARG; } else if (idCmd < _idFolderMax) { DFMICS dfmics; LPARAM lParam; #ifdef UNICODE WCHAR szLParamBuffer[MAX_PATH]; #endif idCmdLocal = idCmd - _idStdMax; ProcessCommand: #ifdef UNICODE if (!fUnicode || picix->lpParametersW == NULL) { if (pici->lpParameters == NULL) { lParam = (LPARAM)NULL; } else { SHAnsiToUnicode(pici->lpParameters, szLParamBuffer, ARRAYSIZE(szLParamBuffer)); lParam = (LPARAM)szLParamBuffer; } } else lParam = (LPARAM)picix->lpParametersW; #else lParam = (LPARAM)pici->lpParameters; #endif switch (idCmdLocal) { case DFM_CMD_LINK: #ifdef UNICODE if (!fUnicode || picix->lpDirectoryW == NULL) { if (pici->lpDirectory == NULL) { lParam = (LPARAM)NULL; } else { SHAnsiToUnicode(pici->lpDirectory, szLParamBuffer, ARRAYSIZE(szLParamBuffer)); lParam = (LPARAM)szLParamBuffer; } } else lParam = (LPARAM)picix->lpDirectoryW; #else lParam = (LPARAM)pici->lpDirectory; #endif break; case DFM_CMD_PROPERTIES: if (SHRestricted(REST_NOVIEWCONTEXTMENU)) { // This is what the NT4 QFE returned, but I wonder // if HRESULT_FROM_WIN32(E_ACCESSDENIED) would be better? return hres; } break; } // call the callback // try to use a DFM_INVOKECOMMANDEX first so the callback can see // the INVOKECOMMANDINFO struct (for stuff like the 'no ui' flag) dfmics.cbSize = sizeof(dfmics); dfmics.fMask = pici->fMask; dfmics.lParam = lParam; dfmics.idCmdFirst = _idCmdFirst; dfmics.idDefMax = _idStdMax; dfmics.pici = pici; // HACK alert: (dli) This is a hack for the property pages to show up right at // the POINT where they were activated. if ((idCmdLocal == DFM_CMD_PROPERTIES) && (pici->fMask & CMIC_MASK_PTINVOKE) && _pdtobj) { HRESULT hres; ASSERT(pici->cbSize >= SIZEOF(CMINVOKECOMMANDINFOEX)); POINT *ppt = (POINT *)GlobalAlloc(GPTR, SIZEOF(POINT)); if (ppt) { *ppt = picix->ptInvoke; hres = DataObj_SetGlobal(_pdtobj, g_cfOFFSETS, ppt); if (FAILED(hres)) GlobalFree(ppt); } } hres = _pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_INVOKECOMMANDEX, idCmdLocal, (LPARAM)&dfmics); if (hres == E_NOTIMPL) { // the callback didn't understand the DFM_INVOKECOMMANDEX // fall back to a regular DFM_INVOKECOMMAND instead hres = _pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_INVOKECOMMAND, idCmdLocal, lParam); } // Check if we need to execute the default code. if (hres == S_FALSE) { hres = NOERROR; // assume no error if (_pdtobj) { switch (idCmdLocal) { case DFM_CMD_MOVE: case DFM_CMD_COPY: DataObj_SetDWORD(_pdtobj, g_cfPreferredDropEffect, (idCmdLocal == DFM_CMD_MOVE) ? DROPEFFECT_MOVE : (DROPEFFECT_COPY | DROPEFFECT_LINK)); ShellFolderView_SetPoints(_hwnd, _pdtobj); OleSetClipboard(_pdtobj); // this needs to be done after the set clipboard so // that the hwndView chain will be set right ShellFolderView_SetClipboard(_hwnd, idCmdLocal); break; case DFM_CMD_LINK: SHCreateLinks(pici->hwnd, NULL, _pdtobj, lParam ? SHCL_USETEMPLATE | SHCL_USEDESKTOP : SHCL_USETEMPLATE, NULL); break; case DFM_CMD_PASTE: case DFM_CMD_PASTELINK: hres = _ProcessEditPaste(idCmdLocal == DFM_CMD_PASTELINK); break; case DFM_CMD_RENAME: hres = _ProcessRename(); break; default: DebugMsg(TF_WARNING, TEXT("DefCM command not processed in %s at %d (%x)"), __FILE__, __LINE__, idCmdLocal); break; } } else { // This is a background menu. Process common command ids. switch(idCmdLocal) { case DFM_CMD_PASTE: case DFM_CMD_PASTELINK: hres = _ProcessEditPaste(idCmdLocal == DFM_CMD_PASTELINK); break; default: // Only our commands should come here break; } } } } else if (idCmd < _idVerbMax) { idCmdLocal = idCmd - _idFolderMax; ProcessVerb: { CMINVOKECOMMANDINFOEX ici; UINT_PTR idCmdSave; CopyInvokeInfo(&ici, pici); if (IS_INTRESOURCE(pici->lpVerb)) ici.lpVerb = (LPSTR)MAKEINTRESOURCE(idCmdLocal); // One of extension menu is selected. idCmdSave = (UINT_PTR)ici.lpVerb; UINT_PTR idCmd = 0; hres = HDXA_LetHandlerProcessCommandEx(_hdxa, &ici, &idCmd); if (SUCCEEDED(hres) && (idCmd == idCmdSave)) { // hdxa failed to handle it hres = E_INVALIDARG; } } } else if (idCmd < _idDelayInvokeMax) { _Static_InvokeCommand((UINT)(idCmd-_idVerbMax)); } else if (idCmd < _idFld2Max) { idCmdLocal = idCmd - _idDelayInvokeMax; goto ProcessCommand; } else { hres = E_INVALIDARG; } return hres; } STDMETHODIMP CDefFolderMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pwReserved, LPSTR pszName, UINT cchMax) { HRESULT hres = E_INVALIDARG; UINT_PTR idCmdLocal; int i; if (!IS_INTRESOURCE(idCmd)) { // This must be a string LPTSTR pCmd = (LPTSTR)idCmd; // BUGBUG raymondc GCS_UNICODE if (HDXA_GetCommandString(_hdxa, idCmd, uType, pwReserved, pszName, cchMax) == NOERROR) { return NOERROR; } // Convert the string into an ID for (i = 0; i < ARRAYSIZE(c_sDFMCmdInfo); i++) { if (!lstrcmpi(pCmd, c_sDFMCmdInfo[i].pszCmd)) { idCmdLocal = (UINT) c_sDFMCmdInfo[i].idDFMCmd; goto ProcessCommand; } } return E_INVALIDARG; } if (idCmd < _idStdMax) { idCmdLocal = idCmd; switch (uType) { case GCS_HELPTEXTA: // HACK: DCM commands are in the same order as SFV commands return(LoadStringA(HINST_THISDLL, (UINT) idCmdLocal + (UINT)(SFVIDM_FIRST + SFVIDS_MH_FIRST), (LPSTR)pszName, cchMax) ? NOERROR : E_OUTOFMEMORY); break; case GCS_HELPTEXTW: // HACK: DCM commands are in the same order as SFV commands return(LoadStringW(HINST_THISDLL, (UINT) idCmdLocal + (UINT)(SFVIDM_FIRST + SFVIDS_MH_FIRST), (LPWSTR)pszName, cchMax) ? NOERROR : E_OUTOFMEMORY); break; case GCS_VERBA: case GCS_VERBW: for (i = 0; i < ARRAYSIZE(c_sDFMCmdInfo); i++) { if (idCmdLocal == c_sDFMCmdInfo[i].idDefCmd) { if (uType == GCS_VERBW) SHTCharToUnicode(c_sDFMCmdInfo[i].pszCmd, (LPWSTR)pszName, cchMax); else SHTCharToAnsi(c_sDFMCmdInfo[i].pszCmd, (LPSTR)pszName, cchMax); return NOERROR; } } return E_INVALIDARG; case GCS_VALIDATEA: case GCS_VALIDATEW: // BUGBUG: We should do something here, but I am too lazy default: return E_NOTIMPL; } } else if (idCmd < _idFolderMax) { idCmdLocal = idCmd - _idStdMax; ProcessCommand: if (!_pcmcb) return E_NOTIMPL; // REVIEW: If no callback, how can idFolderMax be > 0? // This is a folder menu switch (uType) { case GCS_HELPTEXTA: return _pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_GETHELPTEXT, (WPARAM)MAKELONG(idCmdLocal, cchMax), (LPARAM)pszName); case GCS_HELPTEXTW: return _pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_GETHELPTEXTW, (WPARAM)MAKELONG(idCmdLocal, cchMax), (LPARAM)pszName); case GCS_VALIDATEA: case GCS_VALIDATEW: return _pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_VALIDATECMD, idCmdLocal, 0); case GCS_VERBA: return _pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_GETVERBA, (WPARAM)MAKELONG(idCmdLocal, cchMax), (LPARAM)pszName); case GCS_VERBW: return _pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_GETVERBW, (WPARAM)MAKELONG(idCmdLocal, cchMax), (LPARAM)pszName); default: return E_NOTIMPL; } } else if (idCmd < _idVerbMax) { idCmdLocal = idCmd - _idFolderMax; // One of extension menu is selected. hres = HDXA_GetCommandString(_hdxa, idCmdLocal, uType, pwReserved, pszName, cchMax); } else if (idCmd < _idDelayInvokeMax) { // menu extensions that are loaded at invoke time don't support this } else if (idCmd < _idFld2Max) { idCmdLocal = idCmd - _idDelayInvokeMax; goto ProcessCommand; } return hres; } STDMETHODIMP CDefFolderMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT* plResult) { IContextMenu3 *pcmItem; UINT uMsgFld; WPARAM wParamFld; // map the folder call back params to these LPARAM lParamFld; UINT idCmd; UINT id; //temp var switch (uMsg) { case WM_MEASUREITEM: idCmd = GET_WM_COMMAND_ID(((MEASUREITEMSTRUCT *)lParam)->itemID, 0); // cannot use InRange because _idVerbMax can be equal to _idDelayInvokeMax id = idCmd-_idCmdFirst; if ((_bInitMenuPopup || (_hdsaStatics && _idVerbMax <= id)) && id < _idDelayInvokeMax) { _MeasureItem((MEASUREITEMSTRUCT *)lParam); return S_OK; } uMsgFld = DFM_WM_MEASUREITEM; wParamFld = GetFldFirst(this); lParamFld = lParam; break; case WM_DRAWITEM: idCmd = GET_WM_COMMAND_ID(((LPDRAWITEMSTRUCT)lParam)->itemID, 0); // cannot use InRange because _idVerbMax can be equal to _idDelayInvokeMax id = idCmd-_idCmdFirst; if ((_bInitMenuPopup || (_hdsaStatics && _idVerbMax <= id)) && id < _idDelayInvokeMax) { _DrawItem((LPDRAWITEMSTRUCT)lParam); return S_OK; } uMsgFld = DFM_WM_DRAWITEM; wParamFld = GetFldFirst(this); lParamFld = lParam; break; case WM_INITMENUPOPUP: idCmd = GetMenuItemID((HMENU)wParam, 0); if (_bFindHack) { int i; HMENU hmenu = (HMENU)wParam; int cItems = GetMenuItemCount(hmenu); _bInitMenuPopup = TRUE; if (!_hdsaCustomInfo) _hdsaCustomInfo = DSA_Create(SIZEOF(SEARCHINFO), 1); if (_hdsaCustomInfo && cItems > 0) { // need to go bottom up because we may delete some items for (i=cItems-1; i >= 0; i--) { MENUITEMINFO mii; TCHAR szMenuText[MAX_PATH]; mii.cbSize = SIZEOF(mii); mii.fMask = MIIM_TYPE | MIIM_DATA | MIIM_ID; mii.dwTypeData = szMenuText; mii.cch = ARRAYSIZE(szMenuText); if (GetMenuItemInfo(hmenu, i, TRUE, &mii)) { SEARCHINFO sinfo; // static items already have correct dwItemData (pointer to SEARCHEXTDATA added in _Static_Add) // we now have to change other find extension's dwItemData from having an index into the icon // cache to pointer to SEARCHEXTDATA // cannot use InRange because _idVerbMax can be equal to _idDelayInvokeMax id = mii.wID-_idCmdFirst; if (!(_hdsaStatics && _idVerbMax <= id && id < _idDelayInvokeMax)) { UINT iIcon = (UINT) mii.dwItemData; SEARCHEXTDATA *psed = (SEARCHEXTDATA *)LocalAlloc(LPTR, sizeof(SEARCHEXTDATA)); if (psed) { psed->iIcon = iIcon; SHTCharToUnicode(szMenuText, psed->wszMenuText, ARRAYSIZE(psed->wszMenuText)); } mii.fMask = MIIM_DATA | MIIM_TYPE; mii.fType = MFT_OWNERDRAW; mii.dwItemData = (DWORD_PTR)psed; sinfo.psed = psed; sinfo.idCmd = mii.wID; if (DSA_AppendItem(_hdsaCustomInfo, &sinfo) == -1) { DeleteMenu(hmenu, i, MF_BYPOSITION); if (psed) LocalFree(psed); } else SetMenuItemInfo(hmenu, i, TRUE, &mii); } } } } else if (!_hdsaCustomInfo) { // we could not allocate space for _hdsaCustomInfo // delete all items because there will be no pointer hanging off dwItemData // so start | search will fault for (i=0; i < cItems; i++) DeleteMenu(hmenu, i, MF_BYPOSITION); } } uMsgFld = DFM_WM_INITMENUPOPUP; wParamFld = wParam; lParamFld = GetFldFirst(this); break; case WM_MENUSELECT: idCmd = (UINT) LOWORD(wParam); // cannot use InRange because _idVerbMax can be equal to _idDelayInvokeMax id = idCmd-_idCmdFirst; if (_pSite && (_bInitMenuPopup || (_hdsaStatics && _idVerbMax <= id)) && id < _idDelayInvokeMax) { IShellBrowser *psb; if (SUCCEEDED(IUnknown_QueryService(_pSite, SID_STopLevelBrowser, IID_PPV_ARG(IShellBrowser, &psb)))) { MENUITEMINFO mii; mii.cbSize = SIZEOF(mii); mii.fMask = MIIM_DATA; mii.cch = 0; //just in case if (GetMenuItemInfo(_hmenu, idCmd, FALSE, &mii)) { SEARCHEXTDATA *psed = (SEARCHEXTDATA *)mii.dwItemData; psb->SetStatusTextSB(psed->wszHelpText); } psb->Release(); } } return S_OK; case WM_MENUCHAR: if (_bFindHack && _hdsaCustomInfo) { int cItems = DSA_GetItemCount(_hdsaCustomInfo); for (int i=0; i < cItems; i++) { SEARCHINFO* psinfo = (SEARCHINFO*)DSA_GetItemPtr(_hdsaCustomInfo, i); ASSERT(psinfo); SEARCHEXTDATA* psed = psinfo->psed; if (psed) { TCHAR szMenu[MAX_PATH]; SHUnicodeToTChar(psed->wszMenuText, szMenu, ARRAYSIZE(szMenu)); if (_MenuCharMatch(szMenu,(TCHAR)LOWORD(wParam), FALSE)) { if (plResult) *plResult = MAKELONG(GetMenuPosFromID((HMENU)lParam, psinfo->idCmd), MNC_EXECUTE); return S_OK; } } } if (plResult) *plResult = MAKELONG(0, MNC_IGNORE); return S_FALSE; } else idCmd = GetMenuItemID((HMENU)lParam, 0); break; case WM_NEXTMENU: idCmd = GetMenuItemID((HMENU)lParam, 0); break; default: return E_FAIL; } // bias this down to the extension range (comes right after the folder range) idCmd -= _idCmdFirst + _idFolderMax; // Only forward along on IContextMenu3 as some shell extensions say they support // IContextMenu2, but fail and bring down the shell... if (SUCCEEDED(HDXA_FindByCommand(_hdxa, idCmd, IID_PPV_ARG(IContextMenu3, &pcmItem)))) { HRESULT hres; hres = pcmItem->HandleMenuMsg2(uMsg, wParam, lParam, plResult); pcmItem->Release(); return hres; } // redirect to the folder callback if (_pcmcb) return _pcmcb->CallBack(_psf, _hwnd, _pdtobj, uMsgFld, wParamFld, lParamFld); return E_FAIL; } STDMETHODIMP CDefFolderMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam) { return HandleMenuMsg2(uMsg,wParam,lParam,NULL); } STDMETHODIMP CDefFolderMenu::GetSite(REFIID riid, void** ppvSite) { if (_pSite) return _pSite->QueryInterface(riid, ppvSite); else { *ppvSite = NULL; return E_FAIL; // OLE spec says to return E_FAIL if there is no site } } STDMETHODIMP CDefFolderMenu::SetSite(IUnknown* pUnk) { IUnknown_Set(&_pSite, pUnk); IUnknown_SetSite(_pcmcb, pUnk); for (int icmi = 0; icmi < DSA_GetItemCount(_hdxa); icmi++) { ContextMenuInfo *pcmi = (ContextMenuInfo *)DSA_GetItemPtr(_hdxa, icmi); // APPCOMPAT: PGP50 can only be QIed for IContextMenu, IShellExtInit, and IUnknown. if (!(pcmi->dwCompat & OBJCOMPATF_CTXMENU_LIMITEDQI)) IUnknown_SetSite((IUnknown*)pcmi->pcm, pUnk); } return NOERROR; } STDMETHODIMP CDefFolderMenu::QueryService(REFGUID guidService, REFIID riid, void ** ppvObj) { return IUnknown_QueryService(_pSite, guidService, riid, ppvObj); } STDMETHODIMP CDefFolderMenu::GetSearchGUID(GUID *pGuid) { HRESULT hres = E_FAIL; if (_iStaticInvoked != -1) { PSTATICITEMINFO psii = (PSTATICITEMINFO)DSA_GetItemPtr(_hdsaStatics, _iStaticInvoked); if (psii) { *pGuid = psii->guidSearch; hres = S_OK; } } return hres; } // HDXA stuff // This function enumerate all the context menu handlers and let them // append menuitems. Each context menu handler will create an object // which support IContextMenu interface. We call QueryContextMenu() // member function of all those IContextMenu object to let them append // menuitems. For each IContextMenu object, we create ContextMenuInfo // struct and append it to hdxa (which is a dynamic array of ContextMenuInfo). // The caller will release all those IContextMenu objects, by calling // its Release() member function. // Arguments: // hdxa -- Handler of the dynamic ContextMenuInfo struct array // pdata -- Specifies the selected items (files) // hkeyShellEx -- Specifies the reg.dat class we should enumurate handlers // hkeyProgID -- Specifies the program identifier of the selected file/directory // pszHandlerKey -- Specifies the reg.dat key to the handler list // pidlFolder -- Specifies the folder (drop target) // hmenu -- Specifies the menu to be modified // uInsert -- Specifies the position to be insert menuitems // idCmdFirst -- Specifies the first menuitem ID to be used // idCmdLast -- Specifies the last menuitem ID to be used // Returns: // The first menuitem ID which is not used. // History: // 02-25-93 SatoNa Created // 06-30-97 lAmadio Modified to add ID mapping support. UINT HDXA_AppendMenuItems(HDXA hdxa, IDataObject *pdtobj, UINT nKeys, HKEY *ahkeyClsKeys, LPCITEMIDLIST pidlFolder, HMENU hmenu, UINT uInsert, UINT idCmdFirst, UINT idCmdLast, UINT fFlags, HDCA hdca) { QCMINFO qcm = {hmenu,uInsert,idCmdFirst,idCmdLast,NULL}; return HDXA_AppendMenuItems2(hdxa, pdtobj,nKeys,ahkeyClsKeys,pidlFolder,&qcm,fFlags,hdca,NULL); } UINT HDXA_AppendMenuItems2(HDXA hdxa, IDataObject *pdtobj, UINT nKeys, HKEY *ahkeyClsKeys, LPCITEMIDLIST pidlFolder, QCMINFO* pqcm, UINT fFlags, HDCA hdca, IUnknown* pSite) { int idca; const UINT idCmdBase = pqcm->idCmdFirst; UINT idCmdFirst = pqcm->idCmdFirst; ASSERT(pqcm != NULL); // Apparently, somebody has already called into here with this object. We // need to keep the ID ranges separate, so we'll put the new ones at the // end. // BUGBUG: If QueryContextMenu is called too many times, we will run out of // ID range and not add anything. We could try storing the information // used to create each pcm (HKEY, GUID, and fFlags) and reuse some of them, // but then we would have to worry about what if the number of commands // grows and other details; this is just not worth the effort since // probably nobody will ever have a problem. The rule of thumb is to // create an IContextMenu, do the QueryContextMenu and InvokeCommand, and // then Release it. idca = DSA_GetItemCount(hdxa); if (idca > 0) { ContextMenuInfo *pcmi = (ContextMenuInfo *)DSA_GetItemPtr(hdxa, idca-1); idCmdFirst += pcmi->idCmdMax; } // Note that we need to reverse the order because each extension // will intert menuitems "above" uInsert. for (idca = DCA_GetItemCount(hdca) - 1; idca >= 0; idca--) { int nCurKey; IShellExtInit *psei = NULL; IContextMenu *pcm = NULL; IObjectWithSite* pows = NULL; TCHAR szCLSID[GUIDSTR_MAX]; TCHAR szRegKey[GUIDSTR_MAX + 40]; DWORD dwType; DWORD dwSize; DWORD dwExtType; const CLSID* pclsid = DCA_GetItem(hdca, idca); SHStringFromGUID(*pclsid, szCLSID, ARRAYSIZE(szCLSID)); // Let's avoid creating an instance (loading the DLL) when: // 1. fFlags has CMF_DEFAULTONLY and // 2. CLSID\clsid\MayChangeDefault does not exist if (fFlags & CMF_DEFAULTONLY) { if (pclsid && (*pclsid) != CLSID_ShellFileDefExt) { wsprintf(szRegKey, TEXT("CLSID\\%s\\shellex\\MayChangeDefaultMenu"), szCLSID); if (SHRegQueryValue(HKEY_CLASSES_ROOT, szRegKey, NULL, NULL) != ERROR_SUCCESS) { DebugMsg(TF_MENU, TEXT("HDXA_AppendMenuItems skipping %s"), szCLSID); continue; } } } for (nCurKey = 0; nCurKey < (int)nKeys; nCurKey++) { UINT citems; DWORD dwCompat = SHGetObjectCompatFlags(NULL, pclsid); if (!psei && FAILED(DCA_CreateInstance(hdca, idca, IID_PPV_ARG(IShellExtInit, &psei)))) break; // Try all the class keys in order HRESULT hres = psei->Initialize(pidlFolder, pdtobj, ahkeyClsKeys[nCurKey]); if (FAILED(hres)) { continue; } if (!(dwCompat & OBJCOMPATF_CTXMENU_LIMITEDQI)) IUnknown_SetSite((IUnknown *)psei, pSite); // Only get the pcm after initializing if (!pcm && FAILED(psei->QueryInterface(IID_PPV_ARG(IContextMenu, &pcm)))) { continue; // break? } wsprintf(szRegKey, TEXT("CLSID\\%s"), szCLSID); dwSize = SIZEOF(DWORD); if (SHGetValue(HKEY_CLASSES_ROOT, szRegKey, TEXT("flags"),&dwType, (BYTE*)&dwExtType, &dwSize) == ERROR_SUCCESS && dwType == REG_DWORD && pqcm->pIdMap != NULL && dwExtType < pqcm->pIdMap->nMaxIds) { //Explanation: //Here we are trying to add a context menu extension to an already //existing menu, owned by the sister object of DefView. We used the callback //to get a list of extension "types" and their place withing the menu, relative //to IDs that the sister object inserted already. That object also told us //where to put extensions, before or after the ID. Since they are IDs and not //positions, we have to convert using GetMenuPosFromID. hres = pcm->QueryContextMenu( pqcm->hmenu, GetMenuPosFromID(pqcm->hmenu,pqcm->pIdMap->pIdList[dwExtType].id) + ((pqcm->pIdMap->pIdList[dwExtType].fFlags & QCMINFO_PLACE_AFTER)? 1: 0), idCmdFirst, pqcm->idCmdLast, fFlags); } else hres = pcm->QueryContextMenu(pqcm->hmenu, pqcm->indexMenu, idCmdFirst, pqcm->idCmdLast, fFlags); if (0 == pqcm->indexMenu && (0 == GetMenuDefaultItem(pqcm->hmenu, TRUE, 0))) pqcm->indexMenu++; citems = HRESULT_CODE(hres); if (SUCCEEDED(hres) && citems) { ContextMenuInfo cmi; cmi.pcm = pcm; cmi.dwCompat = dwCompat; cmi.idCmdFirst = idCmdFirst - idCmdBase; cmi.idCmdMax = cmi.idCmdFirst + citems; if (DSA_AppendItem(hdxa, &cmi) == -1) { // There is no "clean" way to remove menu items, so // we should check the add to the DSA before adding the // menu items DebugMsg(DM_ERROR, TEXT("filemenu.c ERROR: DSA_GetItemPtr failed (memory overflow)")); } else { pcm->AddRef(); } idCmdFirst += citems; FullDebugMsg(TF_MENU, TEXT("HDXA_Append: %d, %d"), idCmdFirst, citems); // keep going if it is our internal handler if ((*(CLSID*)DCA_GetItem(hdca, idca)) != CLSID_ShellFileDefExt) break; // not out handler stop pcm->Release(); pcm = NULL; psei->Release(); psei = NULL; continue; // next hkey } } if (pcm) pcm->Release(); if (psei) psei->Release(); } return idCmdFirst; } // Function: HDXA_LetHandlerProcessCommandEx, public (not exported) // This function is called after the user select one of add-in menu items. // This function calls IncokeCommand method of corresponding context menu // object. // hdxa -- Handler of the dynamic ContextMenuInfo struct array // idCmd -- Specifies the menu item ID // hwndParent -- Specifies the parent window. // pszWorkingDir -- Specifies the working directory. // Returns: // IDCMD_PROCESSED, if InvokeCommand method is called; idCmd, otherwise // History: // 03-03-93 SatoNa Created HRESULT HDXA_LetHandlerProcessCommandEx(HDXA hdxa, LPCMINVOKECOMMANDINFOEX pici, UINT_PTR * pidCmd) { HRESULT hr = S_OK; UINT_PTR idCmd; if (!pidCmd) pidCmd = &idCmd; *pidCmd = (UINT_PTR)pici->lpVerb; // One of add-in menuitems is selected. Let the context // menu handler process it. for (int icmi = 0; icmi < DSA_GetItemCount(hdxa); icmi++) { ContextMenuInfo *pcmi = (ContextMenuInfo *)DSA_GetItemPtr(hdxa, icmi); // Check if it is for this context menu handler. // Notes: We can't use InRange macro because idCmdFirst might // be equal to idCmdLast. // if (InRange(*pidCmd, pcmi->idCmdFirst, pcmi->idCmdMax-1)) if (!IS_INTRESOURCE(pici->lpVerb)) { // some ctx menu extension always succeed regardless // if it is theirs or not. better to never pass them a string if (!(pcmi->dwCompat & OBJCOMPATF_CTXMENU_NOVERBS)) { hr = pcmi->pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)pici); if (SUCCEEDED(hr)) { *pidCmd = IDCMD_PROCESSED; break; } } else hr = E_FAIL; } else if ((*pidCmd >= pcmi->idCmdFirst) && (*pidCmd < pcmi->idCmdMax)) { // Yes, it is. Let it handle this menuitem. CMINVOKECOMMANDINFOEX ici; CopyInvokeInfo(&ici, (CMINVOKECOMMANDINFO *)pici); ici.lpVerb = (LPSTR)MAKEINTRESOURCE(*pidCmd - pcmi->idCmdFirst); #ifdef DEBUG #ifdef SZKEYDEBUG // The keydebug data is not set anywhere causes beeps... DebugMsg(TF_MENU, TEXT("HDXA_LetHandleProcessCommand *pidCmd=%d %s:(%d,%d)"), *pidCmd, pcmi->szKeyDebug, pcmi->idCmdFirst, pcmi->idCmdMax); #else DebugMsg(TF_MENU, TEXT("HDXA_LetHandleProcessCommand *pidCmd=%d:(%d,%d)"), *pidCmd, pcmi->idCmdFirst, pcmi->idCmdMax); #endif #endif if (SUCCEEDED(pcmi->pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici))) { *pidCmd = IDCMD_PROCESSED; } break; } } // It's OK if (idCmd != IDCMD_PROCESSED) because some callers will try to use several // IContextMenu implementations in order to get the IContextMenu for the selected items, // the IContextMenu for the background, etc. CBackgrndMenu::InvokeCommand() does this. // -BryanSt (04/29/1999) return hr; } HRESULT HDXA_GetCommandString(HDXA hdxa, UINT_PTR idCmd, UINT uType, UINT *pwReserved, LPSTR pszName, UINT cchMax) { HRESULT hres = E_INVALIDARG; LPTSTR pCmd = (LPTSTR)idCmd; if (!hdxa) return E_INVALIDARG; // One of add-in menuitems is selected. Let the context // menu handler process it. for (int icmi = 0; icmi < DSA_GetItemCount(hdxa); icmi++) { ContextMenuInfo *pcmi = (ContextMenuInfo *)DSA_GetItemPtr(hdxa, icmi); if (!IS_INTRESOURCE(idCmd)) { // This must be a string command; see if this handler wants it if (pcmi->pcm->GetCommandString(idCmd, uType, pwReserved, pszName, cchMax) == NOERROR) { return NOERROR; } } // Check if it is for this context menu handler. // Notes: We can't use InRange macro because idCmdFirst might // be equal to idCmdLast. // if (InRange(idCmd, pcmi->idCmdFirst, pcmi->idCmdMax-1)) else if (idCmd >= pcmi->idCmdFirst && idCmd < pcmi->idCmdMax) { // Yes, it is. Let it handle this menuitem. hres = pcmi->pcm->GetCommandString(idCmd-pcmi->idCmdFirst, uType, pwReserved, pszName, cchMax); break; } } return hres; } HRESULT HDXA_FindByCommand(HDXA hdxa, UINT idCmd, REFIID riid, void **ppv) { HRESULT hr = E_FAIL; *ppv = NULL; // bug nt power toy does not properly null out in error cases... if (hdxa) { for (int icmi = 0; icmi < DSA_GetItemCount(hdxa); icmi++) { ContextMenuInfo *pcmi = (ContextMenuInfo *)DSA_GetItemPtr(hdxa, icmi); if (idCmd >= pcmi->idCmdFirst && idCmd < pcmi->idCmdMax) { // APPCOMPAT: PGP50 can only be QIed for IContextMenu, IShellExtInit, and IUnknown. if (!(pcmi->dwCompat & OBJCOMPATF_CTXMENU_LIMITEDQI)) hr = pcmi->pcm->QueryInterface(riid, ppv); else hr = E_FAIL; break; } } } return hr; } // This function releases all the IContextMenu objects in the dynamic // array of ContextMenuInfo, void HDXA_DeleteAll(HDXA hdxa) { if (hdxa) { // Release all the IContextMenu objects, then destroy the DSA. for (int icmi = 0; icmi < DSA_GetItemCount(hdxa); icmi++) { ContextMenuInfo *pcmi = (ContextMenuInfo *)DSA_GetItemPtr(hdxa, icmi); IContextMenu *pcm = pcmi->pcm; if (pcm) { pcm->Release(); } } DSA_DeleteAllItems(hdxa); } } // This function releases all the IContextMenu objects in the dynamic // array of ContextMenuInfo, then destroys the dynamic array. void HDXA_Destroy(HDXA hdxa) { if (hdxa) { HDXA_DeleteAll(hdxa); DSA_Destroy(hdxa); } } // class CContextMenuCBImpl class CContextMenuCBImpl : public IContextMenuCB { public: CContextMenuCBImpl(LPFNDFMCALLBACK lpfn) : m_pfn(lpfn), m_cRef(1) {} // IUnknown STDMETHOD(QueryInterface)(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CContextMenuCBImpl, IContextMenuCB), // IID_IContextMenuCB { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHOD_(ULONG,AddRef)() { return InterlockedIncrement(&m_cRef); } STDMETHOD_(ULONG,Release)() { if (InterlockedDecrement(&m_cRef)) return m_cRef; delete this; return 0; } // IContextMenuCB STDMETHOD(CallBack)(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam) { return m_pfn ? m_pfn(psf, hwnd, pdtobj, uMsg, wParam, lParam) : E_FAIL; } private: LPFNDFMCALLBACK m_pfn; LONG m_cRef; }; // CDefFolderMenu_Create methods STDAPI CDefFolderMenu_Create2Ex(LPCITEMIDLIST pidlFolder, HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl, IShellFolder *psf, IContextMenuCB *pcmcb, UINT nKeys, const HKEY *ahkeyClsKeys, IContextMenu **ppcm) { HRESULT hres = E_OUTOFMEMORY; IDLData_InitializeClipboardFormats(); CDefFolderMenu *pmenu = new CDefFolderMenu(nKeys); if (pmenu) { pmenu->_hwnd = hwnd; pmenu->_pcmcb = pcmcb; pcmcb->AddRef(); pmenu->_psf = psf; ASSERT(pmenu->_pSite == NULL); pmenu->_pidlFolder = ILClone(pidlFolder); if (psf) psf->AddRef(); if (cidl) { hres = psf->GetUIObjectOf(hwnd, cidl, apidl, IID_IDataObject, NULL, (void **)&pmenu->_pdtobj); } else { hres = NOERROR; } pmenu->_hdxa = HDXA_Create(); if (pmenu->_hdxa) { if (SUCCEEDED(hres)) { UINT i; for (i = 0; i < nKeys; ++i) { if (ahkeyClsKeys[i]) { // Make a copy of the key for menu's use if (RegOpenKeyEx(ahkeyClsKeys[i], NULL, 0L, MAXIMUM_ALLOWED, &pmenu->_hkeyClsKeys[pmenu->_nKeys]) == ERROR_SUCCESS) { pmenu->_nKeys++; } } } *ppcm = SAFECAST(pmenu, IContextMenu *); } } if (SUCCEEDED(hres)) { hres = pcmcb->CallBack(psf, hwnd, NULL, DFM_ADDREF, 0, 0); if (hres == E_NOTIMPL) { // I guess there was no initialization to do hres = NOERROR; } } if (FAILED(hres)) { delete pmenu; } } return hres; } STDAPI CDefFolderMenu_CreateEx(LPCITEMIDLIST pidlFolder, HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl, IShellFolder *psf, IContextMenuCB *pcmcb, HKEY hkeyProgID, HKEY hkeyBaseProgID, IContextMenu **ppcm) { HKEY ahkeyClsKeys[2]; ahkeyClsKeys[0] = hkeyProgID; ahkeyClsKeys[1] = hkeyBaseProgID; // Note that Create2 will remove NULL and duplicate HKEY's return CDefFolderMenu_Create2Ex(pidlFolder, hwnd, cidl, apidl, psf, pcmcb, ARRAYSIZE(ahkeyClsKeys), ahkeyClsKeys, ppcm); } // old style CDefFolderMenu_Create and CDefFolderMenu_Create2 STDAPI CDefFolderMenu_Create(LPCITEMIDLIST pidlFolder, HWND hwndOwner, UINT cidl, LPCITEMIDLIST * apidl, IShellFolder *psf, LPFNDFMCALLBACK lpfn, HKEY hkeyProgID, HKEY hkeyBaseProgID, IContextMenu **ppcm) { IContextMenuCB *pcmcb = new CContextMenuCBImpl(lpfn); if (!pcmcb) { *ppcm = NULL; return E_OUTOFMEMORY; } HRESULT hr = CDefFolderMenu_CreateEx(pidlFolder, hwndOwner, cidl, apidl, psf, pcmcb, hkeyProgID, hkeyBaseProgID, ppcm); pcmcb->Release(); return hr; } STDAPI CDefFolderMenu_Create2(LPCITEMIDLIST pidlFolder, HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl, IShellFolder *psf, LPFNDFMCALLBACK lpfn, UINT nKeys, const HKEY *ahkeyClsKeys, IContextMenu **ppcm) { IContextMenuCB *pcmcb = new CContextMenuCBImpl(lpfn); if (!pcmcb) { *ppcm = NULL; return E_OUTOFMEMORY; } HRESULT hr = CDefFolderMenu_Create2Ex(pidlFolder, hwnd, cidl, apidl, psf, pcmcb, nKeys, ahkeyClsKeys, ppcm); pcmcb->Release(); return hr; } #define CXIMAGEGAP 6 void CDefFolderMenu::_DrawItem(DRAWITEMSTRUCT *pdi) { SEARCHEXTDATA *psed = (SEARCHEXTDATA *)pdi->itemData; if (psed) { TCHAR szMenuText[MAX_PATH]; SHUnicodeToTChar(psed->wszMenuText, szMenuText, ARRAYSIZE(szMenuText)); DrawMenuItem(pdi, szMenuText, psed->iIcon); } } LRESULT CDefFolderMenu::_MeasureItem(MEASUREITEMSTRUCT *pmi) { SEARCHEXTDATA *psed = (SEARCHEXTDATA *)pmi->itemData; if (psed) { TCHAR szMenuText[MAX_PATH]; SHUnicodeToTChar(psed->wszMenuText, szMenuText, ARRAYSIZE(szMenuText)); return MeasureMenuItem(pmi, szMenuText); } return FALSE; }