4 * Copyright 2014 David Quintana
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 #include "shellmenu.h"
22 #include <shlwapi_undoc.h>
24 #include "CMergedFolder.h"
26 WINE_DEFAULT_DEBUG_CHANNEL(CMergedFolder
);
31 IShellFolder
* parent
;
37 class CEnumMergedFolder
:
38 public CComObjectRootEx
<CComMultiThreadModelNoCS
>,
43 CComPtr
<IShellFolder
> m_UserLocalFolder
;
44 CComPtr
<IShellFolder
> m_AllUSersFolder
;
55 virtual ~CEnumMergedFolder();
57 DECLARE_NOT_AGGREGATABLE(CEnumMergedFolder
)
58 DECLARE_PROTECT_FINAL_CONSTRUCT()
60 BEGIN_COM_MAP(CEnumMergedFolder
)
61 COM_INTERFACE_ENTRY_IID(IID_IEnumIDList
, IEnumIDList
)
64 int DsaDeleteCallback(LocalPidlInfo
* info
);
66 static int CALLBACK
s_DsaDeleteCallback(void *pItem
, void *pData
);
68 HRESULT
SetSources(IShellFolder
* userLocal
, IShellFolder
* allUSers
);
69 HRESULT
Begin(HWND hwndOwner
, SHCONTF flags
);
70 HRESULT
FindPidlInList(HWND hwndOwner
, LPCITEMIDLIST pcidl
, LocalPidlInfo
* pinfo
);
71 HRESULT
FindByName(HWND hwndOwner
, LPCWSTR strParsingName
, LocalPidlInfo
* pinfo
);
73 virtual HRESULT STDMETHODCALLTYPE
Next(
78 virtual HRESULT STDMETHODCALLTYPE
Skip(ULONG celt
);
79 virtual HRESULT STDMETHODCALLTYPE
Reset();
80 virtual HRESULT STDMETHODCALLTYPE
Clone(IEnumIDList
**ppenum
);
83 CEnumMergedFolder::CEnumMergedFolder() :
84 m_UserLocalFolder(NULL
),
85 m_AllUSersFolder(NULL
),
94 CEnumMergedFolder::~CEnumMergedFolder()
96 DSA_DestroyCallback(m_hDsa
, s_DsaDeleteCallback
, this);
99 int CEnumMergedFolder::DsaDeleteCallback(LocalPidlInfo
* info
)
104 CoTaskMemFree((LPVOID
)info
->parseName
);
108 int CALLBACK
CEnumMergedFolder::s_DsaDeleteCallback(void *pItem
, void *pData
)
110 CEnumMergedFolder
* mf
= (CEnumMergedFolder
*) pData
;
111 LocalPidlInfo
* item
= (LocalPidlInfo
*) pItem
;
112 return mf
->DsaDeleteCallback(item
);
115 HRESULT
CEnumMergedFolder::SetSources(IShellFolder
* userLocal
, IShellFolder
* allUSers
)
117 m_UserLocalFolder
= userLocal
;
118 m_AllUSersFolder
= allUSers
;
120 TRACE("SetSources %p %p\n", userLocal
, allUSers
);
124 HRESULT
CEnumMergedFolder::Begin(HWND hwndOwner
, SHCONTF flags
)
127 LPITEMIDLIST pidl
= NULL
;
129 if (m_hDsa
&& m_HwndOwner
== hwndOwner
&& m_Flags
== flags
)
134 TRACE("Search conditions changed, recreating list...\n");
136 CComPtr
<IEnumIDList
> userLocal
;
137 CComPtr
<IEnumIDList
> allUsers
;
139 hr
= m_UserLocalFolder
->EnumObjects(hwndOwner
, flags
, &userLocal
);
140 if (FAILED_UNEXPECTEDLY(hr
))
142 hr
= m_AllUSersFolder
->EnumObjects(hwndOwner
, flags
, &allUsers
);
143 if (FAILED_UNEXPECTEDLY(hr
))
148 m_hDsa
= DSA_Create(sizeof(LocalPidlInfo
), 10);
151 DSA_EnumCallback(m_hDsa
, s_DsaDeleteCallback
, this);
152 DSA_DeleteAllItems(m_hDsa
);
155 // The sources are not ordered so load all of the items for the user folder first
156 TRACE("Loading Local entries...\n");
159 hr
= userLocal
->Next(1, &pidl
, NULL
);
160 if (FAILED_UNEXPECTEDLY(hr
))
167 STRRET str
= { STRRET_WSTR
};
168 hr
= m_UserLocalFolder
->GetDisplayNameOf(pidl
, SHGDN_FORPARSING
| SHGDN_INFOLDER
, &str
);
171 StrRetToStrW(&str
, pidl
, &name
);
173 LocalPidlInfo info
= {
183 TRACE("Inserting item %d with name %S\n", m_hDsaCount
, name
);
184 int idx
= DSA_InsertItem(m_hDsa
, DSA_APPEND
, &info
);
185 TRACE("New index: %d\n", idx
);
190 // Then load the items for the common folder
191 TRACE("Loading Common entries...\n");
194 hr
= allUsers
->Next(1, &pidl
, NULL
);
195 if (FAILED_UNEXPECTEDLY(hr
))
202 STRRET str
= { STRRET_WSTR
};
203 hr
= m_AllUSersFolder
->GetDisplayNameOf(pidl
, SHGDN_FORPARSING
| SHGDN_INFOLDER
, &str
);
206 StrRetToStrW(&str
, pidl
, &name
);
208 LocalPidlInfo info
= {
218 // Try to find an existing entry with the same name, and makr it as shared.
219 // FIXME: This is sub-optimal, a hash table would be a lot more efficient.
220 BOOL bShared
= FALSE
;
221 for (int i
= 0; i
< (int)m_hDsaCount
; i
++)
223 LocalPidlInfo
*pInfo
= (LocalPidlInfo
*) DSA_GetItemPtr(m_hDsa
, i
);
225 int order
= CompareStringW(GetThreadLocale(), NORM_IGNORECASE
,
226 pInfo
->parseName
, lstrlenW(pInfo
->parseName
),
227 info
.parseName
, lstrlenW(info
.parseName
));
229 if (order
== CSTR_EQUAL
)
231 TRACE("Item name already exists! Marking '%S' as shared ...\n", name
);
233 pInfo
->shared
= TRUE
;
234 pInfo
->pidl2
= info
.pidl
;
240 // If an entry was not found, add a new one for this item
243 TRACE("Inserting item %d with name %S\n", m_hDsaCount
, name
);
244 int idx
= DSA_InsertItem(m_hDsa
, DSA_APPEND
, &info
);
245 TRACE("New index: %d\n", idx
);
251 m_HwndOwner
= hwndOwner
;
257 HRESULT
CEnumMergedFolder::FindPidlInList(HWND hwndOwner
, LPCITEMIDLIST pcidl
, LocalPidlInfo
* pinfo
)
263 Begin(hwndOwner
, SHCONTF_FOLDERS
| SHCONTF_NONFOLDERS
);
266 TRACE("Searching for pidl { cb=%d } in a list of %d items\n", pcidl
->mkid
.cb
, m_hDsaCount
);
268 for (int i
= 0; i
< (int)m_hDsaCount
; i
++)
270 LocalPidlInfo
* pInfo
= (LocalPidlInfo
*) DSA_GetItemPtr(m_hDsa
, i
);
274 TRACE("Comparing with item at %d with parent %p and pidl { cb=%d }\n", i
, pInfo
->parent
, pInfo
->pidl
->mkid
.cb
);
276 hr
= pInfo
->parent
->CompareIDs(0, pInfo
->pidl
, pcidl
);
277 if (FAILED_UNEXPECTEDLY(hr
))
287 TRACE("Comparison returned %d\n", (int) (short) (hr
& 0xFFFF));
291 TRACE("Pidl not found\n");
292 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND
);
295 HRESULT
CEnumMergedFolder::FindByName(HWND hwndOwner
, LPCWSTR strParsingName
, LocalPidlInfo
* pinfo
)
299 Begin(hwndOwner
, SHCONTF_FOLDERS
| SHCONTF_NONFOLDERS
);
302 TRACE("Searching for '%S' in a list of %d items\n", strParsingName
, m_hDsaCount
);
304 for (int i
= 0; i
< (int) m_hDsaCount
; i
++)
306 LocalPidlInfo
* pInfo
= (LocalPidlInfo
*) DSA_GetItemPtr(m_hDsa
, i
);
310 int order
= CompareStringW(GetThreadLocale(), NORM_IGNORECASE
,
311 pInfo
->parseName
, lstrlenW(pInfo
->parseName
),
312 strParsingName
, lstrlenW(strParsingName
));
323 TRACE("Pidl not found\n");
324 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND
);
327 HRESULT STDMETHODCALLTYPE
CEnumMergedFolder::Next(
335 if (m_hDsaIndex
== m_hDsaCount
)
338 for (int i
= 0; i
< (int)celt
;)
340 LocalPidlInfo
* tinfo
= (LocalPidlInfo
*) DSA_GetItemPtr(m_hDsa
, m_hDsaIndex
);
344 LocalPidlInfo info
= *tinfo
;
346 TRACE("Returning next item at %d with parent %p and pidl { cb=%d }\n", m_hDsaIndex
, info
.parent
, info
.pidl
->mkid
.cb
);
348 // FIXME: ILClone shouldn't be needed here! This should be causing leaks
349 if (rgelt
) rgelt
[i
] = ILClone(info
.pidl
);
353 if (m_hDsaIndex
== m_hDsaCount
)
357 return (i
== (int)celt
) ? S_OK
: S_FALSE
;
361 if (pceltFetched
) *pceltFetched
= celt
;
365 HRESULT STDMETHODCALLTYPE
CEnumMergedFolder::Skip(ULONG celt
)
367 return Next(celt
, NULL
, NULL
);
370 HRESULT STDMETHODCALLTYPE
CEnumMergedFolder::Reset()
376 HRESULT STDMETHODCALLTYPE
CEnumMergedFolder::Clone(
377 IEnumIDList
**ppenum
)
383 //-----------------------------------------------------------------------------
387 HRESULT WINAPI
CMergedFolder_Constructor(REFIID riid
, LPVOID
*ppv
)
389 return ShellObjectCreator
<CMergedFolder
>(riid
, ppv
);
392 CMergedFolder::CMergedFolder() :
396 m_UserLocalPidl(NULL
),
397 m_AllUsersPidl(NULL
),
402 CMergedFolder::~CMergedFolder()
404 if (m_UserLocalPidl
) ILFree(m_UserLocalPidl
);
405 if (m_AllUsersPidl
) ILFree(m_AllUsersPidl
);
408 // IAugmentedShellFolder2
409 HRESULT STDMETHODCALLTYPE
CMergedFolder::AddNameSpace(LPGUID lpGuid
, IShellFolder
* psf
, LPCITEMIDLIST pcidl
, ULONG dwUnknown
)
413 TRACE("FIXME: No idea how to handle the GUID\n");
417 TRACE("AddNameSpace %p %p\n", m_UserLocal
.p
, m_AllUsers
.p
);
419 // FIXME: Use a DSA to store the list of merged namespaces, together with their related info (psf, pidl, ...)
420 // For now, assume only 2 will ever be used, and ignore all the other data.
424 m_UserLocalPidl
= ILClone(pcidl
);
432 m_AllUsersPidl
= ILClone(pcidl
);
434 m_EnumSource
= new CComObject
<CEnumMergedFolder
>();
435 return m_EnumSource
->SetSources(m_UserLocal
, m_AllUsers
);
438 HRESULT STDMETHODCALLTYPE
CMergedFolder::GetNameSpaceID(LPCITEMIDLIST pcidl
, LPGUID lpGuid
)
444 HRESULT STDMETHODCALLTYPE
CMergedFolder::QueryNameSpace(ULONG dwUnknown
, LPGUID lpGuid
, IShellFolder
** ppsf
)
450 HRESULT STDMETHODCALLTYPE
CMergedFolder::EnumNameSpace(ULONG dwUnknown
, PULONG lpUnknown
)
456 HRESULT STDMETHODCALLTYPE
CMergedFolder::UnWrapIDList(LPCITEMIDLIST pcidl
, LONG lUnknown
, IShellFolder
** ppsf
, LPITEMIDLIST
* ppidl1
, LPITEMIDLIST
*ppidl2
, LONG
* lpUnknown
)
463 HRESULT STDMETHODCALLTYPE
CMergedFolder::ParseDisplayName(
466 LPOLESTR lpszDisplayName
,
469 ULONG
*pdwAttributes
)
483 TRACE("ParseDisplayName name=%S\n", lpszDisplayName
);
485 hr
= m_EnumSource
->FindByName(hwndOwner
, lpszDisplayName
, &info
);
488 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND
);
491 *ppidl
= ILClone(info
.pidl
);
494 *pchEaten
= lstrlenW(info
.parseName
);
497 *pdwAttributes
= info
.parent
->GetAttributesOf(1, (LPCITEMIDLIST
*)ppidl
, pdwAttributes
);
502 HRESULT STDMETHODCALLTYPE
CMergedFolder::EnumObjects(
505 IEnumIDList
**ppenumIDList
)
507 TRACE("EnumObjects\n");
508 HRESULT hr
= m_EnumSource
->QueryInterface(IID_PPV_ARG(IEnumIDList
, ppenumIDList
));
509 if (FAILED_UNEXPECTEDLY(hr
))
511 return m_EnumSource
->Begin(hwndOwner
, grfFlags
);
514 HRESULT STDMETHODCALLTYPE
CMergedFolder::BindToObject(
523 hr
= m_EnumSource
->FindPidlInList(NULL
, pidl
, &info
);
524 if (FAILED_UNEXPECTEDLY(hr
))
527 TRACE("BindToObject shared = %d\n", info
.shared
);
530 return info
.parent
->BindToObject(info
.pidl
, pbcReserved
, riid
, ppvOut
);
532 if (riid
!= IID_IShellFolder
)
535 // Construct a child MergedFolder and return it
536 CComPtr
<IShellFolder
> fld1
;
537 CComPtr
<IShellFolder
> fld2
;
539 // In shared folders, the user one takes precedence over the common one, so it will always be on pidl1
540 hr
= m_UserLocal
->BindToObject(info
.pidl
, pbcReserved
, IID_PPV_ARG(IShellFolder
, &fld1
));
541 if (FAILED_UNEXPECTEDLY(hr
))
544 hr
= m_AllUsers
->BindToObject(info
.pidl2
, pbcReserved
, IID_PPV_ARG(IShellFolder
, &fld2
));
545 if (FAILED_UNEXPECTEDLY(hr
))
548 CComPtr
<IAugmentedShellFolder
> pasf
;
549 hr
= CMergedFolder_Constructor(IID_PPV_ARG(IAugmentedShellFolder
, &pasf
));
550 if (FAILED_UNEXPECTEDLY(hr
))
553 hr
= pasf
->QueryInterface(riid
, ppvOut
);
554 if (FAILED_UNEXPECTEDLY(hr
))
557 hr
= pasf
->AddNameSpace(NULL
, fld1
, info
.pidl
, 0xFF00);
558 if (FAILED_UNEXPECTEDLY(hr
))
561 hr
= pasf
->AddNameSpace(NULL
, fld2
, info
.pidl2
, 0x0000);
562 if (FAILED_UNEXPECTEDLY(hr
))
568 HRESULT STDMETHODCALLTYPE
CMergedFolder::BindToStorage(
578 HRESULT STDMETHODCALLTYPE
CMergedFolder::CompareIDs(
583 TRACE("CompareIDs\n");
584 return m_UserLocal
->CompareIDs(lParam
, pidl1
, pidl2
);
587 HRESULT STDMETHODCALLTYPE
CMergedFolder::CreateViewObject(
596 HRESULT STDMETHODCALLTYPE
CMergedFolder::GetAttributesOf(
598 PCUITEMID_CHILD_ARRAY apidl
,
604 TRACE("GetAttributesOf\n");
606 for (int i
= 0; i
< (int)cidl
; i
++)
608 LPCITEMIDLIST pidl
= apidl
[i
];
610 hr
= m_EnumSource
->FindPidlInList(NULL
, pidl
, &info
);
611 if (FAILED_UNEXPECTEDLY(hr
))
616 SFGAOF
* pinOut1
= rgfInOut
? rgfInOut
+ i
: NULL
;
618 hr
= info
.parent
->GetAttributesOf(1, &pidl
, pinOut1
);
620 if (FAILED_UNEXPECTEDLY(hr
))
627 HRESULT STDMETHODCALLTYPE
CMergedFolder::GetUIObjectOf(
630 PCUITEMID_CHILD_ARRAY apidl
,
638 TRACE("GetUIObjectOf\n");
640 for (int i
= 0; i
< (int)cidl
; i
++)
642 LPCITEMIDLIST pidl
= apidl
[i
];
644 TRACE("Processing GetUIObjectOf item %d of %u...\n", i
, cidl
);
646 hr
= m_EnumSource
->FindPidlInList(hwndOwner
, pidl
, &info
);
647 if (FAILED_UNEXPECTEDLY(hr
))
652 TRACE("FindPidlInList succeeded with parent %p and pidl { db=%d }\n", info
.parent
, info
.pidl
->mkid
.cb
);
654 UINT
* pinOut1
= prgfInOut
? prgfInOut
+i
: NULL
;
655 void** ppvOut1
= ppvOut
? ppvOut
+ i
: NULL
;
657 hr
= info
.parent
->GetUIObjectOf(hwndOwner
, 1, &pidl
, riid
, pinOut1
, ppvOut1
);
659 if (FAILED_UNEXPECTEDLY(hr
))
666 HRESULT STDMETHODCALLTYPE
CMergedFolder::GetDisplayNameOf(
674 TRACE("GetDisplayNameOf\n");
676 hr
= m_EnumSource
->FindPidlInList(NULL
, pidl
, &info
);
677 if (FAILED_UNEXPECTEDLY(hr
))
680 hr
= info
.parent
->GetDisplayNameOf(info
.pidl
, uFlags
, lpName
);
682 if (FAILED_UNEXPECTEDLY(hr
))
687 HRESULT STDMETHODCALLTYPE
CMergedFolder::SetNameOf(
692 LPITEMIDLIST
*ppidlOut
)
699 HRESULT STDMETHODCALLTYPE
CMergedFolder::GetClassID(CLSID
*lpClassId
)
706 HRESULT STDMETHODCALLTYPE
CMergedFolder::Initialize(LPCITEMIDLIST pidl
)
708 m_shellPidl
= ILClone(pidl
);
713 HRESULT STDMETHODCALLTYPE
CMergedFolder::GetCurFolder(LPITEMIDLIST
* pidl
)
721 HRESULT STDMETHODCALLTYPE
CMergedFolder::GetDefaultSearchGUID(
728 HRESULT STDMETHODCALLTYPE
CMergedFolder::EnumSearches(
729 IEnumExtraSearch
**ppenum
)
735 HRESULT STDMETHODCALLTYPE
CMergedFolder::GetDefaultColumn(
744 HRESULT STDMETHODCALLTYPE
CMergedFolder::GetDefaultColumnState(
746 SHCOLSTATEF
*pcsFlags
)
752 HRESULT STDMETHODCALLTYPE
CMergedFolder::GetDetailsEx(
754 const SHCOLUMNID
*pscid
,
760 TRACE("GetDetailsEx\n");
762 hr
= m_EnumSource
->FindPidlInList(NULL
, pidl
, &info
);
763 if (FAILED_UNEXPECTEDLY(hr
))
766 CComPtr
<IShellFolder2
> parent2
;
767 hr
= info
.parent
->QueryInterface(IID_PPV_ARG(IShellFolder2
, &parent2
));
768 if (FAILED_UNEXPECTEDLY(hr
))
771 hr
= parent2
->GetDetailsEx(info
.pidl
, pscid
, pv
);
772 if (FAILED_UNEXPECTEDLY(hr
))
777 HRESULT STDMETHODCALLTYPE
CMergedFolder::GetDetailsOf(
785 TRACE("GetDetailsOf\n");
787 hr
= m_EnumSource
->FindPidlInList(NULL
, pidl
, &info
);
788 if (FAILED_UNEXPECTEDLY(hr
))
791 CComPtr
<IShellFolder2
> parent2
;
792 hr
= info
.parent
->QueryInterface(IID_PPV_ARG(IShellFolder2
, &parent2
));
793 if (FAILED_UNEXPECTEDLY(hr
))
796 hr
= parent2
->GetDetailsOf(info
.pidl
, iColumn
, psd
);
798 if (FAILED_UNEXPECTEDLY(hr
))
803 HRESULT STDMETHODCALLTYPE
CMergedFolder::MapColumnToSCID(
811 // IAugmentedShellFolder3
812 HRESULT STDMETHODCALLTYPE
CMergedFolder::QueryNameSpace2(ULONG
, QUERYNAMESPACEINFO
*)