2025-11-28 00:35:46 +09:00

1429 lines
45 KiB
C++

/**************************************************************************
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
(c) Microsoft Corporation. All Rights Reserved.
**************************************************************************/
#include <windows.h>
#include <shlobj.h>
#include <propkey.h>
#include <shlwapi.h>
#include <strsafe.h>
#include <shellapi.h>
#include <new> // std::nothrow
#include "resource.h"
#include "Utils.h"
#include "Category.h"
#include "Guid.h"
#include "fvcommands.h"
const int g_nMaxLevel = 5;
HRESULT CFolderViewCB_CreateInstance(REFIID riid, void **ppv);
#define MYOBJID 0x1234
// FVITEMID is allocated with a variable size, szName is the beginning
// of a NULL-terminated string buffer.
#pragma pack(1)
typedef struct tagObject
{
USHORT cb;
WORD MyObjID;
BYTE nLevel;
BYTE nSize;
BYTE nSides;
BYTE cchName;
BOOL fIsFolder;
WCHAR szName[1];
} FVITEMID;
#pragma pack()
typedef UNALIGNED FVITEMID *PFVITEMID;
typedef const UNALIGNED FVITEMID *PCFVITEMID;
class CFolderViewImplFolder : public IShellFolder2,
public IPersistFolder2
{
public:
CFolderViewImplFolder(UINT nLevel);
// IUnknown methods
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv);
IFACEMETHODIMP_(ULONG) AddRef();
IFACEMETHODIMP_(ULONG) Release();
// IShellFolder
IFACEMETHODIMP ParseDisplayName(HWND hwnd, IBindCtx *pbc, PWSTR pszName,
ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes);
IFACEMETHODIMP EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList **ppenumIDList);
IFACEMETHODIMP BindToObject(PCUIDLIST_RELATIVE pidl, IBindCtx *pbc, REFIID riid, void **ppv);
IFACEMETHODIMP BindToStorage(PCUIDLIST_RELATIVE pidl, IBindCtx *pbc, REFIID riid, void **ppv);
IFACEMETHODIMP CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2);
IFACEMETHODIMP CreateViewObject(HWND hwnd, REFIID riid, void **ppv);
IFACEMETHODIMP GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, ULONG *rgfInOut);
IFACEMETHODIMP GetUIObjectOf(HWND hwnd, UINT cidl, PCUITEMID_CHILD_ARRAY apidl,
REFIID riid, UINT* prgfInOut, void **ppv);
IFACEMETHODIMP GetDisplayNameOf(PCUITEMID_CHILD pidl, SHGDNF shgdnFlags, STRRET *pName);
IFACEMETHODIMP SetNameOf(HWND hwnd, PCUITEMID_CHILD pidl, PCWSTR pszName, DWORD uFlags, PITEMID_CHILD * ppidlOut);
// IShellFolder2
IFACEMETHODIMP GetDefaultSearchGUID(GUID *pGuid);
IFACEMETHODIMP EnumSearches(IEnumExtraSearch **ppenum);
IFACEMETHODIMP GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay);
IFACEMETHODIMP GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pbState);
IFACEMETHODIMP GetDetailsEx(PCUITEMID_CHILD pidl, const PROPERTYKEY *pkey, VARIANT *pv);
IFACEMETHODIMP GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *pDetails);
IFACEMETHODIMP MapColumnToSCID(UINT iColumn, PROPERTYKEY *pkey);
// IPersist
IFACEMETHODIMP GetClassID(CLSID *pClassID);
// IPersistFolder
IFACEMETHODIMP Initialize(PCIDLIST_ABSOLUTE pidl);
// IPersistFolder2
IFACEMETHODIMP GetCurFolder(PIDLIST_ABSOLUTE *ppidl);
// IDList constructor public for the enumerator object
HRESULT CreateChildID(PCWSTR pszName, int nLevel, int nSize, int nSides, BOOL fIsFolder, PITEMID_CHILD *ppidl);
private:
~CFolderViewImplFolder();
HRESULT _GetName(PCUIDLIST_RELATIVE pidl, PWSTR pszName, int cchMax);
HRESULT _GetName(PCUIDLIST_RELATIVE pidl, PWSTR *pszName);
HRESULT _GetSides(PCUIDLIST_RELATIVE pidl, int* pSides);
HRESULT _GetLevel(PCUIDLIST_RELATIVE pidl, int* pLevel);
HRESULT _GetSize(PCUIDLIST_RELATIVE pidl, int* pSize);
HRESULT _GetFolderness(PCUIDLIST_RELATIVE pidl, BOOL* pfIsFolder);
HRESULT _ValidatePidl(PCUIDLIST_RELATIVE pidl);
PCFVITEMID _IsValid(PCUIDLIST_RELATIVE pidl);
HRESULT _GetColumnDisplayName(PCUITEMID_CHILD pidl, const PROPERTYKEY* pkey, VARIANT* pv, WCHAR* pszRet, UINT cch);
long m_cRef;
int m_nLevel;
PIDLIST_ABSOLUTE m_pidl; // where this folder is in the name space
PWSTR m_rgNames[MAX_OBJS];
WCHAR m_szModuleName[MAX_PATH];
};
typedef struct
{
int nLevel;
int nSize;
int nSides;
BOOL fIsFolder;
WCHAR szName[MAX_PATH];
} ITEMDATA;
class CFolderViewImplEnumIDList : public IEnumIDList
{
public:
CFolderViewImplEnumIDList(DWORD grfFlags, int nCurrent, CFolderViewImplFolder *pFolderViewImplShellFolder);
// IUnknown methods
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv);
IFACEMETHODIMP_(ULONG) AddRef();
IFACEMETHODIMP_(ULONG) Release();
// IEnumIDList
IFACEMETHODIMP Next(ULONG celt, PITEMID_CHILD *rgelt, ULONG *pceltFetched);
IFACEMETHODIMP Skip(DWORD celt);
IFACEMETHODIMP Reset();
IFACEMETHODIMP Clone(IEnumIDList **ppenum);
HRESULT Initialize();
private:
~CFolderViewImplEnumIDList();
long m_cRef;
DWORD m_grfFlags;
int m_nItem;
int m_nLevel;
ITEMDATA m_aData[MAX_OBJS];
CFolderViewImplFolder *m_pFolder;
};
HRESULT CFolderViewImplFolder_CreateInstance(REFIID riid, void **ppv)
{
*ppv = NULL;
CFolderViewImplFolder* pFolderViewImplShellFolder = new (std::nothrow) CFolderViewImplFolder(0);
HRESULT hr = pFolderViewImplShellFolder ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = pFolderViewImplShellFolder->QueryInterface(riid, ppv);
pFolderViewImplShellFolder->Release();
}
return hr;
}
CFolderViewImplFolder::CFolderViewImplFolder(UINT nLevel) : m_cRef(1), m_nLevel(nLevel), m_pidl(NULL)
{
DllAddRef();
ZeroMemory(m_rgNames, sizeof(m_rgNames));
}
CFolderViewImplFolder::~CFolderViewImplFolder()
{
CoTaskMemFree(m_pidl);
for (int i = 0; i < ARRAYSIZE(m_rgNames); i++)
{
CoTaskMemFree(m_rgNames[i]);
}
DllRelease();
}
HRESULT CFolderViewImplFolder::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CFolderViewImplFolder, IShellFolder),
QITABENT(CFolderViewImplFolder, IShellFolder2),
QITABENT(CFolderViewImplFolder, IPersist),
QITABENT(CFolderViewImplFolder, IPersistFolder),
QITABENT(CFolderViewImplFolder, IPersistFolder2),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
ULONG CFolderViewImplFolder::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
ULONG CFolderViewImplFolder::Release()
{
long cRef = InterlockedDecrement(&m_cRef);
if (0 == cRef)
{
delete this;
}
return cRef;
}
// Translates a display name into an item identifier list.
HRESULT CFolderViewImplFolder::ParseDisplayName(HWND hwnd, IBindCtx *pbc, PWSTR pszName,
ULONG *pchEaten, PIDLIST_RELATIVE *ppidl, ULONG *pdwAttributes)
{
HRESULT hr = E_INVALIDARG;
if (NULL != pszName)
{
WCHAR szNameComponent[MAX_PATH] = {};
// extract first component of the display name
PWSTR pszNext = PathFindNextComponent(pszName);
if (pszNext && *pszNext)
{
hr = StringCchCopyN(szNameComponent, ARRAYSIZE(szNameComponent), pszName, lstrlen(pszName) - lstrlen(pszNext));
}
else
{
hr = StringCchCopy(szNameComponent, ARRAYSIZE(szNameComponent), pszName);
}
if (SUCCEEDED(hr))
{
PathRemoveBackslash(szNameComponent);
UINT uIndex = 0;
hr = GetIndexFromDisplayString(szNameComponent, &uIndex);
if (SUCCEEDED(hr))
{
BOOL fIsFolder = ISFOLDERFROMINDEX(uIndex);
PIDLIST_RELATIVE pidlCurrent = NULL;
hr = CreateChildID(szNameComponent, m_nLevel + 1, uIndex, 3, fIsFolder, &pidlCurrent);
if (SUCCEEDED(hr))
{
// If there are more components to parse, delegate to the child folder to handle the rest.
if (pszNext && *pszNext)
{
// Bind to current item
IShellFolder *psf;
hr = BindToObject(pidlCurrent, pbc, IID_PPV_ARGS(&psf));
if (SUCCEEDED(hr))
{
PIDLIST_RELATIVE pidlNext = NULL;
hr = psf->ParseDisplayName(hwnd, pbc, pszNext, pchEaten, &pidlNext, pdwAttributes);
if (SUCCEEDED(hr))
{
*ppidl = ILCombine(pidlCurrent, pidlNext);
ILFree(pidlNext);
}
psf->Release();
}
ILFree(pidlCurrent);
}
else
{
// transfer ownership to caller
*ppidl = pidlCurrent;
}
}
}
}
}
return hr;
}
// Allows a client to determine the contents of a folder by
// creating an item identifier enumeration object and returning
// its IEnumIDList interface. The methods supported by that
// interface can then be used to enumerate the folder's contents.
HRESULT CFolderViewImplFolder::EnumObjects(HWND /* hwnd */, DWORD grfFlags, IEnumIDList **ppenumIDList)
{
HRESULT hr;
if (m_nLevel >= g_nMaxLevel)
{
*ppenumIDList = NULL;
hr = S_FALSE; // S_FALSE is allowed with NULL out param to indicate no contents.
}
else
{
CFolderViewImplEnumIDList *penum = new (std::nothrow) CFolderViewImplEnumIDList(grfFlags, m_nLevel + 1, this);
hr = penum ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = penum->Initialize();
if (SUCCEEDED(hr))
{
hr = penum->QueryInterface(IID_PPV_ARGS(ppenumIDList));
}
penum->Release();
}
}
return hr;
}
// Factory for handlers for the specified item.
HRESULT CFolderViewImplFolder::BindToObject(PCUIDLIST_RELATIVE pidl,
IBindCtx *pbc, REFIID riid, void **ppv)
{
*ppv = NULL;
HRESULT hr = _ValidatePidl(pidl);
if (SUCCEEDED(hr))
{
CFolderViewImplFolder* pCFolderViewImplFolder = new (std::nothrow) CFolderViewImplFolder(m_nLevel + 1);
hr = pCFolderViewImplFolder ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
// Initialize it.
PITEMID_CHILD pidlFirst = ILCloneFirst(pidl);
hr = pidlFirst ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
PIDLIST_ABSOLUTE pidlBind = ILCombine(m_pidl, pidlFirst);
hr = pidlBind ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = pCFolderViewImplFolder->Initialize(pidlBind);
if (SUCCEEDED(hr))
{
PCUIDLIST_RELATIVE pidlNext = ILNext(pidl);
if (ILIsEmpty(pidlNext))
{
// If we're reached the end of the idlist, return the interfaces we support for this item.
// Other potential handlers to return include IPropertyStore, IStream, IStorage, etc.
hr = pCFolderViewImplFolder->QueryInterface(riid, ppv);
}
else
{
// Otherwise we delegate to our child folder to let it bind to the next level.
hr = pCFolderViewImplFolder->BindToObject(pidlNext, pbc, riid, ppv);
}
}
CoTaskMemFree(pidlBind);
}
ILFree(pidlFirst);
}
pCFolderViewImplFolder->Release();
}
}
return hr;
}
HRESULT CFolderViewImplFolder::BindToStorage(PCUIDLIST_RELATIVE pidl,
IBindCtx *pbc, REFIID riid, void **ppv)
{
return BindToObject(pidl, pbc, riid, ppv);
}
// Helper function to help compare relative IDs.
HRESULT _ILCompareRelIDs(IShellFolder *psfParent, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2,
LPARAM lParam)
{
HRESULT hr;
PCUIDLIST_RELATIVE pidlRel1 = ILNext(pidl1);
PCUIDLIST_RELATIVE pidlRel2 = ILNext(pidl2);
if (ILIsEmpty(pidlRel1))
{
if (ILIsEmpty(pidlRel2))
{
hr = ResultFromShort(0); // Both empty
}
else
{
hr = ResultFromShort(-1); // 1 is empty, 2 is not.
}
}
else
{
if (ILIsEmpty(pidlRel2))
{
hr = ResultFromShort(1); // 2 is empty, 1 is not
}
else
{
// pidlRel1 and pidlRel2 point to something, so:
// (1) Bind to the next level of the IShellFolder
// (2) Call its CompareIDs to let it compare the rest of IDs.
PIDLIST_RELATIVE pidlNext = ILCloneFirst(pidl1); // pidl2 would work as well
hr = pidlNext ? S_OK : E_OUTOFMEMORY;
if (pidlNext)
{
IShellFolder *psfNext;
hr = psfParent->BindToObject(pidlNext, NULL, IID_PPV_ARGS(&psfNext));
if (SUCCEEDED(hr))
{
// We do not want to pass the lParam is IShellFolder2 isn't supported.
// Although it isn't important for this example it shoud be considered
// if you are implementing this for other situations.
IShellFolder2 *psf2;
if (SUCCEEDED(psfNext->QueryInterface(&psf2)))
{
psf2->Release(); // We can use the lParam
}
else
{
lParam = 0; // We can't use the lParam
}
// Also, the column mask will not be relevant and should never be passed.
hr = psfNext->CompareIDs((lParam & ~SHCIDS_COLUMNMASK), pidlRel1, pidlRel2);
psfNext->Release();
}
CoTaskMemFree(pidlNext);
}
}
}
return hr;
}
// Called to determine the equivalence and/or sort order of two idlists.
HRESULT CFolderViewImplFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
{
HRESULT hr;
if (lParam & (SHCIDS_CANONICALONLY | SHCIDS_ALLFIELDS))
{
// First do a "canonical" comparison, meaning that we compare with the intent to determine item
// identity as quickly as possible. The sort order is arbitrary but it must be consistent.
PWSTR psz1;
hr = _GetName(pidl1, &psz1);
if (SUCCEEDED(hr))
{
PWSTR psz2;
hr = _GetName(pidl2, &psz2);
if (SUCCEEDED(hr))
{
hr = ResultFromShort(StrCmp(psz1, psz2));
CoTaskMemFree(psz2);
}
CoTaskMemFree(psz1);
}
// If we've been asked to do an all-fields comparison, test for any other fields that
// may be different in an item that shares the same identity. For example if the item
// represents a file, the identity may be just the filename but the other fields contained
// in the idlist may be file size and file modified date, and those may change over time.
// In our example let's say that "level" is the data that could be different on the same item.
if ((ResultFromShort(0) == hr) && (lParam & SHCIDS_ALLFIELDS))
{
int cLevel1 = 0, cLevel2 = 0;
hr = _GetLevel(pidl1, &cLevel1);
if (SUCCEEDED(hr))
{
hr = _GetLevel(pidl2, &cLevel2);
if (SUCCEEDED(hr))
{
hr = ResultFromShort(cLevel1 - cLevel2);
}
}
}
}
else
{
// Compare child ids by column data (lParam & SHCIDS_COLUMNMASK).
hr = ResultFromShort(0);
switch (lParam & SHCIDS_COLUMNMASK)
{
case 0: // Column one, Name.
{
// Load the strings that represent the names
if (!m_rgNames[0])
{
hr = LoadFolderViewImplDisplayStrings(m_rgNames, ARRAYSIZE(m_rgNames));
}
if (SUCCEEDED(hr))
{
PWSTR psz1;
hr = _GetName(pidl1, &psz1);
if (SUCCEEDED(hr))
{
PWSTR psz2;
hr = _GetName(pidl2, &psz2);
if (SUCCEEDED(hr))
{
// Find their place in the array.
// This is a display sort so we want to sort by "one" "two" "three" instead of alphabetically.
int nPidlOne = 0, nPidlTwo = 0;
for (int i = 0; i < ARRAYSIZE(m_rgNames); i++)
{
if (0 == StrCmp(psz1, m_rgNames[i]))
{
nPidlOne = i;
}
if (0 == StrCmp(psz2, m_rgNames[i]))
{
nPidlTwo = i;
}
}
hr = ResultFromShort(nPidlOne - nPidlTwo);
CoTaskMemFree(psz2);
}
CoTaskMemFree(psz1);
}
}
break;
}
case 1: // Column two, Size.
{
int nSize1 = 0, nSize2 = 0;
hr = _GetSize(pidl1, &nSize1);
if (SUCCEEDED(hr))
{
hr = _GetSize(pidl2, &nSize2);
if (SUCCEEDED(hr))
{
hr = ResultFromShort(nSize1 - nSize2);
}
}
break;
}
case 2: // Column Three, Sides.
{
int nSides1 = 0, nSides2 = 0;
hr = _GetSides(pidl1, &nSides1);
if (SUCCEEDED(hr))
{
hr = _GetSides(pidl2, &nSides2);
if (SUCCEEDED(hr))
{
hr = ResultFromShort(nSides1 - nSides2);
}
}
break;
}
case 3: // Column four, Level.
{
int cLevel1 = 0, cLevel2 = 0;
hr = _GetLevel(pidl1, &cLevel1);
if (SUCCEEDED(hr))
{
hr = _GetLevel(pidl2, &cLevel2);
if (SUCCEEDED(hr))
{
hr = ResultFromShort(cLevel1 - cLevel2);
}
}
break;
}
default:
{
hr = ResultFromShort(1);
}
}
}
if (ResultFromShort(0) == hr)
{
// Continue on by binding to the next level.
hr = _ILCompareRelIDs(this, pidl1, pidl2, lParam);
}
return hr;
}
// Called by the Shell to create the View Object and return it.
HRESULT CFolderViewImplFolder::CreateViewObject(HWND hwnd, REFIID riid, void **ppv)
{
*ppv = NULL;
HRESULT hr = E_NOINTERFACE;
if (riid == IID_IShellView)
{
SFV_CREATE csfv = { sizeof(csfv), 0 };
hr = QueryInterface(IID_PPV_ARGS(&csfv.pshf));
if (SUCCEEDED(hr))
{
// Add our callback to the SFV_CREATE. This is optional. We
// are adding it so we can enable searching within our
// namespace.
hr = CFolderViewCB_CreateInstance(IID_PPV_ARGS(&csfv.psfvcb));
if (SUCCEEDED(hr))
{
hr = SHCreateShellFolderView(&csfv, (IShellView**)ppv);
csfv.psfvcb->Release();
}
csfv.pshf->Release();
}
}
else if (riid == IID_ICategoryProvider)
{
CFolderViewImplCategoryProvider* pCatProvider = new (std::nothrow) CFolderViewImplCategoryProvider(this);
hr = pCatProvider ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = pCatProvider->QueryInterface(riid, ppv);
pCatProvider->Release();
}
}
else if (riid == IID_IContextMenu)
{
// This is the background context menu for the folder itself, not the context menu on items within it.
DEFCONTEXTMENU dcm = { hwnd, NULL, m_pidl, static_cast<IShellFolder2 *>(this), 0, NULL, NULL, 0, NULL };
hr = SHCreateDefaultContextMenu(&dcm, riid, ppv);
}
else if (riid == IID_IExplorerCommandProvider)
{
CFolderViewCommandProvider *pProvider = new (std::nothrow) CFolderViewCommandProvider();
hr = pProvider ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = pProvider->QueryInterface(riid, ppv);
pProvider->Release();
}
}
return hr;
}
// Retrieves the attributes of one or more file objects or subfolders.
HRESULT CFolderViewImplFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, ULONG *rgfInOut)
{
// If SFGAO_FILESYSTEM is returned, GetDisplayNameOf(SHGDN_FORPARSING) on that item MUST
// return a filesystem path.
HRESULT hr = E_INVALIDARG;
if (1 == cidl)
{
int nLevel = 0;
hr = _GetLevel(apidl[0], &nLevel);
if (SUCCEEDED(hr))
{
BOOL fIsFolder = FALSE;
hr = _GetFolderness(apidl[0], &fIsFolder);
if (SUCCEEDED(hr))
{
DWORD dwAttribs = 0;
if (fIsFolder)
{
dwAttribs |= SFGAO_FOLDER;
}
if (nLevel < g_nMaxLevel)
{
dwAttribs |= SFGAO_HASSUBFOLDER;
}
*rgfInOut &= dwAttribs;
}
}
}
return hr;
}
// Retrieves an OLE interface that can be used to carry out
// actions on the specified file objects or folders.
HRESULT CFolderViewImplFolder::GetUIObjectOf(HWND hwnd, UINT cidl, PCUITEMID_CHILD_ARRAY apidl,
REFIID riid, UINT * /* prgfInOut */, void **ppv)
{
*ppv = NULL;
HRESULT hr;
if (riid == IID_IContextMenu)
{
// The default context menu will call back for IQueryAssociations to determine the
// file associations with which to populate the menu.
DEFCONTEXTMENU const dcm = { hwnd, NULL, m_pidl, static_cast<IShellFolder2 *>(this),
cidl, apidl, NULL, 0, NULL };
hr = SHCreateDefaultContextMenu(&dcm, riid, ppv);
}
else if (riid == IID_IExtractIconW)
{
IDefaultExtractIconInit *pdxi;
hr = SHCreateDefaultExtractIcon(IID_PPV_ARGS(&pdxi));
if (SUCCEEDED(hr))
{
BOOL fIsFolder = FALSE;
hr = _GetFolderness(apidl[0], &fIsFolder);
if (SUCCEEDED(hr))
{
// This refers to icon indices in shell32. You can also supply custom icons or
// register IExtractImage to support general images.
hr = pdxi->SetNormalIcon(L"shell32.dll", fIsFolder ? 4 : 1);
}
if (SUCCEEDED(hr))
{
hr = pdxi->QueryInterface(riid, ppv);
}
pdxi->Release();
}
}
else if (riid == IID_IDataObject)
{
hr = SHCreateDataObject(m_pidl, cidl, apidl, NULL, riid, ppv);
}
else if (riid == IID_IQueryAssociations)
{
BOOL fIsFolder = FALSE;
hr = _GetFolderness(apidl[0], &fIsFolder);
if (SUCCEEDED(hr))
{
// the type of the item can be determined here. we default to "FolderViewSampleType", which has
// a context menu registered for it.
if (fIsFolder)
{
ASSOCIATIONELEMENT const rgAssocFolder[] =
{
{ ASSOCCLASS_PROGID_STR, NULL, L"FolderViewSampleType"},
{ ASSOCCLASS_FOLDER, NULL, NULL},
};
hr = AssocCreateForClasses(rgAssocFolder, ARRAYSIZE(rgAssocFolder), riid, ppv);
}
else
{
ASSOCIATIONELEMENT const rgAssocItem[] =
{
{ ASSOCCLASS_PROGID_STR, NULL, L"FolderViewSampleType"},
};
hr = AssocCreateForClasses(rgAssocItem, ARRAYSIZE(rgAssocItem), riid, ppv);
}
}
}
else
{
hr = E_NOINTERFACE;
}
return hr;
}
// Retrieves the display name for the specified file object or subfolder.
HRESULT CFolderViewImplFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, SHGDNF shgdnFlags, STRRET *pName)
{
HRESULT hr = S_OK;
if (shgdnFlags & SHGDN_FORPARSING)
{
WCHAR szDisplayName[MAX_PATH];
if (shgdnFlags & SHGDN_INFOLDER)
{
// This form of the display name needs to be handled by ParseDisplayName.
hr = _GetName(pidl, szDisplayName, ARRAYSIZE(szDisplayName));
}
else
{
PWSTR pszThisFolder;
hr = SHGetNameFromIDList(m_pidl, (shgdnFlags & SHGDN_FORADDRESSBAR) ? SIGDN_DESKTOPABSOLUTEEDITING : SIGDN_DESKTOPABSOLUTEPARSING, &pszThisFolder);
if (SUCCEEDED(hr))
{
StringCchCopy(szDisplayName, ARRAYSIZE(szDisplayName), pszThisFolder);
StringCchCat(szDisplayName, ARRAYSIZE(szDisplayName), L"\\");
WCHAR szName[MAX_PATH];
hr = _GetName(pidl, szName, ARRAYSIZE(szName));
if (SUCCEEDED(hr))
{
StringCchCat(szDisplayName, ARRAYSIZE(szDisplayName), szName);
}
CoTaskMemFree(pszThisFolder);
}
}
if (SUCCEEDED(hr))
{
hr = StringToStrRet(szDisplayName, pName);
}
}
else
{
PWSTR pszName;
hr = _GetName(pidl, &pszName);
if (SUCCEEDED(hr))
{
hr = StringToStrRet(pszName, pName);
CoTaskMemFree(pszName);
}
}
return hr;
}
// Sets the display name of a file object or subfolder, changing
// the item identifier in the process.
HRESULT CFolderViewImplFolder::SetNameOf(HWND /* hwnd */, PCUITEMID_CHILD /* pidl */,
PCWSTR /* pszName */, DWORD /* uFlags */, PITEMID_CHILD *ppidlOut)
{
HRESULT hr = E_NOTIMPL;
*ppidlOut = NULL;
return hr;
}
// IPersist method
HRESULT CFolderViewImplFolder::GetClassID(CLSID *pClassID)
{
*pClassID = CLSID_FolderViewImpl;
return S_OK;
}
// IPersistFolder method
HRESULT CFolderViewImplFolder::Initialize(PCIDLIST_ABSOLUTE pidl)
{
m_pidl = ILCloneFull(pidl);
return m_pidl ? S_OK : E_FAIL;
}
// IShellFolder2 methods
HRESULT CFolderViewImplFolder::EnumSearches(IEnumExtraSearch **ppEnum)
{
*ppEnum = NULL;
return E_NOINTERFACE;
}
// Retrieves the default sorting and display column (indices from GetDetailsOf).
HRESULT CFolderViewImplFolder::GetDefaultColumn(DWORD /* dwRes */,
ULONG *pSort,
ULONG *pDisplay)
{
*pSort = 0;
*pDisplay = 0;
return S_OK;
}
// Retrieves the default state for a specified column.
HRESULT CFolderViewImplFolder::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pcsFlags)
{
HRESULT hr = (iColumn < 3) ? S_OK : E_INVALIDARG;
if (SUCCEEDED(hr))
{
*pcsFlags = SHCOLSTATE_ONBYDEFAULT | SHCOLSTATE_TYPE_STR;
}
return hr;
}
// Requests the GUID of the default search object for the folder.
HRESULT CFolderViewImplFolder::GetDefaultSearchGUID(GUID * /* pguid */)
{
return E_NOTIMPL;
}
// Helper function for getting the display name for a column.
// IMPORTANT: If cch is set to 0 the value is returned in the VARIANT.
HRESULT CFolderViewImplFolder::_GetColumnDisplayName(PCUITEMID_CHILD pidl,
const PROPERTYKEY *pkey,
VARIANT *pv,
PWSTR pszRet,
UINT cch)
{
BOOL fIsFolder = FALSE;
HRESULT hr = _GetFolderness(pidl, &fIsFolder);
if (SUCCEEDED(hr))
{
if (IsEqualPropertyKey(*pkey, PKEY_ItemNameDisplay))
{
PWSTR pszName;
hr = _GetName(pidl, &pszName);
if (SUCCEEDED(hr))
{
if (pv != NULL)
{
pv->vt = VT_BSTR;
pv->bstrVal = SysAllocString(pszName);
hr = pv->bstrVal ? S_OK : E_OUTOFMEMORY;
}
else
{
hr = StringCchCopy(pszRet, cch, pszName);
}
CoTaskMemFree(pszName);
}
}
else if ((IsEqualPropertyKey(*pkey, PKEY_Microsoft_SDKSample_AreaSize)) &&
(!fIsFolder))
{
int nSize = 0;
hr = _GetSize(pidl, &nSize);
if (SUCCEEDED(hr))
{
//This property is declared as "String" type. See: ExplorerDataProvider.propdesc
WCHAR szFormattedSize[12];
hr = StringCchPrintf(szFormattedSize, ARRAYSIZE(szFormattedSize), L"%d Sq. Ft.", nSize);
if (cch)
{
hr = StringCchCopy(pszRet, cch, szFormattedSize);
}
else
{
pv->vt = VT_BSTR;
pv->bstrVal = SysAllocString(szFormattedSize);
hr = pv->bstrVal ? S_OK : E_OUTOFMEMORY;
}
}
}
else if ((IsEqualPropertyKey(*pkey, PKEY_Microsoft_SDKSample_NumberOfSides))&&
(!fIsFolder))
{
int nSides = 0;
hr = _GetSides(pidl, &nSides);
if (SUCCEEDED(hr))
{
if (cch)
{
hr = StringCchPrintf(pszRet, cch, L"%d", nSides);
}
else
{
pv->vt = VT_I4;
pv->lVal = nSides;
}
}
}
else if (IsEqualPropertyKey(*pkey, PKEY_Microsoft_SDKSample_DirectoryLevel))
{
int nLevel = 0;
hr = _GetLevel(pidl, &nLevel);
if (SUCCEEDED(hr))
{
if (cch)
{
hr = StringCchPrintf(pszRet, cch, L"%d", nLevel);
}
else
{
pv->vt = VT_I4;
pv->lVal = nLevel;
}
}
}
else
{
if (pv)
{
VariantInit(pv);
}
if (pszRet)
{
*pszRet = '\0';
}
}
}
return hr;
}
// Retrieves detailed information, identified by a
// property set ID (FMTID) and property ID (PID),
// on an item in a Shell folder.
HRESULT CFolderViewImplFolder::GetDetailsEx(PCUITEMID_CHILD pidl,
const PROPERTYKEY *pkey,
VARIANT *pv)
{
BOOL pfIsFolder = FALSE;
HRESULT hr = _GetFolderness(pidl, &pfIsFolder);
if (SUCCEEDED(hr))
{
if (!pfIsFolder && IsEqualPropertyKey(*pkey, PKEY_PropList_PreviewDetails))
{
// This proplist indicates what properties are shown in the details pane at the bottom of the explorer browser.
pv->vt = VT_BSTR;
pv->bstrVal = SysAllocString(L"prop:Microsoft.SDKSample.AreaSize;Microsoft.SDKSample.NumberOfSides;Microsoft.SDKSample.DirectoryLevel");
hr = pv->bstrVal ? S_OK : E_OUTOFMEMORY;
}
else
{
hr = _GetColumnDisplayName(pidl, pkey, pv, NULL, 0);
}
}
return hr;
}
// Retrieves detailed information, identified by a
// column index, on an item in a Shell folder.
HRESULT CFolderViewImplFolder::GetDetailsOf(PCUITEMID_CHILD pidl,
UINT iColumn,
SHELLDETAILS *pDetails)
{
PROPERTYKEY key;
HRESULT hr = MapColumnToSCID(iColumn, &key);
pDetails->cxChar = 24;
WCHAR szRet[MAX_PATH];
if (!pidl)
{
// No item means we're returning information about the column itself.
switch (iColumn)
{
case 0:
pDetails->fmt = LVCFMT_LEFT;
hr = StringCchCopy(szRet, ARRAYSIZE(szRet), L"Name");
break;
case 1:
pDetails->fmt = LVCFMT_CENTER;
hr = StringCchCopy(szRet, ARRAYSIZE(szRet), L"Size");
break;
case 2:
pDetails->fmt = LVCFMT_CENTER;
hr = StringCchCopy(szRet, ARRAYSIZE(szRet), L"Sides");
break;
case 3:
pDetails->fmt = LVCFMT_CENTER;
hr = StringCchCopy(szRet, ARRAYSIZE(szRet), L"Level");
break;
default:
// GetDetailsOf is called with increasing column indices until failure.
hr = E_FAIL;
break;
}
}
else if (SUCCEEDED(hr))
{
hr = _GetColumnDisplayName(pidl, &key, NULL, szRet, ARRAYSIZE(szRet));
}
if (SUCCEEDED(hr))
{
hr = StringToStrRet(szRet, &pDetails->str);
}
return hr;
}
// Converts a column name to the appropriate
// property set ID (FMTID) and property ID (PID).
HRESULT CFolderViewImplFolder::MapColumnToSCID(UINT iColumn, PROPERTYKEY *pkey)
{
// The property keys returned here are used by the categorizer.
HRESULT hr = S_OK;
switch (iColumn)
{
case 0:
*pkey = PKEY_ItemNameDisplay;
break;
case 1:
*pkey = PKEY_Microsoft_SDKSample_AreaSize;
break;
case 2:
*pkey = PKEY_Microsoft_SDKSample_NumberOfSides;
break;
case 3:
*pkey = PKEY_Microsoft_SDKSample_DirectoryLevel;
break;
default:
hr = E_FAIL;
break;
}
return hr;
}
//IPersistFolder2 methods
// Retrieves the PIDLIST_ABSOLUTE for the folder object.
HRESULT CFolderViewImplFolder::GetCurFolder(PIDLIST_ABSOLUTE *ppidl)
{
*ppidl = NULL;
HRESULT hr = m_pidl ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
*ppidl = ILCloneFull(m_pidl);
hr = *ppidl ? S_OK : E_OUTOFMEMORY;
}
return hr;
}
// Item idlists passed to folder methods are guaranteed to have accessible memory as specified
// by the cbSize in the itemid. However they may be loaded from a persisted form (for example
// shortcuts on disk) where they could be corrupted. It is the shell folder's responsibility
// to make sure it's safe against corrupted or malicious itemids.
PCFVITEMID CFolderViewImplFolder::_IsValid(PCUIDLIST_RELATIVE pidl)
{
PCFVITEMID pidmine = NULL;
if (pidl)
{
pidmine = (PCFVITEMID)pidl;
if (!(pidmine->cb && MYOBJID == pidmine->MyObjID && pidmine->nLevel <= g_nMaxLevel))
{
pidmine = NULL;
}
}
return pidmine;
}
HRESULT CFolderViewImplFolder::_GetName(PCUIDLIST_RELATIVE pidl, PWSTR pszName, int cchMax)
{
PCFVITEMID pMyObj = _IsValid(pidl);
HRESULT hr = pMyObj ? S_OK : E_INVALIDARG;
if (SUCCEEDED(hr))
{
// StringCchCopy requires aligned strings, and itemids are not necessarily aligned.
int i = 0;
for ( ; i < cchMax; i++)
{
pszName[i] = pMyObj->szName[i];
if (0 == pszName[i])
{
break;
}
}
// Make sure the string is null-terminated.
if (i == cchMax)
{
pszName[cchMax - 1] = 0;
}
}
return hr;
}
HRESULT CFolderViewImplFolder::_GetName(PCUIDLIST_RELATIVE pidl, PWSTR *ppsz)
{
*ppsz = 0;
PCFVITEMID pMyObj = _IsValid(pidl);
HRESULT hr = pMyObj ? S_OK : E_INVALIDARG;
if (SUCCEEDED(hr))
{
int const cch = pMyObj->cchName;
*ppsz = (PWSTR)CoTaskMemAlloc(cch * sizeof(**ppsz));
hr = *ppsz ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = _GetName(pidl, *ppsz, cch);
}
}
return hr;
}
HRESULT CFolderViewImplFolder::_GetSides(PCUIDLIST_RELATIVE pidl, int *pSides)
{
PCFVITEMID pMyObj = _IsValid(pidl);
HRESULT hr = pMyObj ? S_OK : E_INVALIDARG;
if (SUCCEEDED(hr))
{
*pSides = pMyObj->nSides;
}
return hr;
}
HRESULT CFolderViewImplFolder::_GetLevel(PCUIDLIST_RELATIVE pidl, int *pLevel)
{
PCFVITEMID pMyObj = _IsValid(pidl);
HRESULT hr = pMyObj ? S_OK : E_INVALIDARG;
if (SUCCEEDED(hr))
{
*pLevel = pMyObj->nLevel;
}
else // If this fails we are at level zero.
{
*pLevel = 0;
}
return hr;
}
HRESULT CFolderViewImplFolder::_GetSize(PCUIDLIST_RELATIVE pidl, int *pSize)
{
PCFVITEMID pMyObj = _IsValid(pidl);
HRESULT hr = pMyObj ? S_OK : E_INVALIDARG;
if (SUCCEEDED(hr))
{
*pSize = pMyObj->nSize;
}
return hr;
}
HRESULT CFolderViewImplFolder::_GetFolderness(PCUIDLIST_RELATIVE pidl, BOOL *pfIsFolder)
{
PCFVITEMID pMyObj = _IsValid(pidl);
HRESULT hr = pMyObj ? S_OK : E_INVALIDARG;
if (SUCCEEDED(hr))
{
*pfIsFolder = pMyObj->fIsFolder;
}
return hr;
}
HRESULT CFolderViewImplFolder::_ValidatePidl(PCUIDLIST_RELATIVE pidl)
{
PCFVITEMID pMyObj = _IsValid(pidl);
return pMyObj ? S_OK : E_INVALIDARG;
}
HRESULT CFolderViewImplFolder::CreateChildID(PCWSTR pszName, int nLevel, int nSize, int nSides, BOOL fIsFolder, PITEMID_CHILD *ppidl)
{
// Sizeof an object plus the next cb plus the characters in the string.
UINT nIDSize = sizeof(FVITEMID) +
sizeof(USHORT) +
(lstrlen(pszName) * sizeof(WCHAR)) +
sizeof(WCHAR);
// Allocate and zero the memory.
FVITEMID *lpMyObj = (FVITEMID *)CoTaskMemAlloc(nIDSize);
HRESULT hr = lpMyObj ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
ZeroMemory(lpMyObj, nIDSize);
lpMyObj->cb = static_cast<short>(nIDSize - sizeof(lpMyObj->cb));
lpMyObj->MyObjID = MYOBJID;
lpMyObj->cchName = (BYTE)(lstrlen(pszName) + 1);
lpMyObj->nLevel = (BYTE)nLevel;
lpMyObj->nSize = (BYTE)nSize;
lpMyObj->nSides = (BYTE)nSides;
lpMyObj->fIsFolder = (BOOL)fIsFolder;
hr = StringCchCopy(lpMyObj->szName, lpMyObj->cchName, pszName);
if (SUCCEEDED(hr))
{
*ppidl = (PITEMID_CHILD)lpMyObj;
}
}
return hr;
}
CFolderViewImplEnumIDList::CFolderViewImplEnumIDList(DWORD grfFlags, int nLevel, CFolderViewImplFolder *pFolderViewImplShellFolder) :
m_cRef(1), m_grfFlags(grfFlags), m_nLevel(nLevel), m_nItem(0), m_pFolder(pFolderViewImplShellFolder)
{
m_pFolder->AddRef();
}
CFolderViewImplEnumIDList::~CFolderViewImplEnumIDList()
{
m_pFolder->Release();
}
HRESULT CFolderViewImplEnumIDList::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] = {
QITABENT (CFolderViewImplEnumIDList, IEnumIDList),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
ULONG CFolderViewImplEnumIDList::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
ULONG CFolderViewImplEnumIDList::Release()
{
long cRef = InterlockedDecrement(&m_cRef);
if (0 == cRef)
{
delete this;
}
return cRef;
}
// This initializes the enumerator. In this case we set up a default array of items, this represents our
// data source. In a real-world implementation the array would be replaced and would be backed by some
// other data source that you traverse and convert into IShellFolder item IDs.
HRESULT CFolderViewImplEnumIDList::Initialize()
{
ZeroMemory(m_aData, sizeof(m_aData));
HRESULT hr = S_OK;
for (int i = 0; SUCCEEDED(hr) && i < ARRAYSIZE(m_aData); i++)
{
hr = LoadFolderViewImplDisplayString(i, m_aData[i].szName, ARRAYSIZE(m_aData[i].szName));
if (SUCCEEDED(hr))
{
// Just hardcode the values here now.
m_aData[i].nSize = i;
m_aData[i].nSides = 3;
m_aData[i].fIsFolder = ISFOLDERFROMINDEX(i);
}
}
return hr;
}
// Retrieves the specified number of item identifiers in
// the enumeration sequence and advances the current position
// by the number of items retrieved.
HRESULT CFolderViewImplEnumIDList::Next(ULONG celt, PITEMID_CHILD *rgelt, ULONG *pceltFetched)
{
ULONG celtFetched = 0;
HRESULT hr = (pceltFetched || celt <= 1) ? S_OK : E_INVALIDARG;
if (SUCCEEDED(hr))
{
ULONG i = 0;
while (SUCCEEDED(hr) && i < celt && m_nItem < ARRAYSIZE(m_aData))
{
BOOL fSkip = FALSE;
if (!(m_grfFlags & SHCONTF_STORAGE))
{
if (m_aData[m_nItem].fIsFolder)
{
if (!(m_grfFlags & SHCONTF_FOLDERS))
{
// this is a folder, but caller doesnt want folders
fSkip = TRUE;
}
}
else
{
if (!(m_grfFlags & SHCONTF_NONFOLDERS))
{
// this is a file, but caller doesnt want files
fSkip = TRUE;
}
}
}
if (!fSkip)
{
hr = m_pFolder->CreateChildID(m_aData[m_nItem].szName, m_nLevel, m_aData[m_nItem].nSize, m_aData[m_nItem].nSides, m_aData[m_nItem].fIsFolder, &rgelt[i]);
if (SUCCEEDED(hr))
{
celtFetched++;
i++;
}
}
m_nItem++;
}
}
if (pceltFetched)
{
*pceltFetched = celtFetched;
}
return (celtFetched == celt) ? S_OK : S_FALSE;
}
HRESULT CFolderViewImplEnumIDList::Skip(DWORD celt)
{
m_nItem += celt;
return S_OK;
}
HRESULT CFolderViewImplEnumIDList::Reset()
{
m_nItem = 0;
return S_OK;
}
HRESULT CFolderViewImplEnumIDList::Clone(IEnumIDList **ppenum)
{
// this method is rarely used and it's acceptable to not implement it.
*ppenum = NULL;
return E_NOTIMPL;
}
class CFolderViewCB : public IShellFolderViewCB,
public IFolderViewSettings
{
public:
CFolderViewCB() : _cRef(1) { }
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CFolderViewCB, IShellFolderViewCB),
QITABENT(CFolderViewCB, IFolderViewSettings),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&_cRef); }
IFACEMETHODIMP_(ULONG) Release()
{
long cRef = InterlockedDecrement(&_cRef);
if (0 == cRef)
{
delete this;
}
return cRef;
}
// IShellFolderViewCB
IFACEMETHODIMP MessageSFVCB(UINT /* uMsg */, WPARAM /* wParam */, LPARAM /* lParam */)
{ return E_NOTIMPL; }
// IFolderViewSettings
IFACEMETHODIMP GetColumnPropertyList(REFIID /* riid */, void **ppv)
{ *ppv = NULL; return E_NOTIMPL; }
IFACEMETHODIMP GetGroupByProperty(PROPERTYKEY * /* pkey */, BOOL * /* pfGroupAscending */)
{ return E_NOTIMPL; }
IFACEMETHODIMP GetViewMode(FOLDERLOGICALVIEWMODE * /* plvm */)
{ return E_NOTIMPL; }
IFACEMETHODIMP GetIconSize(UINT * /* puIconSize */)
{ return E_NOTIMPL; }
IFACEMETHODIMP GetFolderFlags(FOLDERFLAGS *pfolderMask, FOLDERFLAGS *pfolderFlags);
IFACEMETHODIMP GetSortColumns(SORTCOLUMN * /* rgSortColumns */, UINT /* cColumnsIn */, UINT * /* pcColumnsOut */)
{ return E_NOTIMPL; }
IFACEMETHODIMP GetGroupSubsetCount(UINT * /* pcVisibleRows */)
{ return E_NOTIMPL; }
private:
~CFolderViewCB() { };
long _cRef;
};
// IFolderViewSettings
IFACEMETHODIMP CFolderViewCB::GetFolderFlags(FOLDERFLAGS *pfolderMask, FOLDERFLAGS *pfolderFlags)
{
if (pfolderMask)
{
*pfolderMask = FWF_USESEARCHFOLDER;
}
if (pfolderFlags)
{
*pfolderFlags = FWF_USESEARCHFOLDER;
}
return S_OK;
}
HRESULT CFolderViewCB_CreateInstance(REFIID riid, void **ppv)
{
*ppv = NULL;
HRESULT hr = E_OUTOFMEMORY;
CFolderViewCB *pfvcb = new (std::nothrow) CFolderViewCB();
if (pfvcb)
{
hr = pfvcb->QueryInterface(riid, ppv);
pfvcb->Release();
}
return hr;
}