You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2741 lines
94 KiB
2741 lines
94 KiB
#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;
|
|
}
|