|
|
#include "shellprv.h"
#include "ids.h"
#include "pidl.h"
#include "fstreex.h"
#include "views.h"
#include "shlwapip.h"
#include "ole2dup.h"
#include "filetbl.h"
#include "datautil.h"
#include "undo.h"
#include "defview.h"
#include "cowsite.h"
#include "defcm.h"
#include "rpctimeout.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 psz, TCHAR ch, BOOL fIgnoreAmpersand);
const ICIVERBTOIDMAP c_sDFMCmdInfo[] = { { L"delete", "delete", DFM_CMD_DELETE, DCMIDM_DELETE }, { c_szCut, "cut", DFM_CMD_MOVE, DCMIDM_CUT }, { c_szCopy, "copy", DFM_CMD_COPY, DCMIDM_COPY }, { c_szPaste, "paste", DFM_CMD_PASTE, DCMIDM_PASTE }, { c_szPaste, "paste", DFM_CMD_PASTE, 0 }, { c_szLink, "link", DFM_CMD_LINK, DCMIDM_LINK }, { c_szProperties, "properties", DFM_CMD_PROPERTIES, DCMIDM_PROPERTIES }, { c_szPasteLink, "pastelink", DFM_CMD_PASTELINK, 0 }, { c_szRename, "rename", DFM_CMD_RENAME, DCMIDM_RENAME }, };
CDefBackgroundMenuCB::CDefBackgroundMenuCB(LPCITEMIDLIST pidlFolder) : _cRef(1) { _pidlFolder = ILClone(pidlFolder); // failure handled in the code
}
CDefBackgroundMenuCB::~CDefBackgroundMenuCB() { ILFree(_pidlFolder); }
STDMETHODIMP CDefBackgroundMenuCB::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CDefBackgroundMenuCB, IContextMenuCB), { 0 }, }; return QISearch(this, qit, riid, ppvObj); }
STDMETHODIMP_(ULONG) CDefBackgroundMenuCB::AddRef() { return InterlockedIncrement(&_cRef); }
STDMETHODIMP_(ULONG) CDefBackgroundMenuCB::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; }
STDMETHODIMP CDefBackgroundMenuCB::CallBack(IShellFolder *psf, HWND hwndOwner, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam) { HRESULT hr = S_OK;
switch (uMsg) { case DFM_MERGECONTEXTMENU_BOTTOM: if (!(wParam & (CMF_VERBSONLY | CMF_DVFILE))) { DWORD dwAttr = SFGAO_HASPROPSHEET; if ((NULL == _pidlFolder) || FAILED(SHGetAttributesOf(_pidlFolder, &dwAttr)) || (SFGAO_HASPROPSHEET & dwAttr)) { CDefFolderMenu_MergeMenu(HINST_THISDLL, POPUP_PROPERTIES_BG, 0, (LPQCMINFO)lParam); } } break;
case DFM_GETHELPTEXT: LoadStringA(HINST_THISDLL, LOWORD(wParam) + IDS_MH_FSIDM_FIRST, (LPSTR)lParam, HIWORD(wParam));; break;
case DFM_GETHELPTEXTW: LoadStringW(HINST_THISDLL, LOWORD(wParam) + IDS_MH_FSIDM_FIRST, (LPWSTR)lParam, HIWORD(wParam));; break;
case DFM_VALIDATECMD: switch (wParam) { case DFM_CMD_NEWFOLDER: break;
default: hr = S_FALSE; break; } break;
case DFM_INVOKECOMMAND: switch (wParam) { case FSIDM_PROPERTIESBG: hr = SHPropertiesForUnk(hwndOwner, psf, (LPCTSTR)lParam); break;
default: hr = S_FALSE; // view menu items, use the default
break; } break;
default: hr = E_NOTIMPL; break; }
return hr; }
class CDefFolderMenu : public CObjectWithSite, public IContextMenu3, public IServiceProvider, public ISearchProvider, public IShellExtInit { 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 *ahkeys, IContextMenu **ppcm);
public: CDefFolderMenu(BOOL fUnderKey); HRESULT Init(DEFCONTEXTMENU *pdcm);
// 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);
// IServiceProvider
STDMETHOD(QueryService)(REFGUID guidService, REFIID riid, void **ppvObj);
// ISearchProvider
STDMETHOD(GetSearchGUID)(GUID *pGuid);
// IShellExtInit
STDMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID);
private: ~CDefFolderMenu();
DWORD _AttributesOfItems(DWORD dwAttrMask); UINT _AddStatic(HMENU hmenu, UINT idCmd, UINT idCmdLast, HKEY hkey); void _InvokeStatic(UINT iCmd); HRESULT _InitDropTarget(); HRESULT _GetMenuVerb(HMENU hmenu, int idFirst, int idMax, int item, LPWSTR psz, DWORD cch); void _UnduplicateVerbs(HMENU hmenu, int idFirst, int idMax); void _SetMenuDefault(HMENU hmenu, UINT idCmdFirst, UINT idMax); HRESULT _ProcessEditPaste(BOOL fPasteLink); HRESULT _ProcessRename(); void _DrawItem(DRAWITEMSTRUCT *pdi); LRESULT _MeasureItem(MEASUREITEMSTRUCT *pmi);
private: LONG _cRef; // Reference count
IDropTarget *_pdtgt; // Drop target of selected item
IContextMenuCB *_pcmcb; // Callback object
IDataObject *_pdtobj; // Data object
IShellFolder *_psf; // Shell folder
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; LPITEMIDLIST *_apidl; UINT _cidl; IAssociationArray *_paa; CSafeServiceSite *_psss; 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; UINT _uFlags; BOOL _bInitMenuPopup; // true if we received WM_INITMENUPOPUP and _uFlags & CMF_FINDHACK
int _iStaticInvoked; // index of the invoked static item
IDMAPFORQCMINFO _idMap; // our named separator mapping table
};
#define GetFldFirst(this) (_idStdMax + _idCmdFirst)
HRESULT HDXA_FindByCommand(HDXA hdxa, UINT idCmd, REFIID riid, void **ppv);
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), QITABENT(CDefFolderMenu, IShellExtInit), { 0 }, }; return QISearch(this, qit, riid, ppvObj); }
STDMETHODIMP_(ULONG) CDefFolderMenu::AddRef() { return InterlockedIncrement(&_cRef); }
CDefFolderMenu::CDefFolderMenu(BOOL fUnderKey) { _cRef = 1; _iStaticInvoked = -1;
_bUnderKeys = fUnderKey;
_psss = new CSafeServiceSite; if (_psss) _psss->SetProviderWeakRef(SAFECAST(this, IServiceProvider *)); IDLData_InitializeClipboardFormats();
ASSERT(_pidlFolder == NULL); ASSERT(_punkSite == NULL); }
HRESULT CDefFolderMenu::Init(DEFCONTEXTMENU *pdcm) { _hwnd = pdcm->hwnd;
_psf = pdcm->psf; if (_psf) _psf->AddRef();
_pcmcb = pdcm->pcmcb; if (_pcmcb) { IUnknown_SetSite(_pcmcb, _psss); _pcmcb->AddRef(); _pcmcb->CallBack(_psf, _hwnd, NULL, DFM_ADDREF, 0, 0); }
_paa = pdcm->paa; if (_paa) _paa->AddRef(); HRESULT hr = CloneIDListArray(pdcm->cidl, pdcm->apidl, &_cidl, &_apidl); if (SUCCEEDED(hr) && pdcm->pidlFolder) { hr = SHILClone(pdcm->pidlFolder, &_pidlFolder); }
if (SUCCEEDED(hr) && _cidl && _psf) { hr = _psf->GetUIObjectOf(_hwnd, _cidl, (LPCITEMIDLIST *)_apidl, IID_PPV_ARG_NULL(IDataObject, &_pdtobj)); }
if (SUCCEEDED(hr)) { _hdxa = HDXA_Create(); if (NULL == _hdxa) hr = E_OUTOFMEMORY; }
if (SUCCEEDED(hr)) { if (pdcm->aKeys) { ASSERT(pdcm->cKeys <= ARRAYSIZE(_hkeyClsKeys)); for (UINT i = 0; i < pdcm->cKeys; ++i) { if (pdcm->aKeys[i]) { // Make a copy of the key for menu's use
_hkeyClsKeys[_nKeys] = SHRegDuplicateHKey(pdcm->aKeys[i]); if (_hkeyClsKeys[_nKeys]) { _nKeys++; } else hr = E_OUTOFMEMORY; } } } else if (_paa) { // we can get it from the _paa
_nKeys = SHGetAssocKeysEx(_paa, ASSOCELEM_MASK_ENUMCONTEXTMENU, _hkeyClsKeys, ARRAYSIZE(_hkeyClsKeys)); } } return hr; }
void ContextMenuInfo_SetSite(ContextMenuInfo *pcmi, IUnknown *pSite) { // APPCOMPAT: PGP50 can only be QIed for IContextMenu, IShellExtInit, and IUnknown.
if (!(pcmi->dwCompat & OBJCOMPATF_CTXMENU_LIMITEDQI)) IUnknown_SetSite((IUnknown*)pcmi->pcm, pSite); }
CDefFolderMenu::~CDefFolderMenu() { if (_psss) { _psss->SetProviderWeakRef(NULL); _psss->Release(); } if (_pcmcb) { IUnknown_SetSite(_pcmcb, NULL); _pcmcb->CallBack(_psf, _hwnd, NULL, DFM_RELEASE, _idStdMax, 0); _pcmcb->Release(); }
if (_hdxa) { for (int i = 0; i < DSA_GetItemCount(_hdxa); i++) { ContextMenuInfo_SetSite((ContextMenuInfo *)DSA_GetItemPtr(_hdxa, i), NULL); }
HDXA_Destroy(_hdxa); }
ATOMICRELEASE(_psf); ATOMICRELEASE(_pdtgt); ATOMICRELEASE(_pdtobj); ATOMICRELEASE(_paa);
for (UINT i = 0; i < _nKeys; i++) { RegCloseKey(_hkeyClsKeys[i]); }
FreeIDListArray(_apidl, _cidl); _cidl = 0; _apidl = NULL;
// 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 _uFlags & CMF_FINDHACK
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() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; }
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, QCMINFO *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; }
DWORD CDefFolderMenu::_AttributesOfItems(DWORD dwAttrMask) { if (!_psf || !_cidl || FAILED(_psf->GetAttributesOf(_cidl, (LPCITEMIDLIST *)_apidl, &dwAttrMask))) dwAttrMask = 0; 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" & popup context menu
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); _DisableRemoveMenuItem(hmInit, SFVIDM_FILE_PROPERTIES + idCmdFirst, dwAttr & SFGAO_HASPROPSHEET, bContext); }
STDAPI_(BOOL) IsClipboardOwnerHung(DWORD dwTimeout) { HWND hwnd = GetClipboardOwner(); if (!hwnd) return FALSE;
DWORD_PTR dwResult; return !SendMessageTimeout(hwnd, WM_NULL, 0, 0, SMTO_ABORTIFHUNG, dwTimeout, &dwResult); }
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 (pdtgt && (CountClipboardFormats() > 0)) { DECLAREWAITCURSOR;
SetWaitCursor();
// The clipboard owner might be hung, so time him out if he takes too long.
// We don't want context menus to hang just because some app is hung.
CRPCTimeout rpctimeout;
IDataObject *pdtobj; if (!IsClipboardOwnerHung(1000) && SUCCEEDED(OleGetClipboard(&pdtobj))) { POINTL pt = {0, 0}; DWORD dwEffectOffered = DataObj_GetDWORD(pdtobj, g_cfPreferredDropEffect, DROPEFFECT_COPY | DROPEFFECT_LINK);
// Unfortunately, OLE turns RPC errors into generic errors
// so we can't use the HRESULT from IDataObject::GetData
// to tell whether the object is alive and doesn't support
// PreferredDropEffect or is hung and OLE turned the
// error code into DV_E_FORMATETC. So see if our timeout fired.
// This is not foolproof because OLE sometimes caches the "is
// the data object hung" state and returns error immediately
// instead of timing out. But it's better than nothing.
if (rpctimeout.TimedOut()) { dwEffectOffered = 0; }
// 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; TCHAR szMenuText[80];
idCmdFirst -= SFVIDM_FIRST;
// Do the UNDO stuff only if the menu has an Undo option
if (GetMenuState(hmInit, SFVIDM_EDIT_UNDO + idCmdFirst, MF_BYCOMMAND) != 0xFFFFFFFF) { // enable undo if there's an undo history
BOOL bEnableUndo = IsUndoAvailable(); if (bEnableUndo) { GetUndoText(szMenuText, ARRAYSIZE(szMenuText), UNDO_MENUTEXT); } else { szMenuText[0] = 0; LoadString(HINST_THISDLL, IDS_UNDOMENU, szMenuText, ARRAYSIZE(szMenuText)); }
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);
// 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_MOVETO + idCmdFirst, dwAttr & SFGAO_CANMOVE, fContext); _DisableRemoveMenuItem(hmInit, SFVIDM_EDIT_COPYTO + idCmdFirst, dwAttr & SFGAO_CANCOPY, fContext); }
int Static_ExtractIcon(HKEY hkeyMenuItem) { HKEY hkeyDefIcon; int iImage = -1;
if (RegOpenKey(hkeyMenuItem, c_szDefaultIcon, &hkeyDefIcon) == ERROR_SUCCESS) { TCHAR szDefIcon[MAX_PATH]; DWORD cb = sizeof(szDefIcon);
if (SHQueryValueEx(hkeyDefIcon, NULL, NULL, NULL, (BYTE*)szDefIcon, &cb) == ERROR_SUCCESS) { iImage = Shell_GetCachedImageIndex(szDefIcon, PathParseIconLocation(szDefIcon), 0); } RegCloseKey(hkeyDefIcon); } return iImage; }
typedef struct { CLSID clsid; UINT idCmd; UINT idMenu; // used in cleanup
GUID guidSearch; //used with search extensions only
} STATICITEMINFO;
#define LAST_ITEM (int)0x7FFFFFFF
UINT CDefFolderMenu::_AddStatic(HMENU hmenu, UINT idCmd, UINT idCmdLast, HKEY hkey) { if (idCmd > idCmdLast) { DebugMsg(DM_ERROR, TEXT("si_a: Out of command ids!")); return idCmd; }
ASSERT(!_hdsaStatics); ASSERT(!_hdsaCustomInfo);
HDSA hdsaCustomInfo = DSA_Create(sizeof(SEARCHINFO), 1); // Create a hdsaStatics.
HDSA hdsaStatics = DSA_Create(sizeof(STATICITEMINFO), 1); if (hdsaStatics && hdsaCustomInfo) { HKEY hkeyStatic; // Try to open the "Static" subkey.
if (RegOpenKey(hkey, TEXT("Static"), &hkeyStatic) == ERROR_SUCCESS) { TCHAR szClass[MAX_PATH]; BOOL bFindFilesInserted = FALSE;
// For each subkey of static.
for (int 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 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, NULL, (BYTE*)szCLSID, &cb) == ERROR_SUCCESS) { HKEY hkeyMenuItem; TCHAR szSubKey[32];
// enum the sub keys 0..N
for (int iMenuItem = 0; wnsprintf(szSubKey, ARRAYSIZE(szSubKey), TEXT("%d"), iMenuItem), RegOpenKey(hkeyClass, szSubKey, &hkeyMenuItem) == ERROR_SUCCESS; iMenuItem++) { TCHAR szMenuText[MAX_PATH]; if (SUCCEEDED(SHLoadLegacyRegUIString(hkeyMenuItem, NULL, szMenuText, ARRAYSIZE(szMenuText)))) { STATICITEMINFO sii; SEARCHINFO sinfo; TCHAR szHelpText[MAX_PATH]; SHLoadLegacyRegUIString(hkeyMenuItem, TEXT("HelpText"), szHelpText, ARRAYSIZE(szHelpText));
SHCLSIDFromString(szCLSID, &sii.clsid); // store it
sii.idCmd = iMenuItem; sii.idMenu = idCmd;
// get the search guid if any...
TCHAR szSearchGUID[MAX_PATH]; cb = sizeof(szSearchGUID); if (SHGetValue(hkeyMenuItem, TEXT("SearchGUID"), NULL, NULL, (BYTE*)szSearchGUID, &cb) == ERROR_SUCCESS) SHCLSIDFromString(szSearchGUID, &sii.guidSearch); else sii.guidSearch = GUID_NULL;
// 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);
SEARCHEXTDATA *psed = (SEARCHEXTDATA *)LocalAlloc(LPTR, sizeof(*psed)); if (psed) { psed->iIcon = Static_ExtractIcon(hkeyMenuItem); SHTCharToUnicode(szHelpText, psed->wszHelpText, ARRAYSIZE(psed->wszHelpText)); SHTCharToUnicode(szMenuText, psed->wszMenuText, ARRAYSIZE(psed->wszMenuText)); }
MENUITEMINFO mii; mii.cbSize = sizeof(mii); mii.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID; mii.fType = MFT_OWNERDRAW; mii.wID = idCmd; mii.dwItemData = (DWORD_PTR)psed;
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; }
void CDefFolderMenu::_InvokeStatic(UINT iCmd) { if (_hdsaStatics) { STATICITEMINFO *psii = (STATICITEMINFO *)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, _psss);
pcm->QueryContextMenu(hmenu, 0, CONTEXTMENU_IDCMD_FIRST, CONTEXTMENU_IDCMD_LAST, CMF_NORMAL); ici.cbSize = sizeof(ici); 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() { HRESULT hr; if (_pdtgt) hr = S_OK; // have cached version
else { // try to create _pdtgt
if (_cidl) { ASSERT(NULL != _psf); // _pdtobj came from _psf
hr = _psf->GetUIObjectOf(_hwnd, 1, (LPCITEMIDLIST *)_apidl, IID_PPV_ARG_NULL(IDropTarget, &_pdtgt)); } else if (_psf) { hr = _psf->CreateViewObject(_hwnd, IID_PPV_ARG(IDropTarget, &_pdtgt)); } else hr = E_FAIL; } return hr; }
// Note on context menus ranges:
// Standard Items // DFM_MERGECONTEXTMENU, context menu extensions, DFM_MERGECONTEXTMENU_TOP
// Separator
// View Items // context menu extensions can get here
// Separator
// (defcm S_FALSE "default" items, if applicable)
// Separator
// Folder Items // context menu extensions can get here
// Separator
// Bottom Items // DFM_MERGECONTEXTMENU_BOTTOM
// Separator
// ("File" menu, if applicable)
//
// Defcm uses names separators to do this magic. Unfortunately _SHPrettyMenu
// removes duplicate separators and we don't always control when that happens.
// So we build up the above empty menu first, and then insert into appropriate ranges.
//
// If you call SHPrepareMenuForDefcm, you must call SHPrettyMenuForDefcm before you return/TrackPopupMenu
//
#define DEFCM_RANGE 5 // the number of FSIDMs belor
#define IS_VALID_DEFCM_RANGE(idCmdFirst, idCmdLast) (((idCmdLast)-(DEFCM_RANGE))>(idCmdFirst))
#define FSIDM_FOLDER_SEP(idCmdLast) ((idCmdLast)-1)
#define FSIDM_VIEW_SEP(idCmdLast) ((idCmdLast)-2)
#define FSIDM_PLACE_SEP(idCmdLast) ((idCmdLast)-3)
#define FSIDM_PLACE_VAL(idCmdLast) ((idCmdLast)-4)
HRESULT SHPrepareMenuForDefcm(HMENU hmenu, UINT indexMenu, UINT uFlags, UINT idCmdFirst, UINT idCmdLast) { HRESULT hr = S_OK;
if (!(uFlags & CMF_DEFAULTONLY) && IS_VALID_DEFCM_RANGE(idCmdFirst, idCmdLast)) { UINT uPosView = GetMenuPosFromID(hmenu, FSIDM_VIEW_SEP(idCmdLast)); UINT uPosFolder = GetMenuPosFromID(hmenu, FSIDM_FOLDER_SEP(idCmdLast));
if (-1 != uPosView && -1 != uPosFolder) { // Menu is already set up correctly
} else if (-1 == uPosView && -1 == uPosFolder) { // Insert everything backwords at position indexMenu
//
InsertMenu(hmenu, indexMenu, MF_BYPOSITION, FSIDM_PLACE_VAL(idCmdLast), TEXT("placeholder")); InsertMenu(hmenu, indexMenu, MF_BYPOSITION | MF_SEPARATOR, FSIDM_PLACE_SEP(idCmdLast), TEXT("")); InsertMenu(hmenu, indexMenu, MF_BYPOSITION, FSIDM_PLACE_VAL(idCmdLast), TEXT("placeholder")); InsertMenu(hmenu, indexMenu, MF_BYPOSITION | MF_SEPARATOR, FSIDM_FOLDER_SEP(idCmdLast), TEXT("")); InsertMenu(hmenu, indexMenu, MF_BYPOSITION, FSIDM_PLACE_VAL(idCmdLast), TEXT("placeholder")); InsertMenu(hmenu, indexMenu, MF_BYPOSITION | MF_SEPARATOR, FSIDM_PLACE_SEP(idCmdLast), TEXT("")); InsertMenu(hmenu, indexMenu, MF_BYPOSITION, FSIDM_PLACE_VAL(idCmdLast), TEXT("placeholder")); InsertMenu(hmenu, indexMenu, MF_BYPOSITION | MF_SEPARATOR, FSIDM_VIEW_SEP(idCmdLast), TEXT("")); InsertMenu(hmenu, indexMenu, MF_BYPOSITION, FSIDM_PLACE_VAL(idCmdLast), TEXT("placeholder"));
hr = S_FALSE; } else { TraceMsg(TF_ERROR, "Some context menu removed a single named separator, we're in a screwy state");
if (-1 == uPosFolder) InsertMenu(hmenu, indexMenu, MF_BYPOSITION | MF_SEPARATOR, FSIDM_FOLDER_SEP(idCmdLast), TEXT("")); if (-1 == uPosView) { InsertMenu(hmenu, indexMenu, MF_BYPOSITION | MF_SEPARATOR, FSIDM_PLACE_SEP(idCmdLast), TEXT("")); InsertMenu(hmenu, indexMenu, MF_BYPOSITION | MF_SEPARATOR, FSIDM_VIEW_SEP(idCmdLast), TEXT("")); } } }
return hr; }
HRESULT SHPrettyMenuForDefcm(HMENU hmenu, UINT uFlags, UINT idCmdFirst, UINT idCmdLast, HRESULT hrPrepare) { if (!(uFlags & CMF_DEFAULTONLY) && IS_VALID_DEFCM_RANGE(idCmdFirst, idCmdLast)) { if (S_FALSE == hrPrepare) { while (DeleteMenu(hmenu, FSIDM_PLACE_VAL(idCmdLast), MF_BYCOMMAND)) { // Remove all our non-separator menu items
} } }
_SHPrettyMenu(hmenu);
return S_OK; }
HRESULT SHUnprepareMenuForDefcm(HMENU hmenu, UINT idCmdFirst, UINT idCmdLast) { if (IS_VALID_DEFCM_RANGE(idCmdFirst, idCmdLast)) { // Remove all the named separators we may have added
DeleteMenu(hmenu, FSIDM_VIEW_SEP(idCmdLast), MF_BYCOMMAND); DeleteMenu(hmenu, FSIDM_FOLDER_SEP(idCmdLast), MF_BYCOMMAND); while (DeleteMenu(hmenu, FSIDM_PLACE_SEP(idCmdLast), MF_BYCOMMAND)) { // Remove all our placeholder separators
} }
return S_OK; }
void CDefFolderMenu::_SetMenuDefault(HMENU hmenu, UINT idCmdFirst, UINT idMax) { // 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.
WPARAM idStatic; 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) { int i = 0; int cMenu = GetMenuItemCount(hmenu); for (; i < cMenu; i++) { // fallback to openas so that files that have progids
// dont endup using AFSO or * for their default verbs
WCHAR szi[CCH_KEYMAX]; HRESULT hr = _GetMenuVerb(hmenu, idCmdFirst, idMax, i, szi, ARRAYSIZE(szi)); if (hr == S_OK && *szi && 0 == StrCmpI(szi, TEXT("openas"))) { SetMenuDefaultItem(hmenu, i, MF_BYPOSITION); break; } }
if (i == cMenu) { ASSERT(GetMenuDefaultItem(hmenu, MF_BYPOSITION, 0) == -1); SetMenuDefaultItem(hmenu, 0, MF_BYPOSITION); } } }
STDMETHODIMP CDefFolderMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { QCMINFO qcm = { hmenu, indexMenu, idCmdFirst, idCmdLast }; DECLAREWAITCURSOR; BOOL fUseDefExt;
SetWaitCursor();
_idCmdFirst = idCmdFirst; _hmenu = hmenu; _uFlags = uFlags; _bInitMenuPopup = FALSE;
// Set up the menu for defcm
HRESULT hrPrepare = SHPrepareMenuForDefcm(hmenu, indexMenu, uFlags, idCmdFirst, idCmdLast);
if (IS_VALID_DEFCM_RANGE(idCmdFirst, idCmdLast)) { _idMap.max = 2; _idMap.list[0].id = FSIDM_FOLDER_SEP(idCmdLast); _idMap.list[0].fFlags = QCMINFO_PLACE_BEFORE; _idMap.list[1].id = FSIDM_VIEW_SEP(idCmdLast); _idMap.list[1].fFlags = QCMINFO_PLACE_AFTER;
qcm.pIdMap = (const QCMINFO_IDMAP *)&_idMap;
qcm.idCmdLast = idCmdLast - DEFCM_RANGE; }
// first add in the folder commands like cut/copy/paste
if (_pdtobj && !(uFlags & (CMF_VERBSONLY | CMF_DVFILE))) { if (!(CMF_DEFAULTONLY & uFlags)) { ATOMICRELEASE(_pdtgt); // If we previously got the drop target, release it.
_InitDropTarget(); // ignore failure, NULL _pdtgt is handled below
}
// We're going to merge two HMENUs into the context menu,
// but we want only one id range for them... Remember the idCmdFirst.
//
UINT idCmdFirstTmp = qcm.idCmdFirst;
UINT indexMenuTmp = qcm.indexMenu;
UINT uPos = GetMenuPosFromID(hmenu, FSIDM_FOLDER_SEP(idCmdLast));
// POPUP_DCM_ITEM2 goes after FSIDM_FOLDER_SEP(idCmdLast)
if (-1 != uPos) qcm.indexMenu = uPos + 1; CDefFolderMenu_MergeMenu(HINST_THISDLL, POPUP_DCM_ITEM2, 0, &qcm);
UINT idCmdFirstMax = qcm.idCmdFirst; qcm.idCmdFirst = idCmdFirstTmp;
// POPUP_DCM_ITEM goes TWO before FSIDM_FOLDER_SEP(idCmdLast)
if (-1 != uPos) qcm.indexMenu = uPos - 1; CDefFolderMenu_MergeMenu(HINST_THISDLL, POPUP_DCM_ITEM, 0, &qcm);
qcm.indexMenu = indexMenuTmp;
qcm.idCmdFirst = max(idCmdFirstTmp, qcm.idCmdFirst);
ULONG dwAttr = _AttributesOfItems( SFGAO_CANRENAME | SFGAO_CANDELETE | SFGAO_CANLINK | SFGAO_HASPROPSHEET | SFGAO_CANCOPY | SFGAO_CANMOVE);
if (!(uFlags & CMF_CANRENAME)) dwAttr &= ~SFGAO_CANRENAME;
Def_InitFileCommands(dwAttr, hmenu, idCmdFirst, TRUE);
// Don't try to figure out paste if we're just going to invoke the default
// (Figuring out paste is expensive)
if (CMF_DEFAULTONLY & uFlags) { ASSERT(_pdtgt == NULL); }
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) { HRESULT hr = _pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_MERGECONTEXTMENU, uFlags, (LPARAM)&qcm); fUseDefExt = (hr == S_OK); UINT indexMenuTmp = qcm.indexMenu; UINT uPos = GetMenuPosFromID(hmenu, FSIDM_FOLDER_SEP(idCmdLast)); if (-1 != uPos) qcm.indexMenu = uPos + 1; hr = _pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_MERGECONTEXTMENU_BOTTOM, uFlags, (LPARAM)&qcm); if (!fUseDefExt) fUseDefExt = (hr == S_OK); qcm.indexMenu = indexMenuTmp; } else { fUseDefExt = FALSE; }
_idFolderMax = qcm.idCmdFirst - idCmdFirst; // add registry verbs
if ((!(uFlags & CMF_NOVERBS)) || (!_pdtobj && !_psf && _nKeys)) // this second case is for find extensions
{ // HACK: Default Extenstions EXPECT a selection, Let's hope all don't
if (!_pdtobj) fUseDefExt = FALSE;
// Put the separator between container menuitems and object menuitems
// only if we don't have the separator at the insertion point.
MENUITEMINFO mii = {0}; mii.cbSize = sizeof(mii); mii.fMask = MIIM_TYPE; 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 hdca = DCA_Create(); if (hdca) { // Add default extensions, only if the folder callback returned
// S_OK. 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 (UINT nKeys = 0; nKeys < _nKeys; ++nKeys) { DCA_AddItemsFromKey(hdca, _hkeyClsKeys[nKeys], _bUnderKeys ? NULL : STRREG_SHEX_MENUHANDLER); } // Work Around:
// 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);
// (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.
qcm.idCmdFirst = HDXA_AppendMenuItems2(_hdxa, _pdtobj, _nKeys, _hkeyClsKeys, !_pdtobj ? _pidlFolder : NULL, &qcm, uFlags, hdca, _psss);
DCA_Destroy(hdca); }
_idVerbMax = qcm.idCmdFirst - idCmdFirst;
// menu extensions that are loaded at invoke time
if (uFlags & CMF_INCLUDESTATIC) { qcm.idCmdFirst = _AddStatic(hmenu, qcm.idCmdFirst, qcm.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); } }
// if no default menu got set, choose the first one.
if (_pdtobj && !(uFlags & CMF_NODEFAULT) && GetMenuDefaultItem(hmenu, MF_BYPOSITION, 0) == -1) { _SetMenuDefault(hmenu, idCmdFirst, qcm.idCmdFirst); }
// 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;
SHPrettyMenuForDefcm(hmenu, uFlags, idCmdFirst, idCmdLast, hrPrepare);
_UnduplicateVerbs(hmenu, idCmdFirst, qcm.idCmdFirst); ResetWaitCursor();
return ResultFromShort(_idFld2Max); }
HRESULT CDefFolderMenu::_GetMenuVerb(HMENU hmenu, int idFirst, int idMax, int item, LPWSTR psz, DWORD cch) { MENUITEMINFO mii = {0}; mii.cbSize = sizeof(MENUITEMINFO); mii.fMask = MIIM_TYPE | MIIM_ID; *psz = 0; if (GetMenuItemInfo(hmenu, item, TRUE, &mii) && ((int)mii.wID >= idFirst && (int)mii.wID < idMax)) { if (mii.fType & MFT_SEPARATOR) return S_FALSE; else return GetCommandString(mii.wID - idFirst, GCS_VERBW, NULL, (LPSTR)psz, cch); } return E_FAIL; }
void CDefFolderMenu::_UnduplicateVerbs(HMENU hmenu, int idFirst, int idMax) { HRESULT hr = S_OK; int iDefault = GetMenuDefaultItem(hmenu, MF_BYPOSITION, 0); for (int i = 0; i < GetMenuItemCount(hmenu); i++) { WCHAR szi[CCH_KEYMAX]; hr = _GetMenuVerb(hmenu, idFirst, idMax, i, szi, ARRAYSIZE(szi)); if (hr == S_OK && *szi) { for (int j = i + 1; j < GetMenuItemCount(hmenu); j++) { WCHAR szj[CCH_KEYMAX]; hr = _GetMenuVerb(hmenu, idFirst, idMax, j, szj, ARRAYSIZE(szj)); if (hr == S_OK && *szj) { if (0 == StrCmpIW(szj, szi)) { if (j != iDefault) { DeleteMenu(hmenu, j, MF_BYPOSITION); j--; } } } } } } }
HRESULT CDefFolderMenu::_ProcessEditPaste(BOOL fPasteLink) { DECLAREWAITCURSOR;
SetWaitCursor();
HRESULT hr = _InitDropTarget(); if (SUCCEEDED(hr)) { IDataObject *pdtobj; hr = OleGetClipboard(&pdtobj); if (SUCCEEDED(hr)) { DWORD grfKeyState; DWORD dwEffect = 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; dwEffect &= DROPEFFECT_LINK; } else { grfKeyState = MK_LBUTTON; dwEffect &= ~DROPEFFECT_LINK; }
hr = SimulateDropWithPasteSucceeded(_pdtgt, pdtobj, grfKeyState, NULL, dwEffect, _psss, TRUE);
pdtobj->Release(); } } ResetWaitCursor();
if (FAILED(hr)) MessageBeep(0);
return hr; }
HRESULT CDefFolderMenu::_ProcessRename() { IDefViewFrame3 *dvf3; HRESULT hr = IUnknown_QueryService(_punkSite, SID_DefView, IID_PPV_ARG(IDefViewFrame3, &dvf3)); if (SUCCEEDED(hr)) { hr = dvf3->DoRename(); dvf3->Release(); } return hr; }
// deal with the versioning of this structure...
void CopyInvokeInfo(CMINVOKECOMMANDINFOEX *pici, const CMINVOKECOMMANDINFO *piciIn) { ASSERT(piciIn->cbSize >= sizeof(*piciIn));
ZeroMemory(pici, sizeof(*pici)); memcpy(pici, piciIn, min(sizeof(*pici), piciIn->cbSize)); pici->cbSize = sizeof(*pici); }
#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
typedef int (WINAPI * PFN_LSTRCMPIW)(LPCWSTR, LPCWSTR); HRESULT SHMapICIVerbToCmdID(LPCMINVOKECOMMANDINFO pici, const ICIVERBTOIDMAP* pmap, UINT cmap, UINT* pid) { HRESULT hr = E_FAIL;
if (!IS_INTRESOURCE(pici->lpVerb)) { PFN_LSTRCMPIW pfnCompare; LPCWSTR pszVerb; BOOL fUnicode;
if (IS_UNICODE_ICI(pici) && ((LPCMINVOKECOMMANDINFOEX)pici)->lpVerbW) { pszVerb = ((LPCMINVOKECOMMANDINFOEX)pici)->lpVerbW; pfnCompare = lstrcmpiW; fUnicode = TRUE; } else { pszVerb = (LPCWSTR)(pici->lpVerb); pfnCompare = (PFN_LSTRCMPIW)lstrcmpiA; fUnicode = FALSE; } for (UINT i = 0; i < cmap ; i++) { LPCWSTR pszCompare = (fUnicode) ? pmap[i].pszCmd : (LPCWSTR)(pmap[i].pszCmdA); if (!pfnCompare(pszVerb, pszCompare)) { *pid = pmap[i].idDFMCmd; hr = S_OK; break; } } } else { *pid = LOWORD((UINT_PTR)pici->lpVerb); hr = S_OK; } return hr; }
HRESULT SHMapCmdIDToVerb(UINT_PTR idCmd, const ICIVERBTOIDMAP* pmap, UINT cmap, LPSTR pszName, UINT cchMax, BOOL bUnicode) { LPCWSTR pszNull = L""; LPCSTR pszVerb = (LPCSTR)pszNull;
for (UINT i = 0 ; i < cmap ; i++) { if (pmap[i].idDefCmd == idCmd) { pszVerb = (bUnicode) ? (LPCSTR)pmap[i].pszCmd : pmap[i].pszCmdA; break; } }
if (bUnicode) StrCpyNW((LPWSTR)pszName, (LPWSTR)pszVerb, cchMax); else StrCpyNA(pszName, pszVerb, cchMax);
return (pszVerb == (LPCSTR)pszNull) ? E_NOTIMPL : S_OK; }
STDMETHODIMP CDefFolderMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) { HRESULT hr = S_OK; UINT idCmd = (UINT)-1; UINT 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)) { if (SUCCEEDED(SHMapICIVerbToCmdID(pici, c_sDFMCmdInfo, ARRAYSIZE(c_sDFMCmdInfo), &idCmdLocal))) { // 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
LPCTSTR pszVerb; WCHAR szVerb[MAX_PATH]; if (!fUnicode || picix->lpVerbW == NULL) { SHAnsiToUnicode(picix->lpVerb, szVerb, ARRAYSIZE(szVerb)); pszVerb = szVerb; } else pszVerb = picix->lpVerbW; idCmdLocal = idCmd;
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; } }
hr = E_INVALIDARG; } else if (idCmd < _idFolderMax) { DFMICS dfmics; LPARAM lParam; WCHAR szLParamBuffer[MAX_PATH];
idCmdLocal = idCmd - _idStdMax; ProcessCommand:
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;
switch (idCmdLocal) { case DFM_CMD_LINK: 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; 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 hr; } break; }
// 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;
// this 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) { ASSERT(pici->cbSize >= sizeof(CMINVOKECOMMANDINFOEX)); POINT *ppt = (POINT *)GlobalAlloc(GPTR, sizeof(*ppt)); if (ppt) { *ppt = picix->ptInvoke; if (FAILED(DataObj_SetGlobal(_pdtobj, g_cfOFFSETS, ppt))) GlobalFree(ppt); } }
hr = _pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_INVOKECOMMANDEX, idCmdLocal, (LPARAM)&dfmics); if (hr == E_NOTIMPL) { // the callback didn't understand the DFM_INVOKECOMMANDEX
// fall back to a regular DFM_INVOKECOMMAND instead
hr = _pcmcb->CallBack(_psf, _hwnd, _pdtobj, DFM_INVOKECOMMAND, idCmdLocal, lParam); }
// Check if we need to execute the default code.
if (hr == S_FALSE) { hr = S_OK; // 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));
IShellFolderView *psfv; if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_SFolderView, IID_PPV_ARG(IShellFolderView, &psfv)))) psfv->SetPoints(_pdtobj);
OleSetClipboard(_pdtobj);
if (psfv) { // notify view so it can setup itself in the
// clipboard viewer chain
psfv->SetClipboard(DFM_CMD_MOVE == idCmdLocal); psfv->Release(); } 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: hr = _ProcessEditPaste(idCmdLocal == DFM_CMD_PASTELINK); break;
case DFM_CMD_RENAME: hr = _ProcessRename(); break;
default: DebugMsg(TF_WARNING, TEXT("DefCM item 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: hr = _ProcessEditPaste(idCmdLocal == DFM_CMD_PASTELINK); break;
default: // Only our commands should come here
DebugMsg(TF_WARNING, TEXT("DefCM background command not processed in %s at %d (%x)"), __FILE__, __LINE__, idCmdLocal); 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;
hr = HDXA_LetHandlerProcessCommandEx(_hdxa, &ici, &idCmd); if (SUCCEEDED(hr) && (idCmd == idCmdSave)) { // hdxa failed to handle it
hr = E_INVALIDARG; } } } else if (idCmd < _idDelayInvokeMax) { _InvokeStatic((UINT)(idCmd-_idVerbMax)); } else if (idCmd < _idFld2Max) { idCmdLocal = idCmd - _idDelayInvokeMax; goto ProcessCommand; } else { hr = E_INVALIDARG; }
return hr; }
STDMETHODIMP CDefFolderMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pwReserved, LPSTR pszName, UINT cchMax) { HRESULT hr = E_INVALIDARG; UINT_PTR idCmdLocal; int i;
if (!IS_INTRESOURCE(idCmd)) { // This must be a string
if (HDXA_GetCommandString(_hdxa, idCmd, uType, pwReserved, pszName, cchMax) == S_OK) { return S_OK; }
// String can either be Ansi or Unicode. Since shell32 is built unicode, we need to compare
// idCmd against the ansi version of the verb string.
LPTSTR pCmd; LPSTR pCmdA; pCmd = (LPTSTR)idCmd; pCmdA = (LPSTR)idCmd;
// Convert the string into an ID
for (i = 0; i < ARRAYSIZE(c_sDFMCmdInfo); i++) { if (!lstrcmpi(pCmd, c_sDFMCmdInfo[i].pszCmd) || !StrCmpIA(pCmdA, c_sDFMCmdInfo[i].pszCmdA)) { 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) ? S_OK : 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) ? S_OK : E_OUTOFMEMORY); break;
case GCS_VERBA: case GCS_VERBW: return SHMapCmdIDToVerb(idCmdLocal, c_sDFMCmdInfo, ARRAYSIZE(c_sDFMCmdInfo), pszName, cchMax, uType == GCS_VERBW);
case GCS_VALIDATEA: case GCS_VALIDATEW: 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.
hr = 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 hr; }
STDMETHODIMP CDefFolderMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT* plResult) { UINT uMsgFld = 0; // must zero init these since _pcmb->Callback() call at bottom passes these through even if uMsg doesn't match anything in the switch statement
WPARAM wParamFld = 0; // map the folder call back params to these
LPARAM lParamFld = 0; 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 (_uFlags & CMF_FINDHACK) { 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 (int i = cItems - 1; i >= 0; i--) { MENUITEMINFO mii = {0}; 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) && (MFT_STRING == mii.fType)) { SEARCHINFO sinfo; // static items already have correct dwItemData (pointer to SEARCHEXTDATA added in _AddStatic)
// 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(*psed)); 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 (int 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 (_punkSite && (_bInitMenuPopup || (_hdsaStatics && _idVerbMax <= id)) && id < _idDelayInvokeMax) { IShellBrowser *psb; if (SUCCEEDED(IUnknown_QueryService(_punkSite, 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 ((_uFlags & CMF_FINDHACK) && _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 { // TODO: this should probably get the idCmd of the MFS_HILITE item so we forward to the correct hdxa...
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...
IContextMenu3 *pcmItem; if (SUCCEEDED(HDXA_FindByCommand(_hdxa, idCmd, IID_PPV_ARG(IContextMenu3, &pcmItem)))) { HRESULT hr = pcmItem->HandleMenuMsg2(uMsg, wParam, lParam, plResult); pcmItem->Release(); return hr; }
// 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::QueryService(REFGUID guidService, REFIID riid, void **ppvObj) { if (IsEqualGUID(guidService, SID_CtxQueryAssociations)) { if (_paa) return _paa->QueryInterface(riid, ppvObj); else { *ppvObj = NULL; return E_NOINTERFACE; } } else return IUnknown_QueryService(_punkSite, guidService, riid, ppvObj); }
STDMETHODIMP CDefFolderMenu::GetSearchGUID(GUID *pGuid) { HRESULT hr = E_FAIL; if (_iStaticInvoked != -1) { STATICITEMINFO *psii = (STATICITEMINFO *)DSA_GetItemPtr(_hdsaStatics, _iStaticInvoked); if (psii) { *pGuid = psii->guidSearch; hr = S_OK; } }
return hr; }
STDMETHODIMP CDefFolderMenu::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID) { IUnknown_Set((IUnknown **)&_pdtobj, pdtobj); // just grab this guy
for (int i = 0; i < DSA_GetItemCount(_hdxa); i++) { ContextMenuInfo *pcmi = (ContextMenuInfo *)DSA_GetItemPtr(_hdxa, i); IShellExtInit *psei; if (SUCCEEDED(pcmi->pcm->QueryInterface(IID_PPV_ARG(IShellExtInit, &psei)))) { psei->Initialize(pidlFolder, pdtobj, hkeyProgID); psei->Release(); } } return S_OK; }
//=============================================================================
// 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 *ahkeys, 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, ahkeys, pidlFolder, &qcm, fFlags, hdca, NULL); }
UINT HDXA_AppendMenuItems2(HDXA hdxa, IDataObject *pdtobj, UINT nKeys, HKEY *ahkeys, LPCITEMIDLIST pidlFolder, QCMINFO* pqcm, UINT fFlags, HDCA hdca, IUnknown* pSite) { const UINT idCmdBase = pqcm->idCmdFirst; UINT idCmdFirst = pqcm->idCmdFirst;
// 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.
// 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.
int 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 insert menuitems "above" uInsert.
UINT uInsertOffset = 0; for (idca = DCA_GetItemCount(hdca) - 1; idca >= 0; idca--) { TCHAR szCLSID[GUIDSTR_MAX]; TCHAR szRegKey[GUIDSTR_MAX + 40];
CLSID clsid = *DCA_GetItem(hdca, idca); SHStringFromGUID(clsid, szCLSID, ARRAYSIZE(szCLSID));
// 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) && (clsid != CLSID_ShellFileDefExt)) { wnsprintf(szRegKey, ARRAYSIZE(szRegKey), TEXT("CLSID\\%s\\shellex\\MayChangeDefaultMenu"), szCLSID);
if (!SHRegSubKeyExists(HKEY_CLASSES_ROOT, szRegKey)) { DebugMsg(TF_MENU, TEXT("HDXA_AppendMenuItems skipping %s"), szCLSID); continue; } }
IShellExtInit *psei = NULL; IContextMenu *pcm = NULL;
// Try all the class keys in order
for (UINT nCurKey = 0; nCurKey < nKeys; nCurKey++) { // These cam from HKCR so need to go through administrator approval
if (!psei && FAILED(DCA_ExtCreateInstance(hdca, idca, IID_PPV_ARG(IShellExtInit, &psei)))) break;
if (FAILED(psei->Initialize(pidlFolder, pdtobj, ahkeys[nCurKey]))) continue;
// Only get the pcm after initializing
if (!pcm && FAILED(psei->QueryInterface(IID_PPV_ARG(IContextMenu, &pcm)))) continue; wnsprintf(szRegKey, ARRAYSIZE(szRegKey), TEXT("CLSID\\%s"), szCLSID);
// Webvw needs the site in order to do its QueryContextMenu
ContextMenuInfo cmi; cmi.pcm = pcm; cmi.dwCompat = SHGetObjectCompatFlags(NULL, &clsid); ContextMenuInfo_SetSite(&cmi, pSite);
HRESULT hr; int cMenuItemsLast = GetMenuItemCount(pqcm->hmenu); DWORD dwExtType, dwType, dwSize = sizeof(dwExtType); if (SHGetValue(HKEY_CLASSES_ROOT, szRegKey, TEXT("flags"), &dwType, (BYTE*)&dwExtType, &dwSize) == ERROR_SUCCESS && dwType == REG_DWORD && (NULL != pqcm->pIdMap) && 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 within 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.
hr = 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 hr = pcm->QueryContextMenu(pqcm->hmenu, pqcm->indexMenu + uInsertOffset, idCmdFirst, pqcm->idCmdLast, fFlags);
UINT citems = HRESULT_CODE(hr);
if (SUCCEEDED(hr) && citems) { cmi.idCmdFirst = idCmdFirst - idCmdBase; cmi.idCmdMax = cmi.idCmdFirst + citems; cmi.clsid = clsid; // for debugging
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 == CLSID_ShellFileDefExt) { //
// for static registry verbs, make sure that
// they are added in priority of their specificity.
//
// the first key needs its verbs at the top
// unless it is not the default handler.
// so if the default hasnt been set,
// then we dont push down the insert position.
//
// like "Directory" is more specific than "Folder"
// but the default verb is on "Folder". so "Directory"
// wont set the default verb, but "Folder" will.
//
if (-1 != GetMenuDefaultItem(pqcm->hmenu, TRUE, 0)) { // a default has been set, so each subsequent
// key is less important.
uInsertOffset += GetMenuItemCount(pqcm->hmenu) - cMenuItemsLast; } } else { // try to bubble up the default to the top if possible,
// since some apps just invoke the 0th index on the menu
// instead of querying the menu for the default
if (0 == uInsertOffset && (0 == GetMenuDefaultItem(pqcm->hmenu, TRUE, 0))) uInsertOffset++;
// only CLSID_ShellFileDefExt gets a shot
// at every key. the rest are assumed
// to do most of their work from the IDataObject
break; }
pcm->Release(); pcm = NULL;
psei->Release(); psei = NULL;
continue; // next hkey
} }
if (pcm) pcm->Release();
if (psei) psei->Release(); }
return idCmdFirst; }
// 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
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;
// try handlers in order, the first to take it wins
for (int i = 0; i < DSA_GetItemCount(hdxa); i++) { ContextMenuInfo *pcmi = (ContextMenuInfo *)DSA_GetItemPtr(hdxa, i); if (!IS_INTRESOURCE(pici->lpVerb)) { // invoke by cannonical name case
// app compat: 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)) { CMINVOKECOMMANDINFOEX ici; CopyInvokeInfo(&ici, (CMINVOKECOMMANDINFO *)pici); ici.lpVerb = (LPSTR)MAKEINTRESOURCE(*pidCmd - pcmi->idCmdFirst);
hr = pcmi->pcm->InvokeCommand((LPCMINVOKECOMMANDINFO)&ici); if (SUCCEEDED(hr)) { *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 hr = 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 i = 0; i < DSA_GetItemCount(hdxa); i++) { ContextMenuInfo *pcmi = (ContextMenuInfo *)DSA_GetItemPtr(hdxa, i);
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) == S_OK) { return S_OK; } } //
// 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.
//
hr = pcmi->pcm->GetCommandString(idCmd-pcmi->idCmdFirst, uType, pwReserved, pszName, cchMax); break; } }
return hr; }
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 i = 0; i < DSA_GetItemCount(hdxa); i++) { ContextMenuInfo *pcmi = (ContextMenuInfo *)DSA_GetItemPtr(hdxa, i);
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 i = 0; i < DSA_GetItemCount(hdxa); i++) { ContextMenuInfo *pcmi = (ContextMenuInfo *)DSA_GetItemPtr(hdxa, i); if (pcmi->pcm) { pcmi->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 : public IContextMenuCB { public: CContextMenuCBImpl(LPFNDFMCALLBACK pfn) : _pfn(pfn), _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(&_cRef); }
STDMETHOD_(ULONG,Release)() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; }
// IContextMenuCB
STDMETHOD(CallBack)(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam) { return _pfn ? _pfn(psf, hwnd, pdtobj, uMsg, wParam, lParam) : E_FAIL; }
private: LONG _cRef; LPFNDFMCALLBACK _pfn; };
STDAPI CreateDefaultContextMenu(DEFCONTEXTMENU *pdcm, IContextMenu **ppcm) { HRESULT hr = E_OUTOFMEMORY; *ppcm = 0; CDefFolderMenu *pmenu = new CDefFolderMenu(FALSE); if (pmenu) { hr = pmenu->Init(pdcm); if (SUCCEEDED(hr)) hr = pmenu->QueryInterface(IID_PPV_ARG(IContextMenu, ppcm)); pmenu->Release(); } return hr; }
STDAPI CDefFolderMenu_CreateHKeyMenu(HWND hwnd, HKEY hkey, IContextMenu **ppcm) { HRESULT hr = E_OUTOFMEMORY; *ppcm = 0; CDefFolderMenu *pmenu = new CDefFolderMenu(TRUE); if (pmenu) { DEFCONTEXTMENU dcm = {0}; dcm.hwnd = hwnd; dcm.aKeys = &hkey; dcm.cKeys = 1; hr = pmenu->Init(&dcm); if (SUCCEEDED(hr)) hr = pmenu->QueryInterface(IID_PPV_ARG(IContextMenu, ppcm)); pmenu->Release(); } return hr; }
STDAPI CDefFolderMenu_Create2Ex(LPCITEMIDLIST pidlFolder, HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl, IShellFolder *psf, IContextMenuCB *pcmcb, UINT nKeys, const HKEY *ahkeys, IContextMenu **ppcm) { DEFCONTEXTMENU dcm = { hwnd, pcmcb, pidlFolder, psf, cidl, apidl, NULL, nKeys, ahkeys};
return CreateDefaultContextMenu(&dcm, ppcm); }
STDAPI CDefFolderMenu_CreateEx(LPCITEMIDLIST pidlFolder, HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl, IShellFolder *psf, IContextMenuCB *pcmcb, HKEY hkeyProgID, HKEY hkeyBaseProgID, IContextMenu **ppcm) { HKEY aKeys[2] = { hkeyProgID, hkeyBaseProgID}; DEFCONTEXTMENU dcm = { hwnd, pcmcb, pidlFolder, psf, cidl, apidl, NULL, 2, aKeys};
return CreateDefaultContextMenu(&dcm, ppcm); }
//
// old style CDefFolderMenu_Create and CDefFolderMenu_Create2
//
STDAPI CDefFolderMenu_Create(LPCITEMIDLIST pidlFolder, HWND hwndOwner, UINT cidl, LPCITEMIDLIST * apidl, IShellFolder *psf, LPFNDFMCALLBACK pfn, HKEY hkeyProgID, HKEY hkeyBaseProgID, IContextMenu **ppcm) { HRESULT hr; IContextMenuCB *pcmcb = new CContextMenuCBImpl(pfn); if (pcmcb) { HKEY aKeys[2] = { hkeyProgID, hkeyBaseProgID}; DEFCONTEXTMENU dcm = { hwndOwner, pcmcb, pidlFolder, psf, cidl, apidl, NULL, 2, aKeys};
hr = CreateDefaultContextMenu(&dcm, ppcm); pcmcb->Release(); } else { *ppcm = NULL; hr = E_OUTOFMEMORY; } return hr; }
STDAPI CDefFolderMenu_Create2(LPCITEMIDLIST pidlFolder, HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl, IShellFolder *psf, LPFNDFMCALLBACK pfn, UINT nKeys, const HKEY *ahkeys, IContextMenu **ppcm) { HRESULT hr; IContextMenuCB *pcmcb = new CContextMenuCBImpl(pfn); if (pcmcb) { hr = CDefFolderMenu_Create2Ex(pidlFolder, hwnd, cidl, apidl, psf, pcmcb, nKeys, ahkeys, ppcm); pcmcb->Release(); } else { *ppcm = NULL; hr = E_OUTOFMEMORY; } return hr; }
#define CXIMAGEGAP 6
void DrawMenuItem(DRAWITEMSTRUCT* pdi, LPCTSTR pszText, UINT iIcon) { if ((pdi->itemAction & ODA_SELECT) || (pdi->itemAction & ODA_DRAWENTIRE)) { int x, y; SIZE sz; RECT rc;
// Draw the image (if there is one).
GetTextExtentPoint(pdi->hDC, pszText, lstrlen(pszText), &sz); if (pdi->itemState & ODS_SELECTED) { SetBkColor(pdi->hDC, GetSysColor(COLOR_HIGHLIGHT)); SetTextColor(pdi->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); FillRect(pdi->hDC,&pdi->rcItem,GetSysColorBrush(COLOR_HIGHLIGHT)); } else { SetTextColor(pdi->hDC, GetSysColor(COLOR_MENUTEXT)); FillRect(pdi->hDC,&pdi->rcItem,GetSysColorBrush(COLOR_MENU)); } rc = pdi->rcItem; rc.left += +2 * CXIMAGEGAP + g_cxSmIcon; DrawText(pdi->hDC,pszText,lstrlen(pszText), &rc, DT_SINGLELINE | DT_VCENTER); if (iIcon != -1) { x = pdi->rcItem.left + CXIMAGEGAP; y = (pdi->rcItem.bottom+pdi->rcItem.top-g_cySmIcon)/2;
HIMAGELIST himlSmall; Shell_GetImageLists(NULL, &himlSmall); ImageList_Draw(himlSmall, iIcon, pdi->hDC, x, y, ILD_TRANSPARENT); } else { x = pdi->rcItem.left + CXIMAGEGAP; y = (pdi->rcItem.bottom+pdi->rcItem.top-g_cySmIcon)/2; } } }
LRESULT MeasureMenuItem(MEASUREITEMSTRUCT *pmi, LPCTSTR pszText) { LRESULT lres = FALSE; // Get the rough height of an item so we can work out when to break the
// menu. User should really do this for us but that would be useful.
HDC hdc = GetDC(NULL); if (hdc) { // REVIEW cache out the menu font?
NONCLIENTMETRICS ncm; ncm.cbSize = sizeof(ncm); if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, FALSE)) { HFONT hfont = CreateFontIndirect(&ncm.lfMenuFont); if (hfont) { SIZE sz; HFONT hfontOld = (HFONT)SelectObject(hdc, hfont); GetTextExtentPoint(hdc, pszText, lstrlen(pszText), &sz); pmi->itemHeight = max (g_cySmIcon + CXIMAGEGAP / 2, ncm.iMenuHeight); pmi->itemWidth = g_cxSmIcon + 2 * CXIMAGEGAP + sz.cx; pmi->itemWidth = 2 * CXIMAGEGAP + sz.cx; SelectObject(hdc, hfontOld); DeleteObject(hfont); lres = TRUE; } } ReleaseDC(NULL, hdc); } return lres; }
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; }
|