#include "shellprv.h" #pragma hdrstop #include "clsobj.h" #include "ids.h" #include #include "datautil.h" #include "idhidden.h" #include "prop.h" #include "stgutil.h" #include "sfstorage.h" #include "util.h" #include "fstreex.h" #include "basefvcb.h" #include "category.h" #include "mergfldr.h" #include "filefldr.h" #include "idldata.h" #include "defcm.h" #define TF_AUGM 0x10000000 // pidl wrapper contains a TAG word for validation and then // the count for the number of packaged pidl object. // // each pidl has a hidden payload which is the name space // index that it originated from. #pragma pack(1) typedef struct { USHORT cb; // pidl wrap length USHORT dwFlags; // flags ULONG ulTag; // signature ULONG ulVersion ; // AugMergeISF pidl version ULONG cSrcs; // Number of source _Namespace objects backing this composite pidl } AUGM_IDWRAP; typedef UNALIGNED AUGM_IDWRAP *PAUGM_IDWRAP; typedef struct { HIDDENITEMID hid; UINT uSrcID; // src _Namespace } AUGM_NAMESPACE; typedef UNALIGNED AUGM_NAMESPACE *PAUGM_NAMESPACE; #pragma pack() #define AUGM_NS_CURRENTVERSION 0 #define AUGM_WRAPVERSION_1_0 MAKELONG(1, 0) #define AUGM_WRAPVERSION_2_0 MAKELONG(2, 0) #define AUGM_WRAPCURRENTVERSION AUGM_WRAPVERSION_2_0 #define AUGM_WRAPTAG MAKELONG(MAKEWORD('A','u'), MAKEWORD('g','M')) #define CB_IDLIST_TERMINATOR sizeof(USHORT) // dwFlags field flags #define AUGMF_ISSIMPLE 0x0001 // helpers. HRESULT CMergedFldrContextMenu_CreateInstance(HWND hwnd, CMergedFolder *pmf, UINT cidl, LPCITEMIDLIST *apidl, IContextMenu *pcmCommon, IContextMenu *pcmUser, IContextMenu **ppcm); HRESULT CMergedFldrEnum_CreateInstance(CMergedFolder*pmf, DWORD grfFlags, IEnumIDList **ppenum); HRESULT CMergedFldrDropTarget_CreateInstance(CMergedFolder*pmf, HWND hwnd, IDropTarget **ppdt); HRESULT CMergedFolderViewCB_CreateInstance(CMergedFolder *pmf, IShellFolderViewCB **ppsfvcb); // Helper function that spans all objects BOOL AffectAllUsers(HWND hwnd) { BOOL bRet = FALSE; // default to NO if (hwnd) { TCHAR szMessage[255]; TCHAR szTitle[20]; if (LoadString(HINST_THISDLL, IDS_ALLUSER_WARNING, szMessage, ARRAYSIZE(szMessage)) > 0 && LoadString(HINST_THISDLL, IDS_WARNING, szTitle, ARRAYSIZE(szTitle)) > 0) { bRet = (IDYES == MessageBox(hwnd, szMessage, szTitle, MB_YESNO | MB_ICONINFORMATION)); } } else bRet = TRUE; // NULL hwnd implies NO UI, say "yes" return bRet; } // CMergedFoldersource _Namespace descriptor. // // Objects of class CMergedFldrNamespace are created by CMergedFolderin // the AddNameSpace() method impl, and are maintained in the collection // CMergedFolder::_hdpaNamespaces. // class CMergedFldrNamespace { public: CMergedFldrNamespace(); ~CMergedFldrNamespace(); IShellFolder* Folder() const { return _psf; } REFGUID GetGUID() const { return _guid; } ULONG FolderAttrib() const { return _dwAttrib; } LPCITEMIDLIST GetIDList() const { return _pidl; } HRESULT GetLocation(LPWSTR pszBuffer, INT cchBuffer) { StrCpyN(pszBuffer, _szLocation, cchBuffer); return S_OK; }; LPCWSTR GetDropFolder() { return _szDrop; }; ULONG FixItemAttributes(ULONG attrib) { return (attrib & _dwItemAttribMask) | _dwItemAttrib; } DWORD GetDropEffect(void) const { return _dwDropEffect; } int GetDefaultOverlayIndex() const { return _iDefaultOverlayIndex; } int GetConflictOverlayIndex() const { return _iConflictOverlayIndex; } int GetNamespaceOverlayIndex(LPCITEMIDLIST pidl); HRESULT SetNamespace(const GUID * pguidUIObject, IShellFolder* psf, LPCITEMIDLIST pidl, ULONG dwAttrib); HRESULT SetDropFolder(LPCWSTR pszDrop); HRESULT RegisterNotify(HWND, UINT, ULONG); HRESULT UnregisterNotify(); BOOL SetOwner(IUnknown *punk); protected: ULONG _RegisterNotify(HWND hwnd, UINT nMsg, LPCITEMIDLIST pidl, DWORD dwEvents, UINT uFlags, BOOL fRecursive); void _ReleaseNamespace(); IShellFolder* _psf; // IShellFolder interface pointer GUID _guid; // optional GUID for specialized UI handling LPITEMIDLIST _pidl; // optional pidl ULONG _dwAttrib; // optional flags UINT _uChangeReg; // Shell change notify registration ID. WCHAR _szLocation[MAX_PATH]; // Location to use for object WCHAR _szDrop[MAX_PATH]; // folder that gets the forced drop effect DWORD _dwItemAttrib; // OR mask for the attributes DWORD _dwItemAttribMask; // AND mask for the attributes DWORD _dwDropEffect; // default drop effect for this folder int _iDefaultOverlayIndex; // overlay icon index for default int _iConflictOverlayIndex; // overlay icon index if the name exists in another namespace }; CMergedFldrNamespace::CMergedFldrNamespace() : _dwItemAttribMask(-1) { } inline CMergedFldrNamespace::~CMergedFldrNamespace() { UnregisterNotify(); _ReleaseNamespace(); } HRESULT CMergedFldrNamespace::SetNamespace(const GUID * pguidUIObject, IShellFolder* psf, LPCITEMIDLIST pidl, ULONG dwAttrib) { _ReleaseNamespace(); // store the IShellFolder object if we have one if (psf) { _psf = psf; _psf->AddRef(); } else if (pidl) { SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, pidl, &_psf)); } // get the IDLIST that this namespace represnets if (pidl) { _pidl = ILClone(pidl); // we have a PIDL passed to us. } else { _pidl = NULL; IPersistFolder3 *ppf3; if (SUCCEEDED(_psf->QueryInterface(IID_PPV_ARG(IPersistFolder3, &ppf3)))) { PERSIST_FOLDER_TARGET_INFO pfti; if (SUCCEEDED(ppf3->GetFolderTargetInfo(&pfti))) { _pidl = pfti.pidlTargetFolder; } ppf3->Release(); } // if it doesnt have IPersistFolder3 or if there's no target folder then // fall back to IPersistFolder2 if (!_pidl) { SHGetIDListFromUnk(psf, &_pidl); } } if (!_psf || !_pidl) return E_FAIL; // now fill out the information about the namespace, including getting the display // information from the registry _guid = pguidUIObject ? *pguidUIObject : GUID_NULL; _dwAttrib = dwAttrib; _szLocation[0] = TEXT('\0'); _dwItemAttrib = 0; // item attribute become a NOP _dwItemAttribMask = (DWORD)-1; _dwDropEffect = 0; // default behaviour _iDefaultOverlayIndex = -1; _iConflictOverlayIndex = -1; // format a key to the property bag stored in the registry, then create the // property bag which we then query against. TCHAR szKey[MAX_PATH], szGUID[GUIDSTR_MAX+1]; SHStringFromGUID(_guid, szGUID, ARRAYSIZE(szGUID)); wnsprintf(szKey, ARRAYSIZE(szKey), TEXT("CLSID\\%s\\MergedFolder"), szGUID); IPropertyBag *ppb; if (SUCCEEDED(SHCreatePropertyBagOnRegKey(HKEY_CLASSES_ROOT, szKey, STGM_READ, IID_PPV_ARG(IPropertyBag, &ppb)))) { TCHAR szLocalized[100]; if (SUCCEEDED(SHPropertyBag_ReadStr(ppb, L"Location", szLocalized, ARRAYSIZE(szLocalized)))) { SHLoadIndirectString(szLocalized, _szLocation, ARRAYSIZE(_szLocation), NULL); } SHPropertyBag_ReadDWORD(ppb, L"Attributes", &_dwItemAttrib); SHPropertyBag_ReadDWORD(ppb, L"AttributeMask", &_dwItemAttribMask); SHPropertyBag_ReadDWORD(ppb, L"DropEffect", &_dwDropEffect); TCHAR szIconLocation[MAX_PATH]; szIconLocation[0] = 0; SHPropertyBag_ReadStr(ppb, L"DefaultOverlayIcon", szIconLocation, ARRAYSIZE(szIconLocation)); _iDefaultOverlayIndex = SHGetIconOverlayIndex(szIconLocation, PathParseIconLocation(szIconLocation)); szIconLocation[0] = 0; SHPropertyBag_ReadStr(ppb, L"ConflictOverlayIcon", szIconLocation, ARRAYSIZE(szIconLocation)); _iConflictOverlayIndex = SHGetIconOverlayIndex(szIconLocation, PathParseIconLocation(szIconLocation)); ppb->Release(); } if (!SHGetPathFromIDList(_pidl, _szDrop)) { _szDrop[0] = 0; } return S_OK; } HRESULT CMergedFldrNamespace::SetDropFolder(LPCWSTR pszDrop) { StrCpyN(_szDrop, pszDrop, ARRAYSIZE(_szDrop)); return S_OK; } void CMergedFldrNamespace::_ReleaseNamespace() { ATOMICRELEASE(_psf); ILFree(_pidl); _pidl = NULL; _guid = GUID_NULL; _dwAttrib = 0L; } ULONG CMergedFldrNamespace::_RegisterNotify(HWND hwnd, UINT nMsg, LPCITEMIDLIST pidl, DWORD dwEvents, UINT uFlags, BOOL fRecursive) { SHChangeNotifyEntry fsne = { 0 }; fsne.fRecursive = fRecursive; fsne.pidl = pidl; return SHChangeNotifyRegister(hwnd, uFlags | SHCNRF_NewDelivery, dwEvents, nMsg, 1, &fsne); } // Register change notification for the _Namespace HRESULT CMergedFldrNamespace::RegisterNotify(HWND hwnd, UINT uMsg, ULONG lEvents) { if (0 == _uChangeReg) { _uChangeReg = _RegisterNotify(hwnd, uMsg, _pidl, lEvents, SHCNRF_ShellLevel | SHCNRF_InterruptLevel | SHCNRF_RecursiveInterrupt, TRUE); } return 0 != _uChangeReg ? S_OK : E_FAIL; } // Unregister change notification for the _Namespace HRESULT CMergedFldrNamespace::UnregisterNotify() { if (_uChangeReg) { ::SHChangeNotifyDeregister(_uChangeReg); _uChangeReg = 0; } return S_OK; } inline BOOL CMergedFldrNamespace::SetOwner(IUnknown *punkOwner) { if (!_psf) return FALSE; IUnknown_SetOwner(_psf, punkOwner); return TRUE; } int CMergedFldrNamespace::GetNamespaceOverlayIndex(LPCITEMIDLIST pidl) { int iIndex = -1; if (_psf) { IShellIconOverlay *psio; if (SUCCEEDED(_psf->QueryInterface(IID_PPV_ARG(IShellIconOverlay, &psio)))) { psio->GetOverlayIndex(pidl, &iIndex); psio->Release(); } } return iIndex; } // object which takes ownership of the IDLIST and handles wrapping and returning information from it. class CMergedFldrItem { public: ~CMergedFldrItem(); BOOL Init(IShellFolder* psf, LPITEMIDLIST pidl, int iNamespace); BOOL Init(CMergedFldrItem *pmfi); BOOL SetDisplayName(LPTSTR pszDispName) { return Str_SetPtr(&_pszDisplayName, pszDispName); } ULONG GetFolderAttrib() { return _rgfAttrib; } LPTSTR GetDisplayName() { return _pszDisplayName; } LPITEMIDLIST GetIDList() { return _pidlWrap; } int GetNamespaceID() { return _iNamespace; } private: ULONG _rgfAttrib; LPTSTR _pszDisplayName; LPITEMIDLIST _pidlWrap; int _iNamespace; friend CMergedFolder; }; CMergedFldrItem::~CMergedFldrItem() { Str_SetPtr(&_pszDisplayName, NULL); ILFree(_pidlWrap); } BOOL CMergedFldrItem::Init(CMergedFldrItem *pmfi) { _iNamespace = pmfi->_iNamespace; _pidlWrap = ILClone(pmfi->GetIDList()); BOOL fRet = (_pidlWrap != NULL); if (fRet) { fRet = SetDisplayName(pmfi->GetDisplayName()); _rgfAttrib = pmfi->GetFolderAttrib(); } return fRet; } BOOL CMergedFldrItem::Init(IShellFolder* psf, LPITEMIDLIST pidl, int iNamespace) { BOOL fRet = FALSE; _pidlWrap = pidl; // evil, hold an alias _rgfAttrib = SFGAO_FOLDER | SFGAO_HIDDEN; _iNamespace = iNamespace; if (SUCCEEDED(psf->GetAttributesOf(1, (LPCITEMIDLIST*)&pidl, &_rgfAttrib))) { TCHAR szDisplayName[MAX_PATH]; if (SUCCEEDED(DisplayNameOf(psf, pidl, SHGDN_FORPARSING | SHGDN_INFOLDER, szDisplayName, ARRAYSIZE(szDisplayName)))) { fRet = SetDisplayName(szDisplayName); } } return fRet; } // shell folder object. CMergedFolder::CMergedFolder(CMergedFolder *pmfParent, REFCLSID clsid) : _clsid(clsid), _cRef(1), _pmfParent(pmfParent), _iColumnOffset(-1) { ASSERT(_hdpaNamespaces == NULL); if (_pmfParent) { _pmfParent->AddRef(); _fDontMerge = _pmfParent->_fDontMerge; _fCDBurn = _pmfParent->_fCDBurn; _fInShellView = _pmfParent->_fInShellView; _dwDropEffect = _pmfParent->_dwDropEffect; } else { _fDontMerge = IsEqualCLSID(_clsid, CLSID_CompositeFolder); _fCDBurn = IsEqualCLSID(_clsid, CLSID_CDBurnFolder); } DllAddRef(); } CMergedFolder::~CMergedFolder() { SetOwner(NULL); ILFree(_pidl); _FreeNamespaces(); _FreeObjects(); ATOMICRELEASE(_pmfParent); ATOMICRELEASE(_pstg); DllRelease(); } // CMergedFolderglobal CreateInstance method for da class factory HRESULT CMergedFolder_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) { // aggregation checking is handled in class factory HRESULT hr = E_OUTOFMEMORY; CMergedFolder* pmf = new CMergedFolder(NULL, CLSID_MergedFolder); if (pmf) { hr = pmf->QueryInterface(riid, ppv); pmf->Release(); } return hr; } HRESULT CCompositeFolder_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) { // aggregation checking is handled in class factory HRESULT hr = E_OUTOFMEMORY; CMergedFolder* pmf = new CMergedFolder(NULL, CLSID_CompositeFolder); if (pmf) { hr = pmf->QueryInterface(riid, ppv); pmf->Release(); } return hr; } #ifdef TESTING_COMPOSITEFOLDER COMPFOLDERINIT s_rgcfiTripleD[] = { {CFITYPE_CSIDL, CSIDL_DRIVES, L"Drives"}, {CFITYPE_PIDL, (int)&c_idlDesktop, L"Desktop"}, {CFITYPE_PATH, (int)L"::{450d8fba-ad25-11d0-98a8-0800361b1103}", L"MyDocs"} }; STDAPI CTripleD_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) { ICompositeFolder *pcf; HRESULT hr = CCompositeFolder_CreateInstance(punkOuter, IID_PPV_ARG(ICompositeFolder, &pcf)); if (SUCCEEDED(hr)) { hr = pcf->InitComposite(0x8877, CLSID_TripleD, CFINITF_FLAT, ARRAYSIZE(s_rgcfiTripleD), s_rgcfiTripleD); if (SUCCEEDED(hr)) { hr = pcf->QueryInterface(riid, ppv); } pcf->Release(); } return hr; } #endif //TESTING_COMPOSITEFOLDER STDMETHODIMP CMergedFolder::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENTMULTI(CMergedFolder, IShellFolder, IAugmentedShellFolder), QITABENT (CMergedFolder, IAugmentedShellFolder), QITABENT (CMergedFolder, IAugmentedShellFolder2), QITABENT (CMergedFolder, IAugmentedShellFolder3), QITABENT (CMergedFolder, IShellFolder2), QITABENT (CMergedFolder, IShellService), QITABENT (CMergedFolder, ITranslateShellChangeNotify), QITABENT (CMergedFolder, IStorage), QITABENT (CMergedFolder, IShellIconOverlay), QITABENTMULTI(CMergedFolder, IPersist, IPersistFolder2), QITABENTMULTI(CMergedFolder, IPersistFolder, IPersistFolder2), QITABENT (CMergedFolder, IPersistFolder2), QITABENT (CMergedFolder, IPersistPropertyBag), QITABENT (CMergedFolder, ICompositeFolder), QITABENT (CMergedFolder, IItemNameLimits), { 0 }, }; if (IsEqualIID(CLSID_MergedFolder, riid)) { *ppv = this; AddRef(); return S_OK; } return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) CMergedFolder::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CMergedFolder::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } // get the count from the enumerated items. int CMergedFolder::_ObjectCount() const { return _hdpaObjects ? DPA_GetPtrCount(_hdpaObjects) : 0; } CMergedFldrItem *CMergedFolder::_GetObject(int i) { return _hdpaObjects ? (CMergedFldrItem *)DPA_GetPtr(_hdpaObjects, i) : NULL; } int CMergedFolder::_NamespaceCount() const { return _hdpaNamespaces ? DPA_GetPtrCount(_hdpaNamespaces) : 0; } // Retrieves a pointer to a source _Namespace descriptor associated with // the specified lookup index. HRESULT CMergedFolder::_Namespace(int iIndex, CMergedFldrNamespace **ppns) { *ppns = NULL; if ((iIndex >= 0) && (iIndex < _NamespaceCount())) *ppns = _Namespace(iIndex); return *ppns ? S_OK : E_INVALIDARG; } // given an index for the name space return it. CMergedFldrNamespace* CMergedFolder::_Namespace(int iNamespace) { if (!_hdpaNamespaces) return NULL; return (CMergedFldrNamespace*)DPA_GetPtr(_hdpaNamespaces, iNamespace); } // Determine whether pidls from the two namespaces should be merged // The NULL namespace is a wildcard that always merges (if merging is permitted at all) BOOL CMergedFolder::_ShouldMergeNamespaces(CMergedFldrNamespace *pns1, CMergedFldrNamespace *pns2) { // Early-out: Identical namespaces can be merged (even if merging // is globally disabled) if (pns1 == pns2) { return TRUE; } // Early-out: Merging globally disabled if (_fDontMerge) { return FALSE; } // Early-out: Merging globally enabled if (!_fPartialMerge) { return TRUE; } if (!pns1 || !pns2) { return TRUE; // wildcard } if (!(pns1->FolderAttrib() & ASFF_MERGESAMEGUID)) { // this namespace will merge with anybody! return TRUE; } // Source namespace will merge only with namespaces of the same GUID // See if destination namespace has the same GUID return IsEqualGUID(pns1->GetGUID(), pns2->GetGUID()); } // Determine whether pidls from the two namespaces should be merged // NAmespace -1 is a wildcard that always merges (if merging is permitted at all) BOOL CMergedFolder::_ShouldMergeNamespaces(int iNS1, int iNS2) { // Early-out: Merging globally disabled if (_fDontMerge) { return FALSE; } // Early-out: Merging globally enabled if (!_fPartialMerge) { return TRUE; } if (iNS1 < 0 || iNS2 < 0) { return TRUE; // wildcard } return _ShouldMergeNamespaces(_Namespace(iNS1), _Namespace(iNS2)); } // check to see if the IDLIST we are given is a wrapped one. HRESULT CMergedFolder::_IsWrap(LPCITEMIDLIST pidl) { HRESULT hr = E_INVALIDARG; if (pidl) { ASSERT(IS_VALID_PIDL(pidl)); PAUGM_IDWRAP pWrap = (PAUGM_IDWRAP)pidl; if ((pWrap->cb >= sizeof(AUGM_IDWRAP)) && (pWrap->ulTag == AUGM_WRAPTAG) && (pWrap->ulVersion == AUGM_WRAPVERSION_2_0)) { hr = S_OK; } else if (ILFindHiddenID(pidl, IDLHID_PARENTFOLDER)) { hr = S_OK; } } return hr; } // STRRET_OFFSET has no meaning in context of the pidl wrapper. // We can either calculate the offset into the wrapper, or allocate // a wide char for the name. For expedience, we'll allocate the name. HRESULT CMergedFolder::_FixStrRetOffset(LPCITEMIDLIST pidl, STRRET *psr) { HRESULT hr = S_OK; if (psr->uType == STRRET_OFFSET) { UINT cch = lstrlenA(STRRET_OFFPTR(pidl, psr)); LPWSTR pwszName = (LPWSTR)SHAlloc((cch + 1) * sizeof(WCHAR)); if (pwszName) { SHAnsiToUnicode(STRRET_OFFPTR(pidl, psr), pwszName, cch + 1); pwszName[cch] = 0; psr->pOleStr = pwszName; psr->uType = STRRET_WSTR; } else hr = E_OUTOFMEMORY; } return hr; } // is the object a folder? BOOL CMergedFolder::_IsFolder(LPCITEMIDLIST pidl) { ULONG rgf = SFGAO_FOLDER | SFGAO_STREAM; return SUCCEEDED(GetAttributesOf(1, &pidl, &rgf)) && (SFGAO_FOLDER == (rgf & (SFGAO_FOLDER | SFGAO_STREAM))); } // the number of source _Namespace pidls in the wrap. ULONG CMergedFolder::_GetSourceCount(LPCITEMIDLIST pidl) { if (SUCCEEDED(_IsWrap(pidl))) { if (ILFindHiddenID(pidl, IDLHID_PARENTFOLDER)) { return 1; } else { PAUGM_IDWRAP pWrap = (PAUGM_IDWRAP)pidl; return pWrap->cSrcs; } } return 0; } // Creates an IDLIST for CMergedFolder that wraps a single source pidl. HRESULT CMergedFolder::_CreateWrap(LPCITEMIDLIST pidlSrc, UINT nSrcID, LPITEMIDLIST *ppidlWrap) { *ppidlWrap = NULL; // incase of failure LPITEMIDLIST pidlSrcWithID; HRESULT hr = SHILClone(pidlSrc, &pidlSrcWithID); if (SUCCEEDED(hr)) { hr = E_OUTOFMEMORY; if (!ILFindHiddenID(pidlSrcWithID, IDLHID_PARENTFOLDER)) { AUGM_NAMESPACE ans = { {sizeof(ans), AUGM_NS_CURRENTVERSION, IDLHID_PARENTFOLDER} , nSrcID }; pidlSrcWithID = ILAppendHiddenID((LPITEMIDLIST)pidlSrcWithID, &ans.hid); } if (pidlSrcWithID) { UINT cbAlloc = sizeof(AUGM_IDWRAP) + CB_IDLIST_TERMINATOR + // header for our IDLIST pidlSrcWithID->mkid.cb + CB_IDLIST_TERMINATOR; // wrapped IDLIST AUGM_IDWRAP *pWrap = (AUGM_IDWRAP *)_ILCreate(cbAlloc); if (pWrap) { // fill out wrapped header pWrap->cb = (USHORT)(cbAlloc - CB_IDLIST_TERMINATOR); pWrap->dwFlags = 0; pWrap->ulTag = AUGM_WRAPTAG; pWrap->ulVersion = AUGM_WRAPVERSION_2_0; pWrap->cSrcs = 1; // copy the IDLIST with the hidden data into the wrapping object LPITEMIDLIST pidl = (LPITEMIDLIST)((BYTE *)pWrap + sizeof(AUGM_IDWRAP)); memcpy(pidl, pidlSrcWithID, pidlSrcWithID->mkid.cb); *ppidlWrap = (LPITEMIDLIST)pWrap; hr = S_OK; } ILFree(pidlSrcWithID); } } return hr; } // does the wrapped IDLIST we are passed contain the given source ID? BOOL CMergedFolder::_ContainsSrcID(LPCITEMIDLIST pidl, UINT uSrcID) { UINT uID; for (UINT nSrc = 0; SUCCEEDED(_GetSubPidl(pidl, nSrc, &uID, NULL, NULL)); nSrc++) { if (uID == uSrcID) return TRUE; } return FALSE; } // returns new pidl in *ppidl free of nSrcID HRESULT CMergedFolder::_WrapRemoveIDList(LPITEMIDLIST pidlWrap, UINT nSrcID, LPITEMIDLIST *ppidl) { ASSERT(IS_VALID_WRITE_PTR(ppidl, LPITEMIDLIST)); *ppidl = NULL; HRESULT hr = _IsWrap(pidlWrap); if (SUCCEEDED(hr)) { UINT uID; LPITEMIDLIST pidl; for (UINT i = 0; SUCCEEDED(hr) && SUCCEEDED(_GetSubPidl(pidlWrap, i, &uID, &pidl, NULL)); i++) { if (uID != nSrcID) hr = _WrapAddIDList(pidl, uID, ppidl); ILFree(pidl); } } return hr; } HRESULT CMergedFolder::_WrapRemoveIDListAbs(LPITEMIDLIST pidlWrapAbs, UINT nSrcID, LPITEMIDLIST *ppidlAbs) { ASSERT(ppidlAbs); HRESULT hr = E_OUTOFMEMORY; *ppidlAbs = ILCloneParent(pidlWrapAbs); if (*ppidlAbs) { LPITEMIDLIST pidlLast; hr = _WrapRemoveIDList(ILFindLastID(pidlWrapAbs), nSrcID, &pidlLast); if (SUCCEEDED(hr)) { // shilappend frees pidlLast hr = SHILAppend(pidlLast, ppidlAbs); } } return hr; } // Adds a source pidl to *ppidlWrap (IN/OUT param!) HRESULT CMergedFolder::_WrapAddIDList(LPCITEMIDLIST pidlSrc, UINT nSrcID, LPITEMIDLIST* ppidlWrap) { HRESULT hr; if (!*ppidlWrap) { // called as a create, rather than append hr = _CreateWrap(pidlSrc, nSrcID, ppidlWrap); } else { // check to see if we already have the ID in this IDLIST we are wrapping onto. LPITEMIDLIST pidlSrcWithID; hr = SHILClone(pidlSrc, &pidlSrcWithID); if (SUCCEEDED(hr)) { hr = E_OUTOFMEMORY; if (!ILFindHiddenID(pidlSrcWithID, IDLHID_PARENTFOLDER)) { AUGM_NAMESPACE ans = { {sizeof(ans), AUGM_NS_CURRENTVERSION, IDLHID_PARENTFOLDER} , nSrcID }; pidlSrcWithID = ILAppendHiddenID((LPITEMIDLIST)pidlSrcWithID, &ans.hid); } // ok, we have an IDLIST that we can use to append to this object. if (pidlSrcWithID) { BOOL fOtherSrcIDsExist = TRUE; // check to see if this ID already exists within the wrap idlist. if (*ppidlWrap && _ContainsSrcID(*ppidlWrap, nSrcID)) { LPITEMIDLIST pidlFree = *ppidlWrap; if (SUCCEEDED(_WrapRemoveIDList(pidlFree, nSrcID, ppidlWrap))) { ILFree(pidlFree); } fOtherSrcIDsExist = (*ppidlWrap != NULL); } if (fOtherSrcIDsExist) { // now compute the new size of the IDLIST. (*ppidlWrap has been updated); PAUGM_IDWRAP pWrap = (PAUGM_IDWRAP)*ppidlWrap; SHORT cbOld = pWrap->cb; SHORT cbNew = cbOld + (pidlSrcWithID->mkid.cb + CB_IDLIST_TERMINATOR); // extra terminator is appended. pWrap = (PAUGM_IDWRAP)SHRealloc(pWrap, cbNew + CB_IDLIST_TERMINATOR); if (pWrap) { // copy the new idlist and its hidden payload (ensure we are terminated) memcpy(((BYTE*)pWrap)+ cbOld, pidlSrcWithID, cbNew-cbOld); *((UNALIGNED SHORT*)(((BYTE*)pWrap)+ cbNew)) = 0; pWrap->cb += cbNew-cbOld; pWrap->cSrcs++; hr = S_OK; } *ppidlWrap = (LPITEMIDLIST)pWrap; } else { hr = _CreateWrap(pidlSrc, nSrcID, ppidlWrap); } ILFree(pidlSrcWithID); } } } return hr; } // used to itterate through the sub pidls in the wrapped pidl // all out params optional // // out: // *ppidl alias into pidlWrap (nested pidl) HRESULT CMergedFolder::_GetSubPidl(LPCITEMIDLIST pidlWrap, int i, UINT *pnSrcID, LPITEMIDLIST *ppidl, CMergedFldrNamespace **ppns) { if (pnSrcID) *pnSrcID = -1; if (ppidl) *ppidl = NULL; if (ppns) *ppns = NULL; HRESULT hr = _IsWrap(pidlWrap); if (SUCCEEDED(hr)) { if ((UINT)i < _GetSourceCount(pidlWrap)) { PAUGM_NAMESPACE pans = (PAUGM_NAMESPACE)ILFindHiddenID(pidlWrap, IDLHID_PARENTFOLDER); if (!pans) { PAUGM_IDWRAP pWrap = (PAUGM_IDWRAP)pidlWrap; LPITEMIDLIST pidlSrc = (LPITEMIDLIST)(((BYTE *)pWrap) + sizeof(AUGM_IDWRAP)); while (i--) { // advance to next item SHORT cb = pidlSrc->mkid.cb; pidlSrc = (LPITEMIDLIST)(((BYTE *)pidlSrc) + cb + CB_IDLIST_TERMINATOR); } if (pnSrcID || ppns) { PAUGM_NAMESPACE pans = (PAUGM_NAMESPACE)ILFindHiddenID(pidlSrc, IDLHID_PARENTFOLDER); ASSERTMSG((pans != NULL), "Failed to find hidden _Namespace in pidlWrap"); if (pans && pnSrcID) *pnSrcID = pans->uSrcID; if (pans && ppns) hr = _Namespace(pans->uSrcID, ppns); } if (SUCCEEDED(hr) && ppidl) { hr = SHILClone(pidlSrc, ppidl); } } else { if (pnSrcID) *pnSrcID = pans->uSrcID; if (ppns) hr = _Namespace(pans->uSrcID, ppns); if (SUCCEEDED(hr) && ppidl) { hr = SHILClone(pidlWrap, ppidl); } } } else { hr = E_INVALIDARG; } } if (SUCCEEDED(hr) && ppidl) { // we need to strip away the hidden id that marks this guy as merged. // this is because the pidl we're returning is supposed to be a child of one of our // namespaces we're merging, so it should know absolutely nothing about being merged. // these guys used to slip through and cause problems. ILRemoveHiddenID(*ppidl, IDLHID_PARENTFOLDER); } ASSERT(!ppidl || (ILFindLastID(*ppidl) == *ppidl)); return hr; } // function to compare two opaque pidls. // this is helpful since in the non-merged case, there's some difficulty // getting defview to contain items with the same name. we need a way to // compare two pidls to say "yes defview, these pidls are actually different!" // note that the actual order doesn't matter, as long as the comparison // is consistent (since this is used in sorting functions). int CMergedFolder::_CompareArbitraryPidls(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { UINT iRet; UINT cbItem1 = ILGetSize(pidl1); UINT cbItem2 = ILGetSize(pidl2); if (cbItem1 != cbItem2) { iRet = (cbItem1 < cbItem2) ? 1 : -1; } else { iRet = memcmp(pidl1, pidl2, cbItem1); ASSERTMSG(iRet != 0, "no two pidls from the enumerators should be EXACTLY alike!"); } return iRet; } int CMergedFolder::_Compare(void *pv1, void *pv2, LPARAM lParam) { int iRet = -1; CMergedFldrItem* pmfiEnum1 = (CMergedFldrItem*)pv1; CMergedFldrItem* pmfiEnum2 = (CMergedFldrItem*)pv2; if (pmfiEnum1 && pmfiEnum2) { // Are these two items of different types? if (BOOLIFY(pmfiEnum1->GetFolderAttrib() & SFGAO_FOLDER) ^ BOOLIFY(pmfiEnum2->GetFolderAttrib() & SFGAO_FOLDER)) { // Yes. Then Folders sort before items. iRet = BOOLIFY(pmfiEnum1->GetFolderAttrib() & SFGAO_FOLDER) ? 1 : -1; } else // They are of the same type. Then compare by name { iRet = lstrcmpi(pmfiEnum1->GetDisplayName(), pmfiEnum2->GetDisplayName()); if (iRet == 0) { CMergedFolder *pmf = (CMergedFolder *) lParam; if (!pmf->_ShouldMergeNamespaces(pmfiEnum1->GetNamespaceID(), pmfiEnum2->GetNamespaceID())) { // these items cannot be merged, // force iRet to be nonzero. the only reason why this comparison // has to be well-defined is so we can pass our ASSERTs that the // list is sorted using this comparison function. iRet = _CompareArbitraryPidls(pmfiEnum1->GetIDList(), pmfiEnum2->GetIDList()); } } } } return iRet; } void *CMergedFolder::_Merge(UINT uMsg, void *pv1, void *pv2, LPARAM lParam) { CMergedFolder*pmf = (CMergedFolder*)lParam; void * pvRet = pv1; switch (uMsg) { case DPAMM_MERGE: { UINT nSrcID; LPITEMIDLIST pidl; CMergedFldrItem* pitemSrc = (CMergedFldrItem*)pv2; if (SUCCEEDED(pmf->_GetSubPidl(pitemSrc->GetIDList(), 0, &nSrcID, &pidl, NULL))) { // add pidl from src to dest CMergedFldrItem* pitemDest = (CMergedFldrItem*)pv1; pmf->_WrapAddIDList(pidl, nSrcID, &pitemDest->_pidlWrap); ILFree(pidl); } } break; case DPAMM_INSERT: { CMergedFldrItem* pmfiNew = new CMergedFldrItem; if (pmfiNew) { CMergedFldrItem* pmfiSrc = (CMergedFldrItem*)pv1; if (!pmfiNew->Init(pmfiSrc)) { delete pmfiNew; pmfiNew = NULL; } } pvRet = pmfiNew; } break; default: ASSERT(0); } return pvRet; } typedef struct { LPTSTR pszDisplayName; BOOL fFolder; CMergedFolder *self; int iNamespace; } SEARCH_FOR_PIDL; int CALLBACK CMergedFolder::_SearchByName(void *p1, void *p2, LPARAM lParam) { SEARCH_FOR_PIDL* psfp = (SEARCH_FOR_PIDL*)p1; CMergedFldrItem* pmfiEnum = (CMergedFldrItem*)p2; // Are they of different types? if (BOOLIFY(pmfiEnum->GetFolderAttrib() & SFGAO_FOLDER) ^ psfp->fFolder) { // Yes. return psfp->fFolder ? 1 : -1; } // They are of the same type. Then compare by name int iRc = StrCmpI(psfp->pszDisplayName, pmfiEnum->GetDisplayName()); if (iRc) return iRc; // They are the same name. But if they're not allowed to merge, then // they're really different. if (!psfp->self->_ShouldMergeNamespaces(pmfiEnum->GetNamespaceID(), psfp->iNamespace)) { // Sort by namespace ID return psfp->iNamespace - pmfiEnum->GetNamespaceID(); } // I guess they're really equal return 0; } // IPersistFolder::Initialize() STDMETHODIMP CMergedFolder::Initialize(LPCITEMIDLIST pidl) { return Pidl_Set(&_pidl, pidl) ? S_OK : E_OUTOFMEMORY; } // IPersistFolder2::GetCurFolder() STDMETHODIMP CMergedFolder::GetCurFolder(LPITEMIDLIST *ppidl) { if (_pidl) return SHILClone(_pidl, ppidl); else { *ppidl = NULL; return S_FALSE; } } // IPersistPropertyBag void CMergedFolder::_GetKeyForProperty(LPWSTR pszName, LPWSTR pszValue, LPWSTR pszBuffer, INT cchBuffer) { StrCpyNW(pszBuffer, L"MergedFolder\\", cchBuffer); StrCatBuffW(pszBuffer, pszName, cchBuffer); StrCatBuffW(pszBuffer, pszValue, cchBuffer); } HRESULT CMergedFolder::_AddNameSpaceFromPropertyBag(IPropertyBag *ppb, LPWSTR pszName) { WCHAR szKey[MAX_PATH]; // get the path of the folder WCHAR szPath[MAX_PATH]; LPITEMIDLIST pidl = NULL; _GetKeyForProperty(pszName, L"Path", szKey, ARRAYSIZE(szKey)); HRESULT hr = SHPropertyBag_ReadStr(ppb, szKey, szPath, ARRAYSIZE(szPath)); if (SUCCEEDED(hr)) { // we picked a path from the property bag, so lets convert // that to an IDLIST so we can do something with it. hr = SHILCreateFromPath(szPath, &pidl, NULL); } else { // attempt to determine the CSIDL for the folder we are going // to show, if that works then convert it to an IDLIST // so that we can pass it to AddNamespace. _GetKeyForProperty(pszName, L"CSIDL", szKey, ARRAYSIZE(szKey)); int csidl; hr = SHPropertyBag_ReadDWORD(ppb, szKey, (DWORD*)&csidl); if (SUCCEEDED(hr)) { hr = SHGetSpecialFolderLocation(NULL, csidl, &pidl); } } if (SUCCEEDED(hr) && pidl) { // we succeeded in getting a location for the folder we // are going to add, so lets pick up the rest of the // information on that object. GUID guid; GUID *pguid = NULL; _GetKeyForProperty(pszName, L"GUID", szKey, ARRAYSIZE(szKey)); pguid = SUCCEEDED(SHPropertyBag_ReadGUID(ppb, szKey, &guid)) ? &guid:NULL; DWORD dwFlags = 0; _GetKeyForProperty(pszName, L"Flags", szKey, ARRAYSIZE(szKey)); SHPropertyBag_ReadDWORD(ppb, szKey, &dwFlags); hr = AddNameSpace(pguid, NULL, pidl, dwFlags); } ILFree(pidl); return hr; } HRESULT CMergedFolder::Load(IPropertyBag* ppb, IErrorLog *pErrLog) { SHPropertyBag_ReadGUID(ppb, L"MergedFolder\\CLSID", &_clsid); // get the folders CLSID SHPropertyBag_ReadDWORD(ppb, L"MergedFolder\\DropEffect", &_dwDropEffect); _fInShellView = SHPropertyBag_ReadBOOLDefRet(ppb, L"MergedFolder\\ShellView", FALSE); WCHAR sz[MAX_PATH]; if (SUCCEEDED(SHPropertyBag_ReadStr(ppb, L"MergedFolder\\Folders", sz, ARRAYSIZE(sz)))) { LPWSTR pszName = sz; while (pszName && *pszName) { LPWSTR pszNext = StrChrW(pszName, L','); if (pszNext) { *pszNext = 0; pszNext++; } _AddNameSpaceFromPropertyBag(ppb, pszName); pszName = pszNext; } } return S_OK; } // IShellFolder STDMETHODIMP CMergedFolder::EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList **ppenumIDList) { *ppenumIDList = NULL; HRESULT hr = E_FAIL; if (_hdpaNamespaces) { _FreeObjects(); hr = CMergedFldrEnum_CreateInstance(this, grfFlags, ppenumIDList); } if (SUCCEEDED(hr) && _fInShellView) { Register(NULL, 0, 0); } return hr; } HRESULT CMergedFolder::_CreateWithCLSID(CLSID clsid, CMergedFolder **ppmf) { *ppmf = new CMergedFolder(this, clsid); return *ppmf ? S_OK : E_OUTOFMEMORY; } BOOL CMergedFolder::_ShouldSuspend(REFGUID rguid) { return FALSE; } // create a new CMergedFolder from the first element in pidlWrap // this is our private init method, IPersistFolder::Initialize() is how we // get inited at our junction point. HRESULT CMergedFolder::_New(LPCITEMIDLIST pidlWrap, CMergedFolder **ppmf) { ASSERT(ppmf); *ppmf = NULL; HRESULT hr = E_OUTOFMEMORY; // assume the worst // just want the first element in pidlWrap LPITEMIDLIST pidlFirst = ILCloneFirst(pidlWrap); if (pidlFirst) { if (_IsFolder(pidlFirst)) { hr = _CreateWithCLSID(_clsid, ppmf); if (SUCCEEDED(hr) && _pidl) { hr = SHILCombine(_pidl, pidlFirst, &(*ppmf)->_pidl); if (FAILED(hr)) { (*ppmf)->Release(); *ppmf = NULL; } } } else { hr = E_NOINTERFACE; } ILFree(pidlFirst); } return hr; } void CMergedFolder::_AddAllOtherNamespaces(LPITEMIDLIST *ppidl) { TCHAR szName[MAX_PATH]; if (SUCCEEDED(DisplayNameOf(static_cast(this), *ppidl, SHGDN_FORPARSING | SHGDN_INFOLDER, szName, ARRAYSIZE(szName)))) { CMergedFldrNamespace *pns; for (int n = 0; pns = _Namespace(n); n++) { if (FAILED(_GetSubPidl(*ppidl, n, NULL, NULL, NULL))) { IBindCtx *pbc; WIN32_FIND_DATA wfd = {0}; wfd.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; if (SUCCEEDED(SHCreateFileSysBindCtx(&wfd, &pbc))) { LPITEMIDLIST pidlNamespace; if (SUCCEEDED(pns->Folder()->ParseDisplayName(NULL, pbc, szName, NULL, &pidlNamespace, NULL))) { _WrapAddIDList(pidlNamespace, n, ppidl); ILFree(pidlNamespace); } pbc->Release(); } } } } } STDMETHODIMP CMergedFolder::BindToObject(LPCITEMIDLIST pidlWrap, LPBC pbc, REFIID riid, void **ppv) { ASSERT(IS_VALID_PIDL(pidlWrap)); *ppv = NULL; HRESULT hr = E_OUTOFMEMORY; LPITEMIDLIST pidlRewrappedFirst; if (_fDontMerge) { // this doesn't contain a wrap consisting of all namespaces but only one instead LPITEMIDLIST pidlWrapFirst = ILCloneFirst(pidlWrap); if (pidlWrapFirst) { TCHAR szName[MAX_PATH]; hr = DisplayNameOf(reinterpret_cast(this), pidlWrapFirst, SHGDN_FORPARSING | SHGDN_INFOLDER, szName, ARRAYSIZE(szName)); if (SUCCEEDED(hr)) { // we want to round-trip the name so the non-merged pidl gets remerged // during a bind (so you dont get just one namespace from here on down) hr = ParseDisplayName(NULL, NULL, szName, NULL, &pidlRewrappedFirst, NULL); } ILFree(pidlWrapFirst); } } else { pidlRewrappedFirst = ILCloneFirst(pidlWrap); if (pidlRewrappedFirst) { hr = S_OK; if (_fCDBurn && _IsFolder(pidlRewrappedFirst)) { // in the cdburn case we need to fake up the other namespaces in the pidl we're about to bind to. // this is so when we navigate into a subfolder that only exists on the CD and not the staging area, // if a file is later added to the staging area it'll still be merged in. _AddAllOtherNamespaces(&pidlRewrappedFirst); } } } if (SUCCEEDED(hr)) { // just in case ASSERT(SUCCEEDED(_IsWrap(pidlRewrappedFirst))); LPCITEMIDLIST pidlNext = _ILNext(pidlWrap); CMergedFolder *pmf; hr = _New(pidlRewrappedFirst, &pmf); if (SUCCEEDED(hr)) { LPITEMIDLIST pidlSrc; CMergedFldrNamespace *pns; for (UINT i = 0; SUCCEEDED(_GetSubPidl(pidlRewrappedFirst, i, NULL, &pidlSrc, &pns)); i++) { hr = E_OUTOFMEMORY; ASSERT(ILFindLastID(pidlSrc) == pidlSrc); LPITEMIDLIST pidlSrcFirst = ILCloneFirst(pidlSrc); if (pidlSrcFirst) { IShellFolder *psf; if (SUCCEEDED(pns->Folder()->BindToObject(pidlSrcFirst, pbc, IID_PPV_ARG(IShellFolder, &psf)))) { LPITEMIDLIST pidlAbs = ILCombine(pns->GetIDList(), pidlSrcFirst); if (pidlAbs) { CMergedFldrNamespace *pnsNew = new CMergedFldrNamespace(); if (pnsNew) { hr = pnsNew->SetNamespace(&(pns->GetGUID()), psf, pidlAbs, pns->FolderAttrib()); if (SUCCEEDED(hr)) { // propagate the drop folder down to the child. hr = pnsNew->SetDropFolder(pns->GetDropFolder()); if (SUCCEEDED(hr)) { hr = pmf->_SimpleAddNamespace(pnsNew); if (SUCCEEDED(hr)) { // success, _SimpleAddNamespace took ownership pnsNew = NULL; } } } if (pnsNew) delete pnsNew; } else { hr = E_OUTOFMEMORY; } ILFree(pidlAbs); } psf->Release(); } ILFree(pidlSrcFirst); } ILFree(pidlSrc); } // it's possible to go through the loop without adding any namespaces. // usually it's when BindToObject above fails -- this can happen if somebody // puts a junction point in the merged folder (like a zip file). in that case // we're in trouble. if (ILIsEmpty(pidlNext)) hr = pmf->QueryInterface(riid, ppv); else hr = pmf->BindToObject(pidlNext, pbc, riid, ppv); pmf->Release(); } if (FAILED(hr) && ILIsEmpty(pidlNext)) { // maybe it's an interface that we don't support ourselves (IStream?). // we cant merge interfaces that we don't know about so lets just // assume we'll pick the interface up from the default namespace in // the wrapped pidl. LPITEMIDLIST pidlSrc; CMergedFldrNamespace *pns; hr = _NamespaceForItem(pidlRewrappedFirst, ASFF_DEFNAMESPACE_BINDSTG, ASFF_DEFNAMESPACE_BINDSTG, NULL, &pidlSrc, &pns); if (SUCCEEDED(hr)) { hr = pns->Folder()->BindToObject(pidlSrc, pbc, riid, ppv); ILFree(pidlSrc); } } ILFree(pidlRewrappedFirst); } if (SUCCEEDED(hr) && _fInShellView) { Register(NULL, 0, 0); } return hr; } STDMETHODIMP CMergedFolder::BindToStorage(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, void **ppv) { return BindToObject(pidl, pbc, riid, ppv); } HRESULT CMergedFolder::_CompareSingleLevelIDs(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { IShellFolder *psf1; LPITEMIDLIST pidlItem1; CMergedFldrNamespace *pns1; HRESULT hr = _NamespaceForItem(pidl1, ASFF_DEFNAMESPACE_DISPLAYNAME, ASFF_DEFNAMESPACE_DISPLAYNAME, &psf1, &pidlItem1, &pns1); if (SUCCEEDED(hr)) { IShellFolder *psf2; LPITEMIDLIST pidlItem2; CMergedFldrNamespace *pns2; hr = _NamespaceForItem(pidl2, ASFF_DEFNAMESPACE_DISPLAYNAME, ASFF_DEFNAMESPACE_DISPLAYNAME, &psf2, &pidlItem2, &pns2); if (SUCCEEDED(hr)) { // Same _Namespace? Just forward the request. if (psf1 == psf2) { hr = psf1->CompareIDs(lParam, pidlItem1, pidlItem2); } else if ((pns1->FolderAttrib() & ASFF_SORTDOWN) ^ (pns2->FolderAttrib() & ASFF_SORTDOWN)) { // One namespace marked ASFF_SORTDOWN and one not? The SORTDOWN one // comes second. hr = ResultFromShort((pns1->FolderAttrib() & ASFF_SORTDOWN) ? 1 : -1); } else { if (!_IsSimple(pidl1) && !_IsSimple(pidl2)) { // Comparison heuristics: // (1) folders take precedence over nonfolders, (2) alphanum comparison int iFolder1 = SHGetAttributes(psf1, pidlItem1, SFGAO_FOLDER) ? 1 : 0; int iFolder2 = SHGetAttributes(psf2, pidlItem2, SFGAO_FOLDER) ? 1 : 0; hr = ResultFromShort(iFolder2 - iFolder1); } else { // if a pidl is simple, compare based on name only. hr = ResultFromShort(0); } if (ResultFromShort(0) == hr) { TCHAR szName1[MAX_PATH], szName2[MAX_PATH]; if (SUCCEEDED(DisplayNameOf(psf1, pidlItem1, SHGDN_INFOLDER, szName1, ARRAYSIZE(szName1))) && SUCCEEDED(DisplayNameOf(psf2, pidlItem2, SHGDN_INFOLDER, szName2, ARRAYSIZE(szName2)))) { int iRet = StrCmp(szName1, szName2); // Comparisons are by name with items of the same type. if ((iRet == 0) && SUCCEEDED(DisplayNameOf(psf1, pidlItem1, SHGDN_FORPARSING | SHGDN_INFOLDER, szName1, ARRAYSIZE(szName1))) && SUCCEEDED(DisplayNameOf(psf2, pidlItem2, SHGDN_FORPARSING | SHGDN_INFOLDER, szName2, ARRAYSIZE(szName2)))) { iRet = lstrcmp(szName1, szName2); // minimal behavior change for xpsp1: fall back to parsing name if theres still a tie. if ((iRet == 0) && !_ShouldMergeNamespaces(pns1, pns2)) { ASSERTMSG(!_fInShellView, "we shouldn't be in this code path for the start menu"); // different namespaces must compare differently in the non-merged case. iRet = _CompareArbitraryPidls(pidlItem1, pidlItem2); } } hr = ResultFromShort(iRet); } } } ILFree(pidlItem2); } ILFree(pidlItem1); } return hr; } STDMETHODIMP CMergedFolder::CompareIDs(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { // just in case //ASSERT(!pidl1 || SUCCEEDED(_IsWrap(pidl1))); //ASSERT(!pidl2 || SUCCEEDED(_IsWrap(pidl2))); HRESULT hr = E_FAIL; LPITEMIDLIST pidlFirst1 = pidl1 ? ILCloneFirst(pidl1) : NULL; LPITEMIDLIST pidlFirst2 = pidl2 ? ILCloneFirst(pidl2) : NULL; if (pidlFirst1 && pidlFirst2) { hr = _CompareSingleLevelIDs(lParam, pidlFirst1, pidlFirst2); } ILFree(pidlFirst1); ILFree(pidlFirst2); // if there was an exact match then lets compare the trailing elements of the IDLIST // if there are some (by binding down) etc. if (ResultFromShort(0) == hr) { IShellFolder *psf; hr = QueryInterface(IID_PPV_ARG(IShellFolder, &psf)); if (SUCCEEDED(hr)) { hr = ILCompareRelIDs(psf, pidl1, pidl2, lParam); psf->Release(); } } if (!_IsSimple(pidl1) && !_IsSimple(pidl2)) { // if we're still the same, compare the number // of namespaces in the pidl. int nCount1, nCount2; if (ResultFromShort(0) == hr) { nCount1 = pidl1 ? _GetSourceCount(pidl1) : 0; nCount2 = pidl2 ? _GetSourceCount(pidl2) : 0; hr = ResultFromShort(nCount1 - nCount2); } // next compare the namespaces themselves. // basically we're only concerned with the two-namespace case, so if both pidls have // elements from 0 or 2 namespaces theyre equal; we're worried about when one pidl has // 1 sub-pidl in namespace 0 and the other one has 1 sub-pidl in namespace 1. // we dont worry about 3+ namespaces and those permutations. if ((ResultFromShort(0) == hr) && (nCount1 == 1) && (nCount2 == 1)) { GUID guid1 = GUID_NULL, guid2 = GUID_NULL; GetNameSpaceID(pidl1, &guid1); GetNameSpaceID(pidl2, &guid2); hr = ResultFromShort(memcmp(&guid1, &guid2, sizeof(GUID))); } } return hr; } STDMETHODIMP CMergedFolder::CreateViewObject(HWND hwnd, REFIID riid, void **ppv) { *ppv = NULL; HRESULT hr; if (IsEqualIID(riid, IID_IDropTarget)) { hr = CMergedFldrDropTarget_CreateInstance(this, hwnd, (IDropTarget**)ppv); } else if (IsEqualIID(riid, IID_IShellView)) { IShellFolderViewCB *psfvcb; hr = CMergedFolderViewCB_CreateInstance(this, &psfvcb); if (SUCCEEDED(hr)) { SFV_CREATE csfv = {0}; csfv.cbSize = sizeof(csfv); csfv.pshf = SAFECAST(this, IAugmentedShellFolder2*); csfv.psfvcb = psfvcb; hr = SHCreateShellFolderView(&csfv, (IShellView **)ppv); psfvcb->Release(); } } else if (_fInShellView && IsEqualIID(riid, IID_ICategoryProvider)) { IShellFolder *psf; hr = QueryInterface(IID_PPV_ARG(IShellFolder, &psf)); if (SUCCEEDED(hr)) { BEGIN_CATEGORY_LIST(s_Categories) CATEGORY_ENTRY_SCIDMAP(SCID_WHICHFOLDER, CLSID_MergedCategorizer) END_CATEGORY_LIST() hr = CCategoryProvider_Create(&CLSID_MergedCategorizer, &SCID_WHICHFOLDER, NULL, s_Categories, psf, riid, ppv); psf->Release(); } } else if (_fInShellView && IsEqualIID(riid, IID_IContextMenu)) { // this is pretty much what filefldr does to create its background // context menu. we don't want to let one of our namespaces take over for the background // context menu because then the context menu will think it's in an unmerged namespace. // for example the new menu would then work with the storage of the child namespace // and couldn't select the new item after its done since it has an unmerged pidl // and the view has a merged one. IShellFolder *psfToPass; hr = QueryInterface(IID_PPV_ARG(IShellFolder, &psfToPass)); if (SUCCEEDED(hr)) { HKEY hkNoFiles = NULL; RegOpenKeyEx(HKEY_CLASSES_ROOT, TEXT("Directory\\Background"), 0, KEY_READ, &hkNoFiles); // initialize with our merged pidl. IContextMenuCB *pcmcb = new CDefBackgroundMenuCB(_pidl); if (pcmcb) { hr = CDefFolderMenu_Create2Ex(_pidl, hwnd, 0, NULL, psfToPass, pcmcb, 1, &hkNoFiles, (IContextMenu **)ppv); pcmcb->Release(); } if (hkNoFiles) // CDefFolderMenu_Create can handle NULL ok RegCloseKey(hkNoFiles); psfToPass->Release(); } } else { CMergedFldrNamespace *pns; hr = _FindNamespace(ASFF_DEFNAMESPACE_VIEWOBJ, ASFF_DEFNAMESPACE_VIEWOBJ, NULL, &pns); if (SUCCEEDED(hr)) { hr = pns->Folder()->CreateViewObject(hwnd, riid, ppv); } } return hr; } STDMETHODIMP CMergedFolder::GetAttributesOf(UINT cidl, LPCITEMIDLIST *apidl, ULONG *rgfInOut) { // attribs of the namespace root. // scope this to the sub-namespaces? if (!cidl || !apidl) { *rgfInOut &= SFGAO_FOLDER | SFGAO_FILESYSTEM | SFGAO_LINK | SFGAO_DROPTARGET | SFGAO_CANRENAME | SFGAO_CANDELETE | SFGAO_CANLINK | SFGAO_CANCOPY | SFGAO_CANMOVE | SFGAO_HASSUBFOLDER; return S_OK; } HRESULT hr = S_OK; for (UINT i = 0; SUCCEEDED(hr) && (i < cidl); i++) { ULONG ulAttribs = *rgfInOut; IShellFolder* psf; LPITEMIDLIST pidlItem; CMergedFldrNamespace *pns; hr = _NamespaceForItem(apidl[0], ASFF_DEFNAMESPACE_ATTRIB, ASFF_DEFNAMESPACE_ATTRIB, &psf, &pidlItem, &pns); if (SUCCEEDED(hr)) { ulAttribs |= SFGAO_FOLDER; hr = psf->GetAttributesOf(1, (LPCITEMIDLIST*)&pidlItem, &ulAttribs); if (SUCCEEDED(hr)) { ulAttribs = pns->FixItemAttributes(ulAttribs); if (_fInShellView || !(*rgfInOut & SFGAO_FOLDER)) { ulAttribs &= ~SFGAO_CANLINK; // avoid people creating links to our pidls } if (*rgfInOut & (SFGAO_CANCOPY | SFGAO_CANMOVE | SFGAO_CANLINK)) { // allow per-type guys to do what they want. IQueryAssociations *pqa; DWORD dwDefEffect = DROPEFFECT_NONE; if (SUCCEEDED(psf->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST*)&pidlItem, IID_X_PPV_ARG(IQueryAssociations, NULL, &pqa)))) { DWORD cb = sizeof(dwDefEffect); pqa->GetData(0, ASSOCDATA_VALUE, L"DefaultDropEffect", &dwDefEffect, &cb); pqa->Release(); } ulAttribs |= dwDefEffect & (SFGAO_CANCOPY | SFGAO_CANMOVE | SFGAO_CANLINK); } } ILFree(pidlItem); } // keep only the attributes common to all pidls. *rgfInOut &= ulAttribs; } return hr; } STDMETHODIMP CMergedFolder::GetUIObjectOf(HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl, REFIID riid, UINT *prgf, void **ppv) { *ppv = NULL; HRESULT hr = E_NOTIMPL; if (IsEqualGUID(riid, IID_IContextMenu)) { hr = _GetContextMenu(hwnd, cidl, apidl, riid, ppv); } else if (IsEqualGUID(riid, IID_IDropTarget) && _IsFolder(apidl[0])) { IShellFolder *psf; hr = BindToObject(apidl[0], NULL, IID_PPV_ARG(IShellFolder, &psf)); if (SUCCEEDED(hr)) { hr = psf->CreateViewObject(hwnd, riid, ppv); psf->Release(); } } else if ((IsEqualIID(riid, IID_IExtractImage) || IsEqualIID(riid, IID_IExtractLogo)) && _IsFolder(apidl[0])) { IShellFolder *psfThis; hr = QueryInterface(IID_PPV_ARG(IShellFolder, &psfThis)); if (SUCCEEDED(hr)) { hr = CFolderExtractImage_Create(psfThis, apidl[0], riid, ppv); psfThis->Release(); } } else if (IsEqualIID(riid, IID_IDataObject) && _pidl) { hr = SHCreateFileDataObject(_pidl, cidl, apidl, NULL, (IDataObject **)ppv); } else { hr = E_OUTOFMEMORY; // Forward to default _Namespace for UI object LPITEMIDLIST *apidlItems = new LPITEMIDLIST[cidl]; if (apidlItems) { hr = E_FAIL; // assume failure UINT cidlItems = 0; IShellFolder *psf, *psfKeep; // not ref counted LPITEMIDLIST pidlItem; for (UINT i = 0; i < cidl; i++) { if (SUCCEEDED(_NamespaceForItem(apidl[i], ASFF_DEFNAMESPACE_UIOBJ, ASFF_DEFNAMESPACE_UIOBJ, &psf, &pidlItem, NULL))) { // only keep the ones that match the default namespace for UI object // if they dont match, too bad. apidlItems[cidlItems++] = pidlItem; psfKeep = psf; } } if (cidlItems) { hr = psfKeep->GetUIObjectOf(hwnd, cidlItems, (LPCITEMIDLIST *)apidlItems, riid, NULL, ppv); } for (UINT j = 0; j < cidlItems; j++) { ILFree(apidlItems[j]); } delete [] apidlItems; } } return hr; } // in: // pidl optional, NULL means get default // out: // *ppidl if pidl is != NULL HRESULT CMergedFolder::_GetFolder2(LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlInner, IShellFolder2 **ppsf) { if (ppidlInner) *ppidlInner = NULL; HRESULT hr; if (NULL == pidl) { CMergedFldrNamespace *pns; hr = _FindNamespace(ASFF_DEFNAMESPACE_DISPLAYNAME, ASFF_DEFNAMESPACE_DISPLAYNAME, NULL, &pns); if (FAILED(hr)) { pns = _Namespace(0); hr = pns ? S_OK : E_FAIL; } if (SUCCEEDED(hr)) hr = pns->Folder()->QueryInterface(IID_PPV_ARG(IShellFolder2, ppsf)); } else { IShellFolder* psf; hr = _NamespaceForItem(pidl, ASFF_DEFNAMESPACE_DISPLAYNAME, ASFF_DEFNAMESPACE_DISPLAYNAME, &psf, ppidlInner, NULL); if (SUCCEEDED(hr)) { hr = psf->QueryInterface(IID_PPV_ARG(IShellFolder2, ppsf)); if (FAILED(hr) && ppidlInner) { ILFree(*ppidlInner); } } } return hr; } // extended column information, these are appended after the set from the merged folder. #define COLID_WHICHFOLDER 0x00 // column index for the merged folder location static struct { const SHCOLUMNID *pscid; UINT iTitle; UINT cchCol; UINT iFmt; } _columns[] = { {&SCID_WHICHFOLDER, IDS_WHICHFOLDER_COL, 20, LVCFMT_LEFT}, }; // column handler helpers BOOL CMergedFolder::_IsOurColumn(UINT iCol) { return ((_iColumnOffset != -1) && ((iCol >= _iColumnOffset) && ((iCol - _iColumnOffset) < ARRAYSIZE(_columns)))); } HRESULT CMergedFolder::_GetWhichFolderColumn(LPCITEMIDLIST pidl, LPWSTR pszBuffer, INT cchBuffer) { CMergedFldrNamespace *pns; HRESULT hr = _NamespaceForItem(pidl, ASFF_DEFNAMESPACE_ATTRIB, ASFF_DEFNAMESPACE_ATTRIB, NULL, NULL, &pns); if (SUCCEEDED(hr)) { hr = pns->GetLocation(pszBuffer, cchBuffer); } return hr; } STDMETHODIMP CMergedFolder::GetDetailsOf(LPCITEMIDLIST pidl, UINT iColumn, SHELLDETAILS *pDetails) { // do we have a column offset, or is this within the range of our the ISFs columns HRESULT hr = E_FAIL; if (!_IsOurColumn(iColumn)) { IShellFolder2 *psf2; LPITEMIDLIST pidlItem; // get the column value from the folder. hr = _GetFolder2(pidl, &pidlItem, &psf2); if (SUCCEEDED(hr)) { hr = psf2->GetDetailsOf(pidlItem, iColumn, pDetails); psf2->Release(); ILFree(pidlItem); } // we failed and we don't know the column offset to handle if (FAILED(hr) && (_iColumnOffset == -1)) _iColumnOffset = iColumn; } if (FAILED(hr) && _IsOurColumn(iColumn)) { iColumn -= _iColumnOffset; pDetails->str.uType = STRRET_CSTR; // we are returning strings pDetails->str.cStr[0] = 0; WCHAR szTemp[MAX_PATH]; if (!pidl) { pDetails->fmt = _columns[iColumn].iFmt; pDetails->cxChar = _columns[iColumn].cchCol; LoadString(HINST_THISDLL, _columns[iColumn].iTitle, szTemp, ARRAYSIZE(szTemp)); hr = StringToStrRet(szTemp, &(pDetails->str)); } else if (SUCCEEDED(_IsWrap(pidl))) { if (iColumn == COLID_WHICHFOLDER) { hr = _GetWhichFolderColumn(pidl, szTemp, ARRAYSIZE(szTemp)); if (SUCCEEDED(hr)) hr = StringToStrRet(szTemp, &(pDetails->str)); } } } return hr; } STDMETHODIMP CMergedFolder::GetDefaultColumnState(UINT iColumn, DWORD *pbState) { IShellFolder2 *psf2; HRESULT hr = _GetFolder2(NULL, NULL, &psf2); if (SUCCEEDED(hr)) { hr = psf2->GetDefaultColumnState(iColumn, pbState); psf2->Release(); } return hr; } STDMETHODIMP CMergedFolder::GetDetailsEx(LPCITEMIDLIST pidl, const SHCOLUMNID *pscid, VARIANT *pv) { HRESULT hr; if (IsEqualSCID(*pscid, SCID_WHICHFOLDER)) { WCHAR szTemp[MAX_PATH]; hr = _GetWhichFolderColumn(pidl, szTemp, ARRAYSIZE(szTemp)); if (SUCCEEDED(hr)) hr = InitVariantFromStr(pv, szTemp); } else { IShellFolder2 *psf2; LPITEMIDLIST pidlItem; hr = _GetFolder2(pidl, &pidlItem, &psf2); if (SUCCEEDED(hr)) { hr = psf2->GetDetailsEx(pidlItem, pscid, pv); psf2->Release(); ILFree(pidlItem); } } return hr; } STDMETHODIMP CMergedFolder::MapColumnToSCID(UINT iCol, SHCOLUMNID *pscid) { HRESULT hr = S_OK; // one of our columns? if (_IsOurColumn(iCol)) { iCol -= _iColumnOffset; *pscid = *_columns[iCol].pscid; } else { IShellFolder2 *psf2; hr = _GetFolder2(NULL, NULL, &psf2); if (SUCCEEDED(hr)) { hr = psf2->MapColumnToSCID(iCol, pscid); psf2->Release(); } } return hr; } // Forward to default _Namespace for display name STDMETHODIMP CMergedFolder::GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD grfFlags, STRRET *psr) { IShellFolder* psf; LPITEMIDLIST pidlItem; HRESULT hr = _NamespaceForItem(pidl, ASFF_DEFNAMESPACE_DISPLAYNAME, ASFF_DEFNAMESPACE_DISPLAYNAME, &psf, &pidlItem, NULL); if (SUCCEEDED(hr)) { ASSERT(ILFindLastID(pidlItem) == pidlItem); hr = psf->GetDisplayNameOf(pidlItem, grfFlags, psr); if (SUCCEEDED(hr)) { hr = _FixStrRetOffset(pidlItem, psr); #ifdef DEBUG // If the trace flags are set, and this is not comming from an internal query, // Then append the location where this name came from if (!((SHGDN_FORPARSING | SHGDN_FOREDITING) & grfFlags) && (g_qwTraceFlags & TF_AUGM)) { LPWSTR pwszOldName; hr = StrRetToStrW(psr, pidlItem, &pwszOldName); if (SUCCEEDED(hr)) { UINT cch = lstrlenW(pwszOldName) + 50; psr->uType = STRRET_WSTR; psr->pOleStr = (LPWSTR)SHAlloc(cch * sizeof(WCHAR)); if (psr->pOleStr) { UINT cchAdded = wnsprintfW(psr->pOleStr, cch, L"%s ", pwszOldName); cch -= cchAdded; LPWSTR pwsz = psr->pOleStr + cchAdded; ULONG nSrc = _GetSourceCount(pidl); UINT uSrc; // Cut off after 10 to avoid buffer overflow for (uSrc = 0; uSrc < nSrc && uSrc < 10; uSrc++) { UINT uID; if (SUCCEEDED(_GetSubPidl(pidl, uSrc, &uID, NULL, NULL))) { cchAdded = wnsprintfW(pwsz, cch, L"%c%d", uSrc ? '+' : '(', uID); cch -= cchAdded; pwsz += cchAdded; } } pwsz += wnsprintfW(pwsz, cch, L")"); } else { hr = E_OUTOFMEMORY; } SHFree(pwszOldName); } } #endif } ILFree(pidlItem); } else { if (IsSelf(1, &pidl) && ((grfFlags & (SHGDN_FORADDRESSBAR | SHGDN_INFOLDER | SHGDN_FORPARSING)) == SHGDN_FORPARSING)) { IShellFolder2 *psf2; hr = _GetFolder2(NULL, NULL, &psf2); if (SUCCEEDED(hr)) { hr = psf2->GetDisplayNameOf(NULL, grfFlags, psr); psf2->Release(); } } else { hr = E_INVALIDARG; } } return hr; } // CFSFolder helper HRESULT _NextSegment(LPCWSTR *ppszIn, LPTSTR pszSegment, UINT cchSegment, BOOL bValidate); void CMergedFolder::_SetSimple(LPITEMIDLIST *ppidl) { PAUGM_IDWRAP pWrap = (PAUGM_IDWRAP)*ppidl; if ((pWrap->cb >= sizeof(AUGM_IDWRAP)) && (pWrap->ulTag == AUGM_WRAPTAG) && (pWrap->ulVersion == AUGM_WRAPVERSION_2_0)) { pWrap->dwFlags |= AUGMF_ISSIMPLE; } } BOOL CMergedFolder::_IsSimple(LPCITEMIDLIST pidl) { BOOL fSimple = FALSE; PAUGM_IDWRAP pWrap = (PAUGM_IDWRAP)pidl; if ((pWrap->cb >= sizeof(AUGM_IDWRAP)) && (pWrap->ulTag == AUGM_WRAPTAG) && (pWrap->ulVersion == AUGM_WRAPVERSION_2_0)) { fSimple = (pWrap->dwFlags & AUGMF_ISSIMPLE); } return fSimple; } STDMETHODIMP CMergedFolder::ParseDisplayName(HWND hwnd, LPBC pbc, LPOLESTR pwszName, ULONG *pchEaten, LPITEMIDLIST *ppidl, ULONG *pdwAttrib) { *ppidl = NULL; TCHAR szName[MAX_PATH]; // ISSUE: this relies on the fact that the path that's getting parsed is FS (with backslashes). // alternatively this could be rearchitected so that the namespaces would be delegate folders // and we could do the merge when we handle their pidl allocation. HRESULT hr = _NextSegment((LPCWSTR *) &pwszName, szName, ARRAYSIZE(szName), TRUE); if (SUCCEEDED(hr)) { // let all name spaces try to parse, append them into the pidl as these are found CMergedFldrNamespace *pns; HRESULT hrParse = S_OK; for (int i = 0; SUCCEEDED(hr) && (pns = _Namespace(i)); i++) { LPITEMIDLIST pidl; hrParse = pns->Folder()->ParseDisplayName(hwnd, pbc, szName, NULL, &pidl, NULL); if (SUCCEEDED(hrParse)) { // let each name space parse, accumulate results // into *ppidl across multiple folders hr = _WrapAddIDList(pidl, i, ppidl); ILFree(pidl); } } if (!*ppidl) { if (SUCCEEDED(hr)) { hr = hrParse; } ASSERT(FAILED(hr)); } else { if (S_OK == SHIsFileSysBindCtx(pbc, NULL)) { _SetSimple(ppidl); } ASSERT(ILFindLastID(*ppidl) == *ppidl); } if (SUCCEEDED(hr) && pwszName) { IShellFolder *psf; hr = BindToObject(*ppidl, pbc, IID_PPV_ARG(IShellFolder, &psf)); if (SUCCEEDED(hr)) { LPITEMIDLIST pidlNext; hr = psf->ParseDisplayName(hwnd, pbc, pwszName, NULL, &pidlNext, pdwAttrib); if (SUCCEEDED(hr)) { // shilappend frees pidlNext hr = SHILAppend(pidlNext, ppidl); } psf->Release(); } if (FAILED(hr)) { Pidl_Set(ppidl, NULL); } } if (SUCCEEDED(hr) && pdwAttrib && *pdwAttrib) { GetAttributesOf(1, (LPCITEMIDLIST *)ppidl, pdwAttrib); } } ASSERT(SUCCEEDED(hr) ? (*ppidl != NULL) : (*ppidl == NULL)); return hr; } STDMETHODIMP CMergedFolder::SetNameOf(HWND hwnd, LPCITEMIDLIST pidlWrap, LPCOLESTR pwszName, DWORD uFlags, LPITEMIDLIST *ppidlOut) { if (ppidlOut) *ppidlOut = NULL; HRESULT hr = E_FAIL; IShellFolder* psf; // not ref counted LPITEMIDLIST pidlItem; if (!_fInShellView) { hr = _NamespaceForItem(pidlWrap, ASFF_COMMON, 0, &psf, &pidlItem, NULL, TRUE); if (FAILED(hr)) { hr = _NamespaceForItem(pidlWrap, ASFF_COMMON, ASFF_COMMON, &psf, &pidlItem, NULL, TRUE); if (SUCCEEDED(hr)) { hr = AffectAllUsers(hwnd) ? S_OK : E_FAIL; if (FAILED(hr)) { ILFree(pidlItem); } } } } else { hr = _NamespaceForItem(pidlWrap, ASFF_DEFNAMESPACE_DISPLAYNAME, ASFF_DEFNAMESPACE_DISPLAYNAME, &psf, &pidlItem, NULL); } if (SUCCEEDED(hr)) { ASSERT(ILFindLastID(pidlItem) == pidlItem); hr = psf->SetNameOf(hwnd, pidlItem, pwszName, uFlags, NULL); ILFree(pidlItem); } if (SUCCEEDED(hr) && ppidlOut) { WCHAR szName[MAX_PATH]; hr = DisplayNameOf(SAFECAST(this, IAugmentedShellFolder2*), pidlWrap, SHGDN_FORPARSING | SHGDN_INFOLDER, szName, ARRAYSIZE(szName)); if (SUCCEEDED(hr)) hr = ParseDisplayName(NULL, NULL, szName, NULL, ppidlOut, NULL); } return hr; } // IAugmentedShellFolder::AddNameSpace // Adds a source _Namespace to the Merge shell folder object STDMETHODIMP CMergedFolder::AddNameSpace(const GUID *pguidObject, IShellFolder *psf, LPCITEMIDLIST pidl, DWORD dwFlags) { // Check for duplicate via full display name CMergedFldrNamespace *pns; for (int i = 0; pns = _Namespace(i); i++) { if (pidl && ILIsEqual(pns->GetIDList(), pidl)) { // If Found, then reassign attributes and return return pns->SetNamespace(pguidObject, psf, pidl, dwFlags); } } HRESULT hr; pns = new CMergedFldrNamespace(); if (pns) { hr = pns->SetNamespace(pguidObject, psf, pidl, dwFlags); if (SUCCEEDED(hr)) { hr = _SimpleAddNamespace(pns); if (SUCCEEDED(hr)) { pns = NULL; // success, don't free below } } if (pns) delete pns; } else hr = E_OUTOFMEMORY; return hr; } HRESULT CMergedFolder::_SimpleAddNamespace(CMergedFldrNamespace *pns) { if (NULL == _hdpaNamespaces) _hdpaNamespaces = DPA_Create(2); HRESULT hr = E_OUTOFMEMORY; if (_hdpaNamespaces && (DPA_AppendPtr(_hdpaNamespaces, pns) != -1)) { // If there is any conditional merging going on, then remember it if (pns->FolderAttrib() & ASFF_MERGESAMEGUID) { _fPartialMerge = TRUE; } hr = S_OK; } return hr; } STDMETHODIMP CMergedFolder::GetNameSpaceID(LPCITEMIDLIST pidl, GUID * pguidOut) { HRESULT hr = E_INVALIDARG; ASSERT(IS_VALID_PIDL(pidl)); ASSERT(IS_VALID_WRITE_PTR(pguidOut, GUID)); if (pidl && pguidOut) { CMergedFldrNamespace *pns; hr = _GetSubPidl(pidl, 0, NULL, NULL, &pns); if (SUCCEEDED(hr)) { *pguidOut = pns->GetGUID(); } } return hr; } // Retrieves data for the _Namespace identified by dwID. STDMETHODIMP CMergedFolder::QueryNameSpace(ULONG iIndex, GUID *pguidOut, IShellFolder **ppsf) { CMergedFldrNamespace *pns; HRESULT hr = _Namespace(iIndex, &pns); if (SUCCEEDED(hr)) { if (pguidOut) *pguidOut = pns->GetGUID(); if (ppsf) { *ppsf = pns->Folder(); if (*ppsf) (*ppsf)->AddRef(); } } return hr; } #define ASFQNSI_SUPPORTED (ASFQNSI_FLAGS | ASFQNSI_FOLDER | ASFQNSI_GUID | ASFQNSI_PIDL) STDMETHODIMP CMergedFolder::QueryNameSpace2(ULONG iIndex, QUERYNAMESPACEINFO *pqnsi) { if (pqnsi->cbSize != sizeof(QUERYNAMESPACEINFO) || (pqnsi->dwMask & ~ASFQNSI_SUPPORTED)) { return E_INVALIDARG; } CMergedFldrNamespace *pns; HRESULT hr = _Namespace(iIndex, &pns); if (SUCCEEDED(hr)) { // Do PIDL first since it's the only one that can fail // so we don't have to do cleanup if (pqnsi->dwMask & ASFQNSI_PIDL) { hr = SHILClone(pns->GetIDList(), &pqnsi->pidl); if (FAILED(hr)) return hr; } if (pqnsi->dwMask & ASFQNSI_FLAGS) pqnsi->dwFlags = pns->FolderAttrib(); if (pqnsi->dwMask & ASFQNSI_FOLDER) { pqnsi->psf = pns->Folder(); if (pqnsi->psf) pqnsi->psf->AddRef(); } if (pqnsi->dwMask & ASFQNSI_GUID) pqnsi->guidObject = pns->GetGUID(); } return hr; } STDMETHODIMP CMergedFolder::EnumNameSpace(DWORD uNameSpace, DWORD *pdwID) { if (uNameSpace == (DWORD)-1) { return ResultFromShort(_NamespaceCount()); } if (uNameSpace < (UINT)_NamespaceCount()) { // Our namespace IDs are just ordinals *pdwID = uNameSpace; return S_OK; } return HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS); } // IAugmentedShellFolder2 methods STDMETHODIMP CMergedFolder::UnWrapIDList(LPCITEMIDLIST pidlWrap, LONG cPidls, IShellFolder** apsf, LPITEMIDLIST* apidlFolder, LPITEMIDLIST* apidlItems, LONG* pcFetched) { if (cPidls <= 0) return E_INVALIDARG; HRESULT hr = S_OK; // Enumerate pidls in wrap LPITEMIDLIST pidlItem; CMergedFldrNamespace *pns; LONG cFetched; for (cFetched = 0; SUCCEEDED(hr) && (cFetched < cPidls) && SUCCEEDED(_GetSubPidl(pidlWrap, cFetched, NULL, &pidlItem, &pns)); cFetched++) { if (apsf) { apsf[cFetched] = pns->Folder(); if (apsf[cFetched]) apsf[cFetched]->AddRef(); } if (apidlFolder) { hr = SHILClone(pns->GetIDList(), &apidlFolder[cFetched]); } if (apidlItems) { apidlItems[cFetched] = NULL; if (SUCCEEDED(hr)) { hr = SHILClone(pidlItem, &apidlItems[cFetched]); } } ILFree(pidlItem); } if (SUCCEEDED(hr)) { if (pcFetched) { *pcFetched = cFetched; } hr = (cFetched == cPidls) ? S_OK : S_FALSE; } else { // clean up items we've already allocated; since the caller won't free if we // return failure for (LONG i = 0; i < cFetched; i++) { if (apsf) ATOMICRELEASE(apsf[i]); if (apidlFolder) ILFree(apidlFolder[i]); if (apidlItems) ILFree(apidlItems[i]); } } return hr; } STDMETHODIMP CMergedFolder::SetOwner(IUnknown* punkOwner) { DPA_EnumCallback(_hdpaNamespaces, _SetOwnerProc, punkOwner); return S_OK; } int CMergedFolder::_SetOwnerProc(void *pv, void *pvParam) { CMergedFldrNamespace *pns = (CMergedFldrNamespace*) pv; return pns->SetOwner((IUnknown*)pvParam); } // ITranslateShellChangeNotify methods // old translate style only LPITEMIDLIST CMergedFolder::_ILCombineBase(LPCITEMIDLIST pidlContainingBase, LPCITEMIDLIST pidlRel) { // This routine differs from ILCombine in that it takes the First pidl's base, and // cats on the last id of the second pidl. We need this so Wrapped pidls // end up with the same base, and we get a valid full pidl. LPITEMIDLIST pidlRet = NULL; LPITEMIDLIST pidlBase = ILClone(pidlContainingBase); if (pidlBase) { ILRemoveLastID(pidlBase); pidlRet = ILCombine(pidlBase, pidlRel); ILFree(pidlBase); } return pidlRet; } BOOL CMergedFolder::_IsFolderEvent(LONG lEvent) { return lEvent == SHCNE_MKDIR || lEvent == SHCNE_RMDIR || lEvent == SHCNE_RENAMEFOLDER; } BOOL GetRealPidlFromSimple(LPCITEMIDLIST pidlSimple, LPITEMIDLIST* ppidlReal) { // Similar to SHGetRealIDL in Function, but SHGetRealIDL does SHGDN_FORPARSING | INFOLDER. // I need the parsing name. I can't rev SHGetRealIDL very easily, so here's this one! TCHAR szFullName[MAX_PATH]; if (SUCCEEDED(SHGetNameAndFlags(pidlSimple, SHGDN_FORPARSING, szFullName, SIZECHARS(szFullName), NULL))) { *ppidlReal = ILCreateFromPath(szFullName); } if (*ppidlReal == NULL) // Unable to create? Then use the simple pidl. This is because it does not exist any more { // For say, a Delete Notify *ppidlReal = ILClone(pidlSimple); } return *ppidlReal != NULL; } // the new way we have of translating ids is better than the old way since it handles // multi-level translation. // the problem is that the old way is used extensively by the start menu, so the // start menu will need to be rewritten to use the new way. when that happens, // then the old method can be ripped out. STDMETHODIMP CMergedFolder::TranslateIDs( LONG *plEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2, LPITEMIDLIST * ppidlOut1, LPITEMIDLIST * ppidlOut2, LONG *plEvent2, LPITEMIDLIST *ppidlOut1Event2, LPITEMIDLIST *ppidlOut2Event2) { if (_fInShellView) { return _NewTranslateIDs(plEvent, pidl1, pidl2, ppidlOut1, ppidlOut2, plEvent2, ppidlOut1Event2, ppidlOut2Event2); } else { return _OldTranslateIDs(plEvent, pidl1, pidl2, ppidlOut1, ppidlOut2, plEvent2, ppidlOut1Event2, ppidlOut2Event2); } } // old version. HRESULT CMergedFolder::_OldTranslateIDs( LONG *plEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2, LPITEMIDLIST * ppidlOut1, LPITEMIDLIST * ppidlOut2, LONG *plEvent2, LPITEMIDLIST *ppidlOut1Event2, LPITEMIDLIST *ppidlOut2Event2) { HRESULT hr = E_FAIL; if (!plEvent) return E_FAIL; switch (*plEvent) { case SHCNE_EXTENDED_EVENT: case SHCNE_ASSOCCHANGED: case SHCNE_UPDATEIMAGE: return S_OK; case SHCNE_UPDATEDIR: _FreeObjects(); return S_OK; } ASSERT(ppidlOut1); ASSERT(ppidlOut2); LONG lEvent = *plEvent; *plEvent2 = (LONG)-1; *ppidlOut1Event2 = NULL; *ppidlOut2Event2 = NULL; *ppidlOut1 = (LPITEMIDLIST)pidl1; *ppidlOut2 = (LPITEMIDLIST)pidl2; // If they are already wrapped, don't wrap twice. if (SUCCEEDED(_IsWrap(ILFindLastID(pidl1))) || SUCCEEDED(_IsWrap(ILFindLastID(pidl2)))) { // We don't want to wrap twice. return E_FAIL; } if (!_hdpaNamespaces) return E_FAIL; if (!_hdpaObjects) return E_FAIL; CMergedFldrItem* pmfi; int iIndex; int iShellFolder1 = -1; int iShellFolder2 = -1; IShellFolder* psf1 = NULL; IShellFolder* psf2 = NULL; LPITEMIDLIST pidlReal1 = NULL; LPITEMIDLIST pidlReal2 = NULL; LPITEMIDLIST pidlRealRel1 = NULL; LPITEMIDLIST pidlRealRel2 = NULL; CMergedFldrNamespace * pns1 = NULL; CMergedFldrNamespace * pns2 = NULL; BOOL fFolder = _IsFolderEvent(*plEvent); // Get the information about these Simple pidls: Are they our Children? If so, what _Namespace? BOOL fChild1 = _IsChildIDInternal(pidl1, TRUE, &iShellFolder1); BOOL fChild2 = _IsChildIDInternal(pidl2, TRUE, &iShellFolder2); // Is either a child? if (!(fChild1 || fChild2)) return hr; // Ok, pidl1 is a child, can we get the Real pidl from the simple one? if (pidl1 && !GetRealPidlFromSimple(pidl1, &pidlReal1)) goto Cleanup; // Ok, pidl2 is a child, can we get the Real pidl from the simple one? if (pidl2 && !GetRealPidlFromSimple(pidl2, &pidlReal2)) goto Cleanup; // These are for code clarity later on. We deal with Relative pidls from here until the very end, // when we combine the base of the in pidls with the outgoing wrapped pidls. if (pidlReal1) pidlRealRel1 = ILFindLastID(pidlReal1); if (pidlReal2) pidlRealRel2 = ILFindLastID(pidlReal2); // Is Pidl1 in our _Namespaces? if (iShellFolder1 != -1) { // Yes, lets get the non-refcounted shell folder that know's about this pidl. pns1 = _Namespace(iShellFolder1); psf1 = pns1->Folder(); // Non ref counted. } // Is Pidl2 in our _Namespaces? if (iShellFolder2 != -1) { // Yes, lets get the non-refcounted shell folder that know's about this pidl. pns2 = _Namespace(iShellFolder2); psf2 = pns2->Folder(); // Non ref counted. } hr = S_OK; switch(*plEvent) { case SHCNE_UPDATEITEM: case 0: // Just look up the pidls and return. { DWORD rgfAttrib = SFGAO_FOLDER; if (iShellFolder1 != -1) { psf1->GetAttributesOf(1, (LPCITEMIDLIST*)&pidlRealRel1, &rgfAttrib); if (S_OK == _SearchForPidl(iShellFolder1, pns1, pidlRealRel1, BOOLIFY(rgfAttrib & SFGAO_FOLDER), &iIndex, &pmfi)) { *ppidlOut1 = _ILCombineBase(pidlReal1, pmfi->GetIDList()); if (!*ppidlOut1) hr = E_OUTOFMEMORY; } } rgfAttrib = SFGAO_FOLDER; if (iShellFolder2 != -1 && SUCCEEDED(hr)) { psf2->GetAttributesOf(1, (LPCITEMIDLIST*)&pidlRealRel2, &rgfAttrib); if (S_OK == _SearchForPidl(iShellFolder2, pns2, pidlRealRel2, BOOLIFY(rgfAttrib & SFGAO_FOLDER), &iIndex, &pmfi)) { *ppidlOut2 = _ILCombineBase(pidlReal2, pmfi->GetIDList()); if (!*ppidlOut2) hr = E_OUTOFMEMORY; } } } break; case SHCNE_CREATE: case SHCNE_MKDIR: { TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: %s", fFolder? TEXT("SHCNE_MKDIR") : TEXT("SHCNE_CREATE")); // Is there a thing of this name already? if (S_OK == _SearchForPidl(iShellFolder1, pns1, pidlRealRel1, fFolder, &iIndex, &pmfi)) { TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: %s needs to be merged. Converting to Rename", pmfi->GetDisplayName()); // Yes; Then we need to merge this new pidl into the wrapped pidl, and change this // to a rename, passing the Old wrapped pidl as the first arg, and the new wrapped pidl // as the second arg. I have to be careful about the freeing: // Free *ppidlOut1 // Clone pmfi->pidlWrap -> *ppidlOut1. // Add pidl1 to pmfi->_pidlWrap. // Clone new pmfi->_pidlWrap -> *ppidlOut2. ASSERT(*ppidlOut2 == NULL) *ppidlOut1 = _ILCombineBase(pidl1, pmfi->GetIDList()); if (*ppidlOut1) { _WrapAddIDList(pidlRealRel1, iShellFolder1, &pmfi->_pidlWrap); *ppidlOut2 = _ILCombineBase(pidl1, pmfi->GetIDList()); if (!*ppidlOut2) TraceMsg(TF_ERROR, "CMergedFolder::TranslateIDs: Failure. Was unable to create new pidl2"); *plEvent = fFolder? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM; } else { TraceMsg(TF_ERROR, "CMergedFolder::TranslateIDs: Failure. Was unable to create new pidl1"); } } else { CMergedFldrItem* pmfiEnum = new CMergedFldrItem; if (pmfiEnum) { LPITEMIDLIST pidlWrap; if (SUCCEEDED(_CreateWrap(pidlRealRel1, (UINT)iShellFolder1, &pidlWrap)) && pmfiEnum->Init(SAFECAST(this, IAugmentedShellFolder2*), pidlWrap, iShellFolder1)) { SEARCH_FOR_PIDL sfp; sfp.pszDisplayName = pmfiEnum->GetDisplayName(); sfp.fFolder = fFolder; sfp.self = this; sfp.iNamespace = -1; int iInsertIndex = DPA_Search(_hdpaObjects, &sfp, 0, _SearchByName, NULL, DPAS_SORTED | DPAS_INSERTAFTER); TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: Creating new unmerged %s at %d", pmfiEnum->GetDisplayName(), iInsertIndex); if (iInsertIndex < 0) iInsertIndex = DA_LAST; if (DPA_InsertPtr(_hdpaObjects, iInsertIndex, pmfiEnum) == -1) { TraceMsg(TF_ERROR, "CMergedFolder::TranslateIDs: Was unable to add %s for some reason. Bailing", pmfiEnum->GetDisplayName()); delete pmfiEnum; } else { *ppidlOut1 = _ILCombineBase(pidl1, pmfiEnum->GetIDList()); } } else delete pmfiEnum; } } } break; case SHCNE_DELETE: case SHCNE_RMDIR: { TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: %s", fFolder? TEXT("SHCNE_RMDIR") : TEXT("SHCNE_DELETE")); int iDeleteIndex; // Is there a folder of this name already? if (S_OK == _SearchForPidl(iShellFolder1, pns1, pidlRealRel1, fFolder, &iDeleteIndex, &pmfi)) { TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: Found %s checking merge state.", pmfi->GetDisplayName()); // Yes; Then we need to unmerge this pidl from the wrapped pidl, and change this // to a rename, passing the Old wrapped pidl as the first arg, and the new wrapped pidl // as the second arg. I have to be careful about the freeing: // Free *ppidlOut1 // Clone pmfi->GetIDList() -> *ppidlOut1. // Remove pidl1 from pmfi->_GetIDList() // Convert to rename, pass new wrapped as second arg. if (_GetSourceCount(pmfi->GetIDList()) > 1) { TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: %s is Merged. Removing pidl, convert to rename", pmfi->GetDisplayName()); *ppidlOut1 = _ILCombineBase(pidl1, pmfi->GetIDList()); if (*ppidlOut1) { LPITEMIDLIST pidlFree = pmfi->GetIDList(); if (SUCCEEDED(_WrapRemoveIDList(pidlFree, iShellFolder1, &pmfi->_pidlWrap))) { ILFree(pidlFree); } *ppidlOut2 = _ILCombineBase(pidl1, pmfi->GetIDList()); if (!*ppidlOut2) TraceMsg(TF_ERROR, "CMergedFolder::TranslateIDs: Failure. Was unable to create new pidl2"); *plEvent = fFolder? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM; } else { TraceMsg(TF_ERROR, "CMergedFolder::TranslateIDs: Failure. Was unable to create new pidl1"); } } else { TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: %s is not Merged. deleteing", pmfi->GetDisplayName()); pmfi = (CMergedFldrItem*)DPA_DeletePtr(_hdpaObjects, iDeleteIndex); if (EVAL(pmfi)) { *ppidlOut1 = _ILCombineBase(pidl1, pmfi->GetIDList()); delete pmfi; } else { TraceMsg(TF_ERROR, "CMergedFolder::TranslateIDs: Failure. Was unable to get %d from DPA", iDeleteIndex); } } } } break; case SHCNE_RENAMEITEM: case SHCNE_RENAMEFOLDER: { // REARCHITECT: (lamadio): When renaming an item in the menu, this code will split it into // a Delete and a Create. We need to detect this situation and convert it to 1 rename. This // will solve the problem of the lost order during a rename.... BOOL fEvent1Set = FALSE; BOOL fFirstPidlInNamespace = FALSE; TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: %s", fFolder? TEXT("SHCNE_RENAMEFOLDER") : TEXT("SHCNE_RENAMEITEM")); // Is this item being renamed FROM the Folder? if (iShellFolder1 != -1) { // Is this pidl a child of the Folder? if (S_OK == _SearchForPidl(iShellFolder1, pns1, pidlRealRel1, fFolder, &iIndex, &pmfi)) // Is it found? { TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: Old pidl %s is in the Folder", pmfi->GetDisplayName()); // Yes. // Then we need to see if the item that it's being renamed from was Merged // Need this for reentrancy if (_ContainsSrcID(pmfi->GetIDList(), iShellFolder1)) { // Was it merged? if (_GetSourceCount(pmfi->GetIDList()) > 1) // Case 3) { // Yes; Then we need to unmerge that item. *ppidlOut1 = _ILCombineBase(pidl1, pmfi->GetIDList()); if (*ppidlOut1) { LPITEMIDLIST pidlFree = pmfi->GetIDList(); if (SUCCEEDED(_WrapRemoveIDList(pidlFree, iShellFolder1, &pmfi->_pidlWrap))) { ILFree(pidlFree); } *ppidlOut2 = _ILCombineBase(pidl1, pmfi->GetIDList()); if (!*ppidlOut2) TraceMsg(TF_ERROR, "CMergedFolder::TranslateIDs: Failure. Was unable to create new pidl2"); // This We need to "Rename" the old wrapped pidl, to this new one // that does not contain the old item. fEvent1Set = TRUE; } else { TraceMsg(TF_ERROR, "CMergedFolder::TranslateIDs: Failure. Was unable to create new pidl1"); } } else { TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: %s is not merged. Nuking item Convert to Delete for event 1.", pmfi->GetDisplayName()); // No, This was not a wrapped pidl. Then, convert to a delete: pmfi = (CMergedFldrItem*)DPA_DeletePtr(_hdpaObjects, iIndex); if (EVAL(pmfi)) { // If we're renaming from this folder, into this folder, Then the first event stays a rename. if (iShellFolder2 == -1) { fEvent1Set = TRUE; *plEvent = fFolder? SHCNE_RMDIR : SHCNE_DELETE; } else { fFirstPidlInNamespace = TRUE; } *ppidlOut1 = _ILCombineBase(pidl1, pmfi->GetIDList()); delete pmfi; } else { TraceMsg(TF_ERROR, "CMergedFolder::TranslateIDs: Failure. Was unable to find Item at %d", iIndex); } } } else { TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: Skipping this because we already processed it." "Dragging To Desktop?"); hr = E_FAIL; } } else { // we were told to rename something that is supposed to exist in the first // namespace, but we couldn't find it. // we dont want to have the caller fire off some more events because of this, // so we fail. hr = E_FAIL; } } // Is this item is being rename INTO the Start Menu? if (iShellFolder2 != -1) { TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: New pidl is in the Folder"); LPITEMIDLIST* ppidlNewWrapped1 = ppidlOut1; LPITEMIDLIST* ppidlNewWrapped2 = ppidlOut2; LONG* plNewEvent = plEvent; if (fEvent1Set) { plNewEvent = plEvent2; ppidlNewWrapped1 = ppidlOut1Event2; ppidlNewWrapped2 = ppidlOut2Event2; } if (S_OK == _SearchForPidl(iShellFolder2, pns2, pidlRealRel2, fFolder, &iIndex, &pmfi)) { // If we're renaming from this folder, into this folder, Check to see if the destination has a // conflict. If there is a confict (This case), then convert first event to a remove, // and the second event to the rename. if (fFirstPidlInNamespace) { fEvent1Set = TRUE; *plEvent = fFolder? SHCNE_RMDIR : SHCNE_DELETE; plNewEvent = plEvent2; ppidlNewWrapped1 = ppidlOut1Event2; ppidlNewWrapped2 = ppidlOut2Event2; } TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: %s is in Folder", pmfi->GetDisplayName()); TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: Adding pidl to %s. Convert to Rename for event %s", pmfi->GetDisplayName(), fEvent1Set? TEXT("2") : TEXT("1")); // Then the destination needs to be merged. *ppidlNewWrapped1 = _ILCombineBase(pidl2, pmfi->GetIDList()); if (*ppidlNewWrapped1) { TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: Successfully created out pidl1"); _WrapAddIDList(pidlRealRel2, iShellFolder2, &pmfi->_pidlWrap); *ppidlNewWrapped2 = _ILCombineBase(pidl2, pmfi->GetIDList()); *plNewEvent = fFolder? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM; } } else { CMergedFldrItem* pmfiEnum = new CMergedFldrItem; if (pmfiEnum) { LPITEMIDLIST pidlWrap; if (SUCCEEDED(_CreateWrap(pidlRealRel2, (UINT)iShellFolder2, &pidlWrap)) && pmfiEnum->Init(SAFECAST(this, IAugmentedShellFolder2*), pidlWrap, iShellFolder2)) { SEARCH_FOR_PIDL sfp; sfp.pszDisplayName = pmfiEnum->GetDisplayName(); sfp.fFolder = BOOLIFY(pmfiEnum->GetFolderAttrib() & SFGAO_FOLDER); sfp.self = this; sfp.iNamespace = -1; int iInsertIndex = DPA_Search(_hdpaObjects, &sfp, 0, _SearchByName, NULL, DPAS_SORTED | DPAS_INSERTAFTER); TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: %s is a new item. Converting to Create", pmfiEnum->GetDisplayName()); if (iInsertIndex < 0) iInsertIndex = DA_LAST; if (DPA_InsertPtr(_hdpaObjects, iInsertIndex, pmfiEnum) == -1) { TraceMsg(TF_ERROR, "CMergedFolder::TranslateIDs: Was unable to add %s for some reason. Bailing", pmfiEnum->GetDisplayName()); delete pmfiEnum; } else { TraceMsg(TF_AUGM, "CMergedFolder::TranslateIDs: Creating new item %s at %d for event %s", pmfiEnum->GetDisplayName(), iInsertIndex, fEvent1Set? TEXT("2") : TEXT("1")); // If we're renaming from this folder, into this folder, Then the first event stays // a rename. if (!fFirstPidlInNamespace) { *plNewEvent = fFolder ? SHCNE_MKDIR : SHCNE_CREATE; *ppidlNewWrapped1 = _ILCombineBase(pidl2, pidlWrap); *ppidlNewWrapped2 = NULL; } else *ppidlOut2 = _ILCombineBase(pidl2, pidlWrap); } } else delete pmfiEnum; } } } } break; default: break; } Cleanup: ILFree(pidlReal1); ILFree(pidlReal2); return hr; } // parsedisplayname with some extras. // this is used to process change notifies. the change notify can be fired AFTER the // item has been moved/deleted/whatever, but we still have to be able to correctly process that // pidl. so here pidlAbsNamespace identifies the absolute pidl of the item being changenotified. // if we're parsing a name within that namespace, force the parsedisplayname with STGM_CREATE to // make sure we get the pidl (because it may have been moved or deleted by now) if fForce==TRUE. HRESULT CMergedFolder::_ForceParseDisplayName(LPCITEMIDLIST pidlAbsNamespace, LPTSTR pszDisplayName, BOOL fForce, BOOL *pfOthersInWrap, LPITEMIDLIST *ppidl) { *ppidl = NULL; *pfOthersInWrap = FALSE; HRESULT hr = S_OK; // set up a bindctx: will have STGM_CREATE if fForce is passed, NULL otherwise IBindCtx *pbc; if (fForce) { hr = BindCtx_CreateWithMode(STGM_CREATE, &pbc); } else { pbc = NULL; } if (SUCCEEDED(hr)) { CMergedFldrNamespace *pnsWrap = NULL; CMergedFldrNamespace *pnsLoop; for (int i = 0; SUCCEEDED(hr) && (pnsLoop = _Namespace(i)); i++) { HRESULT hrLoop = E_FAIL; // not propagated since ParseDisplayName can fail and that's okay LPITEMIDLIST pidlNamespace; // so if the namespace pnsLoop is a parent to the pidl, we know it // came from there so use the bindctx to force creation if necessary if (ILIsParent(pnsLoop->GetIDList(), pidlAbsNamespace, FALSE)) { hrLoop = pnsLoop->Folder()->ParseDisplayName(NULL, pbc, pszDisplayName, NULL, &pidlNamespace, NULL); } else if (_ShouldMergeNamespaces(pnsWrap, pnsLoop)) { pnsWrap = pnsLoop; // only if we're merging, tack on other namespace's pidls. hrLoop = pnsLoop->Folder()->ParseDisplayName(NULL, NULL, pszDisplayName, NULL, &pidlNamespace, NULL); } if (SUCCEEDED(hrLoop)) { if (*ppidl) { *pfOthersInWrap = TRUE; } hr = _WrapAddIDList(pidlNamespace, i, ppidl); ILFree(pidlNamespace); } } // it could be NULL if (pbc) { pbc->Release(); } } return hr; } // this takes an absolute pidl in the given namespace and converts it to a merged pidl. // if the item does not actually exist in the namespace, it forces creation if fForce==TRUE (so if // a changenotify comes in on that namespace it will process it normally, even if the // underlying item has been moved or deleted). HRESULT CMergedFolder::_AbsPidlToAbsWrap(CMergedFldrNamespace *pns, LPCITEMIDLIST pidl, BOOL fForce, BOOL *pfOthersInWrap, LPITEMIDLIST *ppidl) { LPCITEMIDLIST pidlRel = ILFindChild(pns->GetIDList(), pidl); HRESULT hr = SHILClone(_pidl, ppidl); if (SUCCEEDED(hr)) { IBindCtx *pbc; hr = SHCreateSkipBindCtx(NULL, &pbc); // skip all other binding if (SUCCEEDED(hr)) { CMergedFolder *pmfMerged = this; pmfMerged->AddRef(); IShellFolder *psfNamespace = pns->Folder(); psfNamespace->AddRef(); HRESULT hrLoop = S_OK; while (SUCCEEDED(hr) && SUCCEEDED(hrLoop) && !ILIsEmpty(pidlRel)) { hr = E_OUTOFMEMORY; LPITEMIDLIST pidlRelFirst = ILCloneFirst(pidlRel); if (pidlRelFirst) { TCHAR szRelPath[MAX_PATH]; hr = DisplayNameOf(psfNamespace, pidlRelFirst, SHGDN_FORPARSING | SHGDN_INFOLDER, szRelPath, ARRAYSIZE(szRelPath)); if (SUCCEEDED(hr)) { LPITEMIDLIST pidlNextPart; hr = pmfMerged->_ForceParseDisplayName(pidl, szRelPath, fForce, pfOthersInWrap, &pidlNextPart); if (SUCCEEDED(hr)) { // shilappend frees pidlnextpart hr = SHILAppend(pidlNextPart, ppidl); } } if (SUCCEEDED(hr)) { // advance and clean up IShellFolder *psfFree = psfNamespace; psfNamespace = NULL; hrLoop = psfFree->BindToObject(pidlRelFirst, pbc, IID_PPV_ARG(IShellFolder, &psfNamespace)); psfFree->Release(); if (SUCCEEDED(hrLoop)) { CMergedFolder *pmfFree = pmfMerged; pmfMerged = NULL; hrLoop = pmfFree->BindToObject(ILFindLastID(*ppidl), pbc, CLSID_MergedFolder, (void **) &pmfMerged); pmfFree->Release(); } pidlRel = ILGetNext(pidlRel); } ILFree(pidlRelFirst); } } if (pmfMerged) pmfMerged->Release(); if (psfNamespace) psfNamespace->Release(); pbc->Release(); } } return hr; } // new translateids. // when the start menu works a little better this whole interface can be revised. HRESULT CMergedFolder::_NewTranslateIDs( LONG *plEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2, LPITEMIDLIST * ppidlOut1, LPITEMIDLIST * ppidlOut2, LONG *plEvent2, LPITEMIDLIST *ppidlOut1Event2, LPITEMIDLIST *ppidlOut2Event2) { if (!plEvent || !ppidlOut1 || !ppidlOut2) return E_INVALIDARG; // If they are already wrapped, don't wrap twice. if (SUCCEEDED(_IsWrap(ILFindLastID(pidl1))) || SUCCEEDED(_IsWrap(ILFindLastID(pidl2)))) { // We don't want to wrap twice. return E_INVALIDARG; } if (!_hdpaNamespaces) return E_FAIL; HRESULT hr = E_FAIL; switch (*plEvent) { case SHCNE_EXTENDED_EVENT: case SHCNE_ASSOCCHANGED: case SHCNE_UPDATEIMAGE: return S_OK; } LONG lEvent = *plEvent; *plEvent2 = (LONG)-1; *ppidlOut1Event2 = NULL; *ppidlOut2Event2 = NULL; *ppidlOut1 = (LPITEMIDLIST)pidl1; *ppidlOut2 = (LPITEMIDLIST)pidl2; CMergedFldrNamespace *pns1, *pns2; int iShellFolder1, iShellFolder2; BOOL fPidl1IsChild, fPidl2IsChild; // Get the information about these Simple pidls: Are they our Children? If so, what _Namespace? fPidl1IsChild = _IsChildIDInternal(pidl1, FALSE, &iShellFolder1); if (fPidl1IsChild) { pns1 = _Namespace(iShellFolder1); } fPidl2IsChild = _IsChildIDInternal(pidl2, FALSE, &iShellFolder2); if (fPidl2IsChild) { pns2 = _Namespace(iShellFolder2); } // and is either a child? if (fPidl1IsChild || fPidl2IsChild) { hr = S_OK; BOOL fOthersInNamespace1, fOthersInNamespace2; BOOL fFolderEvent = FALSE; switch (*plEvent) { case SHCNE_MKDIR: fFolderEvent = TRUE; case SHCNE_CREATE: if (fPidl1IsChild) { hr = _AbsPidlToAbsWrap(pns1, pidl1, FALSE, &fOthersInNamespace1, ppidlOut1); if (SUCCEEDED(hr) && fOthersInNamespace1 && !_fDontMerge) { // whoops, it was already here and this create should become a rename // since we're just merging in with the existing pidl. *plEvent = fFolderEvent ? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM; // this new wrapped one is what it's renamed TO so bump it to ppidlOut2. *ppidlOut2 = *ppidlOut1; // strip it to get what it used to be. hr = _WrapRemoveIDListAbs(*ppidlOut2, iShellFolder1, ppidlOut1); } } break; case SHCNE_RMDIR: fFolderEvent = TRUE; case SHCNE_DELETE: if (fPidl1IsChild) { hr = _AbsPidlToAbsWrap(pns1, pidl1, TRUE, &fOthersInNamespace1, ppidlOut1); if (SUCCEEDED(hr) && fOthersInNamespace1 && !_fDontMerge) { // whoops, there are still more parts to it and it should become a rename // since we're just unmerging from the existing pidl. *plEvent = fFolderEvent ? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM; // strip it to get the "deleted" version hr = _WrapRemoveIDListAbs(*ppidlOut1, iShellFolder1, ppidlOut2); } } break; case SHCNE_RENAMEFOLDER: fFolderEvent = TRUE; case SHCNE_RENAMEITEM: if (fPidl1IsChild) { // this is just like a delete. *plEvent = fFolderEvent ? SHCNE_RMDIR : SHCNE_DELETE; hr = _AbsPidlToAbsWrap(pns1, pidl1, TRUE, &fOthersInNamespace1, ppidlOut1); if (SUCCEEDED(hr) && fOthersInNamespace1 && !_fDontMerge) { // whoops, there are still more parts to it and it should become a rename // since we're just unmerging from the existing pidl. *plEvent = fFolderEvent ? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM; // strip it to get the "deleted" version hr = _WrapRemoveIDListAbs(*ppidlOut1, iShellFolder1, ppidlOut2); } // set ourselves up so that if fPidl2IsChild, it will write into the second event. plEvent = plEvent2; ppidlOut1 = ppidlOut1Event2; ppidlOut2 = ppidlOut2Event2; } if (fPidl2IsChild) { // this is just like a create. *plEvent = fFolderEvent ? SHCNE_MKDIR : SHCNE_CREATE; hr = _AbsPidlToAbsWrap(pns2, pidl2, FALSE, &fOthersInNamespace2, ppidlOut1); if (SUCCEEDED(hr) && fOthersInNamespace2 && !_fDontMerge) { // whoops, it was already here and this create should become a rename // since we're just merging in with the existing pidl. *plEvent = fFolderEvent ? SHCNE_RENAMEFOLDER : SHCNE_RENAMEITEM; // this new wrapped one is what it's renamed TO so bump it to ppidlOut2. *ppidlOut2 = *ppidlOut1; // strip it to get what it used to be. hr = _WrapRemoveIDListAbs(*ppidlOut2, iShellFolder2, ppidlOut1); } } break; case SHCNE_UPDATEDIR: case SHCNE_UPDATEITEM: case SHCNE_MEDIAINSERTED: case SHCNE_MEDIAREMOVED: hr = _AbsPidlToAbsWrap(pns1, pidl1, FALSE, &fOthersInNamespace1, ppidlOut1); break; default: break; } } return hr; } STDMETHODIMP CMergedFolder::IsChildID(LPCITEMIDLIST pidlKid, BOOL fImmediate) { return _IsChildIDInternal(pidlKid, fImmediate, NULL) ? S_OK : S_FALSE; } STDMETHODIMP CMergedFolder::IsEqualID(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { // This used to return E_NOTIMPL. I'm kinda overloading the interface to mean: // is this equal tp any of your _Namespaces. HRESULT hr = S_FALSE; CMergedFldrNamespace *pns; for (int i = 0; (hr == S_FALSE) && (pns = _Namespace(i)); i++) { if (pidl1) { if (ILIsEqual(pns->GetIDList(), pidl1)) hr = S_OK; } else if (pidl2) // If you pass a pidl2 it means: Is pidl2 a parent of one of my _Namespaces? { if (ILIsParent(pidl2, pns->GetIDList(), FALSE)) hr = S_OK; } } return hr; } typedef struct { HWND hwnd; UINT uMsg; LONG lEvents; } REGISTERNOTIFYINFO; int CMergedFolder::_SetNotifyProc(void *pv, void *pvParam) { CMergedFldrNamespace *pns = (CMergedFldrNamespace*)pv; if (pvParam) { REGISTERNOTIFYINFO *prni = (REGISTERNOTIFYINFO*)pvParam; pns->RegisterNotify(prni->hwnd, prni->uMsg, prni->lEvents); } else { pns->UnregisterNotify(); } return 1; } STDMETHODIMP CMergedFolder::Register(HWND hwnd, UINT uMsg, long lEvents) { if (_fInShellView) { // only register the alias if we have no parent folder // the merged folder at the junction point can take care of the registration // for everybody. if (_pidl && !_pmfParent) { CMergedFldrNamespace *pns; for (int i = 0; pns = _Namespace(i); i++) { if (!ILIsEqual(pns->GetIDList(), _pidl)) { SHChangeNotifyRegisterAlias(pns->GetIDList(), _pidl); } } } } else if (_hdpaNamespaces) { REGISTERNOTIFYINFO rni = {hwnd, uMsg, lEvents}; DPA_EnumCallback(_hdpaNamespaces, _SetNotifyProc, &rni); } return S_OK; } STDMETHODIMP CMergedFolder::Unregister() { if (_hdpaNamespaces) { DPA_EnumCallback(_hdpaNamespaces, _SetNotifyProc, NULL); } return S_OK; } BOOL CMergedFolder::_IsChildIDInternal(LPCITEMIDLIST pidlKid, BOOL fImmediate, int* piShellFolder) { // This is basically the same Method as the interface method, but returns the shell folder // that it came from. BOOL fChild = FALSE; //At this point we should have a translated pidl if (SUCCEEDED(_IsWrap(pidlKid))) { LPCITEMIDLIST pidlRelKid = ILFindLastID(pidlKid); if (pidlRelKid) { CMergedFldrNamespace *pns; for (int i = 0; !fChild && (pns = _Namespace(i)); i++) { if (ILIsParent(pns->GetIDList(), pidlKid, fImmediate) && !ILIsEqual(pns->GetIDList(), pidlKid)) { fChild = TRUE; if (piShellFolder) *piShellFolder = i; } } } } else if (pidlKid) { CMergedFldrNamespace *pns; for (int i = 0; !fChild && (pns = _Namespace(i)); i++) { if (ILIsParent(pns->GetIDList(), pidlKid, fImmediate)) { fChild = TRUE; if (piShellFolder) *piShellFolder = i; } } } return fChild; } HRESULT CMergedFolder::_SearchForPidl(int iNamespace, CMergedFldrNamespace *pns, LPCITEMIDLIST pidl, BOOL fFolder, int* piIndex, CMergedFldrItem** ppmfi) { int iIndex = -1; *ppmfi = NULL; TCHAR szDisplayName[MAX_PATH]; if (SUCCEEDED(DisplayNameOf(pns->Folder(), pidl, SHGDN_FORPARSING | SHGDN_INFOLDER, szDisplayName, ARRAYSIZE(szDisplayName)))) { SEARCH_FOR_PIDL sfp; sfp.pszDisplayName = szDisplayName; sfp.fFolder = fFolder; sfp.self = this; if (_fPartialMerge) { sfp.iNamespace = iNamespace; } else { sfp.iNamespace = -1; } iIndex = DPA_Search(_hdpaObjects, &sfp, 0, _SearchByName, NULL, DPAS_SORTED); if (iIndex >= 0) { *ppmfi = _GetObject(iIndex); } } if (piIndex) *piIndex = iIndex; if (*ppmfi) return S_OK; return S_FALSE; } HRESULT CMergedFolder::_GetTargetUIObjectOf(IShellFolder *psf, HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl, REFIID riid, UINT *prgf, void **ppv) { *ppv = NULL; IPersistFolder3 *ppf3; HRESULT hr = psf->QueryInterface(IID_PPV_ARG(IPersistFolder3, &ppf3)); if (SUCCEEDED(hr)) { PERSIST_FOLDER_TARGET_INFO pfti; hr = ppf3->GetFolderTargetInfo(&pfti); if (SUCCEEDED(hr) && pfti.pidlTargetFolder) { IShellFolder *psfTarget; hr = SHBindToObjectEx(NULL, pfti.pidlTargetFolder, NULL, IID_PPV_ARG(IShellFolder, &psfTarget)); if (SUCCEEDED(hr)) { DWORD dwAttrib = SFGAO_VALIDATE; hr = psfTarget->GetAttributesOf(cidl, apidl, &dwAttrib); if (SUCCEEDED(hr)) { hr = psfTarget->GetUIObjectOf(hwnd, cidl, apidl, riid, prgf, ppv); } psfTarget->Release(); } ILFree(pfti.pidlTargetFolder); } else { hr = E_FAIL; } ppf3->Release(); } if (FAILED(hr)) { DWORD dwAttrib = SFGAO_VALIDATE; hr = psf->GetAttributesOf(cidl, apidl, &dwAttrib); if (SUCCEEDED(hr)) { hr = psf->GetUIObjectOf(hwnd, cidl, apidl, riid, prgf, ppv); } } return hr; } HRESULT CMergedFolder::_GetContextMenu(HWND hwnd, UINT cidl, LPCITEMIDLIST *apidl, REFIID riid, void **ppv) { HRESULT hr = E_OUTOFMEMORY; UINT iNumCommon = 0; LPITEMIDLIST *apidlCommon = new LPITEMIDLIST[cidl]; if (apidlCommon) { UINT iNumUser = 0; LPITEMIDLIST *apidlUser = new LPITEMIDLIST[cidl]; if (apidlUser) { IShellFolder *psf, *psfCommon, *psfUser; // not ref counted LPITEMIDLIST pidl; for (UINT i = 0; i < cidl; i++) { if (SUCCEEDED(_NamespaceForItem(apidl[i], ASFF_COMMON, ASFF_COMMON, &psf, &pidl, NULL, TRUE))) { apidlCommon[iNumCommon++] = pidl; psfCommon = psf; } if (SUCCEEDED(_NamespaceForItem(apidl[i], ASFF_COMMON, 0, &psf, &pidl, NULL, TRUE))) { apidlUser[iNumUser++] = pidl; psfUser = psf; } } IContextMenu *pcmCommon = NULL; if (iNumCommon) { _GetTargetUIObjectOf(psfCommon, hwnd, iNumCommon, (LPCITEMIDLIST *)apidlCommon, IID_X_PPV_ARG(IContextMenu, NULL, &pcmCommon)); } IContextMenu *pcmUser = NULL; if (iNumUser) { _GetTargetUIObjectOf(psfUser, hwnd, iNumUser, (LPCITEMIDLIST *)apidlUser, IID_X_PPV_ARG(IContextMenu, NULL, &pcmUser)); } BOOL fMerge = _fInShellView || (cidl == 1) && _IsFolder(apidl[0]); if (fMerge && (pcmCommon || pcmUser)) { hr = CMergedFldrContextMenu_CreateInstance(hwnd, this, cidl, apidl, pcmCommon, pcmUser, (IContextMenu**)ppv); } else if (pcmUser) { hr = pcmUser->QueryInterface(riid, ppv); } else if (pcmCommon) { hr = pcmCommon->QueryInterface(riid, ppv); } else { hr = E_FAIL; } for (i = 0; i < iNumCommon; i++) { ILFree(apidlCommon[i]); } for (i = 0; i < iNumUser; i++) { ILFree(apidlUser[i]); } ATOMICRELEASE(pcmCommon); ATOMICRELEASE(pcmUser); delete [] apidlUser; } delete [] apidlCommon; } return hr; } // out: // *ppsf unreffed psf, don't call ->Release()! // *ppidlItem clone of pidl in pidlWrap (nested pidl) HRESULT CMergedFolder::_NamespaceForItem(LPCITEMIDLIST pidlWrap, ULONG dwAttribMask, ULONG dwAttrib, IShellFolder **ppsf, LPITEMIDLIST *ppidlItem, CMergedFldrNamespace **ppns, BOOL fExact) { if (ppsf) *ppsf = NULL; if (ppns) *ppns = NULL; // first try to get the prefered name space based HRESULT hr = E_UNEXPECTED; // assume failure from here CMergedFldrNamespace *pns; LPITEMIDLIST pidl; for (UINT i = 0; SUCCEEDED(_GetSubPidl(pidlWrap, i, NULL, &pidl, &pns)); i++) { if ((dwAttribMask & dwAttrib) == (dwAttribMask & pns->FolderAttrib())) { hr = S_OK; // don't free, we're going to hand this one back. break; } ILFree(pidl); } // not found, fall back to the first name space in the wrapped pidl if (!fExact && FAILED(hr)) { hr = _GetSubPidl(pidlWrap, 0, NULL, &pidl, &pns); } // it succeeded, so lets pass out the information that the caller wanted if (SUCCEEDED(hr)) { if (ppsf) *ppsf = pns->Folder(); if (ppns) *ppns = pns; if (ppidlItem) { // transfer ownership *ppidlItem = pidl; } else { ILFree(pidl); } } return hr; } BOOL CMergedFolder::_NamespaceMatches(ULONG dwAttribMask, ULONG dwAttrib, LPCGUID pguid, CMergedFldrNamespace *pns) { BOOL fRet = FALSE; dwAttrib &= dwAttribMask; if (dwAttrib == (dwAttribMask & pns->FolderAttrib())) { // If ASFF_MERGESAMEGUID is set, then the GUID must also match if (!(dwAttrib & ASFF_MERGESAMEGUID) || IsEqualGUID(*pguid, pns->GetGUID())) { fRet = TRUE; } } return fRet; } // find a name space based on its attributes // dwAttribMask is a mask of the bits to test // dwAttrib is the value of the bits in the mask, tested for equality // pguid is the GUID to match if dwAttrib includes ASFF_MERGESAMEGUID // // pnSrcID is optional out param HRESULT CMergedFolder::_FindNamespace(ULONG dwAttribMask, ULONG dwAttrib, LPCGUID pguid, CMergedFldrNamespace **ppns, BOOL fFallback) { *ppns = NULL; CMergedFldrNamespace *pns; int i; // first look for a matching namespace. for (i = 0; !*ppns && (pns = _Namespace(i)); i++) { if (!_ShouldSuspend(pns->GetGUID()) && _NamespaceMatches(dwAttribMask, dwAttrib, pguid, pns)) { *ppns = pns; } } if (fFallback && !*ppns) { // if the matching namespace got scoped out, fall back to another. for (i = 0; !*ppns && (pns = _Namespace(i)); i++) { if (!_ShouldSuspend(pns->GetGUID())) { *ppns = pns; } } } return *ppns ? S_OK : E_FAIL; } int CMergedFolder::_AcquireObjects() { _fAcquiring = TRUE; HDPA hdpa2 = NULL; ASSERT(_hdpaObjects == NULL); CMergedFldrNamespace *pns; for (int i = 0; pns = _Namespace(i); i++) { if (_ShouldSuspend(pns->GetGUID())) { continue; } HDPA *phdpa = (i == 0) ? &_hdpaObjects : &hdpa2; IEnumIDList *peid; if (S_OK == pns->Folder()->EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN, &peid)) { if (!*phdpa) *phdpa = DPA_Create(4); if (*phdpa) { LPITEMIDLIST pidl; ULONG cEnum; while (S_OK == peid->Next(1, &pidl, &cEnum)) { CMergedFldrItem* pmfiEnum = new CMergedFldrItem; if (pmfiEnum) { // This is ok, the memory just gets LPITEMIDLIST pidlWrap; if (SUCCEEDED(_CreateWrap(pidl, i, &pidlWrap)) && pmfiEnum->Init(SAFECAST(this, IAugmentedShellFolder2*), pidlWrap, i)) { if (DPA_AppendPtr(*phdpa, pmfiEnum) != -1) pmfiEnum = NULL; // don't free below } if (pmfiEnum) delete pmfiEnum; } ILFree(pidl); } } peid->Release(); } // if we have only hdpa2 but not _hdpaObjects, do a switcheroo. if (hdpa2 && !_hdpaObjects) { _hdpaObjects = hdpa2; hdpa2 = NULL; } // now that we have both hdpa's (or one) let's merge them. if (_hdpaObjects && hdpa2) { DPA_Merge(_hdpaObjects, hdpa2, DPAM_UNION, _Compare, _Merge, (LPARAM)this); DPA_DestroyCallback(hdpa2, _DestroyObjectsProc, NULL); hdpa2 = NULL; } else if (_hdpaObjects) { DPA_Sort(_hdpaObjects, _Compare, (LPARAM)this); } } _fAcquiring = FALSE; return _ObjectCount(); } int CMergedFolder::_DestroyObjectsProc(void *pv, void *pvData) { CMergedFldrItem* pmfiEnum = (CMergedFldrItem*)pv; if (pmfiEnum) delete pmfiEnum; return TRUE; } void CMergedFolder::_FreeObjects() { if (!_fAcquiring && _hdpaObjects) { DPA_DestroyCallback(_hdpaObjects, _DestroyObjectsProc, NULL); _hdpaObjects = NULL; } } int CMergedFolder::_DestroyNamespacesProc(void *pv, void *pvData) { CMergedFldrNamespace* p = (CMergedFldrNamespace*)pv; if (p) delete p; return TRUE; } void CMergedFolder::_FreeNamespaces() { if (_hdpaNamespaces) { DPA_DestroyCallback(_hdpaNamespaces, _DestroyNamespacesProc, NULL); _hdpaNamespaces = NULL; } } HRESULT CMergedFolder::_GetPidl(int* piPos, DWORD grfEnumFlags, LPITEMIDLIST* ppidl) { *ppidl = NULL; if (_hdpaObjects == NULL) _AcquireObjects(); if (_hdpaObjects == NULL) return E_OUTOFMEMORY; BOOL fWantFolders = 0 != (grfEnumFlags & SHCONTF_FOLDERS), fWantNonFolders = 0 != (grfEnumFlags & SHCONTF_NONFOLDERS), fWantHidden = 0 != (grfEnumFlags & SHCONTF_INCLUDEHIDDEN), fWantAll = 0 != (grfEnumFlags & SHCONTF_STORAGE); HRESULT hr = S_FALSE; while (*piPos < _ObjectCount()) { CMergedFldrItem* pmfiEnum = _GetObject(*piPos); if (pmfiEnum) { BOOL fFolder = 0 != (pmfiEnum->GetFolderAttrib() & SFGAO_FOLDER), fHidden = 0 != (pmfiEnum->GetFolderAttrib() & SFGAO_HIDDEN); if (fWantAll || (!fHidden || fWantHidden) && ((fFolder && fWantFolders) || (!fFolder && fWantNonFolders))) { // Copy out the pidl hr = SHILClone(pmfiEnum->GetIDList(), ppidl); break; } else { (*piPos)++; } } } return hr; } HRESULT CMergedFolder::_GetOverlayInfo(LPCITEMIDLIST pidl, int *pIndex, DWORD dwFlags) { int iOverlayIndex = -1; HRESULT hr = S_OK; CMergedFldrNamespace *pns; if (_fCDBurn) { hr = E_OUTOFMEMORY; LPITEMIDLIST pidlInNamespace; if (SUCCEEDED(_GetSubPidl(pidl, 0, NULL, &pidlInNamespace, &pns))) { ASSERTMSG(!_fDontMerge || _GetSourceCount(pidl) == 1, "item for overlay should have exactly one wrapped pidl if not merged"); LPITEMIDLIST pidlFirst = ILCloneFirst(pidl); if (pidlFirst) { if (_GetSourceCount(pidlFirst) > 1) { // an overlay to indicate overwrite iOverlayIndex = pns->GetConflictOverlayIndex(); } if (iOverlayIndex == -1) { // overlay to indicate staged iOverlayIndex = pns->GetDefaultOverlayIndex(); } if (iOverlayIndex == -1) { // use the one provided by the namespace iOverlayIndex = pns->GetNamespaceOverlayIndex(pidlInNamespace); } ILFree(pidlFirst); } ILFree(pidlInNamespace); } } ASSERT(pIndex); *pIndex = (dwFlags == SIOM_OVERLAYINDEX) ? iOverlayIndex : INDEXTOOVERLAYMASK(iOverlayIndex); return hr; } HRESULT CMergedFolder::GetOverlayIndex(LPCITEMIDLIST pidl, int *pIndex) { return (*pIndex == OI_ASYNC) ? E_PENDING : _GetOverlayInfo(pidl, pIndex, SIOM_OVERLAYINDEX); } HRESULT CMergedFolder::GetOverlayIconIndex(LPCITEMIDLIST pidl, int *pIconIndex) { return _GetOverlayInfo(pidl, pIconIndex, SIOM_ICONINDEX); } STDMETHODIMP CMergedFolder::BindToParent(LPCITEMIDLIST pidl, REFIID riid, void **ppv, LPITEMIDLIST *ppidlLast) { // tybeam: nobody seems to call this any more. // if this is ever needed tell me to add it back. return E_NOTIMPL; } HRESULT CMergedFolder::_AddComposite(const COMPFOLDERINIT *pcfi) { HRESULT hr = E_FAIL; LPITEMIDLIST pidl = NULL; switch (pcfi->uType) { case CFITYPE_CSIDL: SHGetSpecialFolderLocation(NULL, pcfi->csidl, &pidl); break; case CFITYPE_PIDL: pidl = ILClone(pcfi->pidl); break; case CFITYPE_PATH: pidl = ILCreateFromPath(pcfi->pszPath); break; default: ASSERT(FALSE); } if (pidl) { hr = AddNameSpace(NULL, NULL, pidl, ASFF_DEFNAMESPACE_ALL); ILFree(pidl); } return hr; } STDMETHODIMP CMergedFolder::InitComposite(WORD wSignature, REFCLSID refclsid, CFINITF cfiFlags, ULONG celt, const COMPFOLDERINIT *rgCFs) { HRESULT hr = S_OK; _clsid = refclsid; ASSERTMSG(cfiFlags == CFINITF_FLAT, "merged folder doesn't support listing namespaces as children any more"); if (cfiFlags != CFINITF_FLAT) { hr = E_INVALIDARG; } if (SUCCEEDED(hr)) { for (ULONG i = 0; SUCCEEDED(hr) && (i < celt); i++) { hr = _AddComposite(rgCFs + i); } } return hr; } STDMETHODIMP CMergedFolder::_DeleteItemByIDList(LPCITEMIDLIST pidl) { LPITEMIDLIST pidlSrc; CMergedFldrNamespace *pns; HRESULT hr = _NamespaceForItem(pidl, ASFF_DEFNAMESPACE_BINDSTG, ASFF_DEFNAMESPACE_BINDSTG, NULL, &pidlSrc, &pns, FALSE); if (SUCCEEDED(hr)) { IStorage *pstg; hr = pns->Folder()->QueryInterface(IID_PPV_ARG(IStorage, &pstg)); if (SUCCEEDED(hr)) { TCHAR szName[MAX_PATH]; hr = DisplayNameOf(pns->Folder(), pidlSrc, SHGDN_FORPARSING | SHGDN_INFOLDER, szName, ARRAYSIZE(szName)); if (SUCCEEDED(hr)) { hr = pstg->DestroyElement(szName); } pstg->Release(); } ILFree(pidlSrc); } return hr; } HRESULT CMergedFolder::_GetDestinationStorage(DWORD grfMode, IStorage **ppstg) { CMergedFldrNamespace *pns; HRESULT hr = _FindNamespace(ASFF_DEFNAMESPACE_BINDSTG, ASFF_DEFNAMESPACE_BINDSTG, NULL, &pns, TRUE); if (SUCCEEDED(hr)) { IShellFolder *psf; LPCITEMIDLIST pidlLast; hr = SHBindToIDListParent(pns->GetIDList(), IID_PPV_ARG(IShellFolder, &psf), &pidlLast); if (SUCCEEDED(hr)) { // SHGetAttributes doesn't help for SFGAO_VALIDATE DWORD dwAttrib = SFGAO_VALIDATE; hr = psf->GetAttributesOf(1, &pidlLast, &dwAttrib); psf->Release(); } } if (SUCCEEDED(hr)) { IBindCtx *pbc; hr = BindCtx_CreateWithMode(grfMode, &pbc); if (SUCCEEDED(hr)) { hr = SHBindToObjectEx(NULL, pns->GetIDList(), pbc, IID_PPV_ARG(IStorage, ppstg)); pbc->Release(); } } else if (_pmfParent) { // the current pidl doesnt have the storage target namespace, so create it using our parent. IStorage *pstgParent; hr = _pmfParent->_GetDestinationStorage(grfMode, &pstgParent); if (SUCCEEDED(hr)) { TCHAR szName[MAX_PATH]; hr = DisplayNameOf((IShellFolder*)(void*)_pmfParent, ILFindLastID(_pidl), SHGDN_INFOLDER, szName, ARRAYSIZE(szName)); if (SUCCEEDED(hr)) { hr = pstgParent->CreateStorage(szName, STGM_READWRITE, 0, 0, ppstg); } pstgParent->Release(); } } return hr; } STDMETHODIMP CMergedFolder::_StgCreate(LPCITEMIDLIST pidl, DWORD grfMode, REFIID riid, void **ppv) { HRESULT hr = S_OK; // first we need to ensure that the folder exists. if (_pstg == NULL) { hr = _GetDestinationStorage(grfMode, &_pstg); } if (SUCCEEDED(hr)) { TCHAR szName[MAX_PATH]; hr = DisplayNameOf((IShellFolder*)(void*)this, pidl, SHGDN_FORPARSING | SHGDN_INFOLDER, szName, ARRAYSIZE(szName)); if (SUCCEEDED(hr)) { if (IsEqualIID(riid, IID_IStorage)) { hr = _pstg->CreateStorage(szName, grfMode, 0, 0, (IStorage **) ppv); } else if (IsEqualIID(riid, IID_IStream)) { hr = _pstg->CreateStream(szName, grfMode, 0, 0, (IStream **) ppv); } else { hr = E_INVALIDARG; } } } return hr; } // enumerator object class CMergedFldrEnum : public IEnumIDList { public: // IUnknown STDMETHOD (QueryInterface) (REFIID, void **); STDMETHOD_(ULONG,AddRef) (); STDMETHOD_(ULONG,Release) (); // IEnumIDList STDMETHODIMP Next(ULONG, LPITEMIDLIST*, ULONG*); STDMETHODIMP Skip(ULONG); STDMETHODIMP Reset(); STDMETHODIMP Clone(IEnumIDList**); CMergedFldrEnum(CMergedFolder*pmf, DWORD grfEnumFlags, int iPos = 0); private: ~CMergedFldrEnum(); LONG _cRef; CMergedFolder*_pmf; DWORD _grfEnumFlags; int _iPos; }; CMergedFldrEnum::CMergedFldrEnum(CMergedFolder *pfm, DWORD grfEnumFlags, int iPos) : _cRef(1), _iPos(iPos), _pmf(pfm), _grfEnumFlags(grfEnumFlags) { _pmf->AddRef(); } CMergedFldrEnum::~CMergedFldrEnum() { _pmf->Release(); } STDMETHODIMP CMergedFldrEnum::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CMergedFldrEnum, IEnumIDList), { 0 } }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) CMergedFldrEnum::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CMergedFldrEnum::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } // IEnumIDList STDMETHODIMP CMergedFldrEnum::Next(ULONG celt, LPITEMIDLIST *rgelt, ULONG *pceltFetched) { int iStart = _iPos; int cFetched = 0; HRESULT hr = S_OK; if (!(celt > 0 && rgelt) || (NULL == pceltFetched && celt > 1)) return E_INVALIDARG; *rgelt = 0; while (hr == S_OK && (_iPos - iStart) < (int)celt) { LPITEMIDLIST pidl; hr = _pmf->_GetPidl(&_iPos, _grfEnumFlags, &pidl); if (hr == S_OK) { rgelt[cFetched] = pidl; cFetched++; } _iPos++; } if (pceltFetched) *pceltFetched = cFetched; return celt == (ULONG)cFetched ? S_OK : S_FALSE; } STDMETHODIMP CMergedFldrEnum::Skip(ULONG celt) { _iPos += celt; return S_OK; } STDMETHODIMP CMergedFldrEnum::Reset() { _iPos = 0; return S_OK; } STDMETHODIMP CMergedFldrEnum::Clone(IEnumIDList **ppenum) { *ppenum = new CMergedFldrEnum(_pmf, _grfEnumFlags, _iPos); return *ppenum ? S_OK : E_OUTOFMEMORY; } HRESULT CMergedFldrEnum_CreateInstance(CMergedFolder*pmf, DWORD grfFlags, IEnumIDList **ppenum) { CMergedFldrEnum *penum = new CMergedFldrEnum(pmf, grfFlags); if (!penum) return E_OUTOFMEMORY; HRESULT hr = penum->QueryInterface(IID_PPV_ARG(IEnumIDList, ppenum)); penum->Release(); return hr; } // Drop Target handler class CMergedFldrDropTarget : public IDropTarget, public IObjectWithSite { public: // IUnknown STDMETHOD (QueryInterface) (REFIID, void **); STDMETHOD_(ULONG,AddRef) (); STDMETHOD_(ULONG,Release) (); // IDropTarget STDMETHODIMP DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect); STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect); STDMETHODIMP DragLeave(void); STDMETHODIMP Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect); // IObjectWithSite STDMETHODIMP SetSite(IUnknown *punkSite); STDMETHODIMP GetSite(REFIID riid, void **ppvSite); CMergedFldrDropTarget(CMergedFolder*pmf, HWND hwnd); private: ~CMergedFldrDropTarget(); HRESULT _CreateOtherNameSpace(IShellFolder **ppsf); HRESULT _FindTargetNamespace(CMergedFldrNamespace *pnsToMatch, CMergedFldrNamespace **ppns, CMergedFldrNamespace **ppnsMatched); HRESULT _CreateFolder(IShellFolder *psf, LPCWSTR pszName, REFIID riid, void **ppv); LPITEMIDLIST _FolderIDListFromData(IDataObject *pdtobj); BOOL _IsCommonIDList(LPCITEMIDLIST pidlItem); HRESULT _SetDropEffectFolders(); void _DestroyDropEffectFolders(); LONG _cRef; CMergedFolder *_pmf; IDropTarget *_pdt; IDataObject *_pdo; HWND _hwnd; BOOL _fSrcIsCommon; // is _pdt a common programs folder (or its child) LPITEMIDLIST _pidlSrcFolder; // where the source comes from DWORD _grfKeyState; DWORD _dwDragEnterEffect; }; CMergedFldrDropTarget::CMergedFldrDropTarget(CMergedFolder*pfm, HWND hwnd) : _cRef(1), _pmf(pfm), _hwnd(hwnd) { _pmf->AddRef(); } CMergedFldrDropTarget::~CMergedFldrDropTarget() { _pmf->Release(); ASSERT(!_pdt); ASSERT(!_pdo); } STDMETHODIMP CMergedFldrDropTarget::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CMergedFldrDropTarget, IDropTarget), QITABENT(CMergedFldrDropTarget, IObjectWithSite), { 0 } }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) CMergedFldrDropTarget::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CMergedFldrDropTarget::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } // is this pidl in the "All Users" part of the name space BOOL CMergedFldrDropTarget::_IsCommonIDList(LPCITEMIDLIST pidlItem) { BOOL bRet = FALSE; LPITEMIDLIST pidlCommon; if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_STARTMENU, &pidlCommon))) { bRet = ILIsParent(pidlCommon, pidlItem, FALSE); ILFree(pidlCommon); } if (!bRet && SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_PROGRAMS, &pidlCommon))) { bRet = ILIsParent(pidlCommon, pidlItem, FALSE); ILFree(pidlCommon); } if (!bRet && SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, &pidlCommon))) { bRet = ILIsParent(pidlCommon, pidlItem, FALSE); ILFree(pidlCommon); } return bRet; } LPITEMIDLIST CMergedFldrDropTarget::_FolderIDListFromData(IDataObject *pdtobj) { LPITEMIDLIST pidlFolder = NULL; STGMEDIUM medium = {0}; LPIDA pida = DataObj_GetHIDA(pdtobj, &medium); if (pida) { pidlFolder = ILClone(HIDA_GetPIDLFolder(pida)); HIDA_ReleaseStgMedium(pida, &medium); } return pidlFolder; } HRESULT CMergedFldrDropTarget::_CreateFolder(IShellFolder *psf, LPCWSTR pszName, REFIID riid, void **ppv) { *ppv = NULL; IStorage *pstg; HRESULT hr = psf->QueryInterface(IID_PPV_ARG(IStorage, &pstg)); if (SUCCEEDED(hr)) { DWORD grfMode = STGM_READWRITE; IStorage *pstgNew; hr = pstg->OpenStorage(pszName, NULL, grfMode, 0, 0, &pstgNew); if (FAILED(hr)) { // try create if it's not there hr = pstg->CreateStorage(pszName, grfMode, 0, 0, &pstgNew); } if (SUCCEEDED(hr)) { hr = pstgNew->QueryInterface(riid, ppv); pstgNew->Release(); } pstg->Release(); } return hr; } HRESULT CMergedFldrDropTarget::_FindTargetNamespace(CMergedFldrNamespace *pnsToMatch, CMergedFldrNamespace **ppnsDstRoot, CMergedFldrNamespace **ppnsMatched) { *ppnsDstRoot = NULL; *ppnsMatched = NULL; // if the source of the drag drop is in one of our name spaces // we prefer that as the target name space for (CMergedFolder*pmf = this->_pmf; pmf && (NULL == *ppnsDstRoot); pmf = pmf->_Parent()) { pmf->_FindNamespace(pnsToMatch->FolderAttrib(), pnsToMatch->FolderAttrib(), &pnsToMatch->GetGUID(), ppnsMatched); CMergedFldrNamespace *pns; for (int i = 0; (pns = pmf->_Namespace(i)) && (NULL == *ppnsDstRoot); i++) { if (pmf->_ShouldMergeNamespaces(*ppnsMatched, pns) && ILFindChild(pns->GetIDList(), _pidlSrcFolder)) { *ppnsDstRoot = pns; // if the source is in one of our name spaces } } ASSERT(NULL != *ppnsMatched); // pnsToMatch must exist above us // If merging is disabled, then we don't want parent namespaces // to infect us. if (pmf->_fDontMerge) { break; } } if (NULL == *ppnsDstRoot) { // if the source was not found, find the target name space based on // 1) if the source data is an "All Users" item find the first common _Namespace if (_fSrcIsCommon) { for (pmf = this->_pmf; pmf && (NULL == *ppnsDstRoot); pmf = pmf->_Parent()) { pmf->_FindNamespace(pnsToMatch->FolderAttrib(), pnsToMatch->FolderAttrib(), &pnsToMatch->GetGUID(), ppnsMatched); pmf->_FindNamespace(ASFF_COMMON, ASFF_COMMON, NULL, ppnsDstRoot); } } // 2) find the first NON common name space for this guy for (pmf = this->_pmf; pmf && (NULL == *ppnsDstRoot); pmf = pmf->_Parent()) { pmf->_FindNamespace(pnsToMatch->FolderAttrib(), pnsToMatch->FolderAttrib(), &pnsToMatch->GetGUID(), ppnsMatched); pmf->_FindNamespace(ASFF_COMMON, 0, NULL, ppnsDstRoot); // search for non common items } } if (NULL == *ppnsMatched && NULL != *ppnsDstRoot) { delete *ppnsDstRoot; *ppnsDstRoot = NULL; } return *ppnsDstRoot ? S_OK : E_FAIL; } HRESULT CMergedFldrDropTarget::_CreateOtherNameSpace(IShellFolder **ppsf) { *ppsf = NULL; HRESULT hr = E_FAIL; if (_pidlSrcFolder && (!_fSrcIsCommon || AffectAllUsers(_hwnd))) { CMergedFldrNamespace *pnsDstRoot; // name space we want to create this new item in CMergedFldrNamespace *pnsSrc; CMergedFldrNamespace *pnsStart = this->_pmf->_Namespace(0); if (pnsStart) { hr = _FindTargetNamespace(pnsStart, &pnsDstRoot, &pnsSrc); if (SUCCEEDED(hr)) { LPCITEMIDLIST pidlChild = ILFindChild(pnsSrc->GetIDList(), pnsStart->GetIDList()); ASSERT(pidlChild && !ILIsEmpty(pidlChild)); // pnsSrc is a parent of pnsStart IShellFolder *psfDst = pnsDstRoot->Folder(); psfDst->AddRef(); IShellFolder *psfSrc = pnsSrc->Folder(); psfSrc->AddRef(); for (LPCITEMIDLIST pidl = pidlChild; !ILIsEmpty(pidl) && SUCCEEDED(hr); pidl = _ILNext(pidl)) { LPITEMIDLIST pidlFirst = ILCloneFirst(pidl); if (pidlFirst) { WCHAR szName[MAX_PATH]; hr = DisplayNameOf(psfSrc, pidlFirst, SHGDN_FORPARSING | SHGDN_INFOLDER, szName, ARRAYSIZE(szName)); if (SUCCEEDED(hr)) { IShellFolder *psfNew; hr = _CreateFolder(psfDst, szName, IID_PPV_ARG(IShellFolder, &psfNew)); if (SUCCEEDED(hr)) { psfDst->Release(); psfDst = psfNew; } else { break; } } // now advance to the next folder in the source name space IShellFolder *psfNew; hr = psfSrc->BindToObject(pidlFirst, NULL, IID_PPV_ARG(IShellFolder, &psfNew)); if (SUCCEEDED(hr)) { psfSrc->Release(); psfSrc = psfNew; } else { break; } ILFree(pidlFirst); } else hr = E_FAIL; } psfSrc->Release(); if (SUCCEEDED(hr)) *ppsf = psfDst; // copy out our ref else psfDst->Release(); } } } return hr; } HRESULT CMergedFldrDropTarget::_SetDropEffectFolders() { INT i, cFolders = 0; // Compute the number of folders which have special drop effects for (i = 0; i < _pmf->_NamespaceCount(); i++) { if (_pmf->_Namespace(i)->GetDropEffect() != 0) cFolders++; } // If the number is > 0 then lets add to the IDataObject the clipboard format // which defines this information. HRESULT hr = S_OK; if ((cFolders > 0) || (_pmf->_dwDropEffect != 0)) { DWORD cb = sizeof(DROPEFFECTFOLDERLIST) + sizeof(DROPEFFECTFOLDER) * (cFolders - 1); DROPEFFECTFOLDERLIST *pdefl = (DROPEFFECTFOLDERLIST*)LocalAlloc(LPTR, cb); if (pdefl) { pdefl->cFolders = cFolders; pdefl->dwDefaultDropEffect = _pmf->_dwDropEffect; // default effect for the folders. // fill the array backwards with the folders we have and their desired effects for (i = 0, cFolders--; cFolders >= 0; i++) { DWORD dwDropEffect = _pmf->_Namespace(i)->GetDropEffect(); if (dwDropEffect != 0) { pdefl->aFolders[cFolders].dwDropEffect = dwDropEffect; StrCpyN(pdefl->aFolders[cFolders].wszPath, _pmf->_Namespace(i)->GetDropFolder(), ARRAYSIZE(pdefl->aFolders[cFolders].wszPath)); cFolders--; } } ASSERTMSG(g_cfDropEffectFolderList != 0, "Clipboard format for CFSTR_DROPEFFECTFOLDERS not registered"); hr = DataObj_SetBlob(_pdo, g_cfDropEffectFolderList, pdefl, cb); LocalFree(pdefl); } else { hr = E_OUTOFMEMORY; } } return hr; } // nuke the modified drop effects void CMergedFldrDropTarget::_DestroyDropEffectFolders() { // there are no DROPEFFECTFOLDERs so subtract off the [1] array DWORD cb = sizeof(DROPEFFECTFOLDERLIST) - sizeof(DROPEFFECTFOLDER); DROPEFFECTFOLDERLIST *pdefl = (DROPEFFECTFOLDERLIST*)LocalAlloc(LPTR, cb); if (pdefl) { pdefl->cFolders = 0; pdefl->dwDefaultDropEffect = DROPEFFECT_NONE; // means do default handling ASSERTMSG(g_cfDropEffectFolderList != 0, "Clipboard format for CFSTR_DROPEFFECTFOLDERS not registered"); DataObj_SetBlob(_pdo, g_cfDropEffectFolderList, pdefl, cb); LocalFree(pdefl); } } HRESULT CMergedFldrDropTarget::SetSite(IUnknown *punkSite) { IUnknown_SetSite(_pdt, punkSite); return S_OK; } HRESULT CMergedFldrDropTarget::GetSite(REFIID riid, void **ppvSite) { return IUnknown_GetSite(_pdt, riid, ppvSite); } HRESULT CMergedFldrDropTarget::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { ASSERT(!_fSrcIsCommon); ASSERT(_pdt == NULL); ASSERT(_pidlSrcFolder == NULL); ASSERT(_pdo == NULL); IUnknown_Set((IUnknown**)&_pdo, pdtobj); _SetDropEffectFolders(); _pidlSrcFolder = _FolderIDListFromData(pdtobj); if (_pidlSrcFolder) _fSrcIsCommon = _IsCommonIDList(_pidlSrcFolder); HRESULT hr; CMergedFldrNamespace *pns; if (_fSrcIsCommon) { // if the item is comming from an "All Users" folder we want to target // the common folder. if that does not exist yet then we continue on // with a NULL _pdt and create that name space when the drop happens hr = _pmf->_FindNamespace(ASFF_COMMON, ASFF_COMMON, NULL, &pns); } else { // not "All Users" item, get the default name space (if there is one) hr = _pmf->_FindNamespace(ASFF_DEFNAMESPACE_VIEWOBJ, ASFF_DEFNAMESPACE_VIEWOBJ, NULL, &pns); } if (SUCCEEDED(hr) || _pmf->_fCDBurn) { if (_pmf->_fCDBurn) { IShellExtInit *psei; hr = CoCreateInstance(CLSID_CDBurn, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellExtInit, &psei)); if (SUCCEEDED(hr)) { hr = psei->Initialize(_pmf->_pidl, NULL, NULL); if (SUCCEEDED(hr)) { hr = psei->QueryInterface(IID_PPV_ARG(IDropTarget, &_pdt)); } psei->Release(); } } else { hr = pns->Folder()->CreateViewObject(_hwnd, IID_PPV_ARG(IDropTarget, &_pdt)); } if (SUCCEEDED(hr)) { _pdt->DragEnter(pdtobj, grfKeyState, pt, pdwEffect); } } _grfKeyState = grfKeyState; _dwDragEnterEffect = *pdwEffect; return S_OK; } HRESULT CMergedFldrDropTarget::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { _grfKeyState = grfKeyState; return _pdt ? _pdt->DragOver(grfKeyState, pt, pdwEffect) : S_OK; } HRESULT CMergedFldrDropTarget::DragLeave(void) { if (_pdt) { _pdt->DragLeave(); IUnknown_SetSite(_pdt, NULL); IUnknown_Set((IUnknown **)&_pdt, NULL); } if (_pdo) { _DestroyDropEffectFolders(); IUnknown_Set((IUnknown**)&_pdo, NULL); } _fSrcIsCommon = 0; ILFree(_pidlSrcFolder); // accepts NULL _pidlSrcFolder = NULL; return S_OK; } HRESULT CMergedFldrDropTarget::Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) { HRESULT hr = S_OK; if (!_pdt) { // we came here because we don't have _pdt which means that // there is only one folder in our _Namespace and that's not // the one we have to drop on. so we need to create it now IShellFolder *psf; hr = _CreateOtherNameSpace(&psf); if (SUCCEEDED(hr)) { hr = psf->CreateViewObject(_hwnd, IID_PPV_ARG(IDropTarget, &_pdt)); if (SUCCEEDED(hr)) _pdt->DragEnter(pdtobj, _grfKeyState, pt, &_dwDragEnterEffect); psf->Release(); } } if (_pdt) { hr = _pdt->Drop(pdtobj, grfKeyState, pt, pdwEffect); DragLeave(); } return S_OK; } HRESULT CMergedFldrDropTarget_CreateInstance(CMergedFolder*pmf, HWND hwndOwner, IDropTarget **ppdt) { CMergedFldrDropTarget *pdt = new CMergedFldrDropTarget(pmf, hwndOwner); if (!pdt) return E_OUTOFMEMORY; HRESULT hr = pdt->QueryInterface(IID_PPV_ARG(IDropTarget, ppdt)); pdt->Release(); return hr; } // context menu handling. class CMergedFldrContextMenu : public IContextMenu3, public IObjectWithSite { public: // IUnknown STDMETHOD (QueryInterface)(REFIID, void**); STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); // IContextMenu STDMETHODIMP QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags); STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO lpici); STDMETHODIMP GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pwReserved, LPSTR pszName, UINT cchMax); // IContextMenu2 STDMETHODIMP HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam); // IContextMenu3 STDMETHODIMP HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult); // IObjectWithSite STDMETHODIMP SetSite(IUnknown *punkSite); STDMETHODIMP GetSite(REFIID riid, void **ppvSite); CMergedFldrContextMenu(HWND hwnd, IContextMenu *pcmCommon, IContextMenu *pcmUser); private: ~CMergedFldrContextMenu(); HRESULT _Initialize(CMergedFolder *pmf, UINT cidl, LPCITEMIDLIST *apidl); BOOL _IsMergedCommand(LPCMINVOKECOMMANDINFO pici); HRESULT _InvokeCanonical(IContextMenu *pcm, LPCMINVOKECOMMANDINFO pici); HRESULT _InvokeMergedCommand(LPCMINVOKECOMMANDINFO pici); IContextMenu* _DefaultMenu(); LONG _cRef; IContextMenu * _pcmCommon; IContextMenu * _pcmUser; UINT _cidl; LPITEMIDLIST *_apidl; CMergedFolder *_pmfParent; UINT _idFirst; HWND _hwnd; friend HRESULT CMergedFldrContextMenu_CreateInstance(HWND hwnd, CMergedFolder *pmf, UINT cidl, LPCITEMIDLIST *apidl, IContextMenu *pcmCommon, IContextMenu *pcmUser, IContextMenu **ppcm); }; CMergedFldrContextMenu::CMergedFldrContextMenu(HWND hwnd, IContextMenu *pcmCommon, IContextMenu *pcmUser) { ASSERT(pcmCommon || pcmUser); // need at least one of these _cRef = 1; _hwnd = hwnd; IUnknown_Set((IUnknown **)&_pcmCommon, pcmCommon); IUnknown_Set((IUnknown **)&_pcmUser, pcmUser); } CMergedFldrContextMenu::~CMergedFldrContextMenu() { ATOMICRELEASE(_pcmCommon); ATOMICRELEASE(_pcmUser); ATOMICRELEASE(_pmfParent); for (UINT i = 0; i < _cidl; i++) { ILFree(_apidl[i]); } } IContextMenu* CMergedFldrContextMenu::_DefaultMenu() { ASSERT(_pcmUser || _pcmCommon); // need at least one of these; this is pretty much guaranteed since we're given them // at constructor time. return _pcmUser ? _pcmUser : _pcmCommon; } HRESULT CMergedFldrContextMenu::_Initialize(CMergedFolder *pmf, UINT cidl, LPCITEMIDLIST *apidl) { _pmfParent = pmf; if (_pmfParent) { _pmfParent->AddRef(); } HRESULT hr = E_OUTOFMEMORY; LPITEMIDLIST *apidlNew = new LPITEMIDLIST[cidl]; if (apidlNew) { hr = S_OK; for (UINT i = 0; SUCCEEDED(hr) && i < cidl; i++) { hr = SHILClone(apidl[i], &(apidlNew[i])); } if (SUCCEEDED(hr)) { _apidl = apidlNew; _cidl = cidl; } else { for (i = 0; i < cidl; i++) { ILFree(apidlNew[i]); } delete [] apidlNew; } } return hr; } STDMETHODIMP CMergedFldrContextMenu::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENTMULTI(CMergedFldrContextMenu, IContextMenu, IContextMenu3), QITABENTMULTI(CMergedFldrContextMenu, IContextMenu2, IContextMenu3), QITABENT(CMergedFldrContextMenu, IContextMenu3), QITABENT(CMergedFldrContextMenu, IObjectWithSite), { 0 }, }; return QISearch(this, qit, riid, ppv); } STDMETHODIMP_(ULONG) CMergedFldrContextMenu::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CMergedFldrContextMenu::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } HRESULT CMergedFldrContextMenu::SetSite(IUnknown *punkSite) { // setsite/getsite will always be matched because _pcmUser and _pcmCommon never change // after the constructor is called. IUnknown_SetSite(_DefaultMenu(), punkSite); return S_OK; } HRESULT CMergedFldrContextMenu::GetSite(REFIID riid, void **ppvSite) { return IUnknown_GetSite(_DefaultMenu(), riid, ppvSite); } HRESULT CMergedFldrContextMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { HRESULT hr; if (_pmfParent->_fInShellView) { hr = _DefaultMenu()->QueryContextMenu(hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags); } else { if (hmenu) { HMENU hmContext = SHLoadMenuPopup(HINST_THISDLL, MENU_SM_CONTEXTMENU); if (hmContext) { int i; if (!_pcmCommon || !_pcmUser) { DeleteMenu(hmContext, SMIDM_OPENCOMMON, MF_BYCOMMAND); DeleteMenu(hmContext, SMIDM_EXPLORECOMMON, MF_BYCOMMAND); } _idFirst = idCmdFirst; i = Shell_MergeMenus(hmenu, hmContext, -1, idCmdFirst, idCmdLast, MM_ADDSEPARATOR); DestroyMenu(hmContext); // Make it look "Shell Like" SetMenuDefaultItem(hmenu, 0, MF_BYPOSITION); hr = ResultFromShort(i); } else hr = E_OUTOFMEMORY; } else hr = E_INVALIDARG; } return hr; } BOOL CMergedFldrContextMenu::_IsMergedCommand(LPCMINVOKECOMMANDINFO pici) { HRESULT hr = S_OK; WCHAR szVerb[32]; if (!IS_INTRESOURCE(pici->lpVerb)) { lstrcpyn(szVerb, (LPCWSTR)pici->lpVerb, ARRAYSIZE(szVerb)); } else { hr = _DefaultMenu()->GetCommandString((UINT_PTR)pici->lpVerb, GCS_VERBW, NULL, (LPSTR)szVerb, ARRAYSIZE(szVerb)); } BOOL fRet = FALSE; if (SUCCEEDED(hr)) { if ((lstrcmpi(szVerb, c_szOpen) == 0) || (lstrcmpi(szVerb, c_szExplore) == 0)) { fRet = TRUE; } } return fRet; } HRESULT CMergedFldrContextMenu::_InvokeMergedCommand(LPCMINVOKECOMMANDINFO pici) { // this code is mostly to navigate into folders from the shell view. // the reason why we have to do this manually is because the context menus for // the different namespaces have dataobjects which refer only to one namespace. // when the navigate happens, that means we get passed bogus non-merged pidls // into our BindToObject which screws things up. ASSERT(_pmfParent->_fInShellView); BOOL fHasFolders = FALSE; BOOL fHasNonFolders = FALSE; for (UINT i = 0; i < _cidl; i++) { if (_pmfParent->_IsFolder(_apidl[i])) { fHasFolders = TRUE; } else { fHasNonFolders = TRUE; } } HRESULT hr; if (fHasFolders && _IsMergedCommand(pici)) { if (!fHasNonFolders) { // reinit the defcm with a new dataobject IShellExtInit *psei; hr = _DefaultMenu()->QueryInterface(IID_PPV_ARG(IShellExtInit, &psei)); if (SUCCEEDED(hr)) { IDataObject *pdo; hr = SHCreateFileDataObject(_pmfParent->_pidl, _cidl, (LPCITEMIDLIST *)_apidl, NULL, &pdo); if (SUCCEEDED(hr)) { hr = psei->Initialize(_pmfParent->_pidl, pdo, NULL); if (SUCCEEDED(hr)) { hr = _DefaultMenu()->InvokeCommand(pici); } pdo->Release(); } psei->Release(); } } else { // to open both folders and items simultaneously means we'd have to // get a new context menu for just the items. // we can do this if this scenario comes up. hr = E_FAIL; } } else { hr = _DefaultMenu()->InvokeCommand(pici); } return hr; } const ICIVERBTOIDMAP c_sIDVerbMap[] = { { L"delete", "delete", SMIDM_DELETE, SMIDM_DELETE, }, { L"rename", "rename", SMIDM_RENAME, SMIDM_RENAME, }, { L"properties", "properties", SMIDM_PROPERTIES, SMIDM_PROPERTIES, }, }; HRESULT CMergedFldrContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) { HRESULT hr = E_FAIL; CMINVOKECOMMANDINFOEX ici = {0}; memcpy(&ici, pici, min(sizeof(ici), pici->cbSize)); ici.cbSize = sizeof(ici); if (_pmfParent->_fInShellView) { hr = _InvokeMergedCommand((LPCMINVOKECOMMANDINFO)&ici); } else { UINT id; hr = SHMapICIVerbToCmdID((LPCMINVOKECOMMANDINFO)&ici, c_sIDVerbMap, ARRAYSIZE(c_sIDVerbMap), &id); if (SUCCEEDED(hr)) { // The below sets an ansi verb only, make sure no lpVerbW is used ici.fMask &= (~CMIC_MASK_UNICODE); switch (id) { case SMIDM_OPEN: case SMIDM_EXPLORE: case SMIDM_OPENCOMMON: case SMIDM_EXPLORECOMMON: { ASSERT(!_pmfParent->_fInShellView); IContextMenu * pcm; if (id == SMIDM_OPEN || id == SMIDM_EXPLORE) { pcm = _DefaultMenu(); } else { pcm = _pcmCommon; } hr = SHInvokeCommandOnContextMenu(ici.hwnd, NULL, pcm, ici.fMask, (id == SMIDM_EXPLORE || id == SMIDM_EXPLORECOMMON) ? "explore" : "open"); } break; case SMIDM_PROPERTIES: hr = SHInvokeCommandOnContextMenu(ici.hwnd, NULL, _DefaultMenu(), ici.fMask, "properties"); break; case SMIDM_DELETE: ici.lpVerb = "delete"; if (_pcmUser) { hr = SHInvokeCommandOnContextMenu(ici.hwnd, NULL, _pcmUser, ici.fMask, "delete"); } else { ASSERT(_pcmCommon); ici.fMask |= CMIC_MASK_FLAG_NO_UI; if (AffectAllUsers(_hwnd)) hr = SHInvokeCommandOnContextMenu(ici.hwnd, NULL, _pcmCommon, ici.fMask, "delete"); else hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); } break; case SMIDM_RENAME: ASSERT(0); hr = E_NOTIMPL; // sftbar picks this off break; default: ASSERTMSG(FALSE, "shouldn't have unknown command"); hr = E_INVALIDARG; } } } return hr; } HRESULT CMergedFldrContextMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pwReserved, LPSTR pszName, UINT cchMax) { HRESULT hr = E_NOTIMPL; switch (uType) { case GCS_VERBA: case GCS_VERBW: hr = SHMapCmdIDToVerb(idCmd, c_sIDVerbMap, ARRAYSIZE(c_sIDVerbMap), pszName, cchMax, GCS_VERBW == uType); } if (FAILED(hr)) { hr = _DefaultMenu()->GetCommandString(idCmd, uType, pwReserved, pszName, cchMax); } return hr; } HRESULT CMergedFldrContextMenu::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam) { HRESULT hr; if (_pmfParent->_fInShellView) { IContextMenu2 *pcm2; hr = _DefaultMenu()->QueryInterface(IID_PPV_ARG(IContextMenu2, &pcm2)); if (SUCCEEDED(hr)) { hr = pcm2->HandleMenuMsg(uMsg, wParam, lParam); pcm2->Release(); } } else { hr = E_NOTIMPL; } return hr; } HRESULT CMergedFldrContextMenu::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult) { HRESULT hr; if (_pmfParent->_fInShellView) { IContextMenu3 *pcm3; hr = _DefaultMenu()->QueryInterface(IID_PPV_ARG(IContextMenu3, &pcm3)); if (SUCCEEDED(hr)) { hr = pcm3->HandleMenuMsg2(uMsg, wParam, lParam, plResult); pcm3->Release(); } } else { hr = E_NOTIMPL; } return hr; } HRESULT CMergedFldrContextMenu_CreateInstance(HWND hwnd, CMergedFolder *pmf, UINT cidl, LPCITEMIDLIST *apidl, IContextMenu *pcmCommon, IContextMenu *pcmUser, IContextMenu **ppcm) { HRESULT hr = E_OUTOFMEMORY; CMergedFldrContextMenu* pcm = new CMergedFldrContextMenu(hwnd, pcmCommon, pcmUser); if (pcm) { hr = pcm->_Initialize(pmf, cidl, apidl); if (SUCCEEDED(hr)) { hr = pcm->QueryInterface(IID_PPV_ARG(IContextMenu, ppcm)); } pcm->Release(); } return hr; } class CMergedCategorizer : public ICategorizer, public IShellExtInit { public: // IUnknown STDMETHODIMP QueryInterface(REFIID riid, void **ppv); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); // ICategorizer STDMETHODIMP GetDescription(LPWSTR pszDesc, UINT cch); STDMETHODIMP GetCategory(UINT cidl, LPCITEMIDLIST * apidl, DWORD* rgCategoryIds); STDMETHODIMP GetCategoryInfo(DWORD dwCategoryId, CATEGORY_INFO* pci); STDMETHODIMP CompareCategory(CATSORT_FLAGS csfFlags, DWORD dwCategoryId1, DWORD dwCategoryId2); // IShellExtInit STDMETHODIMP Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdobj, HKEY hkeyProgID); CMergedCategorizer(); private: ~CMergedCategorizer(); long _cRef; CMergedFolder *_pmf; }; // Type Categorizer STDAPI CMergedCategorizer_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv) { HRESULT hr = E_OUTOFMEMORY; CMergedCategorizer *pmc = new CMergedCategorizer(); if (pmc) { hr = pmc->QueryInterface(riid, ppv); pmc->Release(); } return hr; } CMergedCategorizer::CMergedCategorizer() : _cRef(1) { } CMergedCategorizer::~CMergedCategorizer() { ATOMICRELEASE(_pmf); } HRESULT CMergedCategorizer::QueryInterface(REFIID riid, void **ppv) { static const QITAB qit[] = { QITABENT(CMergedCategorizer, ICategorizer), QITABENT(CMergedCategorizer, IShellExtInit), { 0 }, }; return QISearch(this, qit, riid, ppv); } ULONG CMergedCategorizer::AddRef() { return InterlockedIncrement(&_cRef); } ULONG CMergedCategorizer::Release() { ASSERT( 0 != _cRef ); ULONG cRef = InterlockedDecrement(&_cRef); if ( 0 == cRef ) { delete this; } return cRef; } HRESULT CMergedCategorizer::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdobj, HKEY hkeyProgID) { ATOMICRELEASE(_pmf); return SHBindToObjectEx(NULL, pidlFolder, NULL, CLSID_MergedFolder, (void**)&_pmf); } HRESULT CMergedCategorizer::GetDescription(LPWSTR pszDesc, UINT cch) { LoadString(HINST_THISDLL, IDS_WHICHFOLDER_COL, pszDesc, cch); return S_OK; } HRESULT CMergedCategorizer::GetCategory(UINT cidl, LPCITEMIDLIST *apidl, DWORD *rgCategoryIds) { HRESULT hr = E_ACCESSDENIED; if (_pmf) { for (UINT i = 0; i < cidl; i++) { rgCategoryIds[i] = -1; CMergedFldrNamespace *pns; UINT uSrcID; for (UINT j = 0; SUCCEEDED(_pmf->_GetSubPidl(apidl[i], j, &uSrcID, NULL, &pns)); j++) { if (_pmf->_ShouldSuspend(pns->GetGUID())) { continue; } rgCategoryIds[i] = uSrcID; if (ASFF_DEFNAMESPACE_ATTRIB & pns->FolderAttrib()) { // if we have more than one, keep the one from the defnamespace for attrib. break; } } } hr = S_OK; } return hr; } HRESULT CMergedCategorizer::GetCategoryInfo(DWORD dwCategoryId, CATEGORY_INFO* pci) { CMergedFldrNamespace *pns; HRESULT hr = _pmf->_Namespace(dwCategoryId, &pns); if (SUCCEEDED(hr)) { hr = pns->GetLocation(pci->wszName, ARRAYSIZE(pci->wszName)); } return hr; } HRESULT CMergedCategorizer::CompareCategory(CATSORT_FLAGS csfFlags, DWORD dwCategoryId1, DWORD dwCategoryId2) { if (dwCategoryId1 == dwCategoryId2) return ResultFromShort(0); else if (dwCategoryId1 > dwCategoryId2) return ResultFromShort(-1); else return ResultFromShort(1); } #define NORMALEVENTS (SHCNE_RENAMEITEM | SHCNE_RENAMEFOLDER | SHCNE_CREATE | SHCNE_DELETE | SHCNE_UPDATEDIR | SHCNE_UPDATEITEM | SHCNE_MKDIR | SHCNE_RMDIR) #define CDBURNEVENTS (SHCNE_MEDIAREMOVED | SHCNE_MEDIAINSERTED) CMergedFolderViewCB::CMergedFolderViewCB(CMergedFolder *pmf) : CBaseShellFolderViewCB(pmf->_pidl, (pmf->_fCDBurn) ? CDBURNEVENTS|NORMALEVENTS : NORMALEVENTS), _pmf(pmf) { _pmf->AddRef(); } CMergedFolderViewCB::~CMergedFolderViewCB() { _pmf->Release(); } HRESULT CMergedFolderViewCB::_RefreshObjectsWithSameName(IShellFolderView *psfv, LPITEMIDLIST pidl) { TCHAR szName[MAX_PATH]; HRESULT hr = DisplayNameOf(SAFECAST(_pmf, IShellFolder2 *), pidl, SHGDN_FORPARSING | SHGDN_INFOLDER, szName, ARRAYSIZE(szName)); if (SUCCEEDED(hr)) { CMergedFldrNamespace *pns; for (int i = 0; SUCCEEDED(hr) && (pns = _pmf->_Namespace(i)); i++) { LPITEMIDLIST pidlItem; if (SUCCEEDED(pns->Folder()->ParseDisplayName(NULL, NULL, szName, NULL, &pidlItem, NULL))) { LPITEMIDLIST pidlItemWrap; hr = _pmf->_CreateWrap(pidlItem, i, &pidlItemWrap); if (SUCCEEDED(hr)) { UINT uItem; hr = psfv->UpdateObject(pidlItemWrap, pidlItemWrap, &uItem); ILFree(pidlItemWrap); } ILFree(pidlItem); } } } return hr; } HRESULT CMergedFolderViewCB::_OnFSNotify(DWORD pv, LPCITEMIDLIST *ppidl, LPARAM lp) { // bail out early if we're merging. // S_OK indicates to defview to do what it usually does if (!_pmf->_fDontMerge) return S_OK; IShellFolderView *psfv; HRESULT hr = IUnknown_GetSite(SAFECAST(this, IShellFolderViewCB*), IID_PPV_ARG(IShellFolderView, &psfv)); if (SUCCEEDED(hr)) { LONG lEvent = (LONG) lp; LPITEMIDLIST pidl1 = ppidl[0] ? ILFindChild(_pidl, ppidl[0]) : NULL; LPITEMIDLIST pidl2 = ppidl[1] ? ILFindChild(_pidl, ppidl[1]) : NULL; UINT uItem; switch (lEvent) { case SHCNE_RENAMEFOLDER: case SHCNE_RENAMEITEM: // we need to handle the in/out for the view if (pidl1 && pidl2) { if (SUCCEEDED(psfv->UpdateObject(pidl1, pidl2, &uItem))) hr = S_FALSE; _RefreshObjectsWithSameName(psfv, pidl1); _RefreshObjectsWithSameName(psfv, pidl2); } else if (pidl1) { if (SUCCEEDED(psfv->RemoveObject(pidl1, &uItem))) hr = S_FALSE; _RefreshObjectsWithSameName(psfv, pidl1); } else if (pidl2) { if (SUCCEEDED(psfv->AddObject(pidl2, &uItem))) hr = S_FALSE; _RefreshObjectsWithSameName(psfv, pidl2); } break; case SHCNE_CREATE: case SHCNE_MKDIR: ASSERTMSG(pidl1 != NULL, "incoming notify should be child of _pidl because thats what we were listening for"); if (SUCCEEDED(psfv->AddObject(pidl1, &uItem))) hr = S_FALSE; _RefreshObjectsWithSameName(psfv, pidl1); break; case SHCNE_DELETE: case SHCNE_RMDIR: ASSERTMSG(pidl1 != NULL, "incoming notify should be child of _pidl because thats what we were listening for"); if (SUCCEEDED(psfv->RemoveObject(pidl1, &uItem))) hr = S_FALSE; _RefreshObjectsWithSameName(psfv, pidl1); break; default: break; } psfv->Release(); } return hr; } STDMETHODIMP CMergedFolderViewCB::RealMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { HANDLE_MSG(0, SFVM_FSNOTIFY, _OnFSNotify); default: return E_NOTIMPL; } return S_OK; } HRESULT CMergedFolderViewCB_CreateInstance(CMergedFolder *pmf, IShellFolderViewCB **ppsfvcb) { HRESULT hr = E_OUTOFMEMORY; CMergedFolderViewCB *pcmfvcb = new CMergedFolderViewCB(pmf); if (pcmfvcb) { hr = pcmfvcb->QueryInterface(IID_PPV_ARG(IShellFolderViewCB, ppsfvcb)); pcmfvcb->Release(); } return hr; }