diff --git a/base/shell/rshell/CMakeLists.txt b/base/shell/rshell/CMakeLists.txt index 741b4a91643..1d2684f800c 100644 --- a/base/shell/rshell/CMakeLists.txt +++ b/base/shell/rshell/CMakeLists.txt @@ -31,6 +31,7 @@ add_importlibs(rshell uxtheme shlwapi shell32 + comctl32 gdi32 ole32 user32 diff --git a/base/shell/rshell/CMenuToolbars.cpp b/base/shell/rshell/CMenuToolbars.cpp index 3114193dddb..4d67c391694 100644 --- a/base/shell/rshell/CMenuToolbars.cpp +++ b/base/shell/rshell/CMenuToolbars.cpp @@ -1260,10 +1260,9 @@ HRESULT CMenuSFToolbar::FillToolbar(BOOL clearFirst) IEnumIDList * eidl; m_shellFolder->EnumObjects(GetToolbar(), SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &eidl); - LPITEMIDLIST item = static_cast(CoTaskMemAlloc(sizeof(ITEMIDLIST))); - ULONG fetched; - hr = eidl->Next(1, &item, &fetched); - while (SUCCEEDED(hr) && fetched > 0) + LPITEMIDLIST item = { 0 }; + hr = eidl->Next(1, &item, NULL); + while (hr == S_OK) { INT index = 0; INT indexOpen = 0; @@ -1286,13 +1285,13 @@ HRESULT CMenuSFToolbar::FillToolbar(BOOL clearFirst) DWORD_PTR dwData = reinterpret_cast(ILClone(item)); // Fetch next item already, so we know if the current one is the last - hr = eidl->Next(1, &item, &fetched); + hr = eidl->Next(1, &item, NULL); - AddButton(++i, MenuString, attrs & SFGAO_FOLDER, index, dwData, FAILED(hr) || fetched == 0); + AddButton(++i, MenuString, attrs & SFGAO_FOLDER, index, dwData, hr != S_OK); CoTaskMemFree(MenuString); } - CoTaskMemFree(item); + ILFree(item); // If no items were added, show the "empty" placeholder if (i == 0) diff --git a/base/shell/rshell/CMergedFolder.cpp b/base/shell/rshell/CMergedFolder.cpp index 7af73891ed4..8758f6454a5 100644 --- a/base/shell/rshell/CMergedFolder.cpp +++ b/base/shell/rshell/CMergedFolder.cpp @@ -25,18 +25,33 @@ WINE_DEFAULT_DEBUG_CHANNEL(CMergedFolder); +struct LocalPidlInfo +{ + int side; // -1 local, 0 shared, 1 common + LPITEMIDLIST pidl; +}; + class CEnumMergedFolder : public CComObjectRootEx, public IEnumIDList { + private: + CComPtr m_UserLocalFolder; + CComPtr m_AllUSersFolder; CComPtr m_UserLocal; CComPtr m_AllUSers; - BOOL m_FirstDone; + + HWND m_HwndOwner; + SHCONTF m_Flags; + + HDSA m_hDsa; + UINT m_hDsaIndex; + UINT m_hDsaCount; public: - CEnumMergedFolder() : m_UserLocal(NULL), m_AllUSers(NULL), m_FirstDone(FALSE) {} - virtual ~CEnumMergedFolder() {} + CEnumMergedFolder(); + virtual ~CEnumMergedFolder(); DECLARE_NOT_AGGREGATABLE(CEnumMergedFolder) DECLARE_PROTECT_FINAL_CONSTRUCT() @@ -45,84 +60,346 @@ public: COM_INTERFACE_ENTRY_IID(IID_IEnumIDList, IEnumIDList) END_COM_MAP() - HRESULT Begin(HWND hwndOwner, SHCONTF flags, IShellFolder * userLocal, IShellFolder * allUSers) - { - HRESULT hr; - hr = userLocal->EnumObjects(hwndOwner, flags, &m_UserLocal); - if (FAILED_UNEXPECTEDLY(hr)) - return hr; - hr = userLocal->EnumObjects(hwndOwner, flags, &m_AllUSers); - if (FAILED_UNEXPECTEDLY(hr)) - { - m_UserLocal = NULL; - return hr; - } - m_FirstDone = FALSE; - return S_OK; - } + int DsaDeleteCallback(LocalPidlInfo * info); + + static int CALLBACK s_DsaDeleteCallback(void *pItem, void *pData); + + HRESULT SetSources(IShellFolder * userLocal, IShellFolder * allUSers); + HRESULT Begin(HWND hwndOwner, SHCONTF flags); + HRESULT FindPidlInList(LPCITEMIDLIST pcidl, LocalPidlInfo * pinfo); virtual HRESULT STDMETHODCALLTYPE Next( ULONG celt, LPITEMIDLIST *rgelt, - ULONG *pceltFetched) - { - HRESULT hr; + ULONG *pceltFetched); - *pceltFetched = 0; - - if (!m_FirstDone) - { - hr = m_UserLocal->Next(celt, rgelt, pceltFetched); - if (FAILED_UNEXPECTEDLY(hr)) - return hr; - if (hr == S_FALSE) - m_FirstDone = true; - else if (celt < 2) - return hr; - } - - DWORD offset = *pceltFetched; - if (*pceltFetched < celt) - { - rgelt += *pceltFetched; - celt = (celt - *pceltFetched); - *pceltFetched = 0; - - hr = m_AllUSers->Next(celt, rgelt, pceltFetched); - if (FAILED_UNEXPECTEDLY(hr)) - return hr; - - *pceltFetched += offset; - - return hr; - } - - return S_OK; - } - - virtual HRESULT STDMETHODCALLTYPE Skip( - ULONG celt) - { - UNIMPLEMENTED; - return E_NOTIMPL; - } - - virtual HRESULT STDMETHODCALLTYPE Reset( - ) - { - if (m_FirstDone) - m_AllUSers->Reset(); - return m_UserLocal->Reset(); - } - - virtual HRESULT STDMETHODCALLTYPE Clone( - IEnumIDList **ppenum) - { - UNIMPLEMENTED; - return E_NOTIMPL; - } + virtual HRESULT STDMETHODCALLTYPE Skip(ULONG celt); + virtual HRESULT STDMETHODCALLTYPE Reset(); + virtual HRESULT STDMETHODCALLTYPE Clone(IEnumIDList **ppenum); }; +CEnumMergedFolder::CEnumMergedFolder() : + m_UserLocalFolder(NULL), + m_AllUSersFolder(NULL), + m_UserLocal(NULL), + m_AllUSers(NULL), + m_HwndOwner(NULL), + m_Flags(0), + m_hDsaIndex(0) +{ + m_hDsa = DSA_Create(sizeof(LocalPidlInfo), 10); +} + +CEnumMergedFolder::~CEnumMergedFolder() +{ + DSA_DestroyCallback(m_hDsa, s_DsaDeleteCallback, this); +} + +int CEnumMergedFolder::DsaDeleteCallback(LocalPidlInfo * info) +{ + ILFree(info->pidl); + return 0; +} + +int CALLBACK CEnumMergedFolder::s_DsaDeleteCallback(void *pItem, void *pData) +{ + CEnumMergedFolder * mf = (CEnumMergedFolder*) pData; + LocalPidlInfo * item = (LocalPidlInfo*) pItem; + return mf->DsaDeleteCallback(item); +} + +HRESULT CEnumMergedFolder::SetSources(IShellFolder * userLocal, IShellFolder * allUSers) +{ + m_UserLocalFolder = userLocal; + m_AllUSersFolder = allUSers; + return S_OK; +} + +HRESULT CEnumMergedFolder::Begin(HWND hwndOwner, SHCONTF flags) +{ + HRESULT hr; + + if (m_HwndOwner == hwndOwner && m_Flags == flags) + { + return Reset(); + } + + TRACE("Search conditions changed, recreating list...\n"); + + hr = m_UserLocalFolder->EnumObjects(hwndOwner, flags, &m_UserLocal); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + hr = m_AllUSersFolder->EnumObjects(hwndOwner, flags, &m_AllUSers); + if (FAILED_UNEXPECTEDLY(hr)) + { + m_UserLocal = NULL; + return hr; + } + + DSA_EnumCallback(m_hDsa, s_DsaDeleteCallback, this); + DSA_DeleteAllItems(m_hDsa); + m_hDsaCount = 0; + + HRESULT hr1 = S_OK; + HRESULT hr2 = S_OK; + LPITEMIDLIST pidl1 = NULL; + LPITEMIDLIST pidl2 = NULL; + int order = 0; + do + { + if (order <= 0) + { + if (hr1 == S_OK) + { + hr1 = m_UserLocal->Next(1, &pidl1, NULL); + if (FAILED_UNEXPECTEDLY(hr1)) + return hr1; + } + else + { + pidl1 = NULL; + } + } + if (order >= 0) + { + if (hr2 == S_OK) + { + hr2 = m_AllUSers->Next(1, &pidl2, NULL); + if (FAILED_UNEXPECTEDLY(hr2)) + return hr2; + } + else + { + pidl2 = NULL; + } + } + + if (hr1 == S_OK && hr2 == S_OK) + { + LPWSTR name1; + LPWSTR name2; + STRRET str1 = { STRRET_WSTR }; + STRRET str2 = { STRRET_WSTR }; + hr = m_UserLocalFolder->GetDisplayNameOf(pidl1, SHGDN_FORPARSING | SHGDN_INFOLDER, &str1); + if (FAILED(hr)) + return hr; + hr = m_AllUSersFolder->GetDisplayNameOf(pidl2, SHGDN_FORPARSING | SHGDN_INFOLDER, &str2); + if (FAILED(hr)) + return hr; + StrRetToStrW(&str1, pidl1, &name1); + StrRetToStrW(&str2, pidl2, &name2); + order = StrCmpW(name1, name2); + CoTaskMemFree(name1); + CoTaskMemFree(name2); + } + else if (hr1 == S_OK) + { + order = -1; + } + else if (hr2 == S_OK) + { + order = 1; + } + else + { + break; + } + + LocalPidlInfo info; + if (order < 0) + { + info.side = -1; + info.pidl = ILClone(pidl1); + ILFree(pidl1); + } + else if (order > 0) + { + info.side = 1; + info.pidl = ILClone(pidl2); + ILFree(pidl2); + } + else // if (order == 0) + { + info.side = 0; + info.pidl = ILClone(pidl1); + ILFree(pidl1); + ILFree(pidl2); + } + + TRACE("Inserting item %d with side %d and pidl { cb=%d }\n", m_hDsaCount, info.side, info.pidl->mkid.cb); + int idx = DSA_InsertItem(m_hDsa, DSA_APPEND, &info); + TRACE("New index: %d\n", idx); + + m_hDsaCount++; + + } while (hr1 == S_OK || hr2 == S_OK); + + m_HwndOwner = hwndOwner; + m_Flags = flags; + + return Reset(); +} + +HRESULT CEnumMergedFolder::FindPidlInList(LPCITEMIDLIST pcidl, LocalPidlInfo * pinfo) +{ + HRESULT hr; + + TRACE("Searching for pidl { cb=%d } in a list of %d items\n", pcidl->mkid.cb, m_hDsaCount); + + for (int i = 0; i < (int)m_hDsaCount; i++) + { + LocalPidlInfo * tinfo = (LocalPidlInfo *)DSA_GetItemPtr(m_hDsa, i); + if (!tinfo) + return E_FAIL; + + LocalPidlInfo info = *tinfo; + + TRACE("Comparing with item at %d with side %d and pidl { cb=%d }\n", i, info.side, info.pidl->mkid.cb); + + if (info.side <= 0) + { +#if 0 + LPWSTR name1; + LPWSTR name2; + STRRET str1 = { STRRET_WSTR, 0 }; + STRRET str2 = { STRRET_WSTR, 0 }; + hr = m_UserLocalFolder->GetDisplayNameOf(info->pidl, SHGDN_FORPARSING | SHGDN_INFOLDER, &str1); + if (FAILED(hr)) + return hr; + hr = m_UserLocalFolder->GetDisplayNameOf(pcidl, SHGDN_FORPARSING | SHGDN_INFOLDER, &str2); + if (FAILED(hr)) + return hr; + StrRetToStrW(&str1, info->pidl, &name1); + StrRetToStrW(&str2, pcidl, &name2); + int order = StrCmpW(name1, name2); + CoTaskMemFree(name1); + CoTaskMemFree(name2); + + if (order == 0) + { + *pinfo = *info; + return S_OK; + } +#else + // FIXME: This works in windows. + hr = m_UserLocalFolder->CompareIDs(0, info.pidl, pcidl); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + if (hr == S_OK) + { + *pinfo = info; + return S_OK; + } + else + { + TRACE("Comparison returned %d\n", (int) (short) (hr & 0xFFFF)); + } +#endif + } + else + { +#if 0 + LPWSTR name1; + LPWSTR name2; + STRRET str1 = { STRRET_WSTR, 0 }; + STRRET str2 = { STRRET_WSTR, 0 }; + hr = m_AllUSersFolder->GetDisplayNameOf(info->pidl, SHGDN_FORPARSING | SHGDN_INFOLDER, &str1); + if (FAILED(hr)) + return hr; + hr = m_AllUSersFolder->GetDisplayNameOf(pcidl, SHGDN_FORPARSING | SHGDN_INFOLDER, &str2); + if (FAILED(hr)) + return hr; + StrRetToStrW(&str1, info->pidl, &name1); + StrRetToStrW(&str2, pcidl, &name2); + int order = StrCmpW(name1, name2); + CoTaskMemFree(name1); + CoTaskMemFree(name2); + + if (order == 0) + { + *pinfo = *info; + return S_OK; + } +#else + // FIXME: This works in windows. + hr = m_AllUSersFolder->CompareIDs(0, info.pidl, pcidl); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + if (hr == S_OK) + { + *pinfo = info; + return S_OK; + } + else + { + TRACE("Comparison returned %d\n", (int) (short) (hr & 0xFFFF)); + } +#endif + } + } + + TRACE("Pidl not found\n"); + return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); +} + +HRESULT STDMETHODCALLTYPE CEnumMergedFolder::Next( + ULONG celt, + LPITEMIDLIST *rgelt, + ULONG *pceltFetched) +{ + if (pceltFetched) *pceltFetched = 0; + + if (m_hDsaIndex == m_hDsaCount) + return S_FALSE; + + for (int i = 0; i < (int)celt;) + { + LocalPidlInfo * tinfo = (LocalPidlInfo *) DSA_GetItemPtr(m_hDsa, m_hDsaIndex); + if (!tinfo) + return E_FAIL; + + LocalPidlInfo info = *tinfo; + + TRACE("Returning next item at %d with side %d and pidl { cb=%d }\n", m_hDsaIndex, info.side, info.pidl->mkid.cb); + + // FIXME: ILClone shouldn't be needed here! This should be causing leaks + if (rgelt) rgelt[i] = ILClone(info.pidl); + + m_hDsaIndex++; + i++; + + if (m_hDsaIndex == m_hDsaCount) + { + if (pceltFetched) *pceltFetched = i; + return (i == (int)celt) ? S_OK : S_FALSE; + } + } + + if (pceltFetched) *pceltFetched = celt; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CEnumMergedFolder::Skip(ULONG celt) +{ + return Next(celt, NULL, NULL); +} + +HRESULT STDMETHODCALLTYPE CEnumMergedFolder::Reset() +{ + m_hDsaIndex = 0; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE CEnumMergedFolder::Clone( + IEnumIDList **ppenum) +{ + UNIMPLEMENTED; + return E_NOTIMPL; +} + +//----------------------------------------------------------------------------- +// CMergedFolder + extern "C" HRESULT WINAPI CMergedFolder_Constructor(IShellFolder* userLocal, IShellFolder* allUsers, REFIID riid, LPVOID *ppv) { @@ -148,7 +425,8 @@ HRESULT CMergedFolder::_SetSources(IShellFolder* userLocal, IShellFolder* allUse { m_UserLocal = userLocal; m_AllUSers = allUsers; - return S_OK; + m_EnumSource = new CComObject(); + return m_EnumSource->SetSources(m_UserLocal, m_AllUSers); } // IShellFolder @@ -169,9 +447,10 @@ HRESULT STDMETHODCALLTYPE CMergedFolder::EnumObjects( SHCONTF grfFlags, IEnumIDList **ppenumIDList) { - CEnumMergedFolder * merged = new CComObject(); - *ppenumIDList = merged; - return merged->Begin(hwndOwner, grfFlags, m_UserLocal, m_AllUSers); + HRESULT hr = m_EnumSource->QueryInterface(IID_PPV_ARG(IEnumIDList, ppenumIDList)); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + return m_EnumSource->Begin(hwndOwner, grfFlags); } HRESULT STDMETHODCALLTYPE CMergedFolder::BindToObject( @@ -180,15 +459,33 @@ HRESULT STDMETHODCALLTYPE CMergedFolder::BindToObject( REFIID riid, void **ppvOut) { + LocalPidlInfo info; HRESULT hr; - hr = m_UserLocal->BindToObject(pidl, pbcReserved, riid, ppvOut); - if (SUCCEEDED(hr)) + hr = m_EnumSource->FindPidlInList(pidl, &info); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + + if (info.side < 0) + return m_UserLocal->BindToObject(pidl, pbcReserved, riid, ppvOut); + if (info.side > 0) + return m_AllUSers->BindToObject(pidl, pbcReserved, riid, ppvOut); + + if (riid != IID_IShellFolder) + return E_FAIL; + + CComPtr fld1; + CComPtr fld2; + + hr = m_UserLocal->BindToObject(pidl, pbcReserved, IID_PPV_ARG(IShellFolder, &fld1)); + if (FAILED_UNEXPECTEDLY(hr)) return hr; - hr = m_AllUSers->BindToObject(pidl, pbcReserved, riid, ppvOut); + hr = m_AllUSers->BindToObject(pidl, pbcReserved, IID_PPV_ARG(IShellFolder, &fld2)); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; - return hr; + return CMergedFolder_Constructor(fld1, fld2, riid, ppvOut); } HRESULT STDMETHODCALLTYPE CMergedFolder::BindToStorage( @@ -206,8 +503,7 @@ HRESULT STDMETHODCALLTYPE CMergedFolder::CompareIDs( LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { - UNIMPLEMENTED; - return E_NOTIMPL; + return m_UserLocal->CompareIDs(lParam, pidl1, pidl2); } HRESULT STDMETHODCALLTYPE CMergedFolder::CreateViewObject( @@ -224,16 +520,29 @@ HRESULT STDMETHODCALLTYPE CMergedFolder::GetAttributesOf( LPCITEMIDLIST *apidl, SFGAOF *rgfInOut) { + LocalPidlInfo info; HRESULT hr; - hr = m_UserLocal->GetAttributesOf(cidl, apidl, rgfInOut); - if (SUCCEEDED(hr)) - return hr; + for (int i = 0; i < (int)cidl; i++) + { + LPCITEMIDLIST pidl = apidl[i]; - *rgfInOut = 0; - hr = m_AllUSers->GetAttributesOf(cidl, apidl, rgfInOut); + hr = m_EnumSource->FindPidlInList(pidl, &info); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; - return hr; + SFGAOF * pinOut1 = rgfInOut ? rgfInOut + i : NULL; + + if (info.side <= 0) + hr = m_UserLocal->GetAttributesOf(1, &pidl, pinOut1); + else + hr = m_AllUSers->GetAttributesOf(1, &pidl, pinOut1); + + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + } + + return S_OK; } HRESULT STDMETHODCALLTYPE CMergedFolder::GetUIObjectOf( @@ -244,15 +553,30 @@ HRESULT STDMETHODCALLTYPE CMergedFolder::GetUIObjectOf( UINT *prgfInOut, void **ppvOut) { + LocalPidlInfo info; HRESULT hr; - hr = m_UserLocal->GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, ppvOut); - if (SUCCEEDED(hr)) - return hr; + for (int i = 0; i < (int)cidl; i++) + { + LPCITEMIDLIST pidl = apidl[i]; - hr = m_AllUSers->GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, ppvOut); + hr = m_EnumSource->FindPidlInList(pidl, &info); + if (FAILED_UNEXPECTEDLY(hr)) + return hr; - return hr; + UINT * pinOut1 = prgfInOut ? prgfInOut+i : NULL; + void** ppvOut1 = ppvOut ? ppvOut + i : NULL; + + if (info.side <= 0) + hr = m_UserLocal->GetUIObjectOf(hwndOwner, 1, &pidl, riid, pinOut1, ppvOut1); + else + hr = m_AllUSers->GetUIObjectOf(hwndOwner, 1, &pidl, riid, pinOut1, ppvOut1); + + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + } + + return S_OK; } HRESULT STDMETHODCALLTYPE CMergedFolder::GetDisplayNameOf( @@ -260,15 +584,21 @@ HRESULT STDMETHODCALLTYPE CMergedFolder::GetDisplayNameOf( SHGDNF uFlags, STRRET *lpName) { + LocalPidlInfo info; HRESULT hr; - hr = m_UserLocal->GetDisplayNameOf(pidl, uFlags, lpName); - if (SUCCEEDED(hr)) + hr = m_EnumSource->FindPidlInList(pidl, &info); + if (FAILED_UNEXPECTEDLY(hr)) return hr; - hr = m_AllUSers->GetDisplayNameOf(pidl, uFlags, lpName); + if (info.side <= 0) + hr = m_UserLocal->GetDisplayNameOf(pidl, uFlags, lpName); + else + hr = m_AllUSers->GetDisplayNameOf(pidl, uFlags, lpName); - return hr; + if (FAILED_UNEXPECTEDLY(hr)) + return hr; + return S_OK; } HRESULT STDMETHODCALLTYPE CMergedFolder::SetNameOf( diff --git a/base/shell/rshell/CMergedFolder.h b/base/shell/rshell/CMergedFolder.h index d757d53bff8..96ddb616a49 100644 --- a/base/shell/rshell/CMergedFolder.h +++ b/base/shell/rshell/CMergedFolder.h @@ -19,6 +19,8 @@ */ #pragma once +class CEnumMergedFolder; + class CMergedFolder : public CComObjectRootEx, public IShellFolder2 @@ -26,6 +28,7 @@ class CMergedFolder : private: CComPtr m_UserLocal; CComPtr m_AllUSers; + CComPtr m_EnumSource; public: CMergedFolder() {} diff --git a/include/psdk/commctrl.h b/include/psdk/commctrl.h index ebb7dda7644..8b76f1d6416 100644 --- a/include/psdk/commctrl.h +++ b/include/psdk/commctrl.h @@ -4716,8 +4716,18 @@ typedef struct { _In_ PFNDSAENUMCALLBACK pfnCB, _In_opt_ void *pData); + WINCOMMCTRLAPI + VOID + WINAPI + DSA_EnumCallback( + _In_ HDSA hdsa, + _In_ PFNDSAENUMCALLBACK enumProc, + _In_opt_ LPVOID lParam); + WINCOMMCTRLAPI PVOID WINAPI DSA_GetItemPtr(_In_ HDSA hdsa, int i); + WINCOMMCTRLAPI BOOL WINAPI DSA_DeleteAllItems(_In_ HDSA hdsa); + WINCOMMCTRLAPI int WINAPI