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.
2207 lines
72 KiB
2207 lines
72 KiB
#include "stdafx.h"
|
|
#pragma hdrstop
|
|
|
|
#include <oleacc.h> // MSAAMENUINFO stuff
|
|
#include <runtask.h>
|
|
#include "datautil.h"
|
|
#include "idlcomm.h"
|
|
#include "stgutil.h"
|
|
#include <winnls.h>
|
|
#include "filetbl.h"
|
|
#include "cdburn.h"
|
|
#include "mtpt.h"
|
|
|
|
#ifndef CMF_DVFILE
|
|
#define CMF_DVFILE 0x00010000 // "File" pulldown
|
|
#endif
|
|
|
|
class CSendToMenu : public IContextMenu3, IShellExtInit, IOleWindow
|
|
{
|
|
public:
|
|
// IUnknown
|
|
STDMETHOD(QueryInterface)(REFIID riid, void **ppvObj);
|
|
STDMETHOD_(ULONG,AddRef)(void);
|
|
STDMETHOD_(ULONG,Release)(void);
|
|
|
|
// 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 *pRes, LPSTR pszName, UINT cchMax);
|
|
|
|
// IContextMenu2
|
|
STDMETHOD(HandleMenuMsg)(UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
|
|
// IContextMenu3
|
|
STDMETHOD(HandleMenuMsg2)(UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT *lResult);
|
|
|
|
// IShellExtInit
|
|
STDMETHOD(Initialize)(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID);
|
|
|
|
// IOleWindow
|
|
STDMETHOD(GetWindow)(HWND *phwnd);
|
|
STDMETHOD(ContextSensitiveHelp)(BOOL fEnterMode) {return E_NOTIMPL;};
|
|
|
|
private:
|
|
CSendToMenu();
|
|
~CSendToMenu();
|
|
|
|
LONG _cRef;
|
|
HMENU _hmenu;
|
|
UINT _idCmdFirst;
|
|
BOOL _bFirstTime;
|
|
HWND _hwnd;
|
|
IDataObject *_pdtobj;
|
|
LPITEMIDLIST _pidlLast;
|
|
|
|
DWORD _GetKeyState(void);
|
|
HRESULT _DoDragDrop(HWND hwndParent, IDropTarget *pdrop);
|
|
BOOL _CanDrop(IShellFolder *psf, LPCITEMIDLIST pidl);
|
|
HRESULT _MenuCallback(UINT fmm, IShellFolder *psf, LPCITEMIDLIST pidl);
|
|
HRESULT _RemovableDrivesMenuCallback(UINT fmm, IShellFolder *psf, LPCITEMIDLIST pidl);
|
|
static HRESULT CALLBACK s_MenuCallback(UINT fmm, LPARAM lParam, IShellFolder *psf, LPCITEMIDLIST pidl);
|
|
static HRESULT CALLBACK s_RemovableDrivesMenuCallback(UINT fmm, LPARAM lParam, IShellFolder *psf, LPCITEMIDLIST pidl);
|
|
|
|
friend HRESULT CSendToMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut);
|
|
};
|
|
|
|
CSendToMenu::CSendToMenu() : _cRef(1)
|
|
{
|
|
DllAddRef();
|
|
}
|
|
|
|
CSendToMenu::~CSendToMenu()
|
|
{
|
|
if (_hmenu)
|
|
FileMenu_DeleteAllItems(_hmenu);
|
|
|
|
if (_pdtobj)
|
|
_pdtobj->Release();
|
|
|
|
ILFree(_pidlLast);
|
|
DllRelease();
|
|
}
|
|
|
|
HRESULT CSendToMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut)
|
|
{
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
CSendToMenu *pstm = new CSendToMenu();
|
|
if (pstm)
|
|
{
|
|
hr = pstm->QueryInterface(riid, ppvOut);
|
|
pstm->Release();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSendToMenu::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
static const QITAB qit[] = {
|
|
QITABENT(CSendToMenu, IShellExtInit), // IID_IShellExtInit
|
|
QITABENT(CSendToMenu, IOleWindow), // IID_IOleWindow
|
|
QITABENT(CSendToMenu, IContextMenu3), // IID_IContextMenu3
|
|
QITABENTMULTI(CSendToMenu, IContextMenu2, IContextMenu3), // IID_IContextMenu2
|
|
QITABENTMULTI(CSendToMenu, IContextMenu, IContextMenu3), // IID_IContextMenu
|
|
{ 0 }
|
|
};
|
|
return QISearch(this, qit, riid, ppvObj);
|
|
}
|
|
|
|
ULONG CSendToMenu::AddRef()
|
|
{
|
|
return InterlockedIncrement(&_cRef);
|
|
}
|
|
|
|
ULONG CSendToMenu::Release()
|
|
{
|
|
ASSERT( 0 != _cRef );
|
|
ULONG cRef = InterlockedDecrement(&_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
HRESULT CSendToMenu::GetWindow(HWND *phwnd)
|
|
{
|
|
HRESULT hr = E_INVALIDARG;
|
|
|
|
if (phwnd)
|
|
{
|
|
*phwnd = _hwnd;
|
|
hr = S_OK;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSendToMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
|
|
{
|
|
// if they want the default menu only (CMF_DEFAULTONLY) OR
|
|
// this is being called for a shortcut (CMF_VERBSONLY)
|
|
// we don't want to be on the context menu
|
|
|
|
if (uFlags & (CMF_DEFAULTONLY | CMF_VERBSONLY))
|
|
return S_OK;
|
|
|
|
UINT idMax = idCmdFirst;
|
|
|
|
_hmenu = CreatePopupMenu();
|
|
if (_hmenu)
|
|
{
|
|
TCHAR szSendLinkTo[80];
|
|
TCHAR szSendPageTo[80];
|
|
MENUITEMINFO mii;
|
|
|
|
// add a dummy item so we are identified at WM_INITMENUPOPUP time
|
|
|
|
LoadString(g_hinst, IDS_SENDLINKTO, szSendLinkTo, ARRAYSIZE(szSendLinkTo));
|
|
LoadString(g_hinst, IDS_SENDPAGETO, szSendPageTo, ARRAYSIZE(szSendPageTo));
|
|
|
|
mii.cbSize = sizeof(MENUITEMINFO);
|
|
mii.fMask = MIIM_ID | MIIM_TYPE;
|
|
mii.fType = MFT_STRING;
|
|
mii.dwTypeData = szSendLinkTo;
|
|
mii.wID = idCmdFirst + 1;
|
|
|
|
if (InsertMenuItem(_hmenu, 0, TRUE, &mii))
|
|
{
|
|
_idCmdFirst = idCmdFirst + 1; // remember this for later
|
|
|
|
mii.fType = MFT_STRING;
|
|
mii.dwTypeData = szSendLinkTo;
|
|
mii.wID = idCmdFirst;
|
|
mii.fState = MF_DISABLED | MF_GRAYED;
|
|
mii.fMask = MIIM_TYPE | MIIM_SUBMENU | MIIM_ID;
|
|
mii.hSubMenu = _hmenu;
|
|
|
|
if (InsertMenuItem(hmenu, indexMenu, TRUE, &mii))
|
|
{
|
|
idMax += 0x40; // reserve space for this many items
|
|
_bFirstTime = TRUE; // fill this at WM_INITMENUPOPUP time
|
|
}
|
|
else
|
|
{
|
|
_hmenu = NULL;
|
|
}
|
|
}
|
|
}
|
|
_hmenu = NULL;
|
|
return ResultFromShort(idMax - idCmdFirst);
|
|
}
|
|
|
|
DWORD CSendToMenu::_GetKeyState(void)
|
|
{
|
|
DWORD grfKeyState = MK_LBUTTON; // default
|
|
|
|
if (GetAsyncKeyState(VK_CONTROL) < 0)
|
|
grfKeyState |= MK_CONTROL;
|
|
|
|
if (GetAsyncKeyState(VK_SHIFT) < 0)
|
|
grfKeyState |= MK_SHIFT;
|
|
|
|
if (GetAsyncKeyState(VK_MENU) < 0)
|
|
grfKeyState |= MK_ALT; // menu's don't really allow this
|
|
|
|
return grfKeyState;
|
|
}
|
|
|
|
HRESULT CSendToMenu::_DoDragDrop(HWND hwndParent, IDropTarget *pdrop)
|
|
{
|
|
|
|
DWORD grfKeyState = _GetKeyState();
|
|
if (grfKeyState == MK_LBUTTON)
|
|
{
|
|
// no modifieres, change default to COPY
|
|
grfKeyState = MK_LBUTTON | MK_CONTROL;
|
|
DataObj_SetDWORD(_pdtobj, g_cfPreferredDropEffect, DROPEFFECT_COPY);
|
|
}
|
|
|
|
_hwnd = hwndParent;
|
|
IUnknown_SetSite(pdrop, SAFECAST(this, IOleWindow *)); // Let them have access to our HWND.
|
|
HRESULT hr = SHSimulateDrop(pdrop, _pdtobj, grfKeyState, NULL, NULL);
|
|
IUnknown_SetSite(pdrop, NULL);
|
|
|
|
if (hr == S_FALSE)
|
|
{
|
|
ShellMessageBox(g_hinst, hwndParent,
|
|
MAKEINTRESOURCE(IDS_SENDTO_ERRORMSG),
|
|
MAKEINTRESOURCE(IDS_CABINET),
|
|
MB_OK|MB_ICONEXCLAMATION);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSendToMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (_pdtobj && _pidlLast)
|
|
{
|
|
IDropTarget *pdrop;
|
|
hr = SHGetUIObjectFromFullPIDL(_pidlLast, pici->hwnd, IID_PPV_ARG(IDropTarget, &pdrop));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = _DoDragDrop(pici->hwnd, pdrop);
|
|
pdrop->Release();
|
|
}
|
|
}
|
|
else
|
|
hr = E_INVALIDARG;
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSendToMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
HRESULT CSendToMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
return HandleMenuMsg2(uMsg, wParam, lParam, NULL);
|
|
}
|
|
|
|
BOOL CSendToMenu::_CanDrop(IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
BOOL fCanDrop = FALSE;
|
|
IDropTarget *pdt;
|
|
if (SUCCEEDED(psf->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST *)&pidl, IID_X_PPV_ARG(IDropTarget, 0, &pdt))))
|
|
{
|
|
POINTL pt = {0};
|
|
DWORD dwEffect = DROPEFFECT_COPY;
|
|
|
|
// Do a drag enter, if they return no drop effect then we can't drop
|
|
if (SUCCEEDED(pdt->DragEnter(_pdtobj, _GetKeyState(), pt, &dwEffect)))
|
|
{
|
|
if (dwEffect != DROPEFFECT_NONE)
|
|
fCanDrop = TRUE; // show it!
|
|
pdt->DragLeave();
|
|
}
|
|
pdt->Release();
|
|
}
|
|
return fCanDrop;
|
|
}
|
|
|
|
HRESULT CSendToMenu::_MenuCallback(UINT fmm, IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
switch (fmm)
|
|
{
|
|
case FMM_ADD:
|
|
hr = _CanDrop(psf, pidl) ? S_OK : S_FALSE;
|
|
break;
|
|
|
|
case FMM_SETLASTPIDL:
|
|
Pidl_Set(&_pidlLast, pidl);
|
|
break;
|
|
|
|
default:
|
|
hr = E_FAIL;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSendToMenu::_RemovableDrivesMenuCallback(UINT fmm, IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
switch (fmm)
|
|
{
|
|
case FMM_ADD:
|
|
hr = S_FALSE; // assume we wont show it
|
|
if (_CanDrop(psf, pidl))
|
|
{
|
|
// now we know it's a removable drive. in general we dont want to display cd-rom drives.
|
|
// we know this is the my computer folder so just get the parsing name, we need it for GetDriveType.
|
|
WCHAR szDrive[MAX_PATH];
|
|
if (SUCCEEDED(DisplayNameOf(psf, pidl, SHGDN_FORPARSING, szDrive, ARRAYSIZE(szDrive))))
|
|
{
|
|
CMountPoint *pmtpt = CMountPoint::GetMountPoint(szDrive);
|
|
if (pmtpt)
|
|
{
|
|
if (pmtpt->IsCDROM())
|
|
{
|
|
// of all cdroms, only the enabled burning folder is okay to put on sendto
|
|
WCHAR szRecorder[4];
|
|
if (SUCCEEDED(CDBurn_GetRecorderDriveLetter(szRecorder, ARRAYSIZE(szRecorder))) &&
|
|
(lstrcmpiW(szRecorder, szDrive) == 0))
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
else if (pmtpt->IsFloppy() || pmtpt->IsStrictRemovable() || pmtpt->IsRemovableDevice())
|
|
{
|
|
// also put on removable devices.
|
|
hr = S_OK;
|
|
}
|
|
pmtpt->Release();
|
|
}
|
|
else
|
|
{
|
|
// if this failed it could be a memory condition but its more likely to be that the
|
|
// parsing name doesnt map to a mountpoint. in that case fall back to SFGAO_REMOVABLE
|
|
// to pick up portable audio devices. if this was because of lowmem its no biggie.
|
|
if (SHGetAttributes(psf, pidl, SFGAO_REMOVABLE))
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case FMM_SETLASTPIDL:
|
|
Pidl_Set(&_pidlLast, pidl);
|
|
break;
|
|
|
|
default:
|
|
hr = E_FAIL;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSendToMenu::s_MenuCallback(UINT fmm, LPARAM lParam, IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
return ((CSendToMenu*)lParam)->_MenuCallback(fmm, psf, pidl);
|
|
}
|
|
|
|
HRESULT CSendToMenu::s_RemovableDrivesMenuCallback(UINT fmm, LPARAM lParam, IShellFolder *psf, LPCITEMIDLIST pidl)
|
|
{
|
|
return ((CSendToMenu*)lParam)->_RemovableDrivesMenuCallback(fmm, psf, pidl);
|
|
}
|
|
|
|
HRESULT GetFolder(int csidl, IShellFolder **ppsf)
|
|
{
|
|
LPITEMIDLIST pidl;
|
|
HRESULT hr = SHGetFolderLocation(NULL, csidl, NULL, 0, &pidl);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, pidl, ppsf));
|
|
ILFree(pidl);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSendToMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LRESULT lRes = 0;
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_INITMENUPOPUP:
|
|
if (_bFirstTime)
|
|
{
|
|
_bFirstTime = FALSE;
|
|
|
|
//In case of Shell_MergeMenus
|
|
if (_hmenu == NULL)
|
|
_hmenu = (HMENU)wParam;
|
|
|
|
// delete the dummy entry
|
|
DeleteMenu(_hmenu, 0, MF_BYPOSITION);
|
|
|
|
FMCOMPOSE fmc = {0};
|
|
if (SUCCEEDED(GetFolder(CSIDL_SENDTO, &fmc.psf)))
|
|
{
|
|
fmc.idCmd = _idCmdFirst;
|
|
fmc.grfFlags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS;
|
|
fmc.pfnCallback = s_MenuCallback;
|
|
fmc.lParam = (LPARAM)this; // not reference counted
|
|
|
|
FileMenu_Compose(_hmenu, FMCM_REPLACE, &fmc);
|
|
fmc.psf->Release();
|
|
}
|
|
if (SUCCEEDED(GetFolder(CSIDL_DRIVES, &fmc.psf)))
|
|
{
|
|
fmc.dwMask = FMC_NOEXPAND;
|
|
fmc.idCmd = _idCmdFirst;
|
|
fmc.grfFlags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS;
|
|
fmc.pfnCallback = s_RemovableDrivesMenuCallback;
|
|
fmc.lParam = (LPARAM)this; // not reference counted
|
|
|
|
FileMenu_Compose(_hmenu, FMCM_APPEND, &fmc);
|
|
fmc.psf->Release();
|
|
}
|
|
}
|
|
else if (_hmenu != (HMENU)wParam)
|
|
{
|
|
// secondary cascade menu
|
|
FileMenu_InitMenuPopup((HMENU)wParam);
|
|
}
|
|
break;
|
|
|
|
case WM_DRAWITEM:
|
|
{
|
|
DRAWITEMSTRUCT *pdi = (DRAWITEMSTRUCT *)lParam;
|
|
|
|
if (pdi->CtlType == ODT_MENU && pdi->itemID == _idCmdFirst)
|
|
{
|
|
lRes = FileMenu_DrawItem(NULL, pdi);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_MEASUREITEM:
|
|
{
|
|
MEASUREITEMSTRUCT *pmi = (MEASUREITEMSTRUCT *)lParam;
|
|
|
|
if (pmi->CtlType == ODT_MENU && pmi->itemID == _idCmdFirst)
|
|
{
|
|
lRes = FileMenu_MeasureItem(NULL, pmi);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_MENUCHAR:
|
|
{
|
|
TCHAR ch = (TCHAR)LOWORD(wParam);
|
|
HMENU hmenu = (HMENU)lParam;
|
|
lRes = FileMenu_HandleMenuChar(hmenu, ch);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
hr = E_NOTIMPL;
|
|
break;
|
|
}
|
|
|
|
if (plResult)
|
|
*plResult = lRes;
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CSendToMenu::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
|
|
{
|
|
IUnknown_Set((IUnknown **)&_pdtobj, pdtobj);
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
#define CXIMAGEGAP 6
|
|
|
|
//This is included by shell32/shellprv.h I'm not sure where this is in shdocvw
|
|
#define CCH_KEYMAX 64
|
|
|
|
typedef struct
|
|
{
|
|
// Accessibility info must be first
|
|
MSAAMENUINFO msaa;
|
|
TCHAR chPrefix;
|
|
TCHAR szMenuText[CCH_KEYMAX];
|
|
TCHAR szExt[MAX_PATH];
|
|
TCHAR szClass[CCH_KEYMAX];
|
|
DWORD dwFlags;
|
|
int iImage;
|
|
TCHAR szUserFile[CCH_KEYMAX];
|
|
} NEWOBJECTINFO;
|
|
|
|
typedef struct
|
|
{
|
|
int type;
|
|
void *lpData;
|
|
DWORD cbData;
|
|
HKEY hkeyNew;
|
|
} NEWFILEINFO;
|
|
|
|
typedef struct
|
|
{
|
|
ULONG cbStruct;
|
|
ULONG ver;
|
|
SYSTEMTIME lastupdate;
|
|
} SHELLNEW_CACHE_STAMP;
|
|
|
|
// ShellNew config flags
|
|
#define SNCF_DEFAULT 0x0000
|
|
#define SNCF_NOEXT 0x0001
|
|
#define SNCF_USERFILES 0x0002
|
|
|
|
#define NEWTYPE_DATA 0x0003
|
|
#define NEWTYPE_FILE 0x0004
|
|
#define NEWTYPE_NULL 0x0005
|
|
#define NEWTYPE_COMMAND 0x0006
|
|
#define NEWTYPE_FOLDER 0x0007
|
|
#define NEWTYPE_LINK 0x0008
|
|
|
|
#define NEWITEM_FOLDER 0
|
|
#define NEWITEM_LINK 1
|
|
#define NEWITEM_MAX 2
|
|
|
|
class CNewMenu : public CObjectWithSite,
|
|
public IContextMenu3,
|
|
public IShellExtInit
|
|
{
|
|
// IUnknown
|
|
STDMETHOD(QueryInterface)(REFIID riid, void **ppvObj);
|
|
STDMETHOD_(ULONG,AddRef)(void);
|
|
STDMETHOD_(ULONG,Release)(void);
|
|
|
|
// 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 *pRes, LPSTR pszName, UINT cchMax);
|
|
|
|
// IContextMenu2
|
|
STDMETHOD(HandleMenuMsg)(UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
|
|
// IContextMenu3
|
|
STDMETHOD(HandleMenuMsg2)(UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT *lResult);
|
|
|
|
// IShellExtInit
|
|
STDMETHOD(Initialize)(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID);
|
|
|
|
|
|
LONG _cRef;
|
|
HMENU _hmenu;
|
|
UINT _idCmdFirst;
|
|
HIMAGELIST _himlSystemImageList;
|
|
IDataObject *_pdtobj;
|
|
LPITEMIDLIST _pidlFolder;
|
|
POINT _ptNewItem; // from the view, point of click
|
|
NEWOBJECTINFO *_pnoiLast;
|
|
HDPA _hdpaMenuInfo;
|
|
|
|
CNewMenu();
|
|
~CNewMenu();
|
|
|
|
friend HRESULT CNewMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut);
|
|
|
|
private:
|
|
//Handle Menu messages submitted to HandleMenuMsg
|
|
BOOL DrawItem(DRAWITEMSTRUCT *lpdi);
|
|
LRESULT MeasureItem(MEASUREITEMSTRUCT *pmi);
|
|
BOOL InitMenuPopup(HMENU hMenu);
|
|
|
|
//Internal Helpers
|
|
NEWOBJECTINFO *GetItemData(HMENU hmenu, UINT iItem);
|
|
HRESULT RunCommand(HWND hwnd, LPCTSTR pszPath, LPCTSTR pszRun);
|
|
HRESULT CopyTemplate(IStream *pStream, NEWFILEINFO *pnfi);
|
|
|
|
// Generates it from the Fragment and _pidlFolder
|
|
BOOL _GeneratePidlFromName(LPTSTR pszName, LPITEMIDLIST* ppidl);
|
|
HRESULT _GetItemName(IUnknown *punkFolder, LPWSTR pszItemName, LPWSTR pszPath, UINT cchPath);
|
|
|
|
HRESULT _MatchMenuItem(TCHAR ch, LRESULT* plRes);
|
|
BOOL _InsertNewMenuItem(HMENU hmenu, UINT idCmd, NEWOBJECTINFO *pnoiClone);
|
|
|
|
HRESULT ConsolidateMenuItems(BOOL bForce);
|
|
|
|
HANDLE _hMutex, _hEvent;
|
|
};
|
|
|
|
void GetConfigFlags(HKEY hkey, DWORD * pdwFlags)
|
|
{
|
|
TCHAR szTemp[MAX_PATH];
|
|
DWORD cbData = ARRAYSIZE(szTemp);
|
|
|
|
*pdwFlags = SNCF_DEFAULT;
|
|
|
|
if (SHQueryValueEx(hkey, TEXT("NoExtension"), 0, NULL, (BYTE *)szTemp, &cbData) == ERROR_SUCCESS)
|
|
{
|
|
*pdwFlags |= SNCF_NOEXT;
|
|
}
|
|
}
|
|
|
|
BOOL GetNewFileInfoForKey(HKEY hkeyExt, NEWFILEINFO *pnfi, DWORD * pdwFlags)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
HKEY hKey; // this gets the \\.ext\progid key
|
|
HKEY hkeyNew;
|
|
TCHAR szProgID[80];
|
|
DWORD cbProgID = sizeof(szProgID);
|
|
|
|
// open the Newcommand
|
|
if (SHRegGetValue(hkeyExt, NULL, NULL, SRRF_RT_REG_SZ, NULL, szProgID, &cbProgID) != ERROR_SUCCESS)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (ERROR_SUCCESS != RegOpenKeyEx(hkeyExt, szProgID, 0, KEY_QUERY_VALUE, &hKey))
|
|
{
|
|
hKey = hkeyExt;
|
|
}
|
|
|
|
if (ERROR_SUCCESS == RegOpenKeyEx(hKey, TEXT("ShellNew"), 0, KEY_QUERY_VALUE, &hkeyNew))
|
|
{
|
|
DWORD dwType, cbData;
|
|
TCHAR szTemp[MAX_PATH];
|
|
HKEY hkeyConfig;
|
|
|
|
// Are there any config flags?
|
|
if (pdwFlags)
|
|
{
|
|
|
|
if (ERROR_SUCCESS == RegOpenKeyEx(hkeyNew, TEXT("Config"), 0, KEY_QUERY_VALUE, &hkeyConfig))
|
|
{
|
|
GetConfigFlags(hkeyConfig, pdwFlags);
|
|
RegCloseKey(hkeyConfig);
|
|
}
|
|
else
|
|
{
|
|
*pdwFlags = 0;
|
|
}
|
|
}
|
|
|
|
if (cbData = sizeof(szTemp), (SHQueryValueEx(hkeyNew, TEXT("FileName"), 0, &dwType, (LPBYTE)szTemp, &cbData) == ERROR_SUCCESS)
|
|
&& ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ)))
|
|
{
|
|
fRet = TRUE;
|
|
if (pnfi)
|
|
{
|
|
pnfi->type = NEWTYPE_FILE;
|
|
pnfi->hkeyNew = hkeyNew; // store this away so we can find out which one held the file easily
|
|
ASSERT((LPTSTR*)pnfi->lpData == NULL);
|
|
pnfi->lpData = StrDup(szTemp);
|
|
|
|
hkeyNew = NULL;
|
|
}
|
|
}
|
|
else if (cbData = sizeof(szTemp), (SHQueryValueEx(hkeyNew, TEXT("command"), 0, &dwType, (LPBYTE)szTemp, &cbData) == ERROR_SUCCESS)
|
|
&& ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ)))
|
|
{
|
|
|
|
fRet = TRUE;
|
|
if (pnfi)
|
|
{
|
|
pnfi->type = NEWTYPE_COMMAND;
|
|
pnfi->hkeyNew = hkeyNew; // store this away so we can find out which one held the command easily
|
|
ASSERT((LPTSTR*)pnfi->lpData == NULL);
|
|
pnfi->lpData = StrDup(szTemp);
|
|
hkeyNew = NULL;
|
|
}
|
|
}
|
|
else if ((SHQueryValueEx(hkeyNew, TEXT("Data"), 0, &dwType, NULL, &cbData) == ERROR_SUCCESS) && cbData)
|
|
{
|
|
// yes! the data for a new file is stored in the registry
|
|
fRet = TRUE;
|
|
// do they want the data?
|
|
if (pnfi)
|
|
{
|
|
pnfi->type = NEWTYPE_DATA;
|
|
pnfi->cbData = cbData;
|
|
pnfi->lpData = (void*)LocalAlloc(LPTR, cbData);
|
|
if (pnfi->lpData)
|
|
{
|
|
if (dwType == REG_SZ)
|
|
{
|
|
// Get the Unicode data from the registry.
|
|
LPWSTR pszTemp = (LPWSTR)LocalAlloc(LPTR, cbData);
|
|
if (pszTemp)
|
|
{
|
|
SHQueryValueEx(hkeyNew, TEXT("Data"), 0, &dwType, (LPBYTE)pszTemp, &cbData);
|
|
|
|
pnfi->cbData = SHUnicodeToAnsi(pszTemp, (LPSTR)pnfi->lpData, cbData);
|
|
if (pnfi->cbData == 0)
|
|
{
|
|
LocalFree(pnfi->lpData);
|
|
pnfi->lpData = NULL;
|
|
}
|
|
|
|
LocalFree(pszTemp);
|
|
}
|
|
else
|
|
{
|
|
LocalFree(pnfi->lpData);
|
|
pnfi->lpData = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SHQueryValueEx(hkeyNew, TEXT("Data"), 0, &dwType, (BYTE*)pnfi->lpData, &cbData);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (cbData = sizeof(szTemp), (SHQueryValueEx(hkeyNew, TEXT("NullFile"), 0, &dwType, (LPBYTE)szTemp, &cbData) == ERROR_SUCCESS))
|
|
{
|
|
fRet = TRUE;
|
|
if (pnfi)
|
|
{
|
|
pnfi->type = NEWTYPE_NULL;
|
|
pnfi->cbData = 0;
|
|
pnfi->lpData = NULL;
|
|
}
|
|
}
|
|
|
|
if (hkeyNew)
|
|
RegCloseKey(hkeyNew);
|
|
}
|
|
|
|
if (hKey != hkeyExt)
|
|
{
|
|
RegCloseKey(hKey);
|
|
}
|
|
return fRet;
|
|
}
|
|
|
|
BOOL GetNewFileInfoForExtension(NEWOBJECTINFO *pnoi, NEWFILEINFO *pnfi, HKEY* phKey, LPINT piIndex)
|
|
{
|
|
TCHAR szValue[80];
|
|
DWORD lSize = sizeof(szValue);
|
|
HKEY hkeyNew;
|
|
BOOL fRet = FALSE;;
|
|
|
|
if (phKey && ((*phKey) == (HKEY)-1))
|
|
{
|
|
// we're done
|
|
return FALSE;
|
|
}
|
|
|
|
// do the ShellNew key stuff if there's no phKey passed in (which means
|
|
// use the info in pnoi to get THE one) and there's no UserFile specified.
|
|
//
|
|
// if there IS a UserFile specified, then it's a file, and that szUserFile points to it..
|
|
if (!phKey && !pnoi->szUserFile[0] ||
|
|
(phKey && !*phKey))
|
|
{
|
|
// check the new keys under the class id (if any)
|
|
TCHAR szSubKey[128];
|
|
HRESULT hr;
|
|
|
|
hr = StringCchPrintf(szSubKey, ARRAYSIZE(szSubKey), TEXT("%s\\CLSID"), pnoi->szClass);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
lSize = sizeof(szValue);
|
|
if (SHRegGetValue(HKEY_CLASSES_ROOT, szSubKey, NULL, SRRF_RT_REG_SZ, NULL, szValue, &lSize) == ERROR_SUCCESS)
|
|
{
|
|
hr = StringCchPrintf(szSubKey, ARRAYSIZE(szSubKey), TEXT("CLSID\\%s"), szValue);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
lSize = sizeof(szValue);
|
|
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, szSubKey, 0, KEY_QUERY_VALUE, &hkeyNew))
|
|
{
|
|
fRet = GetNewFileInfoForKey(hkeyNew, pnfi, &pnoi->dwFlags);
|
|
RegCloseKey(hkeyNew);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// otherwise check under the type extension... do the extension, not the type
|
|
// so that multi-ext to 1 type will work right
|
|
if (!fRet && (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, pnoi->szExt, 0, KEY_QUERY_VALUE, &hkeyNew)))
|
|
{
|
|
fRet = GetNewFileInfoForKey(hkeyNew, pnfi, &pnoi->dwFlags);
|
|
RegCloseKey(hkeyNew);
|
|
}
|
|
|
|
if (phKey)
|
|
{
|
|
// if we're iterating, then we've got to open the key now...
|
|
hr = StringCchPrintf(szSubKey, ARRAYSIZE(szSubKey), TEXT("%s\\%s\\ShellNew\\FileName"), pnoi->szExt, pnoi->szClass);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, szSubKey, 0, KEY_QUERY_VALUE, phKey))
|
|
{
|
|
*piIndex = 0;
|
|
|
|
// if we didn't find one of the default ones above,
|
|
// try it now
|
|
// otherwise just return success or failure on fRet
|
|
if (!fRet)
|
|
{
|
|
goto Iterate;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*phKey = (HKEY)-1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*phKey = (HKEY)-1;
|
|
}
|
|
}
|
|
}
|
|
else if (!phKey && pnoi->szUserFile[0])
|
|
{
|
|
// there's no key, so just return info about szUserFile
|
|
pnfi->type = NEWTYPE_FILE;
|
|
pnfi->lpData = StrDup(pnoi->szUserFile);
|
|
pnfi->hkeyNew = NULL;
|
|
|
|
fRet = TRUE;
|
|
}
|
|
else if (phKey)
|
|
{
|
|
DWORD dwSize;
|
|
DWORD dwData;
|
|
DWORD dwType;
|
|
// we're iterating through...
|
|
|
|
Iterate:
|
|
|
|
dwSize = ARRAYSIZE(pnoi->szUserFile);
|
|
dwData = ARRAYSIZE(pnoi->szMenuText);
|
|
|
|
if (RegEnumValue(*phKey, *piIndex, pnoi->szUserFile, &dwSize, NULL,
|
|
&dwType, (LPBYTE)pnoi->szMenuText, &dwData) == ERROR_SUCCESS)
|
|
{
|
|
(*piIndex)++;
|
|
// if there's something more than the null..
|
|
if (dwData <= 1)
|
|
{
|
|
HRESULT hr = StringCchCopy(pnoi->szMenuText, ARRAYSIZE(pnoi->szMenuText), PathFindFileName(pnoi->szUserFile));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
PathRemoveExtension(pnoi->szMenuText);
|
|
}
|
|
else
|
|
{
|
|
pnoi->szMenuText[0] = TEXT('\0');
|
|
}
|
|
}
|
|
fRet = TRUE;
|
|
}
|
|
else
|
|
{
|
|
RegCloseKey(*phKey);
|
|
*phKey = (HKEY)-1;
|
|
fRet = FALSE;
|
|
}
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
#define SHELLNEW_CONSOLIDATION_MUTEX TEXT("ShellNewConsolidationMutex")
|
|
#define SHELLNEW_CONSOLIDATION_EVENT TEXT("ShellNewConsolidationEvent")
|
|
|
|
CNewMenu::CNewMenu() :
|
|
_cRef(1),
|
|
_hMutex(CreateMutex(NULL, FALSE, SHELLNEW_CONSOLIDATION_MUTEX)),
|
|
_hEvent(CreateEvent(NULL, FALSE, FALSE, SHELLNEW_CONSOLIDATION_EVENT))
|
|
{
|
|
DllAddRef();
|
|
ASSERT(_pnoiLast == NULL);
|
|
}
|
|
|
|
CNewMenu::~CNewMenu()
|
|
{
|
|
if (_hdpaMenuInfo)
|
|
{
|
|
// we dont own the lifetime of _hmenu, and it gets destroyed before the destructor
|
|
// is called. thus maintain the lifetime of our NEWOBJECTINFO data in a dpa.
|
|
for (int i = 0; i < DPA_GetPtrCount(_hdpaMenuInfo); i++)
|
|
{
|
|
NEWOBJECTINFO *pNewObjInfo = (NEWOBJECTINFO *)DPA_GetPtr(_hdpaMenuInfo, i);
|
|
LocalFree(pNewObjInfo);
|
|
}
|
|
DPA_Destroy(_hdpaMenuInfo);
|
|
}
|
|
|
|
ILFree(_pidlFolder);
|
|
|
|
if (_pdtobj)
|
|
_pdtobj->Release();
|
|
|
|
if (_hMutex)
|
|
{
|
|
CloseHandle(_hMutex);
|
|
}
|
|
if (_hEvent)
|
|
{
|
|
CloseHandle(_hEvent);
|
|
}
|
|
|
|
DllRelease();
|
|
}
|
|
|
|
HRESULT CNewMenu_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppvOut)
|
|
{
|
|
// aggregation checking is handled in class factory
|
|
*ppvOut = NULL;
|
|
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
CNewMenu * pShellNew = new CNewMenu();
|
|
if (pShellNew)
|
|
{
|
|
if (!pShellNew->_hMutex || !pShellNew->_hEvent)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
else
|
|
{
|
|
hr = pShellNew->QueryInterface(riid, ppvOut);
|
|
}
|
|
pShellNew->Release();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNewMenu::QueryInterface(REFIID riid, void **ppvObj)
|
|
{
|
|
static const QITAB qit[] = {
|
|
QITABENT(CNewMenu, IShellExtInit), // IID_IShellExtInit
|
|
QITABENT(CNewMenu, IContextMenu3), // IID_IContextMenu3
|
|
QITABENTMULTI(CNewMenu, IContextMenu2, IContextMenu3), // IID_IContextMenu2
|
|
QITABENTMULTI(CNewMenu, IContextMenu, IContextMenu3), // IID_IContextMenu
|
|
QITABENT(CNewMenu, IObjectWithSite), // IID_IObjectWithSite
|
|
{ 0 }
|
|
};
|
|
return QISearch(this, qit, riid, ppvObj);
|
|
}
|
|
|
|
ULONG CNewMenu::AddRef()
|
|
{
|
|
return InterlockedIncrement(&_cRef);
|
|
}
|
|
|
|
ULONG CNewMenu::Release()
|
|
{
|
|
ASSERT( 0 != _cRef );
|
|
ULONG cRef = InterlockedDecrement(&_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
HRESULT CNewMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
|
|
{
|
|
// if they want the default menu only (CMF_DEFAULTONLY) OR
|
|
// this is being called for a shortcut (CMF_VERBSONLY)
|
|
// we don't want to be on the context menu
|
|
MENUITEMINFO mfi = {0};
|
|
|
|
if (uFlags & (CMF_DEFAULTONLY | CMF_VERBSONLY))
|
|
return S_OK;
|
|
|
|
ConsolidateMenuItems(FALSE);
|
|
|
|
_idCmdFirst = idCmdFirst+2;
|
|
TCHAR szNewMenu[80];
|
|
LoadString(g_hinst, IDS_NEWMENU, szNewMenu, ARRAYSIZE(szNewMenu));
|
|
|
|
// HACK: I assume that they are querying during a WM_INITMENUPOPUP or equivalent
|
|
GetCursorPos(&_ptNewItem);
|
|
|
|
_hmenu = CreatePopupMenu();
|
|
mfi.cbSize = sizeof(MENUITEMINFO);
|
|
mfi.fMask = MIIM_ID | MIIM_TYPE;
|
|
mfi.wID = idCmdFirst+1;
|
|
mfi.fType = MFT_STRING;
|
|
mfi.dwTypeData = szNewMenu;
|
|
|
|
InsertMenuItem(_hmenu, 0, TRUE, &mfi);
|
|
|
|
ZeroMemory(&mfi, sizeof (mfi));
|
|
mfi.cbSize = sizeof(MENUITEMINFO);
|
|
mfi.fMask = MIIM_ID | MIIM_SUBMENU | MIIM_TYPE | MIIM_DATA;
|
|
mfi.fType = MFT_STRING;
|
|
mfi.wID = idCmdFirst;
|
|
mfi.hSubMenu = _hmenu;
|
|
mfi.dwTypeData = szNewMenu;
|
|
mfi.dwItemData = 0;
|
|
|
|
InsertMenuItem(hmenu, indexMenu, TRUE, &mfi);
|
|
|
|
_hmenu = NULL;
|
|
return ResultFromShort(_idCmdFirst - idCmdFirst + 1);
|
|
}
|
|
|
|
// This is almost the same as ILCreatePidlFromPath, but
|
|
// uses only the filename from the full path pszPath and
|
|
// the _pidlFolder to generate the pidl. This is used because
|
|
// when creating an item in Desktop\My Documents, it used to create a
|
|
// full pidl c:\documents and Settings\lamadio\My Documents\New folder
|
|
// instead of the pidl desktop\my documents\New Folder.
|
|
BOOL CNewMenu::_GeneratePidlFromName(LPTSTR pszFile, LPITEMIDLIST* ppidl)
|
|
{
|
|
*ppidl = NULL; // Out param
|
|
|
|
IShellFolder* psf;
|
|
if (SUCCEEDED(SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, _pidlFolder, &psf))))
|
|
{
|
|
LPITEMIDLIST pidlItem;
|
|
|
|
if (SUCCEEDED(psf->ParseDisplayName(NULL, NULL, pszFile, NULL, &pidlItem, NULL)))
|
|
{
|
|
*ppidl = ILCombine(_pidlFolder, pidlItem);
|
|
ILFree(pidlItem);
|
|
}
|
|
|
|
psf->Release();
|
|
}
|
|
|
|
return BOOLFROMPTR(*ppidl);
|
|
}
|
|
|
|
HRESULT CNewMenu::_GetItemName(IUnknown *punkFolder, LPWSTR pszItemName, LPWSTR pszPath, UINT cchPath)
|
|
{
|
|
// we need to pick up the name by asking the folder about the item,
|
|
// not by pathappending to the folder's path.
|
|
IShellFolder *psf;
|
|
HRESULT hr = punkFolder->QueryInterface(IID_PPV_ARG(IShellFolder, &psf));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
LPITEMIDLIST pidlFile;
|
|
hr = psf->ParseDisplayName(NULL, NULL, pszItemName, NULL, &pidlFile, NULL);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = DisplayNameOf(psf, pidlFile, SHGDN_FORPARSING, pszPath, cchPath);
|
|
ILFree(pidlFile);
|
|
}
|
|
psf->Release();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
const ICIVERBTOIDMAP c_IDMap[] =
|
|
{
|
|
{ L"NewFolder", "NewFolder", NEWITEM_FOLDER, NEWITEM_FOLDER, },
|
|
{ L"link", "link", NEWITEM_LINK, NEWITEM_LINK, },
|
|
};
|
|
|
|
HRESULT CNewMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
DWORD dwFlags;
|
|
|
|
if (IS_INTRESOURCE(pici->lpVerb) && _pnoiLast)
|
|
dwFlags = _pnoiLast->dwFlags;
|
|
else
|
|
{
|
|
UINT uID;
|
|
if (SUCCEEDED(SHMapICIVerbToCmdID(pici, c_IDMap, ARRAYSIZE(c_IDMap), &uID)))
|
|
{
|
|
switch (uID)
|
|
{
|
|
case NEWITEM_FOLDER:
|
|
dwFlags = NEWTYPE_FOLDER;
|
|
break;
|
|
case NEWITEM_LINK:
|
|
dwFlags = NEWTYPE_LINK;
|
|
break;
|
|
default:
|
|
ASSERTMSG(0, "should not get what we don't put on the menu");
|
|
return E_FAIL;
|
|
}
|
|
}
|
|
}
|
|
|
|
TCHAR szFileSpec[MAX_PATH+80]; // Add some slop incase we overflow
|
|
TCHAR szTemp[MAX_PATH+80]; // Add some slop incase we overflow
|
|
|
|
//See if the pidl is folder shortcut and if so get the target path.
|
|
SHGetTargetFolderPath(_pidlFolder, szTemp, ARRAYSIZE(szTemp));
|
|
BOOL fLFN = IsLFNDrive(szTemp);
|
|
|
|
NEWFILEINFO nfi;
|
|
DWORD dwErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);
|
|
|
|
nfi.lpData = NULL;
|
|
nfi.hkeyNew = NULL;
|
|
|
|
switch (dwFlags)
|
|
{
|
|
case NEWTYPE_FOLDER:
|
|
LoadString(g_hinst, fLFN ? IDS_FOLDERLONGPLATE : IDS_FOLDERTEMPLATE, szFileSpec, ARRAYSIZE(szFileSpec));
|
|
break;
|
|
|
|
case NEWTYPE_LINK:
|
|
LoadString(g_hinst, IDS_NEWLINKTEMPLATE, szFileSpec, ARRAYSIZE(szFileSpec));
|
|
break;
|
|
|
|
default:
|
|
LoadString(g_hinst, IDS_NEWFILEPREFIX, szFileSpec, ARRAYSIZE(szFileSpec));
|
|
|
|
//
|
|
// If we are running on a mirrored BiDi localized system,
|
|
// then flip the order of concatenation so that the
|
|
// string is read properly for Arabic. [samera]
|
|
//
|
|
if (IS_BIDI_LOCALIZED_SYSTEM())
|
|
{
|
|
StringCchCopy(szTemp, ARRAYSIZE(szTemp), szFileSpec); // ok to truncate, for display only
|
|
StringCchPrintf(szFileSpec, ARRAYSIZE(szFileSpec), TEXT("%s %s"), _pnoiLast->szMenuText, szTemp); // ok to truncate, for display only
|
|
}
|
|
else
|
|
{
|
|
StringCchCat(szFileSpec, ARRAYSIZE(szFileSpec), _pnoiLast->szMenuText); // ok to truncate, for display only
|
|
}
|
|
SHStripMneumonic(szFileSpec);
|
|
|
|
if (!(dwFlags & SNCF_NOEXT))
|
|
{
|
|
StringCchCat(szFileSpec, ARRAYSIZE(szFileSpec), _pnoiLast->szExt); // ok to truncate, for display only
|
|
}
|
|
break;
|
|
}
|
|
|
|
BOOL fCreateStorage = (dwFlags == NEWTYPE_FOLDER);
|
|
|
|
//See if the pidl is folder shortcut and if so get the target pidl.
|
|
LPITEMIDLIST pidlTarget;
|
|
hr = SHGetTargetFolderIDList(_pidlFolder, &pidlTarget);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
IStorage * pStorage;
|
|
hr = StgBindToObject(pidlTarget, STGM_READWRITE, IID_PPV_ARG(IStorage, &pStorage));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
IStream *pStreamCreated = NULL;
|
|
IStorage *pStorageCreated = NULL;
|
|
|
|
STATSTG statstg = { 0 };
|
|
if (fCreateStorage)
|
|
{
|
|
hr = StgMakeUniqueName(pStorage, szFileSpec, IID_PPV_ARG(IStorage, &pStorageCreated));
|
|
if (SUCCEEDED(hr))
|
|
pStorageCreated->Stat(&statstg, STATFLAG_DEFAULT);
|
|
}
|
|
else
|
|
{
|
|
hr = StgMakeUniqueName(pStorage, szFileSpec, IID_PPV_ARG(IStream, &pStreamCreated));
|
|
if (SUCCEEDED(hr))
|
|
pStreamCreated->Stat(&statstg, STATFLAG_DEFAULT);
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
switch (dwFlags)
|
|
{
|
|
case NEWTYPE_FOLDER:
|
|
// we're already done.
|
|
break;
|
|
|
|
case NEWTYPE_LINK:
|
|
if (statstg.pwcsName)
|
|
{
|
|
// Lookup Command in Registry under key HKCR/.lnk/ShellNew/Command
|
|
TCHAR szCommand[MAX_PATH];
|
|
DWORD dwLength = sizeof(szCommand);
|
|
if (ERROR_SUCCESS == SHGetValue(HKEY_CLASSES_ROOT,
|
|
TEXT(".lnk\\ShellNew"), TEXT("Command"), NULL, szCommand, &dwLength))
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
hr = _GetItemName(SAFECAST(pStorage, IUnknown*), statstg.pwcsName, szPath, ARRAYSIZE(szPath));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = RunCommand(pici->hwnd, szPath, szCommand);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (GetNewFileInfoForExtension(_pnoiLast, &nfi, NULL, NULL))
|
|
{
|
|
switch (nfi.type)
|
|
{
|
|
case NEWTYPE_FILE:
|
|
hr = CopyTemplate(pStreamCreated, &nfi);
|
|
break;
|
|
|
|
case NEWTYPE_NULL:
|
|
// already created a zero-length file.
|
|
break;
|
|
|
|
case NEWTYPE_DATA:
|
|
ULONG ulWritten;
|
|
hr = pStreamCreated->Write(nfi.lpData, nfi.cbData, &ulWritten);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pStreamCreated->Commit(STGC_DEFAULT);
|
|
}
|
|
break;
|
|
|
|
case NEWTYPE_COMMAND:
|
|
if (statstg.pwcsName)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
hr = _GetItemName(SAFECAST(pStorage, IUnknown*), statstg.pwcsName, szPath, ARRAYSIZE(szPath));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// oops, we already created the stream, but we actually
|
|
// just wanted the filename for the RunCommand, so we
|
|
// have to delete it first.
|
|
ATOMICRELEASE(pStreamCreated);
|
|
hr = pStorage->DestroyElement(statstg.pwcsName);
|
|
// flush out any notifications from the destroy (the
|
|
// destroy causes notifications).
|
|
SHChangeNotifyHandleEvents();
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = RunCommand(pici->hwnd, szPath, (LPTSTR)nfi.lpData);
|
|
if (hr == S_FALSE)
|
|
hr = S_OK;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
hr = E_FAIL;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_BADKEY);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// these have to be released before _GeneratePidlFromName, since that opens
|
|
// the storage in exclusive mode for other reasons. but we can't release
|
|
// them earlier because CopyTemplate might need them.
|
|
if (pStorageCreated)
|
|
pStorageCreated->Release();
|
|
if (pStreamCreated)
|
|
pStreamCreated->Release();
|
|
if (SUCCEEDED(hr))
|
|
hr = pStorage->Commit(STGC_DEFAULT);
|
|
pStorage->Release();
|
|
|
|
LPITEMIDLIST pidlCreatedItem;
|
|
if (SUCCEEDED(hr) &&
|
|
_GeneratePidlFromName(statstg.pwcsName, &pidlCreatedItem))
|
|
{
|
|
SHChangeNotifyHandleEvents();
|
|
IShellView2 *psv2;
|
|
if (SUCCEEDED(IUnknown_QueryService(_punkSite, SID_SFolderView, IID_PPV_ARG(IShellView2, &psv2))))
|
|
{
|
|
DWORD dwFlagsSelFlags = SVSI_SELECT | SVSI_POSITIONITEM;
|
|
|
|
if (!(dwFlags & NEWTYPE_LINK))
|
|
dwFlagsSelFlags |= SVSI_EDIT;
|
|
|
|
psv2->SelectAndPositionItem(ILFindLastID(pidlCreatedItem), dwFlagsSelFlags, NULL);
|
|
psv2->Release();
|
|
}
|
|
ILFree(pidlCreatedItem);
|
|
}
|
|
|
|
CoTaskMemFree(statstg.pwcsName);
|
|
}
|
|
else
|
|
{
|
|
pStorage->Release();
|
|
}
|
|
}
|
|
|
|
ILFree(pidlTarget);
|
|
}
|
|
|
|
if (nfi.lpData)
|
|
LocalFree((HLOCAL)nfi.lpData);
|
|
|
|
if (nfi.hkeyNew)
|
|
RegCloseKey(nfi.hkeyNew);
|
|
|
|
if (FAILED_AND_NOT_CANCELED(hr) && !(pici->fMask & CMIC_MASK_FLAG_NO_UI))
|
|
{
|
|
TCHAR szTitle[MAX_PATH];
|
|
|
|
LoadString(g_hinst, (fCreateStorage ? IDS_DIRCREATEFAILED_TITLE : IDS_FILECREATEFAILED_TITLE), szTitle, ARRAYSIZE(szTitle));
|
|
SHSysErrorMessageBox(pici->hwnd, szTitle, fCreateStorage ? IDS_CANNOTCREATEFOLDER : IDS_CANNOTCREATEFILE,
|
|
HRESULT_CODE(hr), szFileSpec, MB_OK | MB_ICONEXCLAMATION);
|
|
}
|
|
|
|
SetErrorMode(dwErrorMode);
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNewMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pRes, LPSTR pszName, UINT cchMax)
|
|
{
|
|
switch (uType)
|
|
{
|
|
case GCS_HELPTEXT:
|
|
if (idCmd < NEWITEM_MAX)
|
|
{
|
|
LoadString(g_hinst, (UINT)(IDS_NEWHELP_FIRST + idCmd), (LPTSTR)pszName, cchMax);
|
|
return S_OK;
|
|
}
|
|
break;
|
|
case GCS_HELPTEXTA:
|
|
if (idCmd < NEWITEM_MAX)
|
|
{
|
|
LoadStringA(g_hinst, (UINT)(IDS_NEWHELP_FIRST + idCmd), pszName, cchMax);
|
|
return S_OK;
|
|
}
|
|
break;
|
|
|
|
case GCS_VERBW:
|
|
case GCS_VERBA:
|
|
return SHMapCmdIDToVerb(idCmd, c_IDMap, ARRAYSIZE(c_IDMap), pszName, cchMax, (GCS_VERBW == uType));
|
|
}
|
|
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
//Defined in fsmenu.obj
|
|
BOOL _MenuCharMatch(LPCTSTR lpsz, TCHAR ch, BOOL fIgnoreAmpersand);
|
|
|
|
HRESULT CNewMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
return HandleMenuMsg2(uMsg,wParam,lParam,NULL);
|
|
}
|
|
|
|
HRESULT CNewMenu::_MatchMenuItem(TCHAR ch, LRESULT* plRes)
|
|
{
|
|
// If plRes is NULL we're being called on HandleMenuMsg() which
|
|
// doesn't support returning an LRESULT, which is needed for WM_MENUCHAR...
|
|
if (plRes == NULL)
|
|
return S_FALSE;
|
|
|
|
int iLastSelectedItem = -1;
|
|
int iNextMatch = -1;
|
|
BOOL fMoreThanOneMatch = FALSE;
|
|
int c = GetMenuItemCount(_hmenu);
|
|
|
|
// Pass 1: Locate the Selected Item
|
|
for (int i = 0; i < c; i++)
|
|
{
|
|
MENUITEMINFO mii = {0};
|
|
mii.cbSize = sizeof(mii);
|
|
mii.fMask = MIIM_STATE;
|
|
if (GetMenuItemInfo(_hmenu, i, MF_BYPOSITION, &mii))
|
|
{
|
|
if (mii.fState & MFS_HILITE)
|
|
{
|
|
iLastSelectedItem = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass 2: Starting from the selected item, locate the first item with the matching name.
|
|
for (i = iLastSelectedItem + 1; i < c; i++)
|
|
{
|
|
MENUITEMINFO mii = {0};
|
|
mii.cbSize = sizeof(mii);
|
|
mii.fMask = MIIM_DATA | MIIM_STATE;
|
|
if (GetMenuItemInfo(_hmenu, i, MF_BYPOSITION, &mii))
|
|
{
|
|
NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)mii.dwItemData;
|
|
if (pnoi && _MenuCharMatch(pnoi->szMenuText, ch, FALSE))
|
|
{
|
|
_pnoiLast = pnoi;
|
|
|
|
if (iNextMatch != -1)
|
|
{
|
|
fMoreThanOneMatch = TRUE;
|
|
break; // We found all the info we need
|
|
}
|
|
else
|
|
{
|
|
iNextMatch = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass 3: If we did not find a match, or if there was only one match
|
|
// Search from the first item, to the Selected Item
|
|
if (iNextMatch == -1 || fMoreThanOneMatch == FALSE)
|
|
{
|
|
for (i = 0; i <= iLastSelectedItem; i++)
|
|
{
|
|
MENUITEMINFO mii = {0};
|
|
mii.cbSize = sizeof(mii);
|
|
mii.fMask = MIIM_DATA | MIIM_STATE;
|
|
if (GetMenuItemInfo(_hmenu, i, MF_BYPOSITION, &mii))
|
|
{
|
|
NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)mii.dwItemData;
|
|
if (pnoi && _MenuCharMatch(pnoi->szMenuText, ch, FALSE))
|
|
{
|
|
_pnoiLast = pnoi;
|
|
if (iNextMatch != -1)
|
|
{
|
|
fMoreThanOneMatch = TRUE;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
iNextMatch = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (iNextMatch != -1)
|
|
{
|
|
*plRes = MAKELONG(iNextMatch, fMoreThanOneMatch? MNC_SELECT : MNC_EXECUTE);
|
|
}
|
|
else
|
|
{
|
|
*plRes = MAKELONG(0, MNC_IGNORE);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CNewMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam,LRESULT *plResult)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LRESULT lRes = 0;
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_INITMENUPOPUP:
|
|
if (_hmenu == NULL)
|
|
{
|
|
_hmenu = (HMENU)wParam;
|
|
}
|
|
|
|
InitMenuPopup(_hmenu);
|
|
break;
|
|
|
|
case WM_DRAWITEM:
|
|
DrawItem((DRAWITEMSTRUCT *)lParam);
|
|
break;
|
|
|
|
case WM_MEASUREITEM:
|
|
lRes = MeasureItem((MEASUREITEMSTRUCT *)lParam);
|
|
break;
|
|
|
|
case WM_MENUCHAR:
|
|
hr = _MatchMenuItem((TCHAR)LOWORD(wParam), &lRes);
|
|
break;
|
|
|
|
default:
|
|
hr = E_NOTIMPL;
|
|
break;
|
|
}
|
|
|
|
if (plResult)
|
|
*plResult = lRes;
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CNewMenu::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
|
|
{
|
|
ASSERT(_pidlFolder == NULL);
|
|
_pidlFolder = ILClone(pidlFolder);
|
|
|
|
IUnknown_Set((IUnknown **)&_pdtobj, pdtobj);
|
|
return S_OK;
|
|
}
|
|
|
|
BOOL CNewMenu::DrawItem(DRAWITEMSTRUCT *lpdi)
|
|
{
|
|
BOOL fFlatMenu = FALSE;
|
|
BOOL fFrameRect = FALSE;
|
|
|
|
SystemParametersInfo(SPI_GETFLATMENU, 0, (PVOID)&fFlatMenu, 0);
|
|
|
|
if ((lpdi->itemAction & ODA_SELECT) || (lpdi->itemAction & ODA_DRAWENTIRE))
|
|
{
|
|
int x, y;
|
|
SIZE sz;
|
|
NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)lpdi->itemData;
|
|
|
|
// Draw the image (if there is one).
|
|
|
|
GetTextExtentPoint(lpdi->hDC, pnoi->szMenuText, lstrlen(pnoi->szMenuText), &sz);
|
|
|
|
if (lpdi->itemState & ODS_SELECTED)
|
|
{
|
|
// REVIEW HACK - keep track of the last selected item.
|
|
_pnoiLast = pnoi;
|
|
if (fFlatMenu)
|
|
{
|
|
fFrameRect = TRUE;
|
|
SetBkColor(lpdi->hDC, GetSysColor(COLOR_MENUHILIGHT));
|
|
SetTextColor(lpdi->hDC, GetSysColor(COLOR_MENUTEXT));
|
|
FillRect(lpdi->hDC,&lpdi->rcItem,GetSysColorBrush(COLOR_MENUHILIGHT));
|
|
}
|
|
else
|
|
{
|
|
SetBkColor(lpdi->hDC, GetSysColor(COLOR_HIGHLIGHT));
|
|
SetTextColor(lpdi->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
|
|
FillRect(lpdi->hDC,&lpdi->rcItem,GetSysColorBrush(COLOR_HIGHLIGHT));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetTextColor(lpdi->hDC, GetSysColor(COLOR_MENUTEXT));
|
|
FillRect(lpdi->hDC,&lpdi->rcItem,GetSysColorBrush(COLOR_MENU));
|
|
}
|
|
|
|
RECT rc = lpdi->rcItem;
|
|
rc.left += +2*CXIMAGEGAP+g_cxSmIcon;
|
|
|
|
|
|
DrawText(lpdi->hDC,pnoi->szMenuText,lstrlen(pnoi->szMenuText),
|
|
&rc,DT_SINGLELINE|DT_VCENTER);
|
|
if (pnoi->iImage != -1)
|
|
{
|
|
x = lpdi->rcItem.left+CXIMAGEGAP;
|
|
y = (lpdi->rcItem.bottom+lpdi->rcItem.top-g_cySmIcon)/2;
|
|
HIMAGELIST himlSmall;
|
|
Shell_GetImageLists(NULL, &himlSmall);
|
|
ImageList_Draw(himlSmall, pnoi->iImage, lpdi->hDC, x, y, ILD_TRANSPARENT);
|
|
}
|
|
else
|
|
{
|
|
x = lpdi->rcItem.left+CXIMAGEGAP;
|
|
y = (lpdi->rcItem.bottom+lpdi->rcItem.top-g_cySmIcon)/2;
|
|
}
|
|
|
|
if (fFrameRect)
|
|
{
|
|
HBRUSH hbrFill = (HBRUSH)GetSysColorBrush(COLOR_HIGHLIGHT);
|
|
HBRUSH hbrSave = (HBRUSH)SelectObject(lpdi->hDC, hbrFill);
|
|
int x = lpdi->rcItem.left;
|
|
int y = lpdi->rcItem.top;
|
|
int cx = lpdi->rcItem.right - x - 1;
|
|
int cy = lpdi->rcItem.bottom - y - 1;
|
|
|
|
PatBlt(lpdi->hDC, x, y, 1, cy, PATCOPY);
|
|
PatBlt(lpdi->hDC, x + 1, y, cx, 1, PATCOPY);
|
|
PatBlt(lpdi->hDC, x, y + cy, cx, 1, PATCOPY);
|
|
PatBlt(lpdi->hDC, x + cx, y + 1, 1, cy, PATCOPY);
|
|
|
|
SelectObject(lpdi->hDC, hbrSave);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
LRESULT CNewMenu::MeasureItem(MEASUREITEMSTRUCT *pmi)
|
|
{
|
|
LRESULT lres = FALSE;
|
|
NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)pmi->itemData;
|
|
if (pnoi)
|
|
{
|
|
// 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, pnoi->szMenuText, lstrlen(pnoi->szMenuText), &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;
|
|
}
|
|
|
|
BOOL GetClassDisplayName(LPTSTR pszClass,LPTSTR pszDisplayName,DWORD cchDisplayName)
|
|
{
|
|
DWORD cch;
|
|
|
|
return SUCCEEDED(AssocQueryString(0, ASSOCSTR_COMMAND, pszClass, TEXT("open"), NULL, &cch)) &&
|
|
SUCCEEDED(AssocQueryString(0, ASSOCSTR_FRIENDLYDOCNAME, pszClass, NULL, pszDisplayName, &cchDisplayName));
|
|
}
|
|
|
|
// New Menu item consolidation worker task
|
|
class CNewMenuConsolidator : public CRunnableTask
|
|
{
|
|
public:
|
|
virtual STDMETHODIMP RunInitRT(void);
|
|
static const GUID _taskid;
|
|
|
|
static HRESULT CreateInstance(REFIID riid, void **ppv);
|
|
|
|
private:
|
|
CNewMenuConsolidator();
|
|
~CNewMenuConsolidator();
|
|
|
|
HANDLE _hMutex, _hEvent;
|
|
};
|
|
|
|
|
|
CNewMenuConsolidator::CNewMenuConsolidator() :
|
|
CRunnableTask(RTF_DEFAULT),
|
|
_hMutex(CreateMutex(NULL, FALSE, SHELLNEW_CONSOLIDATION_MUTEX)),
|
|
_hEvent(CreateEvent(NULL, FALSE, FALSE, SHELLNEW_CONSOLIDATION_EVENT))
|
|
{
|
|
DllAddRef();
|
|
}
|
|
|
|
CNewMenuConsolidator::~CNewMenuConsolidator()
|
|
{
|
|
if (_hMutex)
|
|
{
|
|
CloseHandle(_hMutex);
|
|
}
|
|
if (_hEvent)
|
|
{
|
|
CloseHandle(_hEvent);
|
|
}
|
|
DllRelease();
|
|
}
|
|
|
|
//
|
|
// Instance generator.
|
|
//
|
|
HRESULT CNewMenuConsolidator::CreateInstance(REFIID riid, void **ppv)
|
|
{
|
|
HRESULT hr = E_OUTOFMEMORY;
|
|
CNewMenuConsolidator *pnmc = new CNewMenuConsolidator();
|
|
if (pnmc)
|
|
{
|
|
if (!pnmc->_hMutex || !pnmc->_hEvent)
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
else
|
|
{
|
|
hr = pnmc->QueryInterface(riid, ppv);
|
|
}
|
|
pnmc->Release();
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
const GUID CNewMenuConsolidator::_taskid =
|
|
{ 0xf87a1f28, 0xc7f, 0x11d2, { 0xbe, 0x1d, 0x0, 0xa0, 0xc9, 0xa8, 0x3d, 0xa1 } };
|
|
|
|
|
|
#define REGSTR_SESSION_SHELLNEW STRREG_DISCARDABLE STRREG_POSTSETUP TEXT("\\ShellNew")
|
|
#define REGVAL_SESSION_SHELLNEW_TIMESTAMP TEXT("~reserved~")
|
|
#define REGVAL_SESSION_SHELLNEW_LANG TEXT("Language")
|
|
|
|
#define SHELLNEW_CACHE_CURRENTVERSION MAKELONG(1, 1)
|
|
|
|
// Constructs a current New submenu cache stamp.
|
|
void CNewMenu_MakeCacheStamp(SHELLNEW_CACHE_STAMP* pStamp)
|
|
{
|
|
pStamp->cbStruct = sizeof(*pStamp);
|
|
pStamp->ver = SHELLNEW_CACHE_CURRENTVERSION;
|
|
GetLocalTime(&pStamp->lastupdate);
|
|
}
|
|
|
|
// Determines whether the New submenu cache needs to be rebuilt.
|
|
BOOL CNewMenu_ShouldUpdateCache(SHELLNEW_CACHE_STAMP* pStamp)
|
|
{
|
|
// Correct version?
|
|
return !(sizeof(*pStamp) == pStamp->cbStruct &&
|
|
SHELLNEW_CACHE_CURRENTVERSION == pStamp->ver);
|
|
}
|
|
|
|
// Gathers up shellnew entries from HKCR into a distinct registry location
|
|
// for faster enumeration of the New submenu items.
|
|
//
|
|
// We'll do a first time cache initialization only if we have to before showing
|
|
// the menu, but will always rebuild the cache following display of the menu.
|
|
HRESULT CNewMenu::ConsolidateMenuItems(BOOL bForce)
|
|
{
|
|
HKEY hkeyShellNew = NULL;
|
|
BOOL bUpdate = TRUE; // unless we discover otherwise
|
|
HRESULT hr = S_OK;
|
|
|
|
// make sure that a worker thread isnt currently slamming the registry info we're inspecting.
|
|
// if we timeout, then do nothing, since the worker thread is already working on it.
|
|
if (WAIT_OBJECT_0 == WaitForSingleObject(_hMutex, 0))
|
|
{
|
|
// If we're not being told to unconditionally update the cache and
|
|
// we validate that we've already established one, then we get out of doing any
|
|
// work.
|
|
if (!bForce &&
|
|
ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, REGSTR_SESSION_SHELLNEW,
|
|
0, KEY_QUERY_VALUE, &hkeyShellNew))
|
|
{
|
|
SHELLNEW_CACHE_STAMP stamp;
|
|
ULONG cbVal = sizeof(stamp);
|
|
if (ERROR_SUCCESS == SHQueryValueEx(hkeyShellNew, REGVAL_SESSION_SHELLNEW_TIMESTAMP, NULL,
|
|
NULL, (LPBYTE)&stamp, &cbVal) &&
|
|
sizeof(stamp) == cbVal)
|
|
{
|
|
bUpdate = CNewMenu_ShouldUpdateCache(&stamp);
|
|
}
|
|
|
|
LCID lcid;
|
|
ULONG cblcid = sizeof(lcid);
|
|
|
|
if (!bUpdate &&
|
|
ERROR_SUCCESS == SHQueryValueEx(hkeyShellNew, REGVAL_SESSION_SHELLNEW_LANG, NULL,
|
|
NULL, (LPBYTE)&lcid, &cblcid) &&
|
|
sizeof(lcid) == cblcid)
|
|
{
|
|
bUpdate = (GetUserDefaultUILanguage() != lcid); // if the languages are different, then update
|
|
}
|
|
RegCloseKey(hkeyShellNew);
|
|
}
|
|
|
|
// end synchronize
|
|
ReleaseMutex(_hMutex);
|
|
|
|
if (bUpdate)
|
|
{
|
|
IShellTaskScheduler* pScheduler;
|
|
hr = CoCreateInstance(CLSID_SharedTaskScheduler, NULL, CLSCTX_INPROC,
|
|
IID_PPV_ARG(IShellTaskScheduler, &pScheduler));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
IRunnableTask *pTask;
|
|
hr = CNewMenuConsolidator::CreateInstance(IID_PPV_ARG(IRunnableTask, &pTask));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// the background task will set _hEvent for us when it's done.
|
|
hr = pScheduler->AddTask(pTask, CNewMenuConsolidator::_taskid, NULL, ITSAT_DEFAULT_PRIORITY);
|
|
pTask->Release();
|
|
}
|
|
pScheduler->Release();
|
|
}
|
|
}
|
|
|
|
if (!bUpdate || FAILED(hr))
|
|
{
|
|
// if the scheduler wont activate the event, we do it ourselves.
|
|
SetEvent(_hEvent);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
// Consolidation worker.
|
|
STDMETHODIMP CNewMenuConsolidator::RunInitRT()
|
|
{
|
|
ULONG dwErr = ERROR_SUCCESS;
|
|
|
|
// the possible owners of the mutex are
|
|
// - nobody, we'll own it
|
|
// - other worker threads like this one
|
|
// - the guy who checks to see if the cached info is in the registry.
|
|
|
|
// if there's another worker thread which owns this mutex, then bail, since that one
|
|
// will do all the work we're going to do.
|
|
// if the guy who checks the cached info has it, then bail, since it'll spawn
|
|
// another one of these soon enough.
|
|
// so use a 0 timeout.
|
|
if (WAIT_OBJECT_0 == WaitForSingleObject(_hMutex, 0))
|
|
{
|
|
HKEY hkeyShellNew = NULL;
|
|
TCHAR szExt[MAX_PATH];
|
|
ULONG dwDisposition;
|
|
// Delete the existing cache; we'll build it from scratch each time.
|
|
while (ERROR_SUCCESS == (dwErr = RegCreateKeyEx(HKEY_CURRENT_USER, REGSTR_SESSION_SHELLNEW,
|
|
0, NULL, 0, KEY_SET_VALUE, NULL,
|
|
&hkeyShellNew, &dwDisposition)) &&
|
|
REG_CREATED_NEW_KEY != dwDisposition)
|
|
{
|
|
// Key already existed, so delete it, and loop to reopen.
|
|
RegCloseKey(hkeyShellNew);
|
|
SHDeleteKey(HKEY_CURRENT_USER, REGSTR_SESSION_SHELLNEW);
|
|
hkeyShellNew = NULL;
|
|
}
|
|
|
|
if (ERROR_SUCCESS == dwErr)
|
|
{
|
|
// Enumerate each subkey of HKCR, looking for New menu items.
|
|
for (int i = 0; RegEnumKey(HKEY_CLASSES_ROOT, i, szExt, ARRAYSIZE(szExt)) == ERROR_SUCCESS; i++)
|
|
{
|
|
TCHAR szClass[CCH_KEYMAX];
|
|
TCHAR szDisplayName[CCH_KEYMAX];
|
|
DWORD cbVal = sizeof(szClass);
|
|
|
|
// find .ext that have proper class descriptions with them.
|
|
if ((szExt[0] == TEXT('.')) &&
|
|
SHRegGetValue(HKEY_CLASSES_ROOT, szExt, NULL, SRRF_RT_REG_SZ, NULL, szClass, &cbVal) == ERROR_SUCCESS
|
|
&& GetClassDisplayName(szClass, szDisplayName, ARRAYSIZE(szDisplayName)))
|
|
{
|
|
NEWOBJECTINFO noi = {0};
|
|
HKEY hkeyIterate = NULL;
|
|
int iIndex = 0;
|
|
HRESULT hr;
|
|
BOOL fOk = TRUE;
|
|
|
|
hr = StringCchCopy(noi.szExt, ARRAYSIZE(noi.szExt), szExt);
|
|
if (FAILED(hr))
|
|
{
|
|
fOk = FALSE;
|
|
}
|
|
hr = StringCchCopy(noi.szClass, ARRAYSIZE(noi.szClass), szClass);
|
|
if (FAILED(hr))
|
|
{
|
|
fOk = FALSE;
|
|
}
|
|
hr = StringCchCopy(noi.szMenuText, ARRAYSIZE(noi.szMenuText), szDisplayName);
|
|
if (FAILED(hr))
|
|
{
|
|
fOk = FALSE;
|
|
}
|
|
noi.iImage = -1;
|
|
|
|
if (fOk)
|
|
{
|
|
// Retrieve all additional information for the key.
|
|
while (GetNewFileInfoForExtension(&noi, NULL, &hkeyIterate, &iIndex))
|
|
{
|
|
// Stick it in the cache.
|
|
RegSetValueEx(hkeyShellNew, noi.szMenuText, NULL, REG_BINARY,
|
|
(LPBYTE)&noi, sizeof(noi));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stamp the cache.
|
|
SHELLNEW_CACHE_STAMP stamp;
|
|
CNewMenu_MakeCacheStamp(&stamp);
|
|
RegSetValueEx(hkeyShellNew, REGVAL_SESSION_SHELLNEW_TIMESTAMP,
|
|
NULL, REG_BINARY, (LPBYTE)&stamp, sizeof(stamp));
|
|
LCID lcid = GetUserDefaultUILanguage();
|
|
|
|
RegSetValueEx(hkeyShellNew, REGVAL_SESSION_SHELLNEW_LANG,
|
|
NULL, REG_DWORD, (LPBYTE)&lcid, sizeof(lcid));
|
|
}
|
|
if (NULL != hkeyShellNew)
|
|
RegCloseKey(hkeyShellNew);
|
|
|
|
// signal the event so InitMenuPopup can proceed.
|
|
SetEvent(_hEvent);
|
|
ReleaseMutex(_hMutex);
|
|
}
|
|
|
|
return HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
|
|
BOOL CNewMenu::_InsertNewMenuItem(HMENU hmenu, UINT idCmd, NEWOBJECTINFO *pnoiClone)
|
|
{
|
|
if (pnoiClone->szMenuText[0])
|
|
{
|
|
NEWOBJECTINFO *pnoi = (NEWOBJECTINFO *)LocalAlloc(LPTR, sizeof(NEWOBJECTINFO));
|
|
if (pnoi)
|
|
{
|
|
*pnoi = *pnoiClone;
|
|
|
|
pnoi->msaa.dwMSAASignature = MSAA_MENU_SIG;
|
|
if (StrChr(pnoi->szMenuText, TEXT('&')) == NULL)
|
|
{
|
|
pnoi->chPrefix = TEXT('&');
|
|
pnoi->msaa.pszWText = &pnoi->chPrefix;
|
|
}
|
|
else
|
|
{
|
|
pnoi->msaa.pszWText = pnoi->szMenuText;
|
|
}
|
|
pnoi->msaa.cchWText = lstrlen(pnoi->msaa.pszWText);
|
|
|
|
MENUITEMINFO mii = {0};
|
|
mii.cbSize = sizeof(mii);
|
|
mii.fMask = MIIM_TYPE | MIIM_DATA | MIIM_ID;
|
|
mii.fType = MFT_OWNERDRAW;
|
|
mii.fState = MFS_ENABLED;
|
|
mii.wID = idCmd;
|
|
mii.dwItemData = (DWORD_PTR)pnoi;
|
|
mii.dwTypeData = (LPTSTR)pnoi;
|
|
|
|
if (-1 != DPA_AppendPtr(_hdpaMenuInfo, pnoi))
|
|
{
|
|
InsertMenuItem(hmenu, -1, TRUE, &mii);
|
|
}
|
|
else
|
|
{
|
|
LocalFree(pnoi);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// WM_INITMENUPOPUP handler
|
|
BOOL CNewMenu::InitMenuPopup(HMENU hmenu)
|
|
{
|
|
UINT iStart = 3;
|
|
NEWOBJECTINFO noi = {0};
|
|
if (GetItemData(hmenu, iStart)) //Position 0 is New Folder, 1 shortcut, 2 sep
|
|
return FALSE; //already initialized. No need to do anything
|
|
|
|
_hdpaMenuInfo = DPA_Create(4);
|
|
if (!_hdpaMenuInfo)
|
|
return FALSE;
|
|
|
|
//Remove the place holder.
|
|
DeleteMenu(hmenu,0,MF_BYPOSITION);
|
|
|
|
//Insert New Folder menu item
|
|
LoadString(g_hinst, IDS_NEWFOLDER, noi.szMenuText, ARRAYSIZE(noi.szMenuText));
|
|
noi.dwFlags = NEWTYPE_FOLDER;
|
|
noi.iImage = Shell_GetCachedImageIndex(TEXT("shell32.dll"), II_FOLDER, 0); //Shange to indicate Folder
|
|
|
|
_InsertNewMenuItem(hmenu, _idCmdFirst-NEWITEM_MAX+NEWITEM_FOLDER, &noi);
|
|
|
|
TCHAR szTemp[MAX_PATH+80]; // Add some slop incase we overflow
|
|
//See if the pidl is folder shortcut and if so get the target path.
|
|
SHGetTargetFolderPath(_pidlFolder, szTemp, ARRAYSIZE(szTemp));
|
|
if (IsLFNDrive(szTemp)) //for short filename servers we don't support anything but new folder
|
|
{
|
|
//Insert New Shortcut menu item
|
|
LoadString(g_hinst, IDS_NEWLINK, noi.szMenuText, ARRAYSIZE(noi.szMenuText));
|
|
noi.iImage = Shell_GetCachedImageIndex(TEXT("shell32.dll"), II_LINK, 0); //Shange to indicate Link
|
|
noi.dwFlags = NEWTYPE_LINK;
|
|
|
|
_InsertNewMenuItem(hmenu, _idCmdFirst-NEWITEM_MAX+NEWITEM_LINK, &noi);
|
|
|
|
//Insert menu item separator
|
|
AppendMenu(hmenu, MF_SEPARATOR, 0, NULL);
|
|
|
|
// This may take a while, so put up the hourglass
|
|
DECLAREWAITCURSOR;
|
|
SetWaitCursor();
|
|
|
|
// Retrieve extension menu items from cache:
|
|
|
|
// begin synchronize.
|
|
//
|
|
if (WAIT_OBJECT_0 == WaitForSingleObject(_hEvent, INFINITE))
|
|
{
|
|
HKEY hkeyShellNew;
|
|
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, REGSTR_SESSION_SHELLNEW,
|
|
0, KEY_QUERY_VALUE, &hkeyShellNew))
|
|
{
|
|
TCHAR szVal[CCH_KEYMAX];
|
|
ULONG cbVal = ARRAYSIZE(szVal);
|
|
ULONG cbData = sizeof(noi);
|
|
ULONG dwType = REG_BINARY;
|
|
|
|
for (int i = 0;
|
|
ERROR_SUCCESS == RegEnumValue(hkeyShellNew, i, szVal, &cbVal, 0,
|
|
&dwType, (LPBYTE)&noi, &cbData);
|
|
i++)
|
|
{
|
|
if (lstrcmp(szVal, REGVAL_SESSION_SHELLNEW_TIMESTAMP) != 0 &&
|
|
sizeof(noi) == cbData &&
|
|
REG_BINARY == dwType)
|
|
{
|
|
SHFILEINFO sfi;
|
|
_himlSystemImageList = (HIMAGELIST)SHGetFileInfo(noi.szExt, FILE_ATTRIBUTE_NORMAL,
|
|
&sfi, sizeof(SHFILEINFO),
|
|
SHGFI_USEFILEATTRIBUTES |
|
|
SHGFI_SYSICONINDEX |
|
|
SHGFI_SMALLICON);
|
|
if (_himlSystemImageList)
|
|
{
|
|
//pnoi->himlSmallIcons = sfi.hIcon;
|
|
noi.iImage = sfi.iIcon;
|
|
}
|
|
else
|
|
{
|
|
//pnoi->himlSmallIcons = INVALID_HANDLE_VALUE;
|
|
noi.iImage = -1;
|
|
}
|
|
|
|
_InsertNewMenuItem(hmenu, _idCmdFirst, &noi);
|
|
}
|
|
cbVal = ARRAYSIZE(szVal);
|
|
cbData = sizeof(noi);
|
|
dwType = REG_BINARY;
|
|
}
|
|
|
|
RegCloseKey(hkeyShellNew);
|
|
}
|
|
|
|
// consolidate menu items following display.
|
|
ConsolidateMenuItems(TRUE);
|
|
}
|
|
ResetWaitCursor();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
NEWOBJECTINFO *CNewMenu::GetItemData(HMENU hmenu, UINT iItem)
|
|
{
|
|
MENUITEMINFO mii;
|
|
|
|
mii.cbSize = sizeof(MENUITEMINFO);
|
|
mii.fMask = MIIM_DATA | MIIM_STATE;
|
|
mii.cch = 0; // just in case...
|
|
|
|
if (GetMenuItemInfo(hmenu, iItem, TRUE, &mii))
|
|
return (NEWOBJECTINFO *)mii.dwItemData;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
LPTSTR ProcessArgs(LPTSTR szArgs,...)
|
|
{
|
|
LPTSTR szRet;
|
|
va_list ArgList;
|
|
va_start(ArgList,szArgs);
|
|
if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING,
|
|
szArgs, 0, 0, (LPTSTR)&szRet, 0, &ArgList))
|
|
{
|
|
return NULL;
|
|
}
|
|
va_end(ArgList);
|
|
return szRet;
|
|
}
|
|
|
|
|
|
HRESULT CNewMenu::RunCommand(HWND hwnd, LPCTSTR pszPath, LPCTSTR pszRun)
|
|
{
|
|
SHELLEXECUTEINFO ei = { 0 };
|
|
TCHAR szCommand[MAX_PATH];
|
|
TCHAR szRun[MAX_PATH];
|
|
HRESULT hr;
|
|
|
|
SHExpandEnvironmentStrings(pszRun, szCommand, ARRAYSIZE(szCommand));
|
|
|
|
hr = StringCchCopy(szRun, ARRAYSIZE(szRun), szCommand);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
PathRemoveArgs(szCommand);
|
|
|
|
//
|
|
// Mondo hackitude-o-rama.
|
|
//
|
|
// Win95, IE3, SDK: %1 - filename
|
|
//
|
|
// IE4: %1 - hwnd, %2 = filename
|
|
//
|
|
// So IE4 broken Win95 compat and broke compat with the SDK.
|
|
// For IE5 we restore compat with Win95 and the SDK, while
|
|
// still generating an IE4-style command if we detect that the
|
|
// registry key owner tested with IE4 rather than following the
|
|
// instructions in the SDK.
|
|
//
|
|
// The algorithm is like this:
|
|
//
|
|
// If we see a "%2", then use %1 - hwnd, %2 - filename
|
|
// Otherwise, use %1 - filename, %2 - hwnd
|
|
//
|
|
|
|
LPTSTR pszArgs = PathGetArgs(szRun);
|
|
LPTSTR ptszPercent2 = StrStr(pszArgs, TEXT("%2"));
|
|
if (ptszPercent2 && ptszPercent2[2] != TEXT('!'))
|
|
{
|
|
// App wants %1 = hwnd and %2 = filename
|
|
pszArgs = ProcessArgs(pszArgs, (DWORD_PTR)hwnd, pszPath);
|
|
}
|
|
else
|
|
{
|
|
// App wants %2 = hwnd and %1 = filename
|
|
pszArgs = ProcessArgs(pszArgs, pszPath, (DWORD_PTR)hwnd);
|
|
}
|
|
|
|
if (pszArgs)
|
|
{
|
|
HMONITOR hMon = MonitorFromPoint(_ptNewItem, MONITOR_DEFAULTTONEAREST);
|
|
if (hMon)
|
|
{
|
|
ei.fMask |= SEE_MASK_HMONITOR;
|
|
ei.hMonitor = (HANDLE)hMon;
|
|
}
|
|
ei.hwnd = hwnd;
|
|
ei.lpFile = szCommand;
|
|
ei.lpParameters = pszArgs;
|
|
ei.nShow = SW_SHOWNORMAL;
|
|
ei.cbSize = sizeof(ei);
|
|
|
|
if (ShellExecuteEx(&ei))
|
|
hr = S_FALSE; // Return S_FALSE because ShellExecuteEx is not atomic
|
|
else
|
|
hr = E_FAIL;
|
|
|
|
LocalFree(pszArgs);
|
|
}
|
|
else
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT CNewMenu::CopyTemplate(IStream *pStream, NEWFILEINFO *pnfi)
|
|
{
|
|
TCHAR szSrcFolder[MAX_PATH], szSrc[MAX_PATH];
|
|
|
|
szSrc[0] = 0;
|
|
|
|
// failure here is OK, we will try CSIDL_COMMON_TEMPLATES too.
|
|
if (SHGetSpecialFolderPath(NULL, szSrcFolder, CSIDL_TEMPLATES, FALSE))
|
|
{
|
|
if (PathCombine(szSrc, szSrcFolder, (LPTSTR)pnfi->lpData))
|
|
{
|
|
if (!PathFileExistsAndAttributes(szSrc, NULL))
|
|
szSrc[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
szSrc[0] = TEXT('\0');
|
|
}
|
|
}
|
|
|
|
if (szSrc[0] == 0)
|
|
{
|
|
if (SHGetSpecialFolderPath(NULL, szSrcFolder, CSIDL_COMMON_TEMPLATES, FALSE))
|
|
{
|
|
if (PathCombine(szSrc, szSrcFolder, (LPTSTR)pnfi->lpData))
|
|
{
|
|
if (!PathFileExistsAndAttributes(szSrc, NULL))
|
|
szSrc[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
szSrc[0] = TEXT('\0');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (szSrc[0] == 0)
|
|
{
|
|
// work around CSIDL_TEMPLATES not being setup right or
|
|
// templates that are left in the old %windir%\shellnew location
|
|
|
|
UINT cch = GetWindowsDirectory(szSrcFolder, ARRAYSIZE(szSrcFolder));
|
|
if (cch != 0 && cch < ARRAYSIZE(szSrcFolder))
|
|
{
|
|
if (PathAppend(szSrcFolder, TEXT("ShellNew")))
|
|
{
|
|
// note: if the file spec is fully qualified szSrcFolder is ignored
|
|
if (PathCombine(szSrc, szSrcFolder, (LPTSTR)pnfi->lpData))
|
|
{
|
|
// groovy
|
|
}
|
|
else
|
|
{
|
|
szSrc[0] = TEXT('\0');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
szSrc[0] = TEXT('\0');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
szSrc[0] = TEXT('\0');
|
|
}
|
|
}
|
|
|
|
//
|
|
// we just allow a null file to be created when the copy fails.
|
|
// this is for appcompat with office97. they fail to copy winword8.doc
|
|
// anywhere on the system. on win2k we succeed anyway with an empty file.
|
|
//
|
|
return SUCCEEDED(StgCopyFileToStream(szSrc, pStream)) ? S_OK : S_FALSE;
|
|
}
|