2 * PROJECT: ReactOS Search Shell Extension
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Search results folder
5 * COPYRIGHT: Copyright 2019 Brock Mammen
8 #include "CFindFolder.h"
11 WINE_DEFAULT_DEBUG_CHANNEL(shellfind
);
13 // FIXME: Remove this declaration after the function has been fully implemented
16 SHOpenFolderAndSelectItems(LPITEMIDLIST pidlFolder
,
18 PCUITEMID_CHILD_ARRAY apidl
,
21 struct FolderViewColumns
29 static FolderViewColumns g_ColumnDefs
[] =
31 {L
"Name", SHCOLSTATE_TYPE_STR
| SHCOLSTATE_ONBYDEFAULT
, LVCFMT_LEFT
, 30},
32 {L
"In Folder", SHCOLSTATE_TYPE_STR
| SHCOLSTATE_ONBYDEFAULT
, LVCFMT_LEFT
, 30},
33 {L
"Relevance", SHCOLSTATE_TYPE_STR
, LVCFMT_LEFT
, 0}
36 CFindFolder::CFindFolder() :
41 static LPITEMIDLIST
_ILCreate(LPCWSTR lpszPath
)
43 CComHeapPtr
<ITEMIDLIST
> lpFSPidl(ILCreateFromPathW(lpszPath
));
44 if (!(LPITEMIDLIST
)lpFSPidl
)
46 ERR("Failed to create pidl from path\n");
49 LPITEMIDLIST lpLastFSPidl
= ILFindLastID(lpFSPidl
);
51 int pathLen
= (wcslen(lpszPath
) + 1) * sizeof(WCHAR
);
52 int cbData
= sizeof(WORD
) + pathLen
+ lpLastFSPidl
->mkid
.cb
;
53 LPITEMIDLIST pidl
= (LPITEMIDLIST
) SHAlloc(cbData
+ sizeof(WORD
));
57 LPBYTE p
= (LPBYTE
) pidl
;
58 *((WORD
*) p
) = cbData
;
61 memcpy(p
, lpszPath
, pathLen
);
64 memcpy(p
, lpLastFSPidl
, lpLastFSPidl
->mkid
.cb
);
65 p
+= lpLastFSPidl
->mkid
.cb
;
72 static LPCWSTR
_ILGetPath(LPCITEMIDLIST pidl
)
74 if (!pidl
|| !pidl
->mkid
.cb
)
76 return (LPCWSTR
) pidl
->mkid
.abID
;
79 static LPCITEMIDLIST
_ILGetFSPidl(LPCITEMIDLIST pidl
)
81 if (!pidl
|| !pidl
->mkid
.cb
)
83 return (LPCITEMIDLIST
) ((LPBYTE
) pidl
->mkid
.abID
84 + ((wcslen((LPCWSTR
) pidl
->mkid
.abID
) + 1) * sizeof(WCHAR
)));
95 CComPtr
<CFindFolder
> pFindFolder
;
98 template<typename TChar
, typename TString
, int (&StrNCmp
)(const TChar
*, const TChar
*, size_t)>
99 static const TChar
* StrStrN(const TChar
*lpFirst
, const TString
&lpSrch
, UINT cchMax
)
101 if (!lpFirst
|| lpSrch
.IsEmpty() || !cchMax
)
104 for (UINT i
= cchMax
; i
> 0 && *lpFirst
; i
--, lpFirst
++)
106 if (!StrNCmp(lpFirst
, lpSrch
, lpSrch
.GetLength()))
107 return (const TChar
*)lpFirst
;
113 template<typename TChar
, typename TString
, int (&StrNCmp
)(const TChar
*, const TChar
*, size_t)>
114 static UINT
StrStrNCount(const TChar
*lpFirst
, const TString
&lpSrch
, UINT cchMax
)
116 const TChar
*lpSearchEnd
= lpFirst
+ cchMax
;
118 while (lpFirst
< lpSearchEnd
&& (lpFirst
= StrStrN
<TChar
, TString
, StrNCmp
>(lpFirst
, lpSrch
, cchMax
)))
121 lpFirst
+= lpSrch
.GetLength();
122 cchMax
= lpSearchEnd
- lpFirst
;
127 static UINT
StrStrCountNIA(const CHAR
*lpFirst
, const CStringA
&lpSrch
, UINT cchMax
)
129 return StrStrNCount
<CHAR
, CStringA
, _strnicmp
>(lpFirst
, lpSrch
, cchMax
);
132 static UINT
StrStrCountNIW(const WCHAR
*lpFirst
, const CStringW
&lpSrch
, UINT cchMax
)
134 return StrStrNCount
<WCHAR
, CStringW
, _wcsnicmp
>(lpFirst
, lpSrch
, cchMax
);
137 static UINT
SearchFile(LPCWSTR lpFilePath
, _SearchData
*pSearchData
)
139 HANDLE hFile
= CreateFileW(lpFilePath
, GENERIC_READ
, 0, NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_READONLY
, NULL
);
140 if (hFile
== INVALID_HANDLE_VALUE
)
143 DWORD size
= GetFileSize(hFile
, NULL
);
144 HANDLE hFileMap
= CreateFileMappingW(hFile
, NULL
, PAGE_READONLY
, 0, 0, NULL
);
146 if (hFileMap
== INVALID_HANDLE_VALUE
)
149 LPBYTE lpFileContent
= (LPBYTE
) MapViewOfFile(hFileMap
, FILE_MAP_READ
, 0, 0, 0);
150 CloseHandle(hFileMap
);
155 // Check for UTF-16 BOM
156 if (size
>= 2 && lpFileContent
[0] == 0xFF && lpFileContent
[1] == 0xFE)
158 uMatches
= StrStrCountNIW((LPCWSTR
) lpFileContent
, pSearchData
->szQueryW
, size
/ sizeof(WCHAR
));
162 uMatches
= StrStrCountNIA((LPCSTR
) lpFileContent
, pSearchData
->szQueryA
, size
/ sizeof(CHAR
));
165 UnmapViewOfFile(lpFileContent
);
170 static UINT
RecursiveFind(LPCWSTR lpPath
, _SearchData
*pSearchData
)
172 if (WaitForSingleObject(pSearchData
->hStopEvent
, 0) != WAIT_TIMEOUT
)
175 WCHAR szPath
[MAX_PATH
];
176 WIN32_FIND_DATAW FindData
;
178 BOOL bMoreFiles
= TRUE
;
179 UINT uTotalFound
= 0;
181 PathCombineW(szPath
, lpPath
, L
"*.*");
183 for (hFindFile
= FindFirstFileW(szPath
, &FindData
);
184 bMoreFiles
&& hFindFile
!= INVALID_HANDLE_VALUE
;
185 bMoreFiles
= FindNextFileW(hFindFile
, &FindData
))
187 if (!wcscmp(FindData
.cFileName
, L
".") || !wcscmp(FindData
.cFileName
, L
".."))
190 PathCombineW(szPath
, lpPath
, FindData
.cFileName
);
192 if (FindData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
195 status
.Format(IDS_SEARCH_FOLDER
, FindData
.cFileName
);
196 PostMessageW(pSearchData
->hwnd
, WM_SEARCH_UPDATE_STATUS
, 0, (LPARAM
) StrDupW(status
.GetBuffer()));
198 uTotalFound
+= RecursiveFind(szPath
, pSearchData
);
200 else if ((pSearchData
->szFileName
.IsEmpty() || PathMatchSpecW(FindData
.cFileName
, pSearchData
->szFileName
))
201 && (pSearchData
->szQueryA
.IsEmpty() || SearchFile(szPath
, pSearchData
)))
204 PostMessageW(pSearchData
->hwnd
, WM_SEARCH_ADD_RESULT
, 0, (LPARAM
) StrDupW(szPath
));
208 if (hFindFile
!= INVALID_HANDLE_VALUE
)
209 FindClose(hFindFile
);
214 DWORD WINAPI
CFindFolder::SearchThreadProc(LPVOID lpParameter
)
216 _SearchData
*data
= static_cast<_SearchData
*>(lpParameter
);
218 data
->pFindFolder
->NotifyConnections(DISPID_SEARCHSTART
);
220 UINT uTotalFound
= RecursiveFind(data
->szPath
, data
);
222 data
->pFindFolder
->NotifyConnections(DISPID_SEARCHCOMPLETE
);
225 status
.Format(IDS_SEARCH_FILES_FOUND
, uTotalFound
);
226 ::PostMessageW(data
->hwnd
, WM_SEARCH_UPDATE_STATUS
, 0, (LPARAM
) StrDupW(status
.GetBuffer()));
233 void CFindFolder::NotifyConnections(DISPID id
)
235 DISPPARAMS dispatchParams
= {0};
236 CComDynamicUnkArray
&subscribers
=
237 IConnectionPointImpl
<CFindFolder
, &DIID_DSearchCommandEvents
>::m_vec
;
238 for (IUnknown
** pSubscriber
= subscribers
.begin(); pSubscriber
< subscribers
.end(); pSubscriber
++)
243 CComPtr
<IDispatch
> pDispatch
;
244 HRESULT hResult
= (*pSubscriber
)->QueryInterface(IID_PPV_ARG(IDispatch
, &pDispatch
));
245 if (!FAILED_UNEXPECTEDLY(hResult
))
246 pDispatch
->Invoke(id
, GUID_NULL
, 0, DISPATCH_METHOD
, &dispatchParams
, NULL
, NULL
, NULL
);
250 LRESULT
CFindFolder::StartSearch(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
255 // Clear all previous search results
257 m_shellFolderView
->RemoveObject(NULL
, &uItemIndex
);
259 _SearchData
* pSearchData
= new _SearchData();
260 pSearchData
->pFindFolder
= this;
261 pSearchData
->hwnd
= m_hWnd
;
263 SearchStart
*pSearchParams
= (SearchStart
*) lParam
;
264 pSearchData
->szPath
= pSearchParams
->szPath
;
265 pSearchData
->szFileName
= pSearchParams
->szFileName
;
266 pSearchData
->szQueryA
= pSearchParams
->szQuery
;
267 pSearchData
->szQueryW
= pSearchParams
->szQuery
;
268 SHFree(pSearchParams
);
271 SetEvent(m_hStopEvent
);
272 pSearchData
->hStopEvent
= m_hStopEvent
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
274 if (!SHCreateThread(SearchThreadProc
, pSearchData
, NULL
, NULL
))
277 return HRESULT_FROM_WIN32(GetLastError());
283 LRESULT
CFindFolder::StopSearch(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
287 SetEvent(m_hStopEvent
);
293 LRESULT
CFindFolder::AddResult(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
298 CComHeapPtr
<WCHAR
> lpPath((LPWSTR
) lParam
);
300 CComHeapPtr
<ITEMIDLIST
> lpSearchPidl(_ILCreate(lpPath
));
304 m_shellFolderView
->AddObject(lpSearchPidl
, &uItemIndex
);
310 LRESULT
CFindFolder::UpdateStatus(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
312 CComHeapPtr
<WCHAR
> status((LPWSTR
) lParam
);
315 m_shellBrowser
->SetStatusTextSB(status
);
321 // *** IShellFolder2 methods ***
322 STDMETHODIMP
CFindFolder::GetDefaultSearchGUID(GUID
*pguid
)
328 STDMETHODIMP
CFindFolder::EnumSearches(IEnumExtraSearch
**ppenum
)
334 STDMETHODIMP
CFindFolder::GetDefaultColumn(DWORD
, ULONG
*pSort
, ULONG
*pDisplay
)
343 STDMETHODIMP
CFindFolder::GetDefaultColumnState(UINT iColumn
, DWORD
*pcsFlags
)
347 if (iColumn
>= _countof(g_ColumnDefs
))
348 return m_pisfInner
->GetDefaultColumnState(iColumn
- _countof(g_ColumnDefs
) + 1, pcsFlags
);
349 *pcsFlags
= g_ColumnDefs
[iColumn
].dwDefaultState
;
353 STDMETHODIMP
CFindFolder::GetDetailsEx(PCUITEMID_CHILD pidl
, const SHCOLUMNID
*pscid
, VARIANT
*pv
)
355 return m_pisfInner
->GetDetailsEx(pidl
, pscid
, pv
);
358 STDMETHODIMP
CFindFolder::GetDetailsOf(PCUITEMID_CHILD pidl
, UINT iColumn
, SHELLDETAILS
*pDetails
)
360 if (iColumn
>= _countof(g_ColumnDefs
))
361 return m_pisfInner
->GetDetailsOf(_ILGetFSPidl(pidl
), iColumn
- _countof(g_ColumnDefs
) + 1, pDetails
);
363 pDetails
->cxChar
= g_ColumnDefs
[iColumn
].cxChar
;
364 pDetails
->fmt
= g_ColumnDefs
[iColumn
].fmt
;
367 return SHSetStrRet(&pDetails
->str
, g_ColumnDefs
[iColumn
].wzColumnName
);
371 WCHAR path
[MAX_PATH
];
372 wcscpy(path
, _ILGetPath(pidl
));
373 PathRemoveFileSpecW(path
);
374 return SHSetStrRet(&pDetails
->str
, path
);
377 return GetDisplayNameOf(pidl
, SHGDN_NORMAL
, &pDetails
->str
);
380 STDMETHODIMP
CFindFolder::MapColumnToSCID(UINT iColumn
, SHCOLUMNID
*pscid
)
386 // *** IShellFolder methods ***
387 STDMETHODIMP
CFindFolder::ParseDisplayName(HWND hwndOwner
, LPBC pbc
, LPOLESTR lpszDisplayName
, ULONG
*pchEaten
,
388 PIDLIST_RELATIVE
*ppidl
, ULONG
*pdwAttributes
)
394 STDMETHODIMP
CFindFolder::EnumObjects(HWND hwndOwner
, DWORD dwFlags
, LPENUMIDLIST
*ppEnumIDList
)
396 *ppEnumIDList
= NULL
;
400 STDMETHODIMP
CFindFolder::BindToObject(PCUIDLIST_RELATIVE pidl
, LPBC pbcReserved
, REFIID riid
, LPVOID
*ppvOut
)
406 STDMETHODIMP
CFindFolder::BindToStorage(PCUIDLIST_RELATIVE pidl
, LPBC pbcReserved
, REFIID riid
, LPVOID
*ppvOut
)
412 STDMETHODIMP
CFindFolder::CompareIDs(LPARAM lParam
, PCUIDLIST_RELATIVE pidl1
, PCUIDLIST_RELATIVE pidl2
)
414 return m_pisfInner
->CompareIDs(lParam
, _ILGetFSPidl(pidl1
), _ILGetFSPidl(pidl2
));
417 STDMETHODIMP
CFindFolder::CreateViewObject(HWND hwndOwner
, REFIID riid
, LPVOID
*ppvOut
)
419 if (riid
== IID_IShellView
)
421 SFV_CREATE sfvparams
= {};
422 sfvparams
.cbSize
= sizeof(SFV_CREATE
);
423 sfvparams
.pshf
= this;
424 sfvparams
.psfvcb
= this;
425 HRESULT hr
= SHCreateShellFolderView(&sfvparams
, (IShellView
**) ppvOut
);
426 if (FAILED_UNEXPECTEDLY(hr
))
431 return ((IShellView
* ) * ppvOut
)->QueryInterface(IID_PPV_ARG(IShellFolderView
, &m_shellFolderView
));
433 return E_NOINTERFACE
;
436 STDMETHODIMP
CFindFolder::GetAttributesOf(UINT cidl
, PCUITEMID_CHILD_ARRAY apidl
, DWORD
*rgfInOut
)
438 CComHeapPtr
<PCITEMID_CHILD
> aFSPidl
;
439 aFSPidl
.Allocate(cidl
);
440 for (UINT i
= 0; i
< cidl
; i
++)
442 aFSPidl
[i
] = _ILGetFSPidl(apidl
[i
]);
445 return m_pisfInner
->GetAttributesOf(cidl
, aFSPidl
, rgfInOut
);
448 STDMETHODIMP
CFindFolder::GetUIObjectOf(HWND hwndOwner
, UINT cidl
, PCUITEMID_CHILD_ARRAY apidl
, REFIID riid
,
449 UINT
*prgfInOut
, LPVOID
*ppvOut
)
451 if (riid
== IID_IDataObject
&& cidl
== 1)
453 WCHAR path
[MAX_PATH
];
454 wcscpy(path
, (LPCWSTR
) apidl
[0]->mkid
.abID
);
455 PathRemoveFileSpecW(path
);
456 CComHeapPtr
<ITEMIDLIST
> rootPidl(ILCreateFromPathW(path
));
458 return E_OUTOFMEMORY
;
459 PCITEMID_CHILD aFSPidl
[1];
460 aFSPidl
[0] = _ILGetFSPidl(apidl
[0]);
461 return IDataObject_Constructor(hwndOwner
, rootPidl
, aFSPidl
, cidl
, (IDataObject
**) ppvOut
);
466 return m_pisfInner
->GetUIObjectOf(hwndOwner
, cidl
, apidl
, riid
, prgfInOut
, ppvOut
);
469 PCITEMID_CHILD
*aFSPidl
= new PCITEMID_CHILD
[cidl
];
470 for (UINT i
= 0; i
< cidl
; i
++)
472 aFSPidl
[i
] = _ILGetFSPidl(apidl
[i
]);
475 if (riid
== IID_IContextMenu
)
479 AddFSClassKeysToArray(aFSPidl
[0], hKeys
, &cKeys
);
482 dcm
.hwnd
= hwndOwner
;
484 dcm
.pidlFolder
= m_pidl
;
490 dcm
.punkAssociationInfo
= NULL
;
491 HRESULT hr
= SHCreateDefaultContextMenu(&dcm
, riid
, ppvOut
);
497 HRESULT hr
= m_pisfInner
->GetUIObjectOf(hwndOwner
, cidl
, aFSPidl
, riid
, prgfInOut
, ppvOut
);
503 STDMETHODIMP
CFindFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl
, DWORD dwFlags
, LPSTRRET pName
)
505 return m_pisfInner
->GetDisplayNameOf(_ILGetFSPidl(pidl
), dwFlags
, pName
);
508 STDMETHODIMP
CFindFolder::SetNameOf(HWND hwndOwner
, PCUITEMID_CHILD pidl
, LPCOLESTR lpName
, DWORD dwFlags
,
509 PITEMID_CHILD
*pPidlOut
)
515 //// *** IShellFolderViewCB method ***
516 STDMETHODIMP
CFindFolder::MessageSFVCB(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
520 case SFVM_DEFVIEWMODE
:
522 FOLDERVIEWMODE
*pViewMode
= (FOLDERVIEWMODE
*) lParam
;
523 *pViewMode
= FVM_DETAILS
;
526 case SFVM_WINDOWCREATED
:
528 SubclassWindow((HWND
) wParam
);
530 CComPtr
<IServiceProvider
> pServiceProvider
;
531 HRESULT hr
= m_shellFolderView
->QueryInterface(IID_PPV_ARG(IServiceProvider
, &pServiceProvider
));
532 if (FAILED_UNEXPECTEDLY(hr
))
536 return pServiceProvider
->QueryService(SID_SShellBrowser
, IID_PPV_ARG(IShellBrowser
, &m_shellBrowser
));
542 //// *** IContextMenuCB method ***
543 STDMETHODIMP
CFindFolder::CallBack(IShellFolder
*psf
, HWND hwndOwner
, IDataObject
*pdtobj
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
547 case DFM_MERGECONTEXTMENU
:
549 QCMINFO
*pqcminfo
= (QCMINFO
*) lParam
;
550 _InsertMenuItemW(pqcminfo
->hmenu
, pqcminfo
->indexMenu
++, TRUE
, pqcminfo
->idCmdFirst
++, MFT_SEPARATOR
, NULL
, 0);
551 _InsertMenuItemW(pqcminfo
->hmenu
, pqcminfo
->indexMenu
++, TRUE
, pqcminfo
->idCmdFirst
++, MFT_STRING
, L
"Open Containing Folder", MFS_ENABLED
);
552 _InsertMenuItemW(pqcminfo
->hmenu
, pqcminfo
->indexMenu
++, TRUE
, pqcminfo
->idCmdFirst
++, MFT_SEPARATOR
, NULL
, 0);
555 case DFM_INVOKECOMMAND
:
556 case DFM_INVOKECOMMANDEX
:
561 PCUITEMID_CHILD
*apidl
;
563 HRESULT hr
= m_shellFolderView
->GetSelectedObjects(&apidl
, &cidl
);
564 if (FAILED_UNEXPECTEDLY(hr
))
567 for (UINT i
= 0; i
< cidl
; i
++)
569 CComHeapPtr
<ITEMIDLIST
> pidl
;
571 hr
= SHILCreateFromPathW((LPCWSTR
) apidl
[i
]->mkid
.abID
, &pidl
, &attrs
);
574 SHOpenFolderAndSelectItems(NULL
, 1, &pidl
, 0);
580 case DFM_GETDEFSTATICID
:
583 return Shell_DefaultContextMenuCallBack(m_pisfInner
, pdtobj
);
586 //// *** IPersistFolder2 methods ***
587 STDMETHODIMP
CFindFolder::GetCurFolder(LPITEMIDLIST
*pidl
)
589 *pidl
= ILClone(m_pidl
);
593 // *** IPersistFolder methods ***
594 STDMETHODIMP
CFindFolder::Initialize(LPCITEMIDLIST pidl
)
596 m_pidl
= ILClone(pidl
);
598 return E_OUTOFMEMORY
;
600 return SHELL32_CoCreateInitSF(m_pidl
,
603 &CLSID_ShellFSFolder
,
604 IID_PPV_ARG(IShellFolder2
, &m_pisfInner
));
607 // *** IPersist methods ***
608 STDMETHODIMP
CFindFolder::GetClassID(CLSID
*pClassId
)
610 if (pClassId
== NULL
)
612 *pClassId
= CLSID_FindFolder
;