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()));
227 ::SendMessageW(data
->hwnd
, WM_SEARCH_STOP
, 0, 0);
229 CloseHandle(data
->hStopEvent
);
235 void CFindFolder::NotifyConnections(DISPID id
)
237 DISPPARAMS dispatchParams
= {0};
238 CComDynamicUnkArray
&subscribers
=
239 IConnectionPointImpl
<CFindFolder
, &DIID_DSearchCommandEvents
>::m_vec
;
240 for (IUnknown
** pSubscriber
= subscribers
.begin(); pSubscriber
< subscribers
.end(); pSubscriber
++)
245 CComPtr
<IDispatch
> pDispatch
;
246 HRESULT hResult
= (*pSubscriber
)->QueryInterface(IID_PPV_ARG(IDispatch
, &pDispatch
));
247 if (!FAILED_UNEXPECTEDLY(hResult
))
248 pDispatch
->Invoke(id
, GUID_NULL
, 0, DISPATCH_METHOD
, &dispatchParams
, NULL
, NULL
, NULL
);
252 LRESULT
CFindFolder::StartSearch(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
257 // Clear all previous search results
259 m_shellFolderView
->RemoveObject(NULL
, &uItemIndex
);
261 _SearchData
* pSearchData
= new _SearchData();
262 pSearchData
->pFindFolder
= this;
263 pSearchData
->hwnd
= m_hWnd
;
265 SearchStart
*pSearchParams
= (SearchStart
*) lParam
;
266 pSearchData
->szPath
= pSearchParams
->szPath
;
267 pSearchData
->szFileName
= pSearchParams
->szFileName
;
268 pSearchData
->szQueryA
= pSearchParams
->szQuery
;
269 pSearchData
->szQueryW
= pSearchParams
->szQuery
;
270 SHFree(pSearchParams
);
273 SetEvent(m_hStopEvent
);
274 pSearchData
->hStopEvent
= m_hStopEvent
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
276 if (!SHCreateThread(SearchThreadProc
, pSearchData
, NULL
, NULL
))
279 return HRESULT_FROM_WIN32(GetLastError());
285 LRESULT
CFindFolder::StopSearch(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
289 SetEvent(m_hStopEvent
);
295 LRESULT
CFindFolder::AddResult(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
300 CComHeapPtr
<WCHAR
> lpPath((LPWSTR
) lParam
);
302 CComHeapPtr
<ITEMIDLIST
> lpSearchPidl(_ILCreate(lpPath
));
306 m_shellFolderView
->AddObject(lpSearchPidl
, &uItemIndex
);
312 LRESULT
CFindFolder::UpdateStatus(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
&bHandled
)
314 CComHeapPtr
<WCHAR
> status((LPWSTR
) lParam
);
317 m_shellBrowser
->SetStatusTextSB(status
);
323 // *** IShellFolder2 methods ***
324 STDMETHODIMP
CFindFolder::GetDefaultSearchGUID(GUID
*pguid
)
330 STDMETHODIMP
CFindFolder::EnumSearches(IEnumExtraSearch
**ppenum
)
336 STDMETHODIMP
CFindFolder::GetDefaultColumn(DWORD
, ULONG
*pSort
, ULONG
*pDisplay
)
345 STDMETHODIMP
CFindFolder::GetDefaultColumnState(UINT iColumn
, DWORD
*pcsFlags
)
349 if (iColumn
>= _countof(g_ColumnDefs
))
350 return m_pisfInner
->GetDefaultColumnState(iColumn
- _countof(g_ColumnDefs
) + 1, pcsFlags
);
351 *pcsFlags
= g_ColumnDefs
[iColumn
].dwDefaultState
;
355 STDMETHODIMP
CFindFolder::GetDetailsEx(PCUITEMID_CHILD pidl
, const SHCOLUMNID
*pscid
, VARIANT
*pv
)
357 return m_pisfInner
->GetDetailsEx(pidl
, pscid
, pv
);
360 STDMETHODIMP
CFindFolder::GetDetailsOf(PCUITEMID_CHILD pidl
, UINT iColumn
, SHELLDETAILS
*pDetails
)
362 if (iColumn
>= _countof(g_ColumnDefs
))
363 return m_pisfInner
->GetDetailsOf(_ILGetFSPidl(pidl
), iColumn
- _countof(g_ColumnDefs
) + 1, pDetails
);
365 pDetails
->cxChar
= g_ColumnDefs
[iColumn
].cxChar
;
366 pDetails
->fmt
= g_ColumnDefs
[iColumn
].fmt
;
369 return SHSetStrRet(&pDetails
->str
, g_ColumnDefs
[iColumn
].wzColumnName
);
373 WCHAR path
[MAX_PATH
];
374 wcscpy(path
, _ILGetPath(pidl
));
375 PathRemoveFileSpecW(path
);
376 return SHSetStrRet(&pDetails
->str
, path
);
379 return GetDisplayNameOf(pidl
, SHGDN_NORMAL
, &pDetails
->str
);
382 STDMETHODIMP
CFindFolder::MapColumnToSCID(UINT iColumn
, SHCOLUMNID
*pscid
)
388 // *** IShellFolder methods ***
389 STDMETHODIMP
CFindFolder::ParseDisplayName(HWND hwndOwner
, LPBC pbc
, LPOLESTR lpszDisplayName
, ULONG
*pchEaten
,
390 PIDLIST_RELATIVE
*ppidl
, ULONG
*pdwAttributes
)
396 STDMETHODIMP
CFindFolder::EnumObjects(HWND hwndOwner
, DWORD dwFlags
, LPENUMIDLIST
*ppEnumIDList
)
398 *ppEnumIDList
= NULL
;
402 STDMETHODIMP
CFindFolder::BindToObject(PCUIDLIST_RELATIVE pidl
, LPBC pbcReserved
, REFIID riid
, LPVOID
*ppvOut
)
408 STDMETHODIMP
CFindFolder::BindToStorage(PCUIDLIST_RELATIVE pidl
, LPBC pbcReserved
, REFIID riid
, LPVOID
*ppvOut
)
414 STDMETHODIMP
CFindFolder::CompareIDs(LPARAM lParam
, PCUIDLIST_RELATIVE pidl1
, PCUIDLIST_RELATIVE pidl2
)
416 return m_pisfInner
->CompareIDs(lParam
, _ILGetFSPidl(pidl1
), _ILGetFSPidl(pidl2
));
419 STDMETHODIMP
CFindFolder::CreateViewObject(HWND hwndOwner
, REFIID riid
, LPVOID
*ppvOut
)
421 if (riid
== IID_IShellView
)
423 SFV_CREATE sfvparams
= {};
424 sfvparams
.cbSize
= sizeof(SFV_CREATE
);
425 sfvparams
.pshf
= this;
426 sfvparams
.psfvcb
= this;
427 HRESULT hr
= SHCreateShellFolderView(&sfvparams
, (IShellView
**) ppvOut
);
428 if (FAILED_UNEXPECTEDLY(hr
))
433 return ((IShellView
* ) * ppvOut
)->QueryInterface(IID_PPV_ARG(IShellFolderView
, &m_shellFolderView
));
435 return E_NOINTERFACE
;
438 STDMETHODIMP
CFindFolder::GetAttributesOf(UINT cidl
, PCUITEMID_CHILD_ARRAY apidl
, DWORD
*rgfInOut
)
440 CComHeapPtr
<PCITEMID_CHILD
> aFSPidl
;
441 aFSPidl
.Allocate(cidl
);
442 for (UINT i
= 0; i
< cidl
; i
++)
444 aFSPidl
[i
] = _ILGetFSPidl(apidl
[i
]);
447 return m_pisfInner
->GetAttributesOf(cidl
, aFSPidl
, rgfInOut
);
450 STDMETHODIMP
CFindFolder::GetUIObjectOf(HWND hwndOwner
, UINT cidl
, PCUITEMID_CHILD_ARRAY apidl
, REFIID riid
,
451 UINT
*prgfInOut
, LPVOID
*ppvOut
)
453 if (riid
== IID_IDataObject
&& cidl
== 1)
455 WCHAR path
[MAX_PATH
];
456 wcscpy(path
, (LPCWSTR
) apidl
[0]->mkid
.abID
);
457 PathRemoveFileSpecW(path
);
458 CComHeapPtr
<ITEMIDLIST
> rootPidl(ILCreateFromPathW(path
));
460 return E_OUTOFMEMORY
;
461 PCITEMID_CHILD aFSPidl
[1];
462 aFSPidl
[0] = _ILGetFSPidl(apidl
[0]);
463 return IDataObject_Constructor(hwndOwner
, rootPidl
, aFSPidl
, cidl
, (IDataObject
**) ppvOut
);
468 return m_pisfInner
->GetUIObjectOf(hwndOwner
, cidl
, apidl
, riid
, prgfInOut
, ppvOut
);
471 PCITEMID_CHILD
*aFSPidl
= new PCITEMID_CHILD
[cidl
];
472 for (UINT i
= 0; i
< cidl
; i
++)
474 aFSPidl
[i
] = _ILGetFSPidl(apidl
[i
]);
477 if (riid
== IID_IContextMenu
)
481 AddFSClassKeysToArray(aFSPidl
[0], hKeys
, &cKeys
);
484 dcm
.hwnd
= hwndOwner
;
486 dcm
.pidlFolder
= m_pidl
;
492 dcm
.punkAssociationInfo
= NULL
;
493 HRESULT hr
= SHCreateDefaultContextMenu(&dcm
, riid
, ppvOut
);
499 HRESULT hr
= m_pisfInner
->GetUIObjectOf(hwndOwner
, cidl
, aFSPidl
, riid
, prgfInOut
, ppvOut
);
505 STDMETHODIMP
CFindFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl
, DWORD dwFlags
, LPSTRRET pName
)
507 return m_pisfInner
->GetDisplayNameOf(_ILGetFSPidl(pidl
), dwFlags
, pName
);
510 STDMETHODIMP
CFindFolder::SetNameOf(HWND hwndOwner
, PCUITEMID_CHILD pidl
, LPCOLESTR lpName
, DWORD dwFlags
,
511 PITEMID_CHILD
*pPidlOut
)
517 //// *** IShellFolderViewCB method ***
518 STDMETHODIMP
CFindFolder::MessageSFVCB(UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
522 case SFVM_DEFVIEWMODE
:
524 FOLDERVIEWMODE
*pViewMode
= (FOLDERVIEWMODE
*) lParam
;
525 *pViewMode
= FVM_DETAILS
;
528 case SFVM_WINDOWCREATED
:
530 SubclassWindow((HWND
) wParam
);
532 CComPtr
<IServiceProvider
> pServiceProvider
;
533 HRESULT hr
= m_shellFolderView
->QueryInterface(IID_PPV_ARG(IServiceProvider
, &pServiceProvider
));
534 if (FAILED_UNEXPECTEDLY(hr
))
538 return pServiceProvider
->QueryService(SID_SShellBrowser
, IID_PPV_ARG(IShellBrowser
, &m_shellBrowser
));
544 //// *** IContextMenuCB method ***
545 STDMETHODIMP
CFindFolder::CallBack(IShellFolder
*psf
, HWND hwndOwner
, IDataObject
*pdtobj
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
549 case DFM_MERGECONTEXTMENU
:
551 QCMINFO
*pqcminfo
= (QCMINFO
*) lParam
;
552 _InsertMenuItemW(pqcminfo
->hmenu
, pqcminfo
->indexMenu
++, TRUE
, pqcminfo
->idCmdFirst
++, MFT_SEPARATOR
, NULL
, 0);
553 _InsertMenuItemW(pqcminfo
->hmenu
, pqcminfo
->indexMenu
++, TRUE
, pqcminfo
->idCmdFirst
++, MFT_STRING
, L
"Open Containing Folder", MFS_ENABLED
);
554 _InsertMenuItemW(pqcminfo
->hmenu
, pqcminfo
->indexMenu
++, TRUE
, pqcminfo
->idCmdFirst
++, MFT_SEPARATOR
, NULL
, 0);
557 case DFM_INVOKECOMMAND
:
558 case DFM_INVOKECOMMANDEX
:
563 PCUITEMID_CHILD
*apidl
;
565 HRESULT hr
= m_shellFolderView
->GetSelectedObjects(&apidl
, &cidl
);
566 if (FAILED_UNEXPECTEDLY(hr
))
569 for (UINT i
= 0; i
< cidl
; i
++)
571 CComHeapPtr
<ITEMIDLIST
> pidl
;
573 hr
= SHILCreateFromPathW((LPCWSTR
) apidl
[i
]->mkid
.abID
, &pidl
, &attrs
);
576 SHOpenFolderAndSelectItems(NULL
, 1, &pidl
, 0);
582 case DFM_GETDEFSTATICID
:
585 return Shell_DefaultContextMenuCallBack(m_pisfInner
, pdtobj
);
588 //// *** IPersistFolder2 methods ***
589 STDMETHODIMP
CFindFolder::GetCurFolder(LPITEMIDLIST
*pidl
)
591 *pidl
= ILClone(m_pidl
);
595 // *** IPersistFolder methods ***
596 STDMETHODIMP
CFindFolder::Initialize(LPCITEMIDLIST pidl
)
598 m_pidl
= ILClone(pidl
);
600 return E_OUTOFMEMORY
;
602 return SHELL32_CoCreateInitSF(m_pidl
,
605 &CLSID_ShellFSFolder
,
606 IID_PPV_ARG(IShellFolder2
, &m_pisfInner
));
609 // *** IPersist methods ***
610 STDMETHODIMP
CFindFolder::GetClassID(CLSID
*pClassId
)
612 if (pClassId
== NULL
)
614 *pClassId
= CLSID_FindFolder
;