WindowsXP-SP1/shell/shell32/findfldr.cpp

5397 lines
170 KiB
C++

#include "shellprv.h"
#pragma hdrstop
#include <regstr.h>
#include <varutil.h>
#include "ids.h"
#include "findhlp.h"
#include "pidl.h"
#include "shitemid.h"
#include "defview.h"
#include "fstreex.h"
#include "views.h"
#include "cowsite.h"
#include "exdisp.h"
#include "shguidp.h"
#include "prop.h" // COLUMN_INFO
#include <limits.h>
#include "stgutil.h"
#include "netview.h"
#include "basefvcb.h"
#include "findfilter.h"
#include "defvphst.h"
#include "perhist.h"
#include "adoint.h"
#include "dspsprt.h"
#include "defcm.h"
#include "enumidlist.h"
#include "contextmenu.h"
// findband.cpp
STDAPI GetCIStatus(BOOL *pbRunning, BOOL *pbIndexed, BOOL *pbPermission);
STDAPI CatalogUptodate(LPCWSTR pszCatalog, LPCWSTR pszMachine);
class CFindFolder;
class CFindLVRange : public ILVRange
{
public:
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// ILVRange
STDMETHODIMP IncludeRange(LONG iBegin, LONG iEnd);
STDMETHODIMP ExcludeRange(LONG iBegin, LONG iEnd);
STDMETHODIMP InvertRange(LONG iBegin, LONG iEnd);
STDMETHODIMP InsertItem(LONG iItem);
STDMETHODIMP RemoveItem(LONG iItem);
STDMETHODIMP Clear();
STDMETHODIMP IsSelected(LONG iItem);
STDMETHODIMP IsEmpty();
STDMETHODIMP NextSelected(LONG iItem, LONG *piItem);
STDMETHODIMP NextUnSelected(LONG iItem, LONG *piItem);
STDMETHODIMP CountIncluded(LONG *pcIncluded);
// Helperfunctions...
void SetOwner(CFindFolder *pff, DWORD dwMask)
{
// don't AddRef -- we're a member variable of the object punk points to
_pff = pff;
_dwMask = dwMask;
_cIncluded = 0;
}
void IncrementIncludedCount() {_cIncluded++;}
void DecrementIncludedCount() {_cIncluded--;}
protected:
CFindFolder *_pff;
DWORD _dwMask; // The mask we use to know which "selection" bit we are tracking...
LONG _cIncluded; // count included... (selected)
};
class CFindFolder : public IFindFolder,
public IShellFolder2,
public IShellIcon,
public IShellIconOverlay,
public IPersistFolder2
{
public:
CFindFolder(IFindFilter *pff);
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IShellFolder
STDMETHODIMP ParseDisplayName(HWND hwnd, LPBC pbc, LPOLESTR pszDisplayName, ULONG * pchEaten, LPITEMIDLIST * ppidl, ULONG *pdwAttributes);
STDMETHODIMP EnumObjects(THIS_ HWND hwnd, DWORD grfFlags, IEnumIDList **ppenumIDList);
STDMETHODIMP BindToObject(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, void **ppv);
STDMETHODIMP BindToStorage(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, void **ppv)
{ return BindToObject(pidl, pbc, riid, ppv); }
STDMETHODIMP CompareIDs(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2);
STDMETHODIMP CreateViewObject(HWND hwnd, REFIID riid, void **ppv);
STDMETHODIMP GetAttributesOf(UINT cidl, LPCITEMIDLIST * apidl, ULONG * rgfInOut);
STDMETHODIMP GetUIObjectOf(HWND hwnd, UINT cidl, LPCITEMIDLIST * apidl, REFIID riid, UINT * prgfInOut, void **ppv);
STDMETHODIMP GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD uFlags, STRRET *pName);
STDMETHODIMP SetNameOf(HWND hwnd, LPCITEMIDLIST pidl, LPCOLESTR pszName, DWORD uFlags, LPITEMIDLIST *ppidlOut);
// IShellFolder2
STDMETHODIMP GetDefaultSearchGUID(GUID *pGuid);
STDMETHODIMP EnumSearches(LPENUMEXTRASEARCH *ppenum);
STDMETHODIMP GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay);
STDMETHODIMP GetDefaultColumnState(UINT iColumn, DWORD *pbState);
STDMETHODIMP GetDetailsEx(LPCITEMIDLIST pidl, const SHCOLUMNID *pscid, VARIANT *pv);
STDMETHODIMP GetDetailsOf(LPCITEMIDLIST pidl, UINT iColumn, SHELLDETAILS *pDetails);
STDMETHODIMP MapColumnToSCID(UINT iCol, SHCOLUMNID *pscid);
// IFindFolder
STDMETHODIMP GetFindFilter(IFindFilter **pfilter);
STDMETHODIMP AddPidl(int i, LPCITEMIDLIST pidl, DWORD dwItemID, FIND_ITEM **ppItem);
STDMETHODIMP GetItem(int iItem, FIND_ITEM **ppItem);
STDMETHODIMP DeleteItem(int iItem);
STDMETHODIMP GetItemCount(INT *pcItems);
STDMETHODIMP ValidateItems(IUnknown *punkView, int iItemFirst, int cItems, BOOL bSearchComplete);
STDMETHODIMP GetFolderListItemCount(INT *pcCount);
STDMETHODIMP GetFolderListItem(int iItem, FIND_FOLDER_ITEM **ppItem);
STDMETHODIMP GetFolder(int iFolder, REFIID riid, void **ppv);
STDMETHODIMP_(UINT) GetFolderIndex(LPCITEMIDLIST pidl);
STDMETHODIMP SetItemsChangedSinceSort();
STDMETHODIMP ClearItemList();
STDMETHODIMP ClearFolderList();
STDMETHODIMP AddFolder(LPITEMIDLIST pidl, BOOL fCheckForDup, int * piFolder);
STDMETHODIMP SetAsyncEnum(IFindEnum *pfenum);
STDMETHODIMP GetAsyncEnum(IFindEnum **ppfenum);
STDMETHODIMP CacheAllAsyncItems();
STDMETHODIMP_(BOOL) AllAsyncItemsCached();
STDMETHODIMP SetAsyncCount(DBCOUNTITEM cCount);
STDMETHODIMP ClearSaveStateList();
STDMETHODIMP GetStateFromSaveStateList(DWORD dwItemID, DWORD *pdwState);
STDMETHODIMP MapToSearchIDList(LPCITEMIDLIST pidl, BOOL fMapToReal, LPITEMIDLIST *ppidl);
STDMETHODIMP GetParentsPIDL(LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlParent);
STDMETHODIMP SetControllerNotifyObject(IFindControllerNotify *pfcn);
STDMETHODIMP GetControllerNotifyObject(IFindControllerNotify **ppfcn);
STDMETHODIMP RememberSelectedItems();
STDMETHODIMP SaveFolderList(IStream *pstm);
STDMETHODIMP RestoreFolderList(IStream *pstm);
STDMETHODIMP SaveItemList(IStream *pstm);
STDMETHODIMP RestoreItemList(IStream *pstm, int *pcItems);
STDMETHODIMP RestoreSearchFromSaveFile(LPCITEMIDLIST pidlSaveFile, IShellFolderView *psfv);
STDMETHODIMP_(BOOL) HandleUpdateDir(LPCITEMIDLIST pidl, BOOL fCheckSubDirs);
STDMETHODIMP_(void) HandleRMDir(IShellFolderView *psfv, LPCITEMIDLIST pidl);
STDMETHODIMP_(void) UpdateOrMaybeAddPidl(IShellFolderView *psfv, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlOld);
STDMETHODIMP_(void) Save(IFindFilter* pfilter, HWND hwnd, DFBSAVEINFO * pSaveInfo, IShellView* psv, IUnknown * pObject);
STDMETHODIMP OpenContainingFolder(IUnknown *punkSite);
STDMETHODIMP AddDataToIDList(LPCITEMIDLIST pidl, int iFolder, LPCITEMIDLIST pidlFolder, UINT uFlags, UINT uRow, DWORD dwItemID, ULONG ulRank, LPITEMIDLIST *ppidl);
// IShellIcon
STDMETHODIMP GetIconOf(LPCITEMIDLIST pidl, UINT flags, int *piIndex);
// IShellIconOverlay
STDMETHODIMP GetOverlayIndex(LPCITEMIDLIST pidl, int * pIndex);
STDMETHODIMP GetOverlayIconIndex(LPCITEMIDLIST pidl, int * pIndex);
// IPersist
STDMETHODIMP GetClassID(CLSID *pClassID);
// IPersistFolder
STDMETHODIMP Initialize(LPCITEMIDLIST pidl);
// IPersistFolder2
STDMETHODIMP GetCurFolder(LPITEMIDLIST *ppidl);
friend class CFindFolderViewCB;
friend class CFindLVRange;
HRESULT Init();
private:
~CFindFolder();
HRESULT _CompareFolderIndexes(int iFolder1, int iFolder2);
void _AddFIND_ITEMToSaveStateList(FIND_ITEM *pesfi);
LPITEMIDLIST _GetFullPidlForItem(LPCITEMIDLIST pidl);
HRESULT _UpdateItemList();
static int CALLBACK _SortForDataObj(void *p1, void *p2, LPARAM lparam);
static ULONG _Rank(LPCITEMIDLIST pidl);
static DWORD _ItemID(LPCITEMIDLIST pidl);
static PCHIDDENDOCFINDDATA _HiddenData(LPCITEMIDLIST pidl);
FIND_FOLDER_ITEM *_FolderListItem(int iFolder);
FIND_FOLDER_ITEM *_FolderListItem(LPCITEMIDLIST pidl);
static BOOL _MapColIndex(UINT *piColumn);
HRESULT _PrepareHIDA(UINT cidl, LPCITEMIDLIST *apidl, HDPA *phdpa);
HRESULT _GetDetailsFolder();
HRESULT _QueryItemShellFolder(LPCITEMIDLIST pidl, IShellFolder **ppsf);
HRESULT _QueryItemInterface(LPCITEMIDLIST pidl, REFIID riid, void **ppv);
HRESULT _Folder(FIND_FOLDER_ITEM *pffli, REFIID riid, void **ppv);
HRESULT _FolderFromItem(LPCITEMIDLIST pidl, REFIID riid, void **ppv);
HRESULT _GetFolderName(LPCITEMIDLIST pidl, DWORD gdnFlags, LPTSTR psz, UINT cch);
int _CompareByCachedSCID(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2);
int _CompareNames(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2, DWORD dwFlags);
HRESULT _GetItemDisplayName(LPCITEMIDLIST pidl, DWORD dwFlags, LPWSTR wzName, UINT cch);
HRESULT _GetFolderIDList(int iFolder, LPITEMIDLIST *ppidlParent);
LONG _cRef;
CFindLVRange _dflvrSel; // manage selection and cut range information...
CFindLVRange _dflvrCut;
HDPA _hdpaItems; // all of the items in the results
HDPA _hdpaPidf; // folders that the items came from
IFindFilter *_pfilter;
BOOL _fItemsChangedSinceSort;// Has the list changed since the last sort?
BOOL _fAllAsyncItemsCached; // Have we already cached all of the items?
BOOL _fSearchComplete;
BOOL _fInRefresh; // true if received prerefresh callback but postrefresh
LPITEMIDLIST _pidl;
IFindEnum *_pDFEnumAsync; // we have an async one, will need to call back for PIDLS and the like
DBCOUNTITEM _cAsyncItems; // Count of async items
int _iGetIDList; // index of the last IDlist we retrieved in callback.
HDSA _hdsaSaveStateForIDs; // Async - Remember which items are selected when we sort
int _cSaveStateSelected; // Number of items in selection list which are selected
IFindControllerNotify *_pfcn; // Sometimes need to let the "Controller" object know about things
CRITICAL_SECTION _csSearch;
int _iCompareFolderCache1, _iCompareFolderCache2, _iCompareFolderCacheResult;
IShellFolder2 *_psfDetails;
SHCOLUMNID _scidCached; // Cached SCID for sorting the columns
UINT _uiColumnCached; // The index to the cached column for _scidCached
#if DEBUG
DWORD _GUIThreadID; // Items can only be added to _hdpaItems on the UI thread.
#endif
};
class CFindFolderViewCB : public CBaseShellFolderViewCB
{
public:
STDMETHODIMP RealMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
HRESULT SetShellView(IShellView *psv);
CFindFolderViewCB(CFindFolder* pff);
// IServiceProvider override
STDMETHODIMP QueryService(REFGUID guidService, REFIID riid, void **ppv);
// Web View Tasks
static HRESULT _OnOpenContainingFolder(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc);
private:
~CFindFolderViewCB();
HRESULT OnMergeMenu(DWORD pv, QCMINFO*lP);
HRESULT OnReArrange(DWORD pv, LPARAM lp);
HRESULT OnGETWORKINGDIR(DWORD pv, UINT wP, LPTSTR lP);
HRESULT OnINVOKECOMMAND(DWORD pv, UINT wP);
HRESULT OnGETCOLSAVESTREAM(DWORD pv, WPARAM wP, IStream**lP);
HRESULT OnGETITEMIDLIST(DWORD pv, WPARAM iItem, LPITEMIDLIST *ppidl);
HRESULT OnGetItemIconIndex(DWORD pv, WPARAM iItem, int *piIcon);
HRESULT OnSetItemIconOverlay(DWORD pv, WPARAM iItem, int dwOverlayState);
HRESULT OnGetItemIconOverlay(DWORD pv, WPARAM iItem, int * pdwOverlayState);
HRESULT OnSETITEMIDLIST(DWORD pv, WPARAM iItem, LPITEMIDLIST pidl);
HRESULT OnGetIndexForItemIDList(DWORD pv, int * piItem, LPITEMIDLIST pidl);
HRESULT OnDeleteItem(DWORD pv, LPCITEMIDLIST pidl);
HRESULT OnODFindItem(DWORD pv, int * piItem, NM_FINDITEM* pnmfi);
HRESULT OnODCacheHint(DWORD pv, NMLVCACHEHINT* pnmlvc);
HRESULT OnSelChange(DWORD pv, UINT wPl, UINT wPh, SFVM_SELCHANGE_DATA*lP);
HRESULT OnGetEmptyText(DWORD pv, UINT cchTextMax, LPTSTR pszText);
HRESULT OnSetEmptyText(DWORD pv, UINT res, LPCTSTR pszText);
HRESULT OnHwndMain(DWORD pv, HWND hwndMain);
HRESULT OnIsOwnerData(DWORD pv, DWORD *pdwFlags);
HRESULT OnSetISFV(DWORD pv, IShellFolderView* pisfv);
HRESULT OnWindowCreated(DWORD pv, HWND hwnd);
HRESULT OnWindowDestroy(DWORD pv, HWND wP);
HRESULT OnGetODRangeObject(DWORD pv, WPARAM wWhich, ILVRange **pplvr);
HRESULT OnDEFVIEWMODE(DWORD pv, FOLDERVIEWMODE*lP);
HRESULT OnGetIPersistHistory(DWORD pv, IPersistHistory **ppph);
HRESULT OnRefresh(DWORD pv, BOOL fPreRefresh);
HRESULT OnGetHelpTopic(DWORD pv, SFVM_HELPTOPIC_DATA *shtd);
HRESULT OnSortListData(DWORD pv, PFNLVCOMPARE pfnCompare, LPARAM lParamSort);
HRESULT _ProfferService(BOOL bProffer);
HRESULT OnGetWebViewLayout(DWORD pv, UINT uViewMode, SFVM_WEBVIEW_LAYOUT_DATA* pData);
HRESULT OnGetWebViewContent(DWORD pv, SFVM_WEBVIEW_CONTENT_DATA* pData);
HRESULT OnGetWebViewTasks(DWORD pv, SFVM_WEBVIEW_TASKSECTION_DATA* pTasks);
HRESULT OnGetWebViewTheme(DWORD pv, SFVM_WEBVIEW_THEME_DATA* pTheme);
CFindFolder* _pff;
UINT _iColSort; // Which column are we sorting by
BOOL _fIgnoreSelChange; // Sort in process
UINT _iFocused; // Which item has the focus?
UINT _cSelected; // Count of items selected
IProfferService* _pps; // ProfferService site.
DWORD _dwServiceCookie; // ProfferService cookie.
TCHAR _szEmptyText[128]; // empty results list text.
friend class CFindLVRange;
};
// Class to save and restore find state on the travel log
class CFindPersistHistory : public CDefViewPersistHistory
{
public:
CFindPersistHistory();
// IPersist
STDMETHODIMP GetClassID(CLSID *pClassID);
// IPersistHistory
STDMETHODIMP LoadHistory(IStream *pStream, IBindCtx *pbc);
STDMETHODIMP SaveHistory(IStream *pStream);
protected:
IFindFolder* _GetDocFindFolder();
};
// {a5df1ea0-5702-11d1-83fa-00a0c90dc849}
const IID IID_IFindFolder = {0xa5df1ea0, 0x5702, 0x11d1, {0x83, 0xfa, 0x00, 0xa0, 0xc9, 0x0d, 0xc8, 0x49}};
// {5B8DCBF0-B096-11d1-9217-00403393B8F0}
const IID IID_IFindControllerNotify = {0x5b8dcbf0, 0xb096, 0x11d1, {0x92, 0x17, 0x0, 0x40, 0x33, 0x93, 0xb8, 0xf0}};
// Listview doesn't support more than 100000000 items, so if our
// client returns more than that, just stop after that point.
//
// Instead of 100000000, we use the next lower 64K boundary. This keeps
// us away from strange boundary cases (where a +1 might push us over the
// top), and it keeps the Alpha happy.
//
#define MAX_LISTVIEWITEMS (100000000 & ~0xFFFF)
#define SANE_ITEMCOUNT(c) ((int)min(c, MAX_LISTVIEWITEMS))
// Unicode descriptor:
//
// Structure written at the end of NT-generated find stream serves dual purpose.
// 1. Contains an NT-specific signature to identify stream as NT-generated.
// Appears as "NTFF" (NT Find File) in ASCII dump of file.
// 2. Contains an offset to the unicode-formatted criteria section.
//
// The following diagram shows the find criteria/results stream format including
// the NT-specific unicode criteria and descriptor.
//
// +-----------------------------------------+ --------------
// | DFHEADER structure | . .
// +-----------------------------------------+ . .
// | DF Criteria records (ANSI) | Win95 .
// +-----------------------------------------+ . .
// | DF Results (PIDL) [optional] | . NT
// +-----------------------------------------+ ------- .
// +----->| DF Criteria records (Unicode) [NT only] | .
// | +-----------------------------------------+ .
// | | Unicode Descriptor | .
// | +--------------------+ ----------------------------------
// | / \
// | / \
// | +-----------------+---------+
// +---| Offset (64-bit) | "NTFF" |
// +-----------------+---------+
//
//
const DWORD c_NTsignature = 0x4646544E; // "NTFF" in ASCII file dump.
typedef struct
{
ULARGE_INTEGER oUnicodeCriteria; // Offset of unicode find criteria.
DWORD NTsignature; // Signature of NT-generated find file.
} DFC_UNICODE_DESC;
enum
{
IDFCOL_NAME = 0, // default col from guy we are delegating to
IDFCOL_PATH,
IDFCOL_RANK,
};
const COLUMN_INFO c_find_cols[] =
{
DEFINE_COL_STR_ENTRY(SCID_NAME, 30, IDS_NAME_COL),
DEFINE_COL_STR_ENTRY(SCID_DIRECTORY, 30, IDS_PATH_COL),
DEFINE_COL_STR_MENU_ENTRY(SCID_RANK, 10, IDS_RANK_COL),
};
class CFindMenuBase : public IContextMenuCB , public CObjectWithSite
{
public:
CFindMenuBase();
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IContextMenuCB
STDMETHODIMP CallBack(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam) PURE;
protected:
virtual ~CFindMenuBase();
private:
LONG _cRef;
};
CFindMenuBase::CFindMenuBase() : _cRef(1)
{
}
CFindMenuBase::~CFindMenuBase()
{
}
STDMETHODIMP CFindMenuBase::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] = {
QITABENT(CFindMenuBase, IContextMenuCB), // IID_IContextMenuCB
QITABENT(CFindMenuBase, IObjectWithSite), // IID_IObjectWithSite
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
ULONG CFindMenuBase::AddRef()
{
return InterlockedIncrement(&_cRef);
}
ULONG CFindMenuBase::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
class CFindMenuCB : public CFindMenuBase
{
public:
// IContextMenuCB
STDMETHODIMP CallBack(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam);
};
STDMETHODIMP CFindMenuCB::CallBack(IShellFolder *psf, HWND hwnd,
IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HRESULT hr = S_OK;
switch (uMsg)
{
case DFM_MERGECONTEXTMENU:
if (!(wParam & CMF_VERBSONLY))
{
LPQCMINFO pqcm = (LPQCMINFO)lParam;
if (!pdtobj)
{
UINT idStart = pqcm->idCmdFirst;
UINT idBGMain = 0, idBGPopup = 0;
IFindFolder *pff;
if (SUCCEEDED(psf->QueryInterface(IID_PPV_ARG(IFindFolder, &pff))))
{
IFindFilter *pfilter;
if (SUCCEEDED(pff->GetFindFilter(&pfilter)))
{
pfilter->GetFolderMergeMenuIndex(&idBGMain, &idBGPopup);
CDefFolderMenu_MergeMenu(HINST_THISDLL, idBGMain, idBGPopup, pqcm);
DeleteMenu(pqcm->hmenu, idStart+SFVIDM_EDIT_PASTE, MF_BYCOMMAND);
DeleteMenu(pqcm->hmenu, idStart+SFVIDM_EDIT_PASTELINK, MF_BYCOMMAND);
DeleteMenu(pqcm->hmenu, idStart+SFVIDM_MISC_REFRESH, MF_BYCOMMAND);
IFindControllerNotify *pdcn;
if (S_OK == pff->GetControllerNotifyObject(&pdcn))
{
pdcn->Release();
}
else
{
DeleteMenu(pqcm->hmenu, idStart+FSIDM_SAVESEARCH, MF_BYCOMMAND);
}
pfilter->Release();
}
pff->Release();
}
}
}
break;
case DFM_INVOKECOMMAND:
{
// Check if this is from item context menu
if (pdtobj)
{
switch(wParam)
{
case DFM_CMD_LINK:
hr = SHCreateLinks(hwnd, NULL, pdtobj, SHCL_USETEMPLATE | SHCL_USEDESKTOP | SHCL_CONFIRM, NULL);
break;
case DFM_CMD_DELETE:
// convert to DFM_INVOKCOMMANDEX to get flags bits
hr = DeleteFilesInDataObject(hwnd, 0, pdtobj, 0);
break;
case DFM_CMD_PROPERTIES:
// We need to pass an empty IDlist to combine with.
hr = SHLaunchPropSheet(CFSFolder_PropertiesThread, pdtobj,
(LPCTSTR)lParam, NULL, (void *)&c_idlDesktop);
break;
default:
// Note: Fixing the working of Translator Key is not worth fixing. Hence Punted.
// if GetAttributesOf did not specify the SFGAO_ bit
// that corresponds to this default DFM_CMD, then we should
// fail it here instead of returning S_FALSE. Otherwise,
// accelerator keys (cut/copy/paste/etc) will get here, and
// defcm tries to do the command with mixed results.
// if GetAttributesOf did not specify SFGAO_CANLINK
// or SFGAO_CANDELETE or SFGAO_HASPROPERTIES, then the above
// implementations of these DFM_CMD commands are wrong...
// Let the defaults happen for this object
hr = S_FALSE;
break;
}
}
else
{
switch (wParam)
{
case FSIDM_SAVESEARCH:
{
IFindFolder *pff;
if (SUCCEEDED(psf->QueryInterface(IID_PPV_ARG(IFindFolder, &pff))))
{
IFindControllerNotify *pdcn;
if (S_OK == pff->GetControllerNotifyObject(&pdcn))
{
pdcn->SaveSearch();
pdcn->Release();
}
pff->Release();
}
}
break;
default:
hr = S_FALSE; // one of view menu items, use the default code.
break;
}
}
}
break;
case DFM_GETHELPTEXT: // ansi version
case DFM_GETHELPTEXTW:
{
UINT id = LOWORD(wParam) + IDS_MH_FSIDM_FIRST;
if (uMsg == DFM_GETHELPTEXTW)
LoadStringW(HINST_THISDLL, id, (LPWSTR)lParam, HIWORD(wParam));
else
LoadStringA(HINST_THISDLL, id, (LPSTR)lParam, HIWORD(wParam));
}
break;
default:
hr = E_NOTIMPL;
break;
}
return hr;
}
class CFindFolderContextMenuItemCB : public CFindMenuBase
{
public:
// IContextMenuCB
STDMETHODIMP CallBack(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam);
private:
CFindFolderContextMenuItemCB(IFindFolder* pff);
~CFindFolderContextMenuItemCB();
friend HRESULT CFindItem_Create(HWND hwnd, IFindFolder *pff, IContextMenu **ppcm);
STDMETHODIMP _GetVerb(UINT_PTR idCmd, LPSTR pszName, UINT cchMax, BOOL bUnicode);
IFindFolder *_pff;
};
CFindFolderContextMenuItemCB::CFindFolderContextMenuItemCB(IFindFolder* pff) : _pff(pff)
{
_pff->AddRef();
}
CFindFolderContextMenuItemCB::~CFindFolderContextMenuItemCB()
{
_pff->Release();
}
STDMETHODIMP CFindFolderContextMenuItemCB::_GetVerb(UINT_PTR idCmd, LPSTR pszName, UINT cchMax, BOOL bUnicode)
{
HRESULT hr;
if (idCmd == FSIDM_OPENCONTAININGFOLDER)
{
if (bUnicode)
StrCpyNW((LPWSTR)pszName, L"OpenContainingFolder", cchMax);
else
StrCpyNA((LPSTR)pszName, "OpenContainingFolder", cchMax);
hr = S_OK;
}
else
{
hr = E_NOTIMPL;
}
return hr;
}
STDMETHODIMP CFindFolderContextMenuItemCB::CallBack(IShellFolder *psf, HWND hwnd,
IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HRESULT hr = S_OK;
switch (uMsg)
{
case DFM_MERGECONTEXTMENU:
if (!(wParam & CMF_VERBSONLY))
{
LPQCMINFO pqcm = (LPQCMINFO)lParam;
CDefFolderMenu_MergeMenu(HINST_THISDLL, POPUP_DOCFIND_ITEM_MERGE, 0, pqcm);
}
break;
case DFM_INVOKECOMMAND:
switch (wParam)
{
case FSIDM_OPENCONTAININGFOLDER:
_pff->OpenContainingFolder(_punkSite);
break;
default:
hr = E_FAIL; // not our command
break;
}
break;
case DFM_GETHELPTEXT:
case DFM_GETHELPTEXTW:
// probably need to implement these...
case DFM_GETVERBA:
case DFM_GETVERBW:
hr = _GetVerb((UINT_PTR)(LOWORD(wParam)), (LPSTR)lParam, (UINT)(HIWORD(wParam)), uMsg == DFM_GETVERBW);
break;
default:
hr = E_NOTIMPL;
break;
}
return hr;
}
STDAPI CFindItem_Create(HWND hwnd, IFindFolder* pff, IContextMenu **ppcm)
{
*ppcm = NULL;
HRESULT hr;
// We want a quick IContextMenu implementation -- an empty defcm looks easiest
IContextMenuCB* pcmcb = new CFindFolderContextMenuItemCB(pff);
if (pcmcb)
{
hr = CDefFolderMenu_CreateEx(NULL, hwnd, 0, NULL, NULL, pcmcb, NULL, NULL, ppcm);
pcmcb->Release();
}
else
hr = E_OUTOFMEMORY;
return hr;
}
void cdecl DocFind_SetStatusText(HWND hwndStatus, int iField, UINT ids,...)
{
TCHAR sz2[MAX_PATH+32]; // leave slop for message + max path name
va_list ArgList;
if (hwndStatus)
{
va_start(ArgList, ids);
LPTSTR psz = _ConstructMessageString(HINST_THISDLL, MAKEINTRESOURCE(ids), &ArgList);
if (psz)
{
StrCpyN(sz2, psz, ARRAYSIZE(sz2));
LocalFree(psz);
}
else
{
sz2[0] = 0;
}
va_end(ArgList);
if (iField < 0)
{
SendMessage(hwndStatus, SB_SETTEXT, SBT_NOBORDERS | 255, (LPARAM)(LPTSTR)sz2);
}
else
{
SendMessage(hwndStatus, SB_SETTEXT, iField, (LPARAM)(LPTSTR)sz2);
}
UpdateWindow(hwndStatus);
}
}
HRESULT CFindFolder::AddFolder(LPITEMIDLIST pidl, BOOL fCheckForDup, int *piFolder)
{
*piFolder = -1;
if (fCheckForDup)
{
EnterCriticalSection(&_csSearch);
for (int i = DPA_GetPtrCount(_hdpaPidf) - 1; i >= 0; i--)
{
FIND_FOLDER_ITEM *pffli = _FolderListItem(i);
if (pffli && ILIsEqual(&pffli->idl, pidl))
{
LeaveCriticalSection(&_csSearch);
*piFolder = i;
return S_OK;
}
}
LeaveCriticalSection(&_csSearch);
}
int cb = ILGetSize(pidl);
FIND_FOLDER_ITEM *pffli;;
HRESULT hr = SHLocalAlloc(sizeof(*pffli) - sizeof(pffli->idl) + cb, &pffli);
if (SUCCEEDED(hr))
{
// pddfli->psf = NULL;
// pffli->fUpdateDir = FALSE;
memcpy(&pffli->idl, pidl, cb);
EnterCriticalSection(&_csSearch);
// Now add this item to our DPA...
*piFolder = DPA_AppendPtr(_hdpaPidf, pffli);
LeaveCriticalSection(&_csSearch);
if (-1 != *piFolder)
{
// If this is a network ID list then register a path -> pidl mapping, therefore
// avoiding having to create simple ID lists, which don't work correctly when
// being compared against real ID lists.
if (IsIDListInNameSpace(pidl, &CLSID_NetworkPlaces))
{
TCHAR szPath[ MAX_PATH ];
SHGetPathFromIDList(pidl, szPath);
NPTRegisterNameToPidlTranslation(szPath, _ILNext(pidl)); // skip the My Net Places entry
}
}
else
{
LocalFree((HLOCAL)pffli);
hr = E_OUTOFMEMORY;
}
}
return hr;
}
typedef struct
{
DWORD dwState; // State of the item;
DWORD dwItemID; // Only used for Async support...
} FIND_ITEM_SAVE_STATE;
void CFindFolder::_AddFIND_ITEMToSaveStateList(FIND_ITEM *pesfi)
{
FIND_ITEM_SAVE_STATE essi;
essi.dwState = pesfi->dwState & CDFITEM_STATE_MASK;
essi.dwItemID = _ItemID(&pesfi->idl);
DSA_AppendItem(_hdsaSaveStateForIDs, (void *)&essi);
if (essi.dwState & LVIS_SELECTED)
_cSaveStateSelected++;
}
HRESULT CFindFolder::RememberSelectedItems()
{
EnterCriticalSection(&_csSearch);
// Currently has list of pidls...
for (int i = DPA_GetPtrCount(_hdpaItems); i-- > 0;)
{
// Pidl at start of structure...
FIND_ITEM *pesfi = (FIND_ITEM*)DPA_FastGetPtr(_hdpaItems, i);
if (pesfi)
{
if (pesfi->dwState & (LVIS_SELECTED|LVIS_FOCUSED))
_AddFIND_ITEMToSaveStateList(pesfi);
}
}
LeaveCriticalSection(&_csSearch);
return S_OK;
}
STDMETHODIMP CFindFolder::ClearItemList()
{
// Clear out any async enumerators we may have
SetAsyncEnum(NULL);
_cAsyncItems = 0; // clear out our count of items...
_pfilter->ReleaseQuery();
// Also tell the filter to release everything...
EnterCriticalSection(&_csSearch);
if (_hdpaItems)
{
// Currently has list of pidls...
for (int i = DPA_GetPtrCount(_hdpaItems) - 1; i >= 0; i--)
{
// Pidl at start of structure...
FIND_ITEM *pesfi = (FIND_ITEM*)DPA_FastGetPtr(_hdpaItems, i);
if (pesfi)
LocalFree((HLOCAL)pesfi);
}
_fSearchComplete = FALSE;
DPA_DeleteAllPtrs(_hdpaItems);
}
LeaveCriticalSection(&_csSearch);
return S_OK;
}
STDMETHODIMP CFindFolder::ClearFolderList()
{
EnterCriticalSection(&_csSearch);
if (_hdpaPidf)
{
for (int i = DPA_GetPtrCount(_hdpaPidf) - 1; i >= 0; i--)
{
FIND_FOLDER_ITEM *pffli = _FolderListItem(i);
if (pffli)
{
// Release the IShellFolder if we have one
if (pffli->psf)
pffli->psf->Release();
// And delete the item
LocalFree((HLOCAL)pffli);
}
}
DPA_DeleteAllPtrs(_hdpaPidf);
}
LeaveCriticalSection(&_csSearch);
return S_OK;
}
CFindFolder::CFindFolder(IFindFilter *pff) : _cRef(1), _iGetIDList(-1), _pfilter(pff), _iCompareFolderCache1(-1), _uiColumnCached(-1)
{
ASSERT(_pidl == NULL);
_pfilter->AddRef();
// initialize our LV selection objects...
_dflvrSel.SetOwner(this, LVIS_SELECTED);
_dflvrCut.SetOwner(this, LVIS_CUT);
InitializeCriticalSection(&_csSearch);
#if DEBUG
_GUIThreadID = GetCurrentThreadId();
#endif
}
CFindFolder::~CFindFolder()
{
ASSERT(_cRef==0);
// We will need to call our function to Free our items in our
// Folder list. We will use the same function that we use to
// clear it when we do a new search
ClearItemList();
ClearFolderList();
ClearSaveStateList();
EnterCriticalSection(&_csSearch);
DPA_Destroy(_hdpaPidf);
DPA_Destroy(_hdpaItems);
_hdpaPidf = NULL;
_hdpaItems = NULL;
LeaveCriticalSection(&_csSearch);
DSA_Destroy(_hdsaSaveStateForIDs);
_pfilter->Release();
if (_psfDetails)
_psfDetails->Release();
DeleteCriticalSection(&_csSearch);
}
HRESULT CFindFolder::Init()
{
// Create the heap for the folder lists.
_hdpaPidf = DPA_CreateEx(64, GetProcessHeap());
// Create the DPA and DSA for the item list.
_hdpaItems = DPA_CreateEx(64, GetProcessHeap());
_hdsaSaveStateForIDs = DSA_Create(sizeof(FIND_ITEM_SAVE_STATE), 16);
return _hdsaSaveStateForIDs && _hdpaItems && _hdpaPidf ? S_OK : E_OUTOFMEMORY;
}
STDMETHODIMP CFindFolder::AddDataToIDList(LPCITEMIDLIST pidl, int iFolder, LPCITEMIDLIST pidlFolder, UINT uFlags, UINT uRow, DWORD dwItemID, ULONG ulRank, LPITEMIDLIST *ppidl)
{
HRESULT hr;
LPITEMIDLIST pidlToFree;
if (pidlFolder)
{
pidlToFree = NULL;
hr = S_OK;
}
else
{
hr = _GetFolderIDList(iFolder, &pidlToFree);
pidlFolder = pidlToFree;
}
if (SUCCEEDED(hr))
{
HIDDENDOCFINDDATA *phfd;
int cb = ILGetSize(pidlFolder);
int cbTotal = sizeof(*phfd) - sizeof(phfd->idlParent) + cb;
hr = SHLocalAlloc(cbTotal, &phfd);
if (SUCCEEDED(hr))
{
phfd->hid.cb = (WORD)cbTotal;
phfd->hid.wVersion = 0;
phfd->hid.id = IDLHID_DOCFINDDATA;
phfd->iFolder = (WORD)iFolder; // index to the folder DPA
phfd->wFlags = (WORD)uFlags;
phfd->uRow = uRow; // Which row in the CI;
phfd->dwItemID = dwItemID; // Only used for Async support...
phfd->ulRank = ulRank; // The rank returned by CI...
memcpy(&phfd->idlParent, pidlFolder, cb);
hr = ILCloneWithHiddenID(pidl, &phfd->hid, ppidl);
LocalFree(phfd);
}
ILFree(pidlToFree);
}
return hr;
}
HRESULT CreateFindWithFilter(IFindFilter *pff, REFIID riid, void **ppv)
{
*ppv = NULL;
HRESULT hr = E_OUTOFMEMORY;
CFindFolder *pfindfldr = new CFindFolder(pff);
if (pfindfldr)
{
hr = pfindfldr->Init();
if (SUCCEEDED(hr))
hr = pfindfldr->QueryInterface(riid, ppv);
pfindfldr->Release();
}
return hr;
}
STDAPI CDocFindFolder_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
{
*ppv = NULL;
IFindFilter *pff;
HRESULT hr = CreateNameSpaceFindFilter(&pff);
if (SUCCEEDED(hr))
{
hr = CreateFindWithFilter(pff, riid, ppv);
pff->Release();
}
return hr;
}
STDAPI CComputerFindFolder_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
{
*ppv = NULL;
IFindFilter *pff;
HRESULT hr = CreateDefaultComputerFindFilter(&pff);
if (pff)
{
hr = CreateFindWithFilter(pff, riid, ppv);
pff->Release();
}
return hr;
}
HRESULT CFindFolder::MapToSearchIDList(LPCITEMIDLIST pidl, BOOL fMapToReal, LPITEMIDLIST *ppidl)
{
*ppidl = NULL;
LPITEMIDLIST pidlParent;
LPCITEMIDLIST pidlChild;
if (SUCCEEDED(SplitIDList(pidl, &pidlParent, &pidlChild)))
{
EnterCriticalSection(&_csSearch);
// loop through our DPA list and see if we can find a matach
for (int i = 0; i < DPA_GetPtrCount(_hdpaPidf); i++)
{
FIND_FOLDER_ITEM *pffli = _FolderListItem(i);
if (pffli && ILIsEqual(pidlParent, &pffli->idl))
{
// We found the right one
// so no lets transform the ID into one of our own
// to return. Note: we must catch the case where the
// original one passed in was a simple pidl and do
// the appropriate thing.
//
LPITEMIDLIST pidlToFree = NULL; // might need to cleanup in one case
// If this is not a FS folder, just clone it.
IShellFolder *psf;
if (fMapToReal && SUCCEEDED(_Folder(pffli, IID_PPV_ARG(IShellFolder, &psf))))
{
if (SUCCEEDED(SHGetRealIDL(psf, pidlChild, &pidlToFree)))
{
pidlChild = pidlToFree; // use this below...
}
psf->Release();
}
// create the doc find version of the pidl witht the
// extra hidden items embedded
AddDataToIDList(pidlChild, i, pidlParent, DFDF_NONE, 0, 0, 0, ppidl);
ILFree(pidlToFree); // may be NULL
break; // done with this loop
}
}
LeaveCriticalSection(&_csSearch);
ILFree(pidlParent);
}
return *ppidl ? S_OK : S_FALSE;
}
// Called before saving folder list. Needed especially in Asynch search,
// (CI). We lazily pull item data from RowSet only when list view asks
// for it. When we are leaving the search folder, we pull all items
// creating all necessary folder lists. This ensure when saving folder
// list, all are included.
// remark : Fix bug#338714.
HRESULT CFindFolder::_UpdateItemList()
{
USHORT cb = 0;
int cItems;
if (SUCCEEDED(GetItemCount(&cItems)))
{
for (int i = 0; i < cItems; i++)
{
FIND_ITEM *pesfi;
if (DB_S_ENDOFROWSET == GetItem(i, &pesfi))
break;
}
}
return S_OK;
}
// IFindFolder
HRESULT CFindFolder::SaveFolderList(IStream *pstm)
{
// We First pull all the items from RowSet (in Asynch case)
_UpdateItemList();
EnterCriticalSection(&_csSearch);
// Now loop through our DPA list and see if we can find a matach
for (int i = 0; i < DPA_GetPtrCount(_hdpaPidf); i++)
{
FIND_FOLDER_ITEM *pffli = _FolderListItem(i);
if (EVAL(pffli))
ILSaveToStream(pstm, &pffli->idl);
else
break;
}
LeaveCriticalSection(&_csSearch);
// Now out a zero size item..
USHORT cb = 0;
pstm->Write(&cb, sizeof(cb), NULL);
return TRUE;
}
// IFindFolder, Restore results out to file.
HRESULT CFindFolder::RestoreFolderList(IStream *pstm)
{
// loop through and all all of the folders to our list...
LPITEMIDLIST pidl = NULL;
HRESULT hr;
for (;;)
{
hr = ILLoadFromStream(pstm, &pidl); // frees [in,out] pidl for us
if (pidl == NULL)
break; // end of the list
else
{
int i;
AddFolder(pidl, FALSE, &i);
}
}
ILFree(pidl); // don't forget to free last pidl
return hr;
}
HRESULT CFindFolder::SaveItemList(IStream *pstm)
{
// We First serialize all of our PIDLS for each item in our list
int cItems;
if (SUCCEEDED(GetItemCount(&cItems)))
{
// And Save the items that are in the list
for (int i = 0; i < cItems; i++)
{
FIND_ITEM *pesfi;
HRESULT hr = GetItem(i, &pesfi);
if (hr == DB_S_ENDOFROWSET)
break;
if (SUCCEEDED(hr) && pesfi)
ILSaveToStream(pstm, &pesfi->idl);
}
}
USHORT cb = 0;
pstm->Write(&cb, sizeof(cb), NULL); // a Trailing NULL size to say end of pidl list...
return S_OK;
}
HRESULT CFindFolder::RestoreItemList(IStream *pstm, int *pcItems)
{
// And the pidls that are associated with the object
int cItems = 0;
LPITEMIDLIST pidl = NULL; // don't free previous one
FIND_ITEM *pesfi;
for (;;)
{
if (FAILED(ILLoadFromStream(pstm, &pidl)) || (pidl == NULL))
break;
if (FAILED(AddPidl(cItems, pidl, (UINT)-1, &pesfi)) || !pesfi)
break;
cItems++;
}
ILFree(pidl); // Free the last one read in
*pcItems = cItems;
return S_OK;
}
HRESULT CFindFolder::_GetFolderIDList(int iFolder, LPITEMIDLIST *ppidlParent)
{
*ppidlParent = NULL;
HRESULT hr = E_FAIL;
EnterCriticalSection(&_csSearch);
FIND_FOLDER_ITEM *pffli = _FolderListItem(iFolder);
if (pffli)
hr = SHILClone(&pffli->idl, ppidlParent);
LeaveCriticalSection(&_csSearch);
return hr;
}
HRESULT CFindFolder::GetParentsPIDL(LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlParent)
{
return _GetFolderIDList(GetFolderIndex(pidl), ppidlParent);
}
HRESULT CFindFolder::SetControllerNotifyObject(IFindControllerNotify *pfcn)
{
IUnknown_Set((IUnknown **)&_pfcn, pfcn);
return S_OK;
}
HRESULT CFindFolder::GetControllerNotifyObject(IFindControllerNotify **ppfcn)
{
*ppfcn = _pfcn;
if (_pfcn)
_pfcn->AddRef();
return _pfcn ? S_OK : S_FALSE;
}
STDMETHODIMP_(ULONG) CFindFolder::AddRef()
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_(ULONG) CFindFolder::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
STDMETHODIMP CFindFolder::ParseDisplayName(HWND hwnd, LPBC pbc, LPOLESTR pwzDisplayName,
ULONG *pchEaten, LPITEMIDLIST *ppidl, ULONG *pdwAttributes)
{
return E_NOTIMPL;
}
STDMETHODIMP CFindFolder::EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList **ppenum)
{
// We do not want the def view to enumerate us, instead we
// will tell defview to call us...
*ppenum = NULL; // No enumerator
return S_FALSE; // no enumerator (not error)
}
STDMETHODIMP CFindFolder::BindToObject(LPCITEMIDLIST pidl, LPBC pbc, REFIID riid, void **ppv)
{
IShellFolder *psf;
HRESULT hr = _QueryItemShellFolder(pidl, &psf);
if (SUCCEEDED(hr))
{
hr = psf->BindToObject(pidl, pbc, riid, ppv);
psf->Release();
}
return hr;
}
// Little helper function for bellow
HRESULT CFindFolder::_CompareFolderIndexes(int iFolder1, int iFolder2)
{
HRESULT hr = E_INVALIDARG;
EnterCriticalSection(&_csSearch);
FIND_FOLDER_ITEM *pffli1 = _FolderListItem(iFolder1);
FIND_FOLDER_ITEM *pffli2 = _FolderListItem(iFolder2);
if (pffli1 && pffli2)
{
// Check our 1-level deep cache. Since its is common for there to be multiple
// items in the same folder, during a sort operation, we often compare the
// same two folders repeatedly.
if ((_iCompareFolderCache1 != iFolder1) || (_iCompareFolderCache2 != iFolder2))
{
TCHAR szPath1[MAX_PATH], szPath2[MAX_PATH];
SHGetPathFromIDList(&pffli1->idl, szPath1);
SHGetPathFromIDList(&pffli2->idl, szPath2);
_iCompareFolderCacheResult = lstrcmpi(szPath1, szPath2);
_iCompareFolderCache1 = iFolder1;
_iCompareFolderCache2 = iFolder2;
}
hr = ResultFromShort(_iCompareFolderCacheResult);
}
LeaveCriticalSection(&_csSearch);
return hr;
}
PCHIDDENDOCFINDDATA CFindFolder::_HiddenData(LPCITEMIDLIST pidl)
{
return (PCHIDDENDOCFINDDATA)ILFindHiddenID(pidl, IDLHID_DOCFINDDATA);
}
UINT CFindFolder::GetFolderIndex(LPCITEMIDLIST pidl)
{
PCHIDDENDOCFINDDATA phdfd = (PCHIDDENDOCFINDDATA)ILFindHiddenID(pidl, IDLHID_DOCFINDDATA);
return phdfd ? phdfd->iFolder : -1;
}
FIND_FOLDER_ITEM *CFindFolder::_FolderListItem(int iFolder)
{
return (FIND_FOLDER_ITEM *)DPA_GetPtr(_hdpaPidf, iFolder);
}
FIND_FOLDER_ITEM *CFindFolder::_FolderListItem(LPCITEMIDLIST pidl)
{
return _FolderListItem(GetFolderIndex(pidl));
}
ULONG CFindFolder::_Rank(LPCITEMIDLIST pidl)
{
PCHIDDENDOCFINDDATA phdfd = _HiddenData(pidl);
// Could be mixed if so put ones without rank at the end...
return phdfd && (phdfd->wFlags & DFDF_EXTRADATA) ? phdfd->ulRank : 0;
}
DWORD CFindFolder::_ItemID(LPCITEMIDLIST pidl)
{
PCHIDDENDOCFINDDATA phdfd = _HiddenData(pidl);
return phdfd && (phdfd->wFlags & DFDF_EXTRADATA) ? phdfd->dwItemID : -1;
}
HRESULT CFindFolder::_GetItemDisplayName(LPCITEMIDLIST pidl, DWORD dwFlags, LPWSTR wzName, UINT cch)
{
// Get the IShellFolder:
IShellFolder *psf;
HRESULT hr = _QueryItemShellFolder(pidl, &psf);
if (SUCCEEDED(hr))
{
// Get the display name:
hr = DisplayNameOf(psf, pidl, dwFlags, wzName, cch);
psf->Release();
}
return hr;
}
// Given the 2 pidls, we extract the display name using DisplayNameOf and then
// if all goes well, we compare the two.
int CFindFolder::_CompareNames(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2, DWORD dwFlags)
{
int iRetVal = 0;
WCHAR szName1[MAX_PATH], szName2[MAX_PATH];
// Get the name for 1
HRESULT hr = _GetItemDisplayName(pidl1, dwFlags, szName1, ARRAYSIZE(szName1));
if (SUCCEEDED(hr))
{
// Get the name for 2
hr = _GetItemDisplayName(pidl2, dwFlags, szName2, ARRAYSIZE(szName2));
if (SUCCEEDED(hr))
{
// Compare and set value
iRetVal = StrCmpLogicalW(szName1, szName2);
}
}
return iRetVal;
}
int CFindFolder::_CompareByCachedSCID(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
{
int iRetVal = 0;
// If sort on name, we will skip this and use the code below.
if (!IsEqualSCID(_scidCached, SCID_NAME))
{
iRetVal = CompareBySCID(this, &_scidCached, pidl1, pidl2);
}
// If they are still the same, sort them alphabetically by the name:
// When we want to sort by name (either becuase we are sorting the
// name column, or because 2 items are identical in other regards) we
// want to display name vs the GetDetailsOf name for 2 reasons:
// 1. Some folders like IE's History don't support GetDetailsEx.
// 2. Recycle bin returns the file name, not the displayable name;
// so we would end up with "DC###..." instead of "New Folder".
if (iRetVal == 0)
{
iRetVal = _CompareNames(pidl1, pidl2, SHGDN_INFOLDER | SHGDN_NORMAL);
if (iRetVal == 0) // the display names are the same, could they be in different folders?
{
iRetVal = _CompareNames(pidl1, pidl2, SHGDN_FORPARSING);
}
}
return iRetVal;
}
STDMETHODIMP CFindFolder::CompareIDs(LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
{
HRESULT hr = E_INVALIDARG;
ASSERT(pidl1 == ILFindLastID(pidl1));
UINT iInputColumn = ((DWORD)lParam & SHCIDS_COLUMNMASK);
UINT iMappedColumn = iInputColumn;
if (_MapColIndex(&iMappedColumn))
{
if (IDFCOL_PATH == iMappedColumn)
{
UINT iFolder1 = GetFolderIndex(pidl1);
UINT iFolder2 = GetFolderIndex(pidl2);
if (iFolder1 != iFolder2)
return _CompareFolderIndexes(iFolder1, iFolder2);
}
else
{
ASSERT(iMappedColumn == IDFCOL_RANK);
ULONG ulRank1 = _Rank(pidl1);
ULONG ulRank2 = _Rank(pidl2);
if (ulRank1 < ulRank2)
return ResultFromShort(-1);
if (ulRank1 > ulRank2)
return ResultFromShort(1);
}
}
// Check the SCID cache and update it if necessary.
if (_uiColumnCached != iInputColumn)
{
hr = MapColumnToSCID(iInputColumn, &_scidCached);
if (SUCCEEDED(hr))
{
_uiColumnCached = iInputColumn;
}
}
// Check if one is a folder and not the other. put folders before files.
int iRes = CompareFolderness(this, pidl1, pidl2);
if (iRes == 0)
{
iRes = _CompareByCachedSCID(pidl1, pidl2);
}
return ResultFromShort(iRes);
}
STDMETHODIMP CFindFolder::CreateViewObject(HWND hwnd, REFIID riid, void **ppv)
{
*ppv = NULL;
HRESULT hr = E_NOINTERFACE;
if (IsEqualIID(riid, IID_IShellView))
{
IShellFolderViewCB* psfvcb = new CFindFolderViewCB(this);
if (psfvcb)
{
SFV_CREATE sSFV = {0};
sSFV.cbSize = sizeof(sSFV);
sSFV.pshf = this;
sSFV.psfvcb = psfvcb;
hr = SHCreateShellFolderView(&sSFV, (IShellView**)ppv);
psfvcb->Release();
}
else
{
hr = E_OUTOFMEMORY;
}
}
else if (IsEqualIID(riid, IID_IContextMenu))
{
IContextMenuCB *pcmcb = new CFindMenuCB();
if (pcmcb)
{
hr = CDefFolderMenu_CreateEx(NULL, hwnd,
0, NULL, this, pcmcb, NULL, NULL, (IContextMenu * *)ppv);
pcmcb->Release();
}
else
hr = E_OUTOFMEMORY;
}
return hr;
}
STDMETHODIMP CFindFolder::GetAttributesOf(UINT cidl, LPCITEMIDLIST *apidl, ULONG *prgfInOut)
{
HRESULT hr;
if (cidl == 0)
{
// defview asks to see if any items can be renamed this way, lame
*prgfInOut = SFGAO_CANRENAME;
hr = S_OK;
}
else
{
ASSERT(*apidl == ILFindLastID(*apidl))
IShellFolder *psf;
hr = _QueryItemShellFolder(apidl[0], &psf);
if (SUCCEEDED(hr))
{
hr = psf->GetAttributesOf(cidl, apidl, prgfInOut);
psf->Release();
}
}
return hr;
}
//
// To be called back from within CDefFolderMenuE - Currently only used
//
// Some helper functions
STDMETHODIMP CFindFolder::SetItemsChangedSinceSort()
{
_fItemsChangedSinceSort = TRUE;
_iCompareFolderCache1 = -1; // an invalid folder index value
return S_OK;
}
STDMETHODIMP CFindFolder::GetItemCount(INT *pcItems)
{
ASSERT(pcItems);
DBCOUNTITEM cItems = 0;
EnterCriticalSection(&_csSearch);
if (_hdpaItems)
cItems = DPA_GetPtrCount(_hdpaItems);
LeaveCriticalSection(&_csSearch);
// If async, then we may not have grown our dpa yet... but in mixed case we have so take
// max of the two...
if (_pDFEnumAsync)
{
if (_cAsyncItems > cItems)
cItems = _cAsyncItems;
}
*pcItems = SANE_ITEMCOUNT(cItems);
return S_OK;
};
STDMETHODIMP CFindFolder::GetItem(int iItem, FIND_ITEM **ppItem)
{
HRESULT hr = E_FAIL; // just to init, use anything
FIND_ITEM *pesfi;
IFindEnum *pidfenum;
GetAsyncEnum(&pidfenum);
DWORD dwItemID = (UINT)-1;
EnterCriticalSection(&_csSearch);
int i = DPA_GetPtrCount(_hdpaItems);
pesfi = (FIND_ITEM *) DPA_GetPtr(_hdpaItems, iItem);
LeaveCriticalSection(&_csSearch);
// Mondo hack to better handle Async searching (ROWSET), we are not sure if we
// can trust the PIDL of the row as new rows may have been inserted...
// Only do this if we are not looking at the previous item..
if (pesfi && pidfenum && !_fSearchComplete && (iItem != _iGetIDList))
{
PCHIDDENDOCFINDDATA phdfd = _HiddenData(&pesfi->idl);
// As we can now have mixed results only blow away if this is an async guy...
if (phdfd && (phdfd->wFlags & DFDF_EXTRADATA))
{
pidfenum->GetItemID(iItem, &dwItemID);
if (dwItemID != phdfd->dwItemID)
{
// Overload, pass NULL to ADDPIDL to tell system to free that item
if (pesfi->dwState & (LVIS_SELECTED|LVIS_FOCUSED))
_AddFIND_ITEMToSaveStateList(pesfi);
AddPidl(iItem, 0, NULL, NULL);
pesfi = NULL;
}
}
}
_iGetIDList = iItem; // remember the last one we retrieved...
if (!pesfi && (iItem >= 0))
{
// See if this is the async case
if (pidfenum)
{
LPITEMIDLIST pidlT;
hr = pidfenum->GetItemIDList(SANE_ITEMCOUNT(iItem), &pidlT);
if (SUCCEEDED(hr) && hr != DB_S_ENDOFROWSET)
{
AddPidl(iItem, pidlT, dwItemID, &pesfi);
// See if this item should show up as selected...
if (dwItemID == (UINT)-1)
pidfenum->GetItemID(iItem, &dwItemID);
GetStateFromSaveStateList(dwItemID, &pesfi->dwState);
}
}
}
*ppItem = pesfi;
if (hr != DB_S_ENDOFROWSET)
hr = pesfi ? S_OK : E_FAIL;
return hr;
}
STDMETHODIMP CFindFolder::DeleteItem(int iItem)
{
HRESULT hr = E_FAIL;
if (!_fInRefresh)
{
FIND_ITEM *pesfi;
hr = E_INVALIDARG;
// make sure the item is in dpa (if using cI)
if (SUCCEEDED(GetItem(iItem, &pesfi)) && pesfi)
{
EnterCriticalSection(&_csSearch);
DPA_DeletePtr(_hdpaItems, iItem);
LeaveCriticalSection(&_csSearch);
PCHIDDENDOCFINDDATA phdfd = _HiddenData(&pesfi->idl);
if (phdfd && (phdfd->wFlags & DFDF_EXTRADATA))
{
//we are deleting async item...
_cAsyncItems--;
}
if (pesfi->dwState &= LVIS_SELECTED)
{
// Need to update the count of items selected...
_dflvrSel.DecrementIncludedCount();
}
LocalFree((HLOCAL)pesfi);
hr = S_OK;
}
}
return hr;
}
// evil window crawling code to get the listview from defview
HWND ListviewFromView(HWND hwnd)
{
HWND hwndLV;
do
{
hwndLV = FindWindowEx(hwnd, NULL, WC_LISTVIEW, NULL);
}
while ((hwndLV == NULL) && (hwnd = GetWindow(hwnd, GW_CHILD)));
return hwndLV;
}
HWND ListviewFromViewUnk(IUnknown *punkView)
{
HWND hwnd;
if (SUCCEEDED(IUnknown_GetWindow(punkView, &hwnd)))
{
hwnd = ListviewFromView(hwnd);
}
return hwnd;
}
STDMETHODIMP CFindFolder::ValidateItems(IUnknown *punkView, int iItem, int cItems, BOOL bSearchComplete)
{
IFindEnum *pidfenum;
if (S_OK != GetAsyncEnum(&pidfenum) || _fAllAsyncItemsCached)
return S_OK; // nothing to validate.
DWORD dwItemID = (UINT)-1;
int cItemsInList;
GetItemCount(&cItemsInList);
// force reload of rows
pidfenum->Reset();
HWND hwndLV = ListviewFromViewUnk(punkView);
int iLVFirst = ListView_GetTopIndex(hwndLV);
int cLVItems = ListView_GetCountPerPage(hwndLV);
if (iItem == -1)
{
iItem = iLVFirst;
cItems = cLVItems;
}
// to avoid failing to update an item...
if (bSearchComplete)
_iGetIDList = -1;
while ((iItem < cItemsInList) && cItems)
{
EnterCriticalSection(&_csSearch);
FIND_ITEM *pesfi = (FIND_ITEM *) DPA_GetPtr(_hdpaItems, iItem);
LeaveCriticalSection(&_csSearch);
if (!pesfi) // Assume that if we have not gotten this one we are in the clear...
break;
PCHIDDENDOCFINDDATA phdfd = _HiddenData(&pesfi->idl);
if (phdfd && (phdfd->wFlags & DFDF_EXTRADATA))
{
pidfenum->GetItemID(iItem, &dwItemID);
if (dwItemID != _ItemID(&pesfi->idl))
{
FIND_ITEM *pItem; // dummy to make GetItem happy
// Oops don't match,
if (InRange(iItem, iLVFirst, iLVFirst+cLVItems))
{
if (SUCCEEDED(GetItem(iItem, &pItem)))
{
ListView_RedrawItems(hwndLV, iItem, iItem);
}
}
else
{
AddPidl(iItem, NULL, 0, NULL);
}
}
}
else
{
break; // stop after we reach first non ci item
}
iItem++;
cItems--;
}
_fSearchComplete = bSearchComplete;
return S_OK;
}
STDMETHODIMP CFindFolder::AddPidl(int i, LPCITEMIDLIST pidl, DWORD dwItemID, FIND_ITEM **ppcdfi)
{
HRESULT hr = S_OK;
ASSERT(GetCurrentThreadId() == _GUIThreadID);
if (NULL == pidl)
{
EnterCriticalSection(&_csSearch);
FIND_ITEM* pesfi = (FIND_ITEM*)DPA_GetPtr(_hdpaItems, i);
if (pesfi)
{
LocalFree((HLOCAL)pesfi);
DPA_SetPtr(_hdpaItems, i, NULL);
}
LeaveCriticalSection(&_csSearch);
if (ppcdfi)
*ppcdfi = NULL;
}
else
{
int cb = ILGetSize(pidl);
FIND_ITEM *pesfi;
hr = SHLocalAlloc(sizeof(*pesfi) - sizeof(pesfi->idl) + cb, &pesfi);
if (SUCCEEDED(hr))
{
// pesfi->dwMask = 0;
// pesfi->dwState = 0;
pesfi->iIcon = -1;
memcpy(&pesfi->idl, pidl, cb);
EnterCriticalSection(&_csSearch);
BOOL bRet = DPA_SetPtr(_hdpaItems, i, (void *)pesfi);
LeaveCriticalSection(&_csSearch);
if (bRet)
{
if (ppcdfi)
*ppcdfi = pesfi;
}
else
{
LocalFree((HLOCAL)pesfi);
pesfi = NULL;
hr = E_OUTOFMEMORY;
}
}
}
return hr;
}
STDMETHODIMP CFindFolder::SetAsyncEnum(IFindEnum *pdfEnumAsync)
{
if (_pDFEnumAsync)
_pDFEnumAsync->Release();
_pDFEnumAsync = pdfEnumAsync;
if (pdfEnumAsync)
pdfEnumAsync->AddRef();
return S_OK;
}
STDMETHODIMP CFindFolder::CacheAllAsyncItems()
{
if (_fAllAsyncItemsCached)
return S_OK; // Allready done it...
IFindEnum *pidfenum;
if (S_OK != GetAsyncEnum(&pidfenum))
return S_FALSE; // nothing to do...
// Probably the easiest thing to do is to simply walk through all of the items...
int maxItems = SANE_ITEMCOUNT(_cAsyncItems);
for (int i = 0; i < maxItems; i++)
{
FIND_ITEM *pesfi;
GetItem(i, &pesfi);
}
_fAllAsyncItemsCached = TRUE;
return S_OK;
}
BOOL CFindFolder::AllAsyncItemsCached()
{
return _fAllAsyncItemsCached;
}
STDMETHODIMP CFindFolder::GetAsyncEnum(IFindEnum **ppdfEnumAsync)
{
*ppdfEnumAsync = _pDFEnumAsync; // unreferecned!
return *ppdfEnumAsync ? S_OK : S_FALSE;
}
STDMETHODIMP CFindFolder::SetAsyncCount(DBCOUNTITEM cCount)
{
_cAsyncItems = cCount;
_fAllAsyncItemsCached = FALSE;
return S_OK;
}
STDMETHODIMP CFindFolder::ClearSaveStateList()
{
DSA_DeleteAllItems(_hdsaSaveStateForIDs);
_cSaveStateSelected = 0;
return S_OK;
}
STDMETHODIMP CFindFolder::GetStateFromSaveStateList(DWORD dwItemID, DWORD *pdwState)
{
for (int i = DSA_GetItemCount(_hdsaSaveStateForIDs); i-- > 0;)
{
// Pidl at start of structure...
FIND_ITEM_SAVE_STATE *pessi = (FIND_ITEM_SAVE_STATE*)DSA_GetItemPtr(_hdsaSaveStateForIDs, i);
if (pessi->dwItemID == dwItemID)
{
*pdwState = pessi->dwState;
if (pessi->dwState & LVIS_SELECTED)
{
// Remember the counts of items that we have touched...
_dflvrSel.IncrementIncludedCount();
_cSaveStateSelected--;
}
// Any items we retrieve we can get rid of...
DSA_DeleteItem(_hdsaSaveStateForIDs, i);
return S_OK;
}
}
return S_FALSE;
}
STDMETHODIMP CFindFolder::GetFolderListItemCount(INT *pcItemCount)
{
*pcItemCount = 0;
EnterCriticalSection(&_csSearch);
if (_hdpaPidf)
*pcItemCount = DPA_GetPtrCount(_hdpaPidf);
LeaveCriticalSection(&_csSearch);
return S_OK;
}
STDMETHODIMP CFindFolder::GetFolderListItem(int iItem, FIND_FOLDER_ITEM **ppdffi)
{
EnterCriticalSection(&_csSearch);
*ppdffi = (FIND_FOLDER_ITEM *)DPA_GetPtr(_hdpaPidf, iItem);
LeaveCriticalSection(&_csSearch);
return *ppdffi ? S_OK : E_FAIL;
}
class CFindMenuWrap : public CContextMenuForwarder
{
public:
// IContextMenu overrides
STDMETHODIMP InvokeCommand(LPCMINVOKECOMMANDINFO lpici);
protected:
CFindMenuWrap(IDataObject* pdo, IContextMenu* pcmArray);
~CFindMenuWrap();
friend HRESULT DFWrapIContextMenus(IDataObject* pdo, IContextMenu* pcm1, IContextMenu* pcm2, REFIID riid, void** ppv);
private:
IDataObject * _pdtobj;
};
CFindMenuWrap::CFindMenuWrap(IDataObject* pdo, IContextMenu* pcmArray) : CContextMenuForwarder(pcmArray)
{
_pdtobj = pdo;
_pdtobj->AddRef();
}
CFindMenuWrap::~CFindMenuWrap()
{
_pdtobj->Release();
}
STDMETHODIMP CFindMenuWrap::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
{
BOOL fIsLink = FALSE;
// "link" has to create a link on the desktop, not in the folder (since we're not a real folder...)
if (IS_INTRESOURCE(lpici->lpVerb))
{
WCHAR szCommandString[64];
if (SUCCEEDED(ContextMenu_GetCommandStringVerb(_pcm, LOWORD((UINT_PTR)lpici->lpVerb), szCommandString, ARRAYSIZE(szCommandString))))
{
fIsLink = !StrCmpIW(szCommandString, L"link");
}
}
else
{
fIsLink = !StrCmpIA(lpici->lpVerb, "link");
}
if (fIsLink)
{
// Note: old code used to check pdtobj, but we don't create this
// object unless we get one of them, so why check?
ASSERT(_pdtobj);
return SHCreateLinks(lpici->hwnd, NULL, _pdtobj,
SHCL_USETEMPLATE | SHCL_USEDESKTOP | SHCL_CONFIRM, NULL);
}
return CContextMenuForwarder::InvokeCommand(lpici);
}
HRESULT DFWrapIContextMenu(HWND hwnd, IShellFolder *psf, LPCITEMIDLIST pidl,
IContextMenu* pcmExtra, void **ppvInOut)
{
IContextMenu *pcmWrap = NULL;
IContextMenu *pcmFree = (IContextMenu*)*ppvInOut;
IDataObject* pdo;
HRESULT hr = psf->GetUIObjectOf(hwnd, 1, &pidl, IID_X_PPV_ARG(IDataObject, NULL, &pdo));
if (SUCCEEDED(hr))
{
hr = DFWrapIContextMenus(pdo, pcmFree, pcmExtra, IID_PPV_ARG(IContextMenu, &pcmWrap));
pdo->Release();
}
pcmFree->Release();
*ppvInOut = pcmWrap;
return hr;
}
HRESULT DFWrapIContextMenus(IDataObject* pdo, IContextMenu* pcm1, IContextMenu* pcm2, REFIID riid, void** ppv)
{
*ppv = NULL;
IContextMenu * pcmArray;
IContextMenu* rgpcm[2] = {pcm2, pcm1};
HRESULT hr = Create_ContextMenuOnContextMenuArray(rgpcm, ARRAYSIZE(rgpcm), IID_PPV_ARG(IContextMenu, &pcmArray));
if (SUCCEEDED(hr))
{
CFindMenuWrap * p = new CFindMenuWrap(pdo, pcmArray);
if (p)
{
hr = p->QueryInterface(riid, ppv);
p->Release();
}
else
{
hr = E_OUTOFMEMORY;
}
pcmArray->Release();
}
return hr;
}
STDMETHODIMP CFindFolder::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] = {
QITABENT(CFindFolder, IShellFolder2), //IID_ISHELLFolder2
QITABENTMULTI(CFindFolder, IShellFolder, IShellFolder2), // IID_IShellFolder
QITABENT(CFindFolder, IFindFolder), //IID_IFindFolder
QITABENT(CFindFolder, IShellIcon), //IID_IShellIcon
QITABENT(CFindFolder, IPersistFolder2), //IID_IPersistFolder2
QITABENTMULTI(CFindFolder, IPersistFolder, IPersistFolder2), //IID_IPersistFolder
QITABENTMULTI(CFindFolder, IPersist, IPersistFolder2), //IID_IPersist
QITABENT(CFindFolder, IShellIconOverlay), //IID_IShellIconOverlay
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
// IPersistFolder2 implementation
STDMETHODIMP CFindFolder::GetClassID(CLSID *pClassID)
{
*pClassID = CLSID_DocFindFolder;
return S_OK;
}
STDMETHODIMP CFindFolder::Initialize(LPCITEMIDLIST pidl)
{
if (_pidl)
ILFree(_pidl);
return SHILClone(pidl, &_pidl);
}
STDMETHODIMP CFindFolder::GetCurFolder(LPITEMIDLIST *ppidl)
{
return GetCurFolderImpl(_pidl, ppidl);
}
// helper function to sort the selected ID list by something that
// makes file operations work reasonably OK, when both an object and it's
// parent is in the list...
//
int CALLBACK CFindFolder::_SortForDataObj(void *p1, void *p2, LPARAM lparam)
{
// Since I do recursion, If I get the Folder index number from the
// last element of each and sort by them such that the higher numbers
// come first, should solve the problem fine...
LPITEMIDLIST pidl1 = (LPITEMIDLIST)ILFindLastID((LPITEMIDLIST)p1);
LPITEMIDLIST pidl2 = (LPITEMIDLIST)ILFindLastID((LPITEMIDLIST)p2);
CFindFolder *pff = (CFindFolder *)lparam;
return pff->GetFolderIndex(pidl2) - pff->GetFolderIndex(pidl1);
}
LPITEMIDLIST CFindFolder::_GetFullPidlForItem(LPCITEMIDLIST pidl)
{
LPITEMIDLIST pidlRet = NULL;
LPITEMIDLIST pidlParent;
if (S_OK == GetParentsPIDL(pidl, &pidlParent))
{
pidlRet = ILCombine(pidlParent, pidl);
ILFree(pidlParent);
}
return pidlRet;
}
// we generate a non flat HIDA. this is so clients that
// use this HIDA will bind to the folder that the results came
// from instead of this folder that has runtime state that won't
// be present if we rebind
HRESULT CFindFolder::_PrepareHIDA(UINT cidl, LPCITEMIDLIST * apidl, HDPA *phdpa)
{
HRESULT hr = E_OUTOFMEMORY;
*phdpa = DPA_Create(0);
if (*phdpa)
{
if (DPA_Grow(*phdpa, cidl))
{
for (UINT i = 0; i < cidl; i++)
{
LPITEMIDLIST pidl = _GetFullPidlForItem(apidl[i]);
if (pidl)
DPA_InsertPtr(*phdpa, i, pidl);
}
// In order to make file manipulation functions work properly we
// need to sort the elements to make sure if an element and one
// of it's parents are in the list, that the element comes
// before it's parents...
DPA_Sort(*phdpa, _SortForDataObj, (LPARAM)this);
hr = S_OK;
}
}
return hr;
}
STDMETHODIMP CFindFolder::GetUIObjectOf(HWND hwnd, UINT cidl, LPCITEMIDLIST * apidl,
REFIID riid, UINT * prgfInOut, void **ppv)
{
HRESULT hr = E_INVALIDARG;
*ppv = NULL;
// if just one item we can deletate to real folder
if (cidl == 1)
{
// Note we may have been passed in a complex item so find the last
ASSERT(ILIsEmpty(_ILNext(*apidl))); // should be a single level PIDL!
IShellFolder *psf;
hr = _QueryItemShellFolder(apidl[0], &psf);
if (SUCCEEDED(hr))
{
hr = psf->GetUIObjectOf(hwnd, cidl, apidl, riid, prgfInOut, ppv);
// if we are doing context menu, then we will wrap this
// interface in a wrapper object, that we can then pick
// off commands like link to process specially
if (SUCCEEDED(hr))
{
if (IsEqualIID(riid, IID_IContextMenu))
{
// we also let the net/file guy add in a context menu if they want to
IContextMenu* pcmExtra = NULL;
_pfilter->GetItemContextMenu(hwnd, SAFECAST(this, IFindFolder*), &pcmExtra);
hr = DFWrapIContextMenu(hwnd, psf, apidl[0], pcmExtra, ppv);
ATOMICRELEASE(pcmExtra);
}
else if (IsEqualIID(riid, IID_IQueryInfo)) // && SHGetAttributes(psf, apidl[0], SFGAO_FILESYSTEM))
{
WrapInfotip(SAFECAST(this, IShellFolder2 *), apidl[0], &SCID_DIRECTORY, (IUnknown *)*ppv);
}
}
psf->Release();
}
}
else if (cidl > 1)
{
if (IsEqualIID(riid, IID_IContextMenu))
{
// Try to create a menu object that we process ourself
// Yes, do context menu.
HKEY ahkeys[MAX_ASSOC_KEYS] = {0};
DWORD ckeys = 0;
LPITEMIDLIST pidlFull = _GetFullPidlForItem(apidl[0]);
if (pidlFull)
{
// Get the hkeyProgID and hkeyBaseProgID from the first item.
ckeys = SHGetAssocKeysForIDList(pidlFull, ahkeys, ARRAYSIZE(ahkeys));
ILFree(pidlFull);
}
IContextMenuCB *pcmcb = new CFindMenuCB();
if (pcmcb)
{
hr = CDefFolderMenu_Create2Ex(NULL, hwnd,
cidl, apidl, this, pcmcb,
ckeys, ahkeys,
(IContextMenu **)ppv);
pcmcb->Release();
}
SHRegCloseKeys(ahkeys, ckeys);
}
else if (IsEqualIID(riid, IID_IDataObject))
{
HDPA hdpa;
hr = _PrepareHIDA(cidl, apidl, &hdpa);
if (SUCCEEDED(hr))
{
hr = SHCreateFileDataObject(&c_idlDesktop, cidl, (LPCITEMIDLIST*)DPA_GetPtrPtr(hdpa),
NULL, (IDataObject **)ppv);
DPA_FreeIDArray(hdpa);
}
}
}
return hr;
}
STDMETHODIMP CFindFolder::GetDisplayNameOf(LPCITEMIDLIST pidl, DWORD dwRes, LPSTRRET pStrRet)
{
IShellFolder *psf;
HRESULT hr = _QueryItemShellFolder(pidl, &psf);
if (SUCCEEDED(hr))
{
if ((dwRes & SHGDN_INFOLDER) && (dwRes & SHGDN_FORPARSING) && !(dwRes & SHGDN_FORADDRESSBAR))
{
// The thumbnail cache uses this as a hit test... in search view we can have files with the same name.
dwRes &= ~SHGDN_INFOLDER;
}
hr = psf->GetDisplayNameOf(pidl, dwRes, pStrRet);
psf->Release();
}
return hr;
}
STDMETHODIMP CFindFolder::SetNameOf(HWND hwnd, LPCITEMIDLIST pidl, LPCOLESTR pszName,
DWORD dwRes, LPITEMIDLIST *ppidlOut)
{
if (ppidlOut)
*ppidlOut = NULL;
IShellFolder *psf;
HRESULT hr = _QueryItemShellFolder(pidl, &psf);
if (SUCCEEDED(hr))
{
LPITEMIDLIST pidlRenamed;
hr = psf->SetNameOf(hwnd, pidl, PathFindFileName(pszName), dwRes, ppidlOut ? &pidlRenamed : NULL);
if (SUCCEEDED(hr) && ppidlOut)
{
hr = AddDataToIDList(pidlRenamed, GetFolderIndex(pidl), NULL, DFDF_NONE, 0, 0, 0, ppidlOut);
ILFree(pidlRenamed);
}
psf->Release();
}
return hr;
}
STDMETHODIMP CFindFolder::GetDefaultSearchGUID(GUID *pGuid)
{
return _pfilter->GetDefaultSearchGUID(SAFECAST(this, IShellFolder2*), pGuid);
}
STDMETHODIMP CFindFolder::EnumSearches(LPENUMEXTRASEARCH *ppenum)
{
return _pfilter->EnumSearches(SAFECAST(this, IShellFolder2*), ppenum);
}
HRESULT CFindFolder::_Folder(FIND_FOLDER_ITEM *pffli, REFIID riid, void **ppv)
{
HRESULT hr;
if (pffli->psf)
hr = S_OK;
else
hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, &pffli->idl, &pffli->psf));
if (SUCCEEDED(hr))
hr = pffli->psf->QueryInterface(riid, ppv);
return hr;
}
HRESULT CFindFolder::GetFolder(int iFolder, REFIID riid, void **ppv)
{
*ppv = NULL;
HRESULT hr = E_FAIL;
EnterCriticalSection(&_csSearch);
FIND_FOLDER_ITEM *pffli = _FolderListItem(iFolder);
if (pffli)
hr = _Folder(pffli, riid, ppv);
LeaveCriticalSection(&_csSearch);
return hr;
}
HRESULT CFindFolder::_FolderFromItem(LPCITEMIDLIST pidl, REFIID riid, void **ppv)
{
*ppv = NULL;
HRESULT hr = E_FAIL;
PCHIDDENDOCFINDDATA phdfd = _HiddenData(pidl);
if (phdfd)
{
hr = SHBindToObject(NULL, riid, &phdfd->idlParent, ppv);
}
return hr;
}
HRESULT CFindFolder::_QueryItemShellFolder(LPCITEMIDLIST pidl, IShellFolder **ppsf)
{
*ppsf = NULL;
HRESULT hr = E_FAIL;
EnterCriticalSection(&_csSearch);
FIND_FOLDER_ITEM *pffli = _FolderListItem(pidl);
if (pffli)
{
if (pffli->psf)
hr = S_OK;
else
hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, &pffli->idl, &pffli->psf));
if (SUCCEEDED(hr))
{
*ppsf = pffli->psf;
(*ppsf)->AddRef();
}
}
LeaveCriticalSection(&_csSearch);
if (FAILED(hr))
{
hr = _FolderFromItem(pidl, IID_PPV_ARG(IShellFolder, ppsf));
}
return hr;
}
HRESULT CFindFolder::_QueryItemInterface(LPCITEMIDLIST pidl, REFIID riid, void **ppv)
{
*ppv = NULL;
HRESULT hr = E_FAIL;
EnterCriticalSection(&_csSearch);
FIND_FOLDER_ITEM *pffli = _FolderListItem(pidl);
if (pffli)
hr = _Folder(pffli, riid, ppv);
LeaveCriticalSection(&_csSearch);
if (FAILED(hr))
{
hr = _FolderFromItem(pidl, riid, ppv);
}
return hr;
}
HRESULT CFindFolder::_GetDetailsFolder()
{
HRESULT hr;
if (_psfDetails)
hr = S_OK; // in cache
else
{
IFindFilter *pfilter;
hr = GetFindFilter(&pfilter);
if (SUCCEEDED(hr))
{
hr = pfilter->GetColumnsFolder(&_psfDetails);
pfilter->Release();
}
}
return hr;
}
STDMETHODIMP CFindFolder::GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay)
{
HRESULT hr = _GetDetailsFolder();
if (SUCCEEDED(hr))
hr = _psfDetails->GetDefaultColumn(dwRes, pSort, pDisplay);
return hr;
}
BOOL CFindFolder::_MapColIndex(UINT *piColumn)
{
switch (*piColumn)
{
case IDFCOL_NAME: // 0
return FALSE;
case IDFCOL_PATH: // 1
case IDFCOL_RANK: // 2
return TRUE;
default: // >= 3
*piColumn -= IDFCOL_RANK;
return FALSE;
}
}
STDMETHODIMP CFindFolder::GetDefaultColumnState(UINT iColumn, DWORD *pdwState)
{
HRESULT hr;
if (_MapColIndex(&iColumn))
{
*pdwState = c_find_cols[iColumn].csFlags;
hr = S_OK;
}
else
{
hr = _GetDetailsFolder();
if (SUCCEEDED(hr))
{
hr = _psfDetails->GetDefaultColumnState(iColumn, pdwState);
*pdwState &= ~SHCOLSTATE_SLOW; // virtual lv and defview
}
}
return hr;
}
HRESULT CFindFolder::_GetFolderName(LPCITEMIDLIST pidl, DWORD gdnFlags, LPTSTR psz, UINT cch)
{
LPITEMIDLIST pidlFolder;
HRESULT hr = GetParentsPIDL(pidl, &pidlFolder);
if (SUCCEEDED(hr))
{
hr = SHGetNameAndFlags(pidlFolder, gdnFlags, psz, cch, NULL);
ILFree(pidlFolder);
}
return hr;
}
STDMETHODIMP CFindFolder::GetDetailsEx(LPCITEMIDLIST pidl, const SHCOLUMNID *pscid, VARIANT *pv)
{
HRESULT hr;
if (IsEqualSCID(*pscid, SCID_RANK))
{
hr = InitVariantFromUINT(pv, _Rank(pidl));
}
else
{
IShellFolder2 *psf;
hr = _QueryItemInterface(pidl, IID_PPV_ARG(IShellFolder2, &psf));
if (SUCCEEDED(hr))
{
hr = psf->GetDetailsEx(pidl, pscid, pv);
psf->Release();
}
if (FAILED(hr))
{
if (IsEqualSCID(*pscid, SCID_DIRECTORY))
{
TCHAR szTemp[MAX_PATH];
hr = _GetFolderName(pidl, SHGDN_FORADDRESSBAR | SHGDN_FORPARSING, szTemp, ARRAYSIZE(szTemp));
if (SUCCEEDED(hr))
{
hr = InitVariantFromStr(pv, szTemp);
}
}
}
}
return hr;
}
// Figure out what the correct column index is to match the scid we are given
// where the returned index is relative to the folder passed in.
int MapSCIDToColumnForFolder(IShellFolder2 *psf, SHCOLUMNID scidIn)
{
SHCOLUMNID scidNew;
for (UINT i = 0; SUCCEEDED(psf->MapColumnToSCID(i, &scidNew)); i++)
{
if (IsEqualSCID(scidNew, scidIn))
{
return i; // found
}
}
return -1; // not found
}
STDMETHODIMP CFindFolder::GetDetailsOf(LPCITEMIDLIST pidl, UINT iColumn, SHELLDETAILS *pdi)
{
HRESULT hr;
if (_MapColIndex(&iColumn))
{
if (pidl)
{
TCHAR szTemp[MAX_PATH];
szTemp[0] = 0;
if (IDFCOL_PATH == iColumn)
{
_GetFolderName(pidl, SHGDN_FORADDRESSBAR | SHGDN_FORPARSING, szTemp, ARRAYSIZE(szTemp));
}
else
{
ASSERT(IDFCOL_RANK == iColumn);
ULONG uRank = _Rank(pidl);
if (uRank)
AddCommas(uRank, szTemp, ARRAYSIZE(szTemp));
}
hr = StringToStrRet(szTemp, &pdi->str);
}
else
{
hr = GetDetailsOfInfo(c_find_cols, ARRAYSIZE(c_find_cols), iColumn, pdi);
}
}
else
{
if (pidl)
{
IShellFolder2 *psf;
hr = _QueryItemInterface(pidl, IID_PPV_ARG(IShellFolder2, &psf));
if (SUCCEEDED(hr))
{
// We cannot simply ask for GetDetailsOf because some folders map different
// column numbers to differnt values.
// Translate the column index to the SHCOLUMNID relative to this folder.
SHCOLUMNID colId;
hr = _GetDetailsFolder();
if (SUCCEEDED(hr))
hr = _psfDetails->MapColumnToSCID(iColumn, &colId);
// Use the SCID to get the correct column index...
if (SUCCEEDED(hr))
{
// Get the column index for the SCID with respect to the other folder
int newIndex = MapSCIDToColumnForFolder(psf, colId);
if (newIndex != -1)
{
// Found the correct column index, so use it to get the data
hr = psf->GetDetailsOf(pidl, newIndex, pdi);
}
else
{
// Failed to find the correct column index.
hr = E_FAIL;
}
}
psf->Release();
}
}
else
{
hr = _GetDetailsFolder();
if (SUCCEEDED(hr))
hr = _psfDetails->GetDetailsOf(NULL, iColumn, pdi);
}
}
return hr;
}
STDMETHODIMP CFindFolder::MapColumnToSCID(UINT iColumn, SHCOLUMNID *pscid)
{
HRESULT hr;
if (_MapColIndex(&iColumn))
{
hr = MapColumnToSCIDImpl(c_find_cols, ARRAYSIZE(c_find_cols), iColumn, pscid);
}
else
{
hr = _GetDetailsFolder();
if (SUCCEEDED(hr))
hr = _psfDetails->MapColumnToSCID(iColumn, pscid);
}
return hr;
}
STDMETHODIMP CFindFolder::GetFindFilter(IFindFilter **ppfilter)
{
return _pfilter->QueryInterface(IID_PPV_ARG(IFindFilter, ppfilter));
}
// IShellIcon::GetIconOf
STDMETHODIMP CFindFolder::GetIconOf(LPCITEMIDLIST pidl, UINT flags, int *piIndex)
{
IShellIcon * psiItem;
HRESULT hr = _QueryItemInterface(pidl, IID_PPV_ARG(IShellIcon, &psiItem));
if (SUCCEEDED(hr))
{
hr = psiItem->GetIconOf(pidl, flags, piIndex);
psiItem->Release();
}
return hr;
}
// IShellIconOverlay
STDMETHODIMP CFindFolder::GetOverlayIndex(LPCITEMIDLIST pidl, int * pIndex)
{
IShellIconOverlay * psioItem;
HRESULT hr = _QueryItemInterface(pidl, IID_PPV_ARG(IShellIconOverlay, &psioItem));
if (SUCCEEDED(hr))
{
hr = psioItem->GetOverlayIndex(pidl, pIndex);
psioItem->Release();
}
return hr;
}
STDMETHODIMP CFindFolder::GetOverlayIconIndex(LPCITEMIDLIST pidl, int * pIndex)
{
return E_NOTIMPL;
}
STDMETHODIMP CFindFolder::RestoreSearchFromSaveFile(LPCITEMIDLIST pidlSaveFile, IShellFolderView *psfv)
{
// See if we can restore most of the search from here...
IStream *pstm;
HRESULT hr = StgBindToObject(pidlSaveFile, STGM_READ | STGM_SHARE_DENY_WRITE, IID_PPV_ARG(IStream, &pstm));
if (SUCCEEDED(hr))
{
ULONG cbRead;
DFHEADER dfh;
// Note: in theory I should test the size read by the size of the
// smaller headers, but if the number of bytes read is smaller than
// the few new things added then there is nothing to restore anyway...
// Note: Win95/NT4 incorrectly failed newer versions of this structure.
// Which is bogus since the struct was backward compatible (that's what
// the offsets are for). We fix for NT5 and beyond, but downlevel
// systems are forever broken. Hopefully this feature is rarely enough
// used (and never mailed) that nobody will notice we're broken.
if (SUCCEEDED(pstm->Read(&dfh, sizeof(dfh), &cbRead)) &&
(sizeof(dfh) == cbRead) && (DOCFIND_SIG == dfh.wSig))
{
DFC_UNICODE_DESC desc;
LARGE_INTEGER dlibMove = {0, 0};
WORD fCharType = 0;
// Check the stream's signature to see if it was generated by Win95 or NT.
dlibMove.QuadPart = -(LONGLONG)sizeof(desc);
pstm->Seek(dlibMove, STREAM_SEEK_END, NULL);
pstm->Read(&desc, sizeof(desc), &cbRead);
if (cbRead > 0 && desc.NTsignature == c_NTsignature)
{
// NT-generated stream. Read in Unicode criteria.
fCharType = DFC_FMT_UNICODE;
dlibMove.QuadPart = desc.oUnicodeCriteria.QuadPart;
}
else
{
// Win95-generated stream. Read in ANSI criteria.
fCharType = DFC_FMT_ANSI;
dlibMove.LowPart = dfh.oCriteria;
}
pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL);
_pfilter->RestoreCriteria(pstm, dfh.cCriteria, fCharType);
// Now read in the results
dlibMove.LowPart = dfh.oResults;
pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL);
if (dfh.wVer > 1)
{
// only restore this way if version 2 data....
// Now Restore away the folder list
RestoreFolderList(pstm);
int cItems = 0;
RestoreItemList(pstm, &cItems);
if (cItems > 0)
psfv->SetObjectCount(cItems, SFVSOC_NOSCROLL);
}
}
else
hr = E_FAIL;
pstm->Release();
}
return hr;
}
// a form of this code is duplicated in browseui searchext.cpp
//
BOOL RealFindFiles(LPCITEMIDLIST pidlFolder, LPCITEMIDLIST pidlSaveFile)
{
// First create the top level browser...
IWebBrowser2 *pwb2;
HRESULT hr = CoCreateInstance(CLSID_ShellBrowserWindow, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARG(IWebBrowser2, &pwb2));
if (SUCCEEDED(hr))
{
VARIANT varClsid;
hr = InitBSTRVariantFromGUID(&varClsid, CLSID_FileSearchBand);
if (SUCCEEDED(hr))
{
VARIANT varEmpty = {0};
// show a search bar
hr = pwb2->ShowBrowserBar(&varClsid, &varEmpty, &varEmpty);
if (SUCCEEDED(hr))
{
// Grab the band's IUnknown from browser property.
VARIANT varFsb;
hr = pwb2->GetProperty(varClsid.bstrVal, &varFsb);
if (SUCCEEDED(hr))
{
// QI for IFileSearchBand, which we'll use to program the search band's
// search type (files or folders), inititial scope, and/or saved query file.
IFileSearchBand* pfsb;
if (SUCCEEDED(QueryInterfaceVariant(varFsb, IID_PPV_ARG(IFileSearchBand, &pfsb))))
{
BSTR bstrSearch;
hr = BSTRFromCLSID(SRCID_SFileSearch, &bstrSearch);
if (SUCCEEDED(hr))
{
VARIANT varQueryFile = {0}, varScope = {0};
// assign initial scope
if (pidlFolder)
InitVariantFromIDList(&varScope, pidlFolder);
// assign query file from which to restore search
else if (pidlSaveFile)
InitVariantFromIDList(&varQueryFile, pidlSaveFile);
pfsb->SetSearchParameters(&bstrSearch, VARIANT_TRUE, &varScope, &varQueryFile);
VariantClear(&varScope);
VariantClear(&varQueryFile);
SysFreeString(bstrSearch);
}
pfsb->Release();
}
VariantClear(&varFsb);
}
if (SUCCEEDED(hr))
hr = pwb2->put_Visible(TRUE);
}
VariantClear(&varClsid); // frees bstrFileSearchBand too
}
pwb2->Release();
}
return hr;
}
HRESULT CFindFolder::OpenContainingFolder(IUnknown *punkSite)
{
IFolderView *pfv;
HRESULT hr = IUnknown_QueryService(punkSite, SID_SFolderView, IID_PPV_ARG(IFolderView, &pfv));
if (SUCCEEDED(hr))
{
IEnumIDList *penum;
hr = pfv->Items(SVGIO_SELECTION, IID_PPV_ARG(IEnumIDList, &penum));
if (S_OK == hr)
{
LPITEMIDLIST pidl;
ULONG c;
while (S_OK == penum->Next(1, &pidl, &c))
{
// Now get the parent of it.
LPITEMIDLIST pidlParent;
if (SUCCEEDED(GetParentsPIDL(pidl, &pidlParent)))
{
SHOpenFolderAndSelectItems(pidlParent, 1, (LPCITEMIDLIST *)&pidl, 0);
ILFree(pidlParent);
}
ILFree(pidl);
}
penum->Release();
}
pfv->Release();
}
return hr;
}
// Save away the current search to a file on the desktop.
// For now the name will be automatically generated.
//
void CFindFolder::Save(IFindFilter* pfilter, HWND hwnd, DFBSAVEINFO * pSaveInfo, IShellView* psv, IUnknown *pObject)
{
TCHAR szFilePath[MAX_PATH];
IStream * pstm;
DFHEADER dfh;
TCHAR szTemp[MAX_PATH];
SHORT cb;
LARGE_INTEGER dlibMove = {0, 0};
ULARGE_INTEGER libCurPos;
FOLDERSETTINGS fs;
//
// See if the search already has a file name associated with it. If so
// we will save it in it, else we will create a new file on the desktop
if (pfilter->FFilterChanged() == S_FALSE)
{
// Lets blow away the save file
ILFree(pSaveInfo->pidlSaveFile);
pSaveInfo->pidlSaveFile = NULL;
}
// If it still looks like we want to continue to use a save file then
// continue.
if (pSaveInfo->pidlSaveFile)
{
SHGetPathFromIDList(pSaveInfo->pidlSaveFile, szFilePath);
}
else
{
// First get the path name to the Desktop.
SHGetSpecialFolderPath(NULL, szFilePath, CSIDL_PERSONAL, TRUE);
// and update the title
// we now do this before getting a filename because we generate
// the file name from the title
LPTSTR pszTitle;
pfilter->GenerateTitle(&pszTitle, TRUE);
if (pszTitle)
{
// Now add on the extension.
lstrcpyn(szTemp, pszTitle, MAX_PATH - (lstrlen(szFilePath) + 1 + 4 + 1+3));
lstrcat(szTemp, TEXT(".fnd"));
LocalFree(pszTitle); // And free the title string.
}
else
{
szTemp[0] = 0;
}
// Now loop through and replace all of the invalid characters with _'s
// we special case a few of the characters...
for (LPTSTR lpsz = szTemp; *lpsz; lpsz = CharNext(lpsz))
{
if (PathGetCharType(*lpsz) & (GCT_INVALID|GCT_WILD|GCT_SEPARATOR))
{
switch (*lpsz)
{
case TEXT(':'):
*lpsz = TEXT('-');
break;
case TEXT('*'):
*lpsz = TEXT('@');
break;
case TEXT('?'):
*lpsz = TEXT('!');
break;
default:
*lpsz = TEXT('_');
}
}
}
TCHAR szShortName[12];
LoadString(HINST_THISDLL, IDS_FIND_SHORT_NAME, szShortName, ARRAYSIZE(szShortName));
if (!PathYetAnotherMakeUniqueName(szFilePath, szFilePath, szShortName, szTemp))
return;
}
// Now lets bring up the save as dialog...
TCHAR szFilter[MAX_PATH];
TCHAR szTitle[MAX_PATH];
TCHAR szFilename[MAX_PATH];
OPENFILENAME ofn = { 0 };
LoadString(g_hinst, IDS_FINDFILESFILTER, szFilter, ARRAYSIZE(szFilter));
LoadString(g_hinst, IDS_FINDSAVERESULTSTITLE, szTitle, ARRAYSIZE(szTitle));
//Strip out the # and make them Nulls for SaveAs Dialog
LPTSTR psz = szFilter;
while (*psz)
{
if (*psz == TEXT('#'))
*psz = 0;
psz++;
}
lstrcpy(szFilename, PathFindFileName(szFilePath));
PathRemoveFileSpec(szFilePath);
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.hInstance = g_hinst;
ofn.lpstrFilter = szFilter;
ofn.lpstrFile = szFilename;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrInitialDir = szFilePath;
ofn.lpstrTitle = szTitle;
ofn.lpstrDefExt = TEXT("fnd");
ofn.Flags = OFN_EXPLORER | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST |
OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR;
ofn.lpTemplateName = NULL;
ofn.lpfnHook= NULL;
ofn.lCustData = NULL;
if (!GetSaveFileName(&ofn))
return;
if (FAILED(SHCreateStreamOnFile(szFilename, STGM_CREATE | STGM_WRITE | STGM_SHARE_DENY_WRITE, &pstm)))
return;
// remember the file that we saved away to...
ILFree(pSaveInfo->pidlSaveFile);
SHParseDisplayName(szFilename, NULL, &pSaveInfo->pidlSaveFile, 0, NULL);
// Now setup and write out header information
ZeroMemory(&dfh, sizeof(dfh));
dfh.wSig = DOCFIND_SIG;
dfh.wVer = DF_CURFILEVER;
dfh.dwFlags = pSaveInfo->dwFlags;
dfh.wSortOrder = (WORD)pSaveInfo->SortMode;
dfh.wcbItem = sizeof(DFITEM);
dfh.oCriteria = sizeof(dfh);
// dfh.cCriteria = sizeof(s_aIndexes) / sizeof(SHORT);
// dfh.oResults =;
// Not used anymore...
dfh.cResults = -1;
// Note: Later we may convert this to DOCFILE where the
// criteria is stored as properties.
// Get the current Folder Settings
if (SUCCEEDED(psv->GetCurrentInfo(&fs)))
dfh.ViewMode = fs.ViewMode;
else
dfh.ViewMode = FVM_DETAILS;
// Now call the filter object to save out his own set of criterias
dlibMove.LowPart = dfh.oCriteria;
pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL);
HRESULT hr = pfilter->SaveCriteria(pstm, DFC_FMT_ANSI);
if (SUCCEEDED(hr))
dfh.cCriteria = GetScode(hr);
// Now setup to output the results
dlibMove.LowPart = 0;
pstm->Seek(dlibMove, STREAM_SEEK_CUR, &libCurPos); // Get current pos
dfh.oResults = libCurPos.LowPart;
//
// Now Let our file folder serialize his results out here also...
// But only if the option is set to do so...
//
cb = 0;
// Write out a Trailing NULL for Folder list
pstm->Write(&cb, sizeof(cb), NULL);
// And item list.
pstm->Write(&cb, sizeof(cb), NULL);
// END of DFHEADER_WIN95 information
// BEGIN of NT5 information:
// Now setup to output the history stream
if (pObject)
{
dlibMove.LowPart = 0;
pstm->Seek(dlibMove, STREAM_SEEK_CUR, &libCurPos); // Get current pos
dfh.oHistory = libCurPos.LowPart;
if (FAILED(SavePersistHistory(pObject, pstm)))
{
// On failure we might as well just pretend we didn't save this bit of data.
// Do we need an error message -- the ui won't be right when relaunched...
//
dfh.oHistory = 0;
dlibMove.LowPart = libCurPos.LowPart;
pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL);
}
}
// In NT the below was done AT THE END OF THE STREAM instead of
// revving the DFHEADER struct. (Okay, DFHEADEREX, since Win95
// already broke DFHEADER back compat by in improper version check)
// This could have been done by putting a propery signatured
// DFHEADEREX that had proper versioning so we could add information
// to. Unfortunately another hardcoded struct was tacked on to
// the end of the stream... Next time, please fix the problem
// instead of work around it.
//
// What this boils down to is we cannot put any information
// after the DFC_UNICODE_DESC section, so might as well
// always do this SaveCriteria section last...
//
// See comment at top of file for DFC_UNICODE_DESC.
//
DFC_UNICODE_DESC desc;
//
// Get the current location in stream. This is the offset where
// we'll write the unicode find criteria. Save this
// value (along with NT-specific signature) in the descriptor
//
dlibMove.LowPart = 0;
pstm->Seek(dlibMove, STREAM_SEEK_CUR, &libCurPos);
desc.oUnicodeCriteria.QuadPart = libCurPos.QuadPart;
desc.NTsignature = c_NTsignature;
// Append the Unicode version of the find criteria.
hr = pfilter->SaveCriteria(pstm, DFC_FMT_UNICODE);
// Append the unicode criteria descriptor to the end of the file.
pstm->Write(&desc, sizeof(desc), NULL);
//
// don't put any code between the above DFC_UNICDE_DESC section
// and this back-patch of the dfh header...
//
// Finally output the header information at the start of the file
// and close the file
//
pstm->Seek(g_li0, STREAM_SEEK_SET, NULL);
pstm->Write(&dfh, sizeof(dfh), NULL);
pstm->Release();
SHChangeNotify(SHCNE_CREATE, SHCNF_IDLIST, pSaveInfo->pidlSaveFile, NULL);
SHChangeNotify(SHCNE_FREESPACE, SHCNF_IDLIST, pSaveInfo->pidlSaveFile, NULL);
}
// Broke out from class to share with old and new code
BOOL CFindFolder::HandleUpdateDir(LPCITEMIDLIST pidl, BOOL fCheckSubDirs)
{
// 1. Start walk through list of dirs. Find list of directories effected
// and mark them
// 2. Walk the list of items that we have and mark each of the items that
// that are in our list of directories and then do a search...
BOOL fCurrentItemsMayBeImpacted = FALSE;
FIND_FOLDER_ITEM *pffli;
INT cPidf;
// First see which directories are effected...
GetFolderListItemCount(&cPidf);
for (int iPidf = 0; iPidf < cPidf; iPidf++)
{
if (SUCCEEDED(GetFolderListItem(iPidf, &pffli))
&& !pffli->fUpdateDir) // We may have already impacted these...
{
pffli->fUpdateDir = ILIsParent(pidl, &pffli->idl, FALSE);
fCurrentItemsMayBeImpacted |= pffli->fUpdateDir;
}
}
if (fCurrentItemsMayBeImpacted)
{
// Now we need to walk through the whole list and remove any entries
// that are no longer there...
//
int iItem;
if (SUCCEEDED(GetItemCount(&iItem)))
{
for (--iItem; iItem >= 0; iItem--)
{
FIND_ITEM *pesfi;
if (SUCCEEDED(GetItem(iItem, &pesfi)) && pesfi)
{
UINT iFolder = GetFolderIndex(&pesfi->idl);
// See if item may be impacted...
if (SUCCEEDED(GetFolderListItem(iFolder, &pffli)) && pffli->fUpdateDir)
pesfi->dwState |= CDFITEM_STATE_MAYBEDELETE;
}
}
}
}
return fCurrentItemsMayBeImpacted;
}
void CFindFolder::UpdateOrMaybeAddPidl(IShellFolderView *psfv, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlOld)
{
HRESULT hr;
// First see if we should try to do an update...
if (pidlOld)
{
LPITEMIDLIST pidlT;
if (S_OK == MapToSearchIDList(pidl, TRUE, &pidlT))
{
SetItemsChangedSinceSort();
UINT iItem;
// cast needed for bad interface def
hr = psfv->UpdateObject((LPITEMIDLIST)pidlOld, (LPITEMIDLIST)pidlT, &iItem);
ILFree(pidlT); // In either case simply blow away our generated pidl...
if (SUCCEEDED(hr))
return;
}
}
IShellFolder *psf;
LPCITEMIDLIST pidlChild;
if (SUCCEEDED(SHBindToIDListParent(pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlChild)))
{
BOOL fMatch = FALSE;
// See if this item matches the filter...
IFindFilter *pfilter;
if (SUCCEEDED(GetFindFilter(&pfilter)))
{
fMatch = pfilter->MatchFilter(psf, pidlChild) != 0;
pfilter->Release();
}
psf->Release();
if (fMatch)
{
LPITEMIDLIST pidlT;
if (S_OK != MapToSearchIDList(pidl, TRUE, &pidlT))
{
fMatch = FALSE;
// The folder has not been added before now...
TCHAR szPath[MAX_PATH];
SHGetPathFromIDList(pidl, szPath);
if (!IsFileInBitBucket(szPath))
{
PathRemoveFileSpec(szPath);
LPITEMIDLIST pidlFolder;
if (SUCCEEDED(SHParseDisplayName(szPath, NULL, &pidlFolder, 0, NULL)))
{
int iFolder;
hr = AddFolder(pidlFolder, TRUE, &iFolder);
if (SUCCEEDED(hr))
{
fMatch = (S_OK == MapToSearchIDList(pidl, TRUE, &pidlT));
}
ILFree(pidlFolder);
}
}
}
if (fMatch)
{
// There are times we get notified twice. To handle this
// see if the item is already in our list. If so punt...
SetItemsChangedSinceSort();
UINT iItem;
if (FAILED(psfv->UpdateObject(pidlT, pidlT, &iItem)))
{
// item not in the view yet... so we need to add it
if (SUCCEEDED(GetItemCount((INT *)&iItem)))
{
// Normal case would be here to add the object
// We need to add this to our dpa and dsa...
FIND_ITEM *pesfi;
AddPidl(iItem, pidlT, (UINT)-1, &pesfi);
if (pesfi)
psfv->SetObjectCount(++iItem, SFVSOC_NOSCROLL);
}
}
ILFree(pidlT);
}
else
{
ASSERT(NULL == pidlT);
}
}
}
}
void CFindFolder::HandleRMDir(IShellFolderView *psfv, LPCITEMIDLIST pidl)
{
BOOL fCurrentItemsMayBeImpacted = FALSE;
FIND_FOLDER_ITEM *pffli;
INT cItems;
FIND_ITEM *pesfi;
// First see which directories are effected...
GetFolderListItemCount(&cItems);
for (int iItem = 0; iItem < cItems; iItem++)
{
if (SUCCEEDED(GetFolderListItem(iItem, &pffli)))
{
pffli->fDeleteDir = ILIsParent(pidl, &pffli->idl, FALSE);
fCurrentItemsMayBeImpacted |= pffli->fDeleteDir;
}
else
{
#ifdef DEBUG
INT cItem;
GetFolderListItemCount(&cItem);
TraceMsg(TF_WARNING, "NULL pffli in _handleRMDir (iItem == %d, ItemCount()==%d)!!!", iItem, cItems);
#endif
}
}
if (fCurrentItemsMayBeImpacted)
{
// Now we need to walk through the whole list and remove any entries
// that are no longer there...
if (SUCCEEDED(GetItemCount(&iItem)))
{
for (--iItem; iItem >= 0; iItem--)
{
if (FAILED(GetItem(iItem, &pesfi)) || pesfi == NULL)
continue;
// See if item may be impacted...
UINT iFolder = GetFolderIndex(&pesfi->idl);
if (SUCCEEDED(GetFolderListItem(iFolder, &pffli))
&& pffli->fDeleteDir)
{
psfv->RemoveObject(&pesfi->idl, (UINT*)&cItems);
}
}
}
}
}
// export used for Start.Search-> cascade menu
STDAPI_(IContextMenu *) SHFind_InitMenuPopup(HMENU hmenu, HWND hwnd, UINT idCmdFirst, UINT idCmdLast)
{
IContextMenu * pcm = NULL;
HKEY hkFind = SHGetShellKey(SHELLKEY_HKLM_EXPLORER, TEXT("FindExtensions"), FALSE);
if (hkFind)
{
if (SUCCEEDED(CDefFolderMenu_CreateHKeyMenu(hwnd, hkFind, &pcm)))
{
int iItems = GetMenuItemCount(hmenu);
// nuke all old entries
while (iItems--)
{
DeleteMenu(hmenu, iItems, MF_BYPOSITION);
}
pcm->QueryContextMenu(hmenu, 0, idCmdFirst, idCmdLast, CMF_NODEFAULT|CMF_INCLUDESTATIC|CMF_FINDHACK);
iItems = GetMenuItemCount(hmenu);
if (!iItems)
{
TraceMsg(TF_DOCFIND, "no menus in find extension, blowing away context menu");
pcm->Release();
pcm = NULL;
}
}
RegCloseKey(hkFind);
}
return pcm;
}
void _SetObjectCount(IShellView *psv, int cItems, DWORD dwFlags)
{
IShellFolderView *psfv;
if (SUCCEEDED(psv->QueryInterface(IID_PPV_ARG(IShellFolderView, &psfv))))
{
psfv->SetObjectCount(cItems, dwFlags);
psfv->Release();
}
}
typedef struct
{
PFNLVCOMPARE pfnCompare;
LPARAM lParamSort;
} FIND_SORT_INFO;
int CALLBACK _FindCompareItems(void *p1, void *p2, LPARAM lParam)
{
FIND_SORT_INFO *pfsi = (FIND_SORT_INFO*)lParam;
return pfsi->pfnCompare(PtrToInt(p1), PtrToInt(p2), pfsi->lParamSort);
}
HRESULT CFindFolderViewCB::OnSortListData(DWORD pv, PFNLVCOMPARE pfnCompare, LPARAM lParamSort)
{
EnterCriticalSection(&_pff->_csSearch);
// First mark the focused item in the list so we can find it later...
FIND_ITEM *pesfi = (FIND_ITEM*)DPA_GetPtr(_pff->_hdpaItems, _iFocused); // indirect
if (pesfi)
pesfi->dwState |= LVIS_FOCUSED;
int cItems = DPA_GetPtrCount(_pff->_hdpaItems);
HDPA hdpaForSorting = NULL;
if (cItems)
{
hdpaForSorting = DPA_Create(cItems);
}
if (hdpaForSorting)
{
for (int i = 0; i< cItems; i++)
{
DPA_SetPtr(hdpaForSorting, i, IntToPtr(i));
}
// sort out items
FIND_SORT_INFO fsi;
fsi.pfnCompare = pfnCompare;
fsi.lParamSort = lParamSort;
DPA_Sort(hdpaForSorting, _FindCompareItems, (LPARAM)&fsi);
for (i = 0; i < cItems; i++)
{
int iIndex = PtrToInt(DPA_FastGetPtr(hdpaForSorting, i));
// Move the items from _hdpaItems to hdpaForSorting in sorted order
DPA_SetPtr(hdpaForSorting, i, DPA_FastGetPtr(_pff->_hdpaItems, iIndex));
}
// Now switch the two HDPA to get the sorted list in the member variable
DPA_Destroy(_pff->_hdpaItems);
_pff->_hdpaItems = hdpaForSorting;
}
// Now find the focused item and scroll it into place...
IShellView *psv;
if (_punkSite && SUCCEEDED(_punkSite->QueryInterface(IID_PPV_ARG(IShellView, &psv))))
{
int iFocused = -1;
// Tell the view we need to reshuffle....
// Gross, this one defaults to invalidate all which for this one is fine...
_SetObjectCount(psv, cItems, SFVSOC_INVALIDATE_ALL); // Invalidate all
for (int iEnd = cItems - 1; iEnd >= 0; iEnd--)
{
pesfi = (FIND_ITEM*)DPA_GetPtr(_pff->_hdpaItems, iEnd); // indirect
if (pesfi && pesfi->dwState & LVIS_FOCUSED)
iFocused = iEnd;
}
// Now handle the focused item...
if (iFocused != -1)
{
_pff->_iGetIDList = iFocused; // remember the last one we retrieved...
pesfi = (FIND_ITEM*)DPA_GetPtr(_pff->_hdpaItems, iFocused); // indirect
if (pesfi)
{
// flags depend on first one and also if selected?
psv->SelectItem(&pesfi->idl, SVSI_FOCUSED | SVSI_ENSUREVISIBLE | SVSI_SELECT);
pesfi->dwState &= ~LVIS_FOCUSED; // don't keep it around to get lost later...
}
}
_iFocused = iFocused;
_fIgnoreSelChange = FALSE;
psv->Release();
}
LeaveCriticalSection(&_pff->_csSearch);
return S_OK;
}
HRESULT CFindFolderViewCB::OnMergeMenu(DWORD pv, QCMINFO*lP)
{
DebugMsg(DM_TRACE, TEXT("sh TR - DF_FSNCallBack DVN_MERGEMENU"));
UINT idCmdFirst = lP->idCmdFirst;
UINT idBGMain = 0, idBGPopup = 0;
_pff->_pfilter->GetFolderMergeMenuIndex(&idBGMain, &idBGPopup);
CDefFolderMenu_MergeMenu(HINST_THISDLL, 0, idBGPopup, lP);
// Lets remove some menu items that are not useful to us.
HMENU hmenu = lP->hmenu;
DeleteMenu(hmenu, idCmdFirst + SFVIDM_EDIT_PASTE, MF_BYCOMMAND);
DeleteMenu(hmenu, idCmdFirst + SFVIDM_EDIT_PASTELINK, MF_BYCOMMAND);
// DeleteMenu(hmenu, idCmdFirst + SFVIDM_EDIT_PASTESPECIAL, MF_BYCOMMAND);
// This is sortof bogus but if after the merge one of the
// menus has no items in it, remove the menu.
for (int i = GetMenuItemCount(hmenu) - 1; i >= 0; i--)
{
HMENU hmenuSub = GetSubMenu(hmenu, i);
if (hmenuSub && (GetMenuItemCount(hmenuSub) == 0))
{
DeleteMenu(hmenu, i, MF_BYPOSITION);
}
}
return S_OK;
}
HRESULT CFindFolderViewCB::OnGETWORKINGDIR(DWORD pv, UINT wP, LPTSTR lP)
{
HRESULT hr = E_FAIL;
IShellFolderView *psfv;
if (_punkSite && SUCCEEDED(_punkSite->QueryInterface(IID_PPV_ARG(IShellFolderView, &psfv))))
{
LPCITEMIDLIST *ppidls; // pointer to a list of pidls.
UINT cpidls = 0; // Count of pidls that were returned.
psfv->GetSelectedObjects(&ppidls, &cpidls);
if (cpidls > 0)
{
LPITEMIDLIST pidl;
if (SUCCEEDED(_pff->GetParentsPIDL(ppidls[0], &pidl)))
{
SHGetPathFromIDList(pidl, lP);
ILFree(pidl);
}
LocalFree((void *)ppidls); // const -> non const
hr = S_OK;
}
else
{
hr = E_FAIL;
}
psfv->Release();
}
return hr;
}
HRESULT CFindFolderViewCB::OnGETCOLSAVESTREAM(DWORD pv, WPARAM wP, IStream **ppstm)
{
return _pff->_pfilter->GetColSaveStream(wP, ppstm);
}
HRESULT CFindFolderViewCB::OnGETITEMIDLIST(DWORD pv, WPARAM iItem, LPITEMIDLIST *ppidl)
{
FIND_ITEM *pesfi;
if (SUCCEEDED(_pff->GetItem((int) iItem, &pesfi)) && pesfi)
{
*ppidl = &pesfi->idl; // return alias!
return S_OK;
}
*ppidl = NULL;
return E_FAIL;
}
// in defviewx.c
STDAPI SHGetIconFromPIDL(IShellFolder *psf, IShellIcon *psi, LPCITEMIDLIST pidl, UINT flags, int *piImage);
HRESULT CFindFolderViewCB::OnGetItemIconIndex(DWORD pv, WPARAM iItem, int *piIcon)
{
FIND_ITEM *pesfi;
*piIcon = -1;
if (SUCCEEDED(_pff->GetItem((int) iItem, &pesfi)) && pesfi)
{
if (pesfi->iIcon == -1)
{
IShellFolder* psf = (IShellFolder*)_pff;
SHGetIconFromPIDL(psf, NULL, &pesfi->idl, 0, &pesfi->iIcon);
}
*piIcon = pesfi->iIcon;
return S_OK;
}
return E_FAIL;
}
HRESULT CFindFolderViewCB::OnSetItemIconOverlay(DWORD pv, WPARAM iItem, int iOverlayIndex)
{
HRESULT hr = E_FAIL;
FIND_ITEM *pesfi;
if (SUCCEEDED(_pff->GetItem((int) iItem, &pesfi)) && pesfi)
{
pesfi->dwMask |= ESFITEM_ICONOVERLAYSET;
pesfi->dwState |= INDEXTOOVERLAYMASK(iOverlayIndex) & LVIS_OVERLAYMASK;
hr = S_OK;
}
return hr;
}
HRESULT CFindFolderViewCB::OnGetItemIconOverlay(DWORD pv, WPARAM iItem, int * piOverlayIndex)
{
HRESULT hr = E_FAIL;
*piOverlayIndex = SFV_ICONOVERLAY_DEFAULT;
FIND_ITEM *pesfi;
if (SUCCEEDED(_pff->GetItem((int) iItem, &pesfi)) && pesfi)
{
if (pesfi->dwMask & ESFITEM_ICONOVERLAYSET)
{
*piOverlayIndex = OVERLAYMASKTO1BASEDINDEX(pesfi->dwState & LVIS_OVERLAYMASK);
}
else
*piOverlayIndex = SFV_ICONOVERLAY_UNSET;
hr = S_OK;
}
return hr;
}
HRESULT CFindFolderViewCB::OnSETITEMIDLIST(DWORD pv, WPARAM iItem, LPITEMIDLIST pidl)
{
FIND_ITEM *pesfi;
_pff->_iGetIDList = (int) iItem; // remember the last one we retrieved...
if (SUCCEEDED(_pff->GetItem((int) iItem, &pesfi)) && pesfi)
{
FIND_ITEM *pesfiNew;
if (SUCCEEDED(_pff->AddPidl((int) iItem, pidl, 0, &pesfiNew) && pesfiNew))
{
pesfiNew->dwState = pesfi->dwState;
LocalFree((HLOCAL)pesfi); // Free the old one...
}
return S_OK;
}
return E_FAIL;
}
BOOL DF_ILIsEqual(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
{
BOOL bRet = (pidl1 == pidl2);
if (!bRet)
{
PCHIDDENDOCFINDDATA phdfd1 = (PCHIDDENDOCFINDDATA) ILFindHiddenID(pidl1, IDLHID_DOCFINDDATA);
PCHIDDENDOCFINDDATA phdfd2 = (PCHIDDENDOCFINDDATA) ILFindHiddenID(pidl2, IDLHID_DOCFINDDATA);
if (phdfd1 && phdfd2)
bRet = (phdfd1->iFolder == phdfd2->iFolder) && ILIsEqual(pidl1, pidl2);
}
return bRet;
}
HRESULT CFindFolderViewCB::OnGetIndexForItemIDList(DWORD pv, int * piItem, LPITEMIDLIST pidl)
{
int cItems;
// Try to short circuit searching for pidls...
if (SUCCEEDED(_pff->GetItemCount(&cItems)) && _pff->_iGetIDList < cItems)
{
FIND_ITEM *pesfi;
if (SUCCEEDED(_pff->GetItem(_pff->_iGetIDList, &pesfi)) && pesfi)
{
if (DF_ILIsEqual(&pesfi->idl, pidl))
{
// Yep it was ours so return the index quickly..
*piItem = _pff->_iGetIDList;
return S_OK;
}
}
}
// Otherwise let it search the old fashion way...
return E_FAIL;
}
HRESULT CFindFolderViewCB::OnDeleteItem(DWORD pv, LPCITEMIDLIST pidl)
{
// We simply need to remove this item from our list. The
// underlying listview will decrement the count on their end...
FIND_ITEM *pesfi;
int iItem;
int cItems;
BOOL bFound;
if (!pidl)
{
_pff->SetAsyncEnum(NULL);
return S_OK; // special case telling us all items deleted...
}
bFound = FALSE;
if (SUCCEEDED(_pff->GetItem(_pff->_iGetIDList, &pesfi))
&& pesfi
&& (DF_ILIsEqual(&pesfi->idl, pidl)))
{
iItem = _pff->_iGetIDList;
bFound = TRUE;
}
else
{
if (SUCCEEDED(_pff->GetItemCount(&cItems)))
{
for (iItem = 0; iItem < cItems; iItem++)
{
if (SUCCEEDED(_pff->GetItem(iItem, &pesfi)) && pesfi && (DF_ILIsEqual(&pesfi->idl, pidl)))
{
bFound = TRUE;
break;
}
}
}
}
if (bFound)
{
_pff->DeleteItem(iItem);
}
return S_OK;
}
HRESULT CFindFolderViewCB::OnODFindItem(DWORD pv, int * piItem, NM_FINDITEM* pnmfi)
{
// We have to do the subsearch ourself to find the correct item...
// As the listview has no information saved in it...
int iItem = pnmfi->iStart;
int cItem;
UINT flags = pnmfi->lvfi.flags;
if (FAILED(_pff->GetItemCount(&cItem)))
return E_FAIL;
if ((flags & LVFI_STRING) == 0)
return E_FAIL; // Not sure what type of search this is...
int cbString = lstrlen(pnmfi->lvfi.psz);
for (int j = cItem; j-- != 0;)
{
if (iItem >= cItem)
{
if (flags & LVFI_WRAP)
iItem = 0;
else
break;
}
// Now we need to get the Display name for this item...
FIND_ITEM *pesfi;
TCHAR szPath[MAX_PATH];
IShellFolder* psf = (IShellFolder*)_pff;
if (SUCCEEDED(_pff->GetItem(iItem, &pesfi)) && pesfi &&
SUCCEEDED(DisplayNameOf(psf, &pesfi->idl, NULL, szPath, ARRAYSIZE(szPath))))
{
if (flags & (LVFI_PARTIAL|LVFI_SUBSTRING))
{
if (CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
pnmfi->lvfi.psz, cbString, szPath, cbString) == 2)
{
*piItem = iItem;
return S_OK;
}
}
else if (lstrcmpi(pnmfi->lvfi.psz, szPath) == 0)
{
*piItem = iItem;
return S_OK;
}
}
++iItem;
}
return E_FAIL;
}
HRESULT CFindFolderViewCB::OnSelChange(DWORD pv, UINT wPl, UINT wPh, SFVM_SELCHANGE_DATA *lP)
{
// Try to remember which item is focused...
if (lP->uNewState & LVIS_FOCUSED)
_iFocused = wPh;
return S_OK;
}
HRESULT CFindFolderViewCB::OnSetEmptyText(DWORD pv, UINT res, LPCTSTR pszText)
{
if (pszText && 0 == lstrcmp(_szEmptyText, pszText))
return S_OK;
lstrcpyn(_szEmptyText, pszText ? pszText : TEXT(""), ARRAYSIZE(_szEmptyText));
HWND hwndLV = ListviewFromViewUnk(_punkSite);
if (hwndLV)
SendMessage(hwndLV, LVM_RESETEMPTYTEXT, 0, 0);
return S_OK;
}
HRESULT CFindFolderViewCB::OnGetEmptyText(DWORD pv, UINT cchTextMax, LPTSTR pszText)
{
if (_szEmptyText[0])
{
lstrcpyn(pszText, _szEmptyText, cchTextMax);
}
else
{
LoadString(HINST_THISDLL, IDS_FINDVIEWEMPTYINIT, pszText, cchTextMax);
}
return S_OK;
}
HRESULT CFindFolderViewCB::OnReArrange(DWORD pv, LPARAM lparam)
{
UINT nCol = (UINT)lparam;
// See if there is any controller object registered that may want to take over this...
// if we are in a mixed query and we have already fetched the async items, simply sort
// the dpa's...
IFindEnum *pidfenum;
if (S_OK == _pff->GetAsyncEnum(&pidfenum))
{
if (!((pidfenum->FQueryIsAsync() == DF_QUERYISMIXED) && _pff->_fAllAsyncItemsCached))
{
if (_pff->_pfcn)
{
// if they return S_FALSE it implies that they handled it and they do not
// want the default processing to happen...
if (_pff->_pfcn->DoSortOnColumn(nCol, _iColSort == nCol) == S_FALSE)
{
_iColSort = nCol;
return S_OK;
}
}
else
{
// If we are running in the ROWSET way, we may want to have the ROWSET do the work...
// pass one we spawn off a new search with the right column sorted
if (_iColSort != nCol)
{
_iColSort = nCol;
}
// Warning the call above may release our AsyncEnum and generate a new one so
// Don't rely on it's existence here...
return S_OK;
}
}
// we must pull in all the results from ci
if (pidfenum->FQueryIsAsync() && !_pff->_fAllAsyncItemsCached)
_pff->CacheAllAsyncItems();
#ifdef DEBUG
#define MAX_LISTVIEWITEMS (100000000 & ~0xFFFF)
#define SANE_ITEMCOUNT(c) ((int)min(c, MAX_LISTVIEWITEMS))
if (pidfenum->FQueryIsAsync())
{
ASSERT(DPA_GetPtrCount(_pff->_hdpaItems) >= SANE_ITEMCOUNT(_pff->_cAsyncItems));
for (int i = 0; i < SANE_ITEMCOUNT(_pff->_cAsyncItems); i++)
{
FIND_ITEM *pesfi = (FIND_ITEM *)DPA_GetPtr(_pff->_hdpaItems, i);
ASSERT(pesfi);
if (!pesfi)
{
ASSERT(SUCCEEDED(_pff->GetItem(i, &pesfi)));
}
}
}
#endif
}
// Use the common sort.
return E_FAIL;
}
HRESULT CFindFolderViewCB::OnWindowCreated(DWORD pv, HWND hwnd)
{
_ProfferService(TRUE); // register our service w/ top level container
return S_OK;
}
HRESULT CFindFolderViewCB::_ProfferService(BOOL bProffer)
{
HRESULT hr = E_FAIL;
if (bProffer)
{
// shouldn't be redundantly registering our service
ASSERT(NULL == _pps);
ASSERT(-1 == _dwServiceCookie);
IProfferService* pps;
hr = IUnknown_QueryService(_punkSite, SID_STopLevelBrowser, IID_PPV_ARG(IProfferService, &pps));
if (SUCCEEDED(hr))
{
hr = pps->ProfferService(SID_DocFindFolder, this, &_dwServiceCookie);
if (SUCCEEDED(hr))
{
pps->AddRef();
_pps = pps;
}
pps->Release();
}
}
else
{
if (NULL == _pps)
{
hr = S_OK;
}
else
{
hr = _pps->RevokeService(_dwServiceCookie);
if (SUCCEEDED(hr))
{
ATOMICRELEASE(_pps);
_dwServiceCookie = -1;
}
}
}
return hr;
}
HRESULT CFindFolderViewCB::OnWindowDestroy(DWORD pv, HWND wP)
{
_ProfferService(FALSE); // unregister our service w/ top level container
if (_pff->_pfcn)
_pff->_pfcn->StopSearch();
// The search may have a circular set of pointers. So call the
// delete items and folders here to remove these back references...
_pff->ClearItemList();
_pff->ClearFolderList();
IFindControllerNotify *pfcn;
if (_pff->GetControllerNotifyObject(&pfcn) == S_OK)
{
pfcn->ViewDestroyed();
pfcn->Release();
}
return S_OK;
}
HRESULT CFindFolderViewCB::OnIsOwnerData(DWORD pv, DWORD *pdwFlags)
{
*pdwFlags |= FWF_OWNERDATA; // we want virtual defview support
return S_OK;
}
HRESULT CFindFolderViewCB::OnGetODRangeObject(DWORD pv, WPARAM wWhich, ILVRange **plvr)
{
HRESULT hr = E_FAIL;
switch (wWhich)
{
case LVSR_SELECTION:
hr = _pff->_dflvrSel.QueryInterface(IID_PPV_ARG(ILVRange, plvr));
break;
case LVSR_CUT:
hr = _pff->_dflvrCut.QueryInterface(IID_PPV_ARG(ILVRange, plvr));
break;
}
return hr;
}
HRESULT CFindFolderViewCB::OnODCacheHint(DWORD pv, NMLVCACHEHINT* pnmlvc)
{
// The listview is giving us a hint of the items it is about to do something in a range
// so make sure we have pidls for each of the items in the range...
int iTo;
_pff->GetItemCount(&iTo);
if (iTo >= pnmlvc->iTo)
iTo = pnmlvc->iTo;
else
iTo--;
for (int i = pnmlvc->iFrom; i <= iTo; i++)
{
FIND_ITEM *pesfi;
if (FAILED(_pff->GetItem(i, &pesfi)))
break;
}
return S_OK;
}
HRESULT CFindFolderViewCB::OnDEFVIEWMODE(DWORD pv, FOLDERVIEWMODE*lP)
{
*lP = FVM_DETAILS; // match the advanced mode of SC (+ Win2K parity)
return S_OK;
}
HRESULT CFindFolderViewCB::OnGetWebViewLayout(DWORD pv, UINT uViewMode, SFVM_WEBVIEW_LAYOUT_DATA* pData)
{
ZeroMemory(pData, sizeof(*pData));
pData->dwLayout = SFVMWVL_DETAILS | SFVMWVL_FILES;
return S_OK;
}
HRESULT CFindFolderViewCB::_OnOpenContainingFolder(IUnknown* pv, IShellItemArray *psiItemArray, IBindCtx *pbc)
{
CFindFolderViewCB* pThis = (CFindFolderViewCB*)(void*)pv;
return pThis->_pff->OpenContainingFolder(pThis->_punkSite);
}
const WVTASKITEM c_FindTaskHeader = WVTI_HEADER(L"shell32.dll", IDS_HEADER_SEARCH, IDS_HEADER_FIND_TT);
const WVTASKITEM c_FindTaskList[] =
{
WVTI_ENTRY_TITLE(CLSID_NULL, L"shell32.dll", IDS_TASK_OPENCONTAININGFOLDER, IDS_TASK_OPENCONTAININGFOLDER, 0, IDS_TASK_OPENCONTAININGFOLDER_TT, IDI_TASK_OPENCONTAININGFOLDER, NULL, CFindFolderViewCB::_OnOpenContainingFolder),
};
HRESULT CFindFolderViewCB::OnGetWebViewContent(DWORD pv, SFVM_WEBVIEW_CONTENT_DATA* pData)
{
ZeroMemory(pData, sizeof(*pData));
Create_IUIElement(&c_FindTaskHeader, &(pData->pSpecialTaskHeader));
LPCTSTR rgCSIDLs[] = { MAKEINTRESOURCE(CSIDL_DRIVES), MAKEINTRESOURCE(CSIDL_PERSONAL), MAKEINTRESOURCE(CSIDL_COMMON_DOCUMENTS), MAKEINTRESOURCE(CSIDL_NETWORK) };
CreateIEnumIDListOnCSIDLs(_pidl, rgCSIDLs, ARRAYSIZE(rgCSIDLs), &pData->penumOtherPlaces);
return S_OK;
}
HRESULT CFindFolderViewCB::OnGetWebViewTasks(DWORD pv, SFVM_WEBVIEW_TASKSECTION_DATA* pTasks)
{
ZeroMemory(pTasks, sizeof(*pTasks));
Create_IEnumUICommand((IUnknown*)(void*)this, c_FindTaskList, ARRAYSIZE(c_FindTaskList), &pTasks->penumSpecialTasks);
return S_OK;
}
HRESULT CFindFolderViewCB::OnGetWebViewTheme(DWORD pv, SFVM_WEBVIEW_THEME_DATA* pTheme)
{
ZeroMemory(pTheme, sizeof(*pTheme));
pTheme->pszThemeID = L"search";
return S_OK;
}
HRESULT CFindFolderViewCB::OnGetIPersistHistory(DWORD pv, IPersistHistory **ppph)
{
// If they call us with ppph == NULL they simply want to know if we support
// the history so return S_OK;
if (ppph == NULL)
return S_OK;
// get the persist history from us and we hold folder and view objects
*ppph = NULL;
CFindPersistHistory *pdfph = new CFindPersistHistory();
if (!pdfph)
return E_OUTOFMEMORY;
HRESULT hr = pdfph->QueryInterface(IID_PPV_ARG(IPersistHistory, ppph));
pdfph->Release();
return hr;
}
HRESULT CFindFolderViewCB::OnRefresh(DWORD pv, BOOL fPreRefresh)
{
EnterCriticalSection(&_pff->_csSearch);
_pff->_fInRefresh = BOOLIFY(fPreRefresh);
// If we have old results tell defview the new count now...
if (!fPreRefresh && _pff->_hdpaItems)
{
IShellFolderView *psfv;
UINT cItems = DPA_GetPtrCount(_pff->_hdpaItems);
if (cItems && _punkSite && SUCCEEDED(_punkSite->QueryInterface(IID_PPV_ARG(IShellFolderView, &psfv))))
{
psfv->SetObjectCount(cItems, SFVSOC_NOSCROLL);
psfv->Release();
}
}
LeaveCriticalSection(&_pff->_csSearch);
return S_OK;
}
HRESULT CFindFolderViewCB::OnGetHelpTopic(DWORD pv, SFVM_HELPTOPIC_DATA *phtd)
{
if (IsOS(OS_ANYSERVER))
{
StrCpyW(phtd->wszHelpFile, L"find.chm");
}
else
{
StrCpyW(phtd->wszHelpTopic, L"hcp://services/subsite?node=Unmapped/Search");
}
return S_OK;
}
STDMETHODIMP CFindFolderViewCB::RealMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
HANDLE_MSG(0, SFVM_MERGEMENU, OnMergeMenu);
HANDLE_MSG(0, SFVM_GETWORKINGDIR, OnGETWORKINGDIR);
HANDLE_MSG(0, SFVM_GETCOLSAVESTREAM, OnGETCOLSAVESTREAM);
HANDLE_MSG(0, SFVM_GETITEMIDLIST, OnGETITEMIDLIST);
HANDLE_MSG(0, SFVM_SETITEMIDLIST, OnSETITEMIDLIST);
HANDLE_MSG(0, SFVM_SELCHANGE, OnSelChange);
HANDLE_MSG(0, SFVM_INDEXOFITEMIDLIST, OnGetIndexForItemIDList);
HANDLE_MSG(0, SFVM_DELETEITEM, OnDeleteItem);
HANDLE_MSG(0, SFVM_ODFINDITEM, OnODFindItem);
HANDLE_MSG(0, SFVM_ARRANGE, OnReArrange);
HANDLE_MSG(0, SFVM_GETEMPTYTEXT, OnGetEmptyText);
HANDLE_MSG(0, SFVM_SETEMPTYTEXT, OnSetEmptyText);
HANDLE_MSG(0, SFVM_GETITEMICONINDEX, OnGetItemIconIndex);
HANDLE_MSG(0, SFVM_SETICONOVERLAY, OnSetItemIconOverlay);
HANDLE_MSG(0, SFVM_GETICONOVERLAY, OnGetItemIconOverlay);
HANDLE_MSG(0, SFVM_FOLDERSETTINGSFLAGS, OnIsOwnerData);
HANDLE_MSG(0, SFVM_WINDOWCREATED, OnWindowCreated);
HANDLE_MSG(0, SFVM_WINDOWDESTROY, OnWindowDestroy);
HANDLE_MSG(0, SFVM_GETODRANGEOBJECT, OnGetODRangeObject);
HANDLE_MSG(0, SFVM_ODCACHEHINT, OnODCacheHint);
HANDLE_MSG(0, SFVM_DEFVIEWMODE, OnDEFVIEWMODE);
HANDLE_MSG(0, SFVM_GETWEBVIEWLAYOUT, OnGetWebViewLayout);
HANDLE_MSG(0, SFVM_GETWEBVIEWCONTENT, OnGetWebViewContent);
HANDLE_MSG(0, SFVM_GETWEBVIEWTASKS, OnGetWebViewTasks);
HANDLE_MSG(0, SFVM_GETWEBVIEWTHEME, OnGetWebViewTheme);
HANDLE_MSG(0, SFVM_GETIPERSISTHISTORY, OnGetIPersistHistory);
HANDLE_MSG(0, SFVM_REFRESH, OnRefresh);
HANDLE_MSG(0, SFVM_GETHELPTOPIC, OnGetHelpTopic);
HANDLE_MSG(0, SFVM_SORTLISTDATA, OnSortListData);
default:
return E_FAIL;
}
return S_OK;
}
CFindFolderViewCB::CFindFolderViewCB(CFindFolder* pff) :
CBaseShellFolderViewCB(pff->_pidl, 0), _pff(pff), _fIgnoreSelChange(FALSE),
_iColSort((UINT)-1), _iFocused((UINT)-1), _cSelected(0), _pps(NULL), _dwServiceCookie(-1)
{
_pff->AddRef();
}
CFindFolderViewCB::~CFindFolderViewCB()
{
_pff->Release();
ASSERT(NULL == _pps);
ASSERT(_dwServiceCookie == -1);
}
// give the find command code access to defview via this QS that we proffered
HRESULT CFindFolderViewCB::QueryService(REFGUID guidService, REFIID riid, void **ppv)
{
HRESULT hr = E_NOTIMPL;
*ppv = NULL;
if (guidService == SID_DocFindFolder)
{
hr = IUnknown_QueryService(_punkSite, SID_DefView, riid, ppv);
}
return hr;
}
CFindPersistHistory::CFindPersistHistory()
{
}
STDAPI CFindPersistHistory_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
{
HRESULT hr;
CFindPersistHistory *pdfph = new CFindPersistHistory();
if (pdfph)
{
hr = pdfph->QueryInterface(riid, ppv);
pdfph->Release();
}
else
{
*ppv = NULL;
hr = E_OUTOFMEMORY;
}
return hr;
}
// Functions to support persisting the document into the history stream...
STDMETHODIMP CFindPersistHistory::GetClassID(CLSID *pClassID)
{
*pClassID = CLSID_DocFindPersistHistory;
return S_OK;
}
IFindFolder *CFindPersistHistory::_GetDocFindFolder()
{
IFindFolder *pdff = NULL;
// the _punksite is to the defview so we can simply QI for frame...
IFolderView *pfv;
if (SUCCEEDED(_punkSite->QueryInterface(IID_PPV_ARG(IFolderView, &pfv))))
{
pfv->GetFolder(IID_PPV_ARG(IFindFolder, &pdff));
pfv->Release();
}
return pdff;
}
STDMETHODIMP CFindPersistHistory::LoadHistory(IStream *pstm, IBindCtx *pbc)
{
int cItems = 0;
IFindFolder *pdff = _GetDocFindFolder();
if (pdff)
{
pdff->RestoreFolderList(pstm);
pdff->RestoreItemList(pstm, &cItems);
pdff->Release();
}
IShellFolderView *psfv;
if (_punkSite && SUCCEEDED(_punkSite->QueryInterface(IID_PPV_ARG(IShellFolderView, &psfv))))
{
psfv->SetObjectCount(cItems, SFVSOC_NOSCROLL);
psfv->Release();
}
// call our base class to allow it to restore it's stuff as well.
return CDefViewPersistHistory::LoadHistory(pstm, pbc);
}
STDMETHODIMP CFindPersistHistory::SaveHistory(IStream *pstm)
{
IFindFolder *pdff = _GetDocFindFolder();
if (pdff)
{
pdff->SaveFolderList(pstm);
pdff->SaveItemList(pstm);
pdff->Release();
}
// Let base class save out as well
return CDefViewPersistHistory::SaveHistory(pstm);
}
// use to manage the selection states for an owner data listview...
STDMETHODIMP_(ULONG) CFindLVRange::AddRef()
{
return _pff->AddRef();
}
STDMETHODIMP_(ULONG) CFindLVRange::Release()
{
return _pff->Release();
}
STDMETHODIMP CFindLVRange::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] = {
QITABENT(CFindLVRange, ILVRange), // IID_ILVRange
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
// ILVRange methods
STDMETHODIMP CFindLVRange::IncludeRange(LONG iBegin, LONG iEnd)
{
// Including the range must load the elements as we need the object ptr...
FIND_ITEM *pesfi;
int iTotal;
_pff->GetItemCount(&iTotal);
if (iEnd > iTotal)
iEnd = iTotal-1;
for (long i = iBegin; i <= iEnd;i++)
{
if (SUCCEEDED(_pff->GetItem(i, &pesfi)) && pesfi)
{
if ((pesfi->dwState & _dwMask) == 0)
{
_cIncluded++;
pesfi->dwState |= _dwMask;
}
}
}
return S_OK;
}
STDMETHODIMP CFindLVRange::ExcludeRange(LONG iBegin, LONG iEnd)
{
// Excluding the range is OK to not load the elements as this would be to deslect all...
EnterCriticalSection(&_pff->_csSearch);
if (iEnd >= DPA_GetPtrCount(_pff->_hdpaItems))
iEnd = DPA_GetPtrCount(_pff->_hdpaItems) - 1;
for (long i = iBegin; i <= iEnd; i++)
{
FIND_ITEM *pesfi = (FIND_ITEM*)DPA_FastGetPtr(_pff->_hdpaItems, i);
if (pesfi)
{
if (pesfi->dwState & _dwMask)
{
_cIncluded--;
pesfi->dwState &= ~_dwMask;
}
}
}
LeaveCriticalSection(&_pff->_csSearch);
return S_OK;
}
STDMETHODIMP CFindLVRange::InvertRange(LONG iBegin, LONG iEnd)
{
// Including the range must load the elements as we need the object ptr...
int iTotal;
_pff->GetItemCount(&iTotal);
if (iEnd > iTotal)
iEnd = iTotal-1;
for (long i = iBegin; i <= iEnd;i++)
{
FIND_ITEM *pesfi;
if (SUCCEEDED(_pff->GetItem(i, &pesfi)) && pesfi)
{
if ((pesfi->dwState & _dwMask) == 0)
{
_cIncluded++;
pesfi->dwState |= _dwMask;
}
else
{
_cIncluded--;
pesfi->dwState &= ~_dwMask;
}
}
}
return S_OK;
}
STDMETHODIMP CFindLVRange::InsertItem(LONG iItem)
{
// We already maintain the list anyway...
return S_OK;
}
STDMETHODIMP CFindLVRange::RemoveItem(LONG iItem)
{
// We maintain the list so don't do anything...
return S_OK;
}
STDMETHODIMP CFindLVRange::Clear()
{
// If there are things selected, need to unselect them now...
if (_cIncluded)
ExcludeRange(0, LONG_MAX);
_cIncluded = 0;
_pff->ClearSaveStateList();
return S_OK;
}
STDMETHODIMP CFindLVRange::IsSelected(LONG iItem)
{
// Don't force the items to be generated if they were not before...
HRESULT hr = S_FALSE;
EnterCriticalSection(&_pff->_csSearch);
FIND_ITEM *pesfi = (FIND_ITEM*)DPA_GetPtr(_pff->_hdpaItems, iItem);
if (pesfi)
hr = pesfi->dwState & _dwMask ? S_OK : S_FALSE;
LeaveCriticalSection(&_pff->_csSearch);
// Assume not selected if we don't have the item yet...
return hr;
}
STDMETHODIMP CFindLVRange::IsEmpty()
{
return _cIncluded ? S_FALSE : S_OK;
}
STDMETHODIMP CFindLVRange::NextSelected(LONG iItem, LONG *piItem)
{
EnterCriticalSection(&_pff->_csSearch);
LONG cItems = DPA_GetPtrCount(_pff->_hdpaItems);
while (iItem < cItems)
{
FIND_ITEM *pesfi = (FIND_ITEM*)DPA_GetPtr(_pff->_hdpaItems, iItem);
if (pesfi && (pesfi->dwState & _dwMask))
{
*piItem = iItem;
LeaveCriticalSection(&_pff->_csSearch);
return S_OK;
}
iItem++;
}
LeaveCriticalSection(&_pff->_csSearch);
*piItem = -1;
return S_FALSE;
}
STDMETHODIMP CFindLVRange::NextUnSelected(LONG iItem, LONG *piItem)
{
EnterCriticalSection(&_pff->_csSearch);
LONG cItems = DPA_GetPtrCount(_pff->_hdpaItems);
while (iItem < cItems)
{
FIND_ITEM *pesfi = (FIND_ITEM*)DPA_GetPtr(_pff->_hdpaItems, iItem);
if (!pesfi || ((pesfi->dwState & _dwMask) == 0))
{
*piItem = iItem;
LeaveCriticalSection(&_pff->_csSearch);
return S_OK;
}
iItem++;
}
LeaveCriticalSection(&_pff->_csSearch);
*piItem = -1;
return S_FALSE;
}
STDMETHODIMP CFindLVRange::CountIncluded(LONG *pcIncluded)
{
*pcIncluded = _cIncluded;
// Sortof Gross, but if looking at selection then also include the list of items
// that are selected in our save list...
if (_dwMask & LVIS_SELECTED)
*pcIncluded += _pff->_cSaveStateSelected;
return S_OK;
}
// Define OleDBEnum translation structure...
typedef struct _dfodbet // DFET for short
{
struct _dfodbet *pdfetNext;
LPWSTR pwszFrom;
int cbFrom;
LPWSTR pwszTo;
} DFODBET;
class CContentIndexEnum : public IFindEnum, public IShellService
{
public:
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IFindEnum
STDMETHODIMP Next(LPITEMIDLIST *ppidl, int *pcObjectSearched, int *pcFoldersSearched, BOOL *pfContinue, int *pState);
STDMETHODIMP Skip(int celt);
STDMETHODIMP Reset();
STDMETHODIMP StopSearch();
STDMETHODIMP_(BOOL) FQueryIsAsync();
STDMETHODIMP GetAsyncCount(DBCOUNTITEM *pdwTotalAsync, int *pnPercentComplete, BOOL *pfQueryDone);
STDMETHODIMP GetItemIDList(UINT iItem, LPITEMIDLIST *ppidl);
STDMETHODIMP GetItemID(UINT iItem, DWORD *puWorkID);
STDMETHODIMP SortOnColumn(UINT iCOl, BOOL fAscending);
// IShellService
STDMETHODIMP SetOwner(IUnknown* punkOwner);
CContentIndexEnum(IFindFilter *pfilter, IFindFolder *pff, DWORD grfFlags,
int iColSort, LPTSTR pszProgressText, IRowsetWatchNotify *prwn);
HRESULT DoQuery(LPWSTR *apwszPaths, UINT *pcPaths);
private:
~CContentIndexEnum();
void HandleWatchNotify(DBWATCHNOTIFY eChangeReason);
HRESULT _BuildAndSetCommandTree(int iCol, BOOL fReverse);
HRESULT _SetCmdProp(ICommand *pCommand);
HRESULT _MapColumns(IUnknown *punk, DBORDINAL cCols, DBBINDING *pBindings, const DBID * pDbCols, HACCESSOR &hAccessor);
void _ReleaseAccessor();
HRESULT _CacheRowSet(UINT iItem);
BOOL _TranslateFolder(LPCWSTR pszParent, LPWSTR pszResult);
void _ClearFolderState();
LONG _cRef;
IFindFilter *_pfilter;
IRowsetWatchNotify *_prwn;
IFindFolder *_pff;
int _iColSort;
DWORD _grfFlags;
DWORD _grfWarnings;
LPTSTR _pszProgressText;
TCHAR _szCurrentDir[MAX_PATH];
IShellFolder *_psfCurrentDir;
LPITEMIDLIST _pidlFolder;
int _iFolder;
HRESULT _hrCurrent;
ICommand *_pCommand;
IRowsetLocate *_pRowset;
IRowsetAsynch *_pRowsetAsync;
HACCESSOR _hAccessor;
HACCESSOR _hAccessorWorkID;
HROW _ahrow[100]; // Cache 100 hrows out for now
UINT _ihrowFirst; // The index of which row is cached out first
DBCOUNTITEM _cRows; // number of hrows in _ahrow
DFODBET *_pdfetFirst; // Name translation list.
};
STDAPI CreateOleDBEnum(IFindFilter *pfilter, IShellFolder *psf,
LPWSTR *apwszPaths, UINT *pcPaths, DWORD grfFlags, int iColSort,
LPTSTR pszProgressText, IRowsetWatchNotify *prwn, IFindEnum **ppdfenum)
{
*ppdfenum = NULL;
HRESULT hr = E_OUTOFMEMORY;
IFindFolder *pff;
psf->QueryInterface(IID_PPV_ARG(IFindFolder, &pff));
CContentIndexEnum* pdfenum = new CContentIndexEnum(pfilter, pff, grfFlags, iColSort, pszProgressText, prwn);
if (pdfenum)
{
hr = pdfenum->DoQuery(apwszPaths, pcPaths);
if (hr == S_OK) // We only continue to use this if query returne S_OK...
*ppdfenum = (IFindEnum*)pdfenum;
else
{
pdfenum->Release(); // release the memory we allocated
}
}
if (pff)
pff->Release();
return hr;
}
const DBID c_aDbCols[] =
{
{{PSGUID_STORAGE}, DBKIND_GUID_PROPID, {(LPOLESTR)(ULONG_PTR)(ULONG)PID_STG_NAME}},
{{PSGUID_STORAGE}, DBKIND_GUID_PROPID, {(LPOLESTR)(ULONG_PTR)(ULONG)PID_STG_PATH}},
{{PSGUID_STORAGE}, DBKIND_GUID_PROPID, {(LPOLESTR)(ULONG_PTR)(ULONG)PID_STG_ATTRIBUTES}},
{{PSGUID_STORAGE}, DBKIND_GUID_PROPID, {(LPOLESTR)(ULONG_PTR)(ULONG)PID_STG_SIZE}},
{{PSGUID_STORAGE}, DBKIND_GUID_PROPID, {(LPOLESTR)(ULONG_PTR)(ULONG)PID_STG_WRITETIME}},
{{PSGUID_QUERY_D}, DBKIND_GUID_PROPID, {(LPOLESTR) PROPID_QUERY_RANK}},
};
const DBID c_aDbWorkIDCols[] =
{
{{PSGUID_QUERY_D}, DBKIND_GUID_PROPID, {(LPOLESTR)PROPID_QUERY_WORKID}}
};
const LPCWSTR c_awszColSortNames[] = {
L"FileName[a],Path[a]",
L"Path[a],FileName[a]",
L"Size[a]",
NULL,
L"Write[a]",
L"Rank[d]"
};
const ULONG c_cDbCols = ARRAYSIZE(c_aDbCols);
const DBID c_dbcolNull = { {0,0,0,{0,0,0,0,0,0,0,0}},DBKIND_GUID_PROPID,0};
const GUID c_guidQueryExt = DBPROPSET_QUERYEXT;
const GUID c_guidRowsetProps = {0xc8b522be,0x5cf3,0x11ce,{0xad,0xe5,0x00,0xaa,0x00,0x44,0x77,0x3d}};
CContentIndexEnum::CContentIndexEnum(IFindFilter *pfilter, IFindFolder *pff,
DWORD grfFlags, int iColSort, LPTSTR pszProgressText, IRowsetWatchNotify *prwn) :
_cRef(1), _ihrowFirst((UINT)-1), _pfilter(pfilter),
_pff(pff), _prwn(prwn), _grfFlags(grfFlags),
_grfWarnings(DFW_DEFAULT), _iColSort(iColSort), _pszProgressText(pszProgressText)
{
_szCurrentDir[0] = 0;
ASSERT(_pRowset == 0);
ASSERT(_pRowsetAsync == 0);
ASSERT(_pCommand == 0);
ASSERT(_hAccessor == 0);
ASSERT(_hAccessorWorkID ==0);
ASSERT(_cRows == 0);
if (_pfilter)
{
_pfilter->AddRef();
_pfilter->GetWarningFlags(&_grfWarnings);
}
if (_pff)
_pff->AddRef();
if (_prwn)
_prwn->AddRef();
}
void CContentIndexEnum::_ClearFolderState()
{
ATOMICRELEASE(_psfCurrentDir);
ILFree(_pidlFolder);
_pidlFolder = NULL;
_iFolder = -1;
_szCurrentDir[0] = 0;
}
CContentIndexEnum::~CContentIndexEnum()
{
ATOMICRELEASE(_pfilter);
ATOMICRELEASE(_pff);
ATOMICRELEASE(_prwn);
_ClearFolderState();
if (_pRowset)
{
ATOMICRELEASE(_pRowsetAsync);
// Release any cached rows.
_CacheRowSet((UINT)-1);
if (_hAccessor || _hAccessorWorkID)
_ReleaseAccessor();
_pRowset->Release();
}
ATOMICRELEASE(_pCommand);
// Release any name translations we may have allocated.
DFODBET *pdfet = _pdfetFirst;
while (pdfet)
{
DFODBET *pdfetT = pdfet;
pdfet = pdfet->pdfetNext; // First setup to look at the next item before we free stuff...
LocalFree((HLOCAL)pdfetT->pwszFrom);
LocalFree((HLOCAL)pdfetT->pwszTo);
LocalFree((HLOCAL)pdfetT);
}
}
HRESULT CContentIndexEnum::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] = {
QITABENTMULTI(CContentIndexEnum, IUnknown, IFindEnum), // IID_IUNKNOWN
QITABENT(CContentIndexEnum, IShellService), // IID_IShellService
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
ULONG CContentIndexEnum::AddRef()
{
return InterlockedIncrement(&_cRef);
}
ULONG CContentIndexEnum::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
HRESULT CContentIndexEnum::Next(LPITEMIDLIST *ppidl, int *pcObjectSearched, int *pcFoldersSearched, BOOL *pfContinue, int *pState)
{
return E_PENDING; // as good a return as any to say that we are async...
}
HRESULT CContentIndexEnum::Skip(int celt)
{
return E_NOTIMPL;
}
HRESULT CContentIndexEnum::Reset()
{
// overload Reset to mean dump the rowset cache!!!
_CacheRowSet(-1);
// still return failiure
return E_NOTIMPL;
}
HRESULT CContentIndexEnum::StopSearch()
{
// Lets see if we can find one that works...
HRESULT hr = _pCommand->Cancel();
if (FAILED(hr))
hr = _pRowsetAsync->Stop();
if (FAILED(hr))
{
IDBAsynchStatus *pdbas;
if (SUCCEEDED(_pRowset->QueryInterface(IID_PPV_ARG(IDBAsynchStatus, &pdbas))))
{
hr = pdbas->Abort(DB_NULL_HCHAPTER, DBASYNCHOP_OPEN);
pdbas->Release();
}
}
return hr;
}
BOOL CContentIndexEnum::FQueryIsAsync()
{
return TRUE;
}
HRESULT CContentIndexEnum::GetAsyncCount(DBCOUNTITEM *pdwTotalAsync, int *pnPercentComplete, BOOL *pfQueryDone)
{
if (!_pRowsetAsync)
return E_FAIL;
BOOL fMore;
DBCOUNTITEM dwDen, dwNum;
HRESULT hr = _pRowsetAsync->RatioFinished(&dwDen, &dwNum, pdwTotalAsync, &fMore);
if (SUCCEEDED(hr))
{
*pfQueryDone = dwDen == dwNum;
*pnPercentComplete = dwDen ? (int)((dwNum * 100) / dwDen) : 100;
}
else
*pfQueryDone = TRUE; // in case that is all they are looking at...
return hr;
}
// modify pszPath until you can parse it, return result in *ppidl
HRESULT _StripToParseableName(LPTSTR pszPath, LPITEMIDLIST *ppidl)
{
*ppidl = NULL;
HRESULT hr = E_FAIL;
PathRemoveBackslash(pszPath);
while (PathRemoveFileSpec(pszPath) && FAILED(hr))
{
hr = SHParseDisplayName(pszPath, NULL, ppidl, 0, NULL);
}
return hr;
}
// we could not get pidl for this item for some reason. we have to put
// it in the list of bad items so that we can tell ci not to give it to
// us the next time we do search
void _ExcludeFromFutureSearch(LPCTSTR pszParent)
{
HKEY hkey;
TCHAR szParent[MAX_PATH];
StrCpyN(szParent, pszParent, ARRAYSIZE(szParent));
if (RegCreateKeyExW(HKEY_CURRENT_USER, CI_SPECIAL_FOLDERS, 0, L"", 0, KEY_WRITE | KEY_QUERY_VALUE, NULL, &hkey, NULL) == ERROR_SUCCESS)
{
LPITEMIDLIST pidlT;
if (SUCCEEDED(_StripToParseableName(szParent, &pidlT)))
{
ILFree(pidlT);
DWORD dwInsert = 0; // init to zero in case query info bellow fails
int iEnd;
TCHAR sz[MAX_PATH], szName[10];
RegQueryInfoKey(hkey, NULL, NULL, NULL, NULL, NULL, NULL, &dwInsert, NULL, NULL, NULL, NULL);
// start from the end as there is a high chance we added this at the end
for (int i = dwInsert - 1; i >= 0; i--)
{
DWORD cb = sizeof(sz);
wsprintf(szName, L"%d", i);
if (RegQueryValueEx(hkey, szName, NULL, NULL, (BYTE *)sz, &cb) == ERROR_SUCCESS)
{
LPTSTR pszTemp = StrStrI(sz + 1, szParent); // +1 to pass " that's at the beginning of the string
if (pszTemp && pszTemp == sz + 1)
{
dwInsert = i; // overwrite this value
break;
}
else
{
iEnd = lstrlen(sz);
if (EVAL(iEnd > 1))
{
int iBackslash = iEnd - 3;
ASSERT(sz[iBackslash] == L'\\');
sz[iBackslash] = L'\0';
pszTemp = StrStrI(szParent, sz + 1);
sz[iBackslash] = L'\\';
if (pszTemp && pszTemp == szParent)
{
dwInsert = -1;
break;
}
}
}
}
}
if (dwInsert != -1)
{
wsprintf(szName, L"%d", dwInsert);
PathAppend(szParent, TEXT("*"));
PathQuoteSpaces(szParent);
RegSetValueEx(hkey, szName, 0, REG_SZ, (BYTE *)szParent, (lstrlen(szParent) + 1) * sizeof(szParent[0]));
}
}
RegCloseKey(hkey);
}
}
// If it is a UNC it might be one we need to translate, to handle the case that
// content index does not support redirected drives.
BOOL CContentIndexEnum::_TranslateFolder(LPCTSTR pszParent, LPTSTR pszResult)
{
BOOL fTranslated = FALSE;
StrCpyW(pszResult, pszParent); // default to the same
if (PathIsUNC(pszParent))
{
for (DFODBET *pdfet = _pdfetFirst; pdfet; pdfet = pdfet->pdfetNext)
{
if ((StrCmpNIW(pszParent, pdfet->pwszFrom, pdfet->cbFrom) == 0)
&& (pszParent[pdfet->cbFrom] == L'\\'))
{
// Ok we have a translation to use.
fTranslated = TRUE;
StrCpyW(pszResult, pdfet->pwszTo);
// need + 1 here or we'll get something like w:\\winnt! bogus path, that is.
StrCatW(pszResult, &pszParent[pdfet->cbFrom + 1]);
}
}
}
return fTranslated;
}
HRESULT CContentIndexEnum::GetItemIDList(UINT iItem, LPITEMIDLIST *ppidl)
{
*ppidl = NULL;
HRESULT hr = _CacheRowSet(iItem);
if (S_OK != hr)
{
return E_FAIL; // we could not get the item someone asked for, so error...
}
PROPVARIANT* data[c_cDbCols];
hr = _pRowset->GetData(_ahrow[iItem - _ihrowFirst], _hAccessor, &data);
if (S_OK == hr)
{
// data[0].pwszVal is the file name
// data[1].pwszVal is the full path (including file name)
// data[2].ulVal is the attribute
// data[3].ulVal is the size in byte
// data[4].filetime is the last write time in UTC
// data[5].ulVal is the rank of the item...
WIN32_FIND_DATA fd = {0};
fd.dwFileAttributes = data[2]->ulVal;
fd.nFileSizeLow = data[3]->ulVal;
fd.ftLastWriteTime = data[4]->filetime;
ASSERT(ShowSuperHidden() || !IsSuperHidden(fd.dwFileAttributes)); // query should exclude these
StrCpyW(fd.cFileName, data[0]->pwszVal);
WCHAR szParent[MAX_PATH];
StrCpyW(szParent, data[1]->pwszVal); // full path
PathRemoveFileSpec(szParent); // strip to parent folder path
WCHAR szTranslatedParent[MAX_PATH];
BOOL fTranslated = _TranslateFolder(szParent, szTranslatedParent);
if (lstrcmp(szParent, _szCurrentDir) != 0)
{
_ClearFolderState(); // our previous "current folder" state is now invalid
hr = SHParseDisplayName(szTranslatedParent, NULL, &_pidlFolder, 0, NULL);
if (SUCCEEDED(hr))
{
hr = _pff->AddFolder(_pidlFolder, TRUE, &_iFolder);
if (SUCCEEDED(hr))
{
hr = _pff->GetFolder(_iFolder, IID_PPV_ARG(IShellFolder, &_psfCurrentDir));
if (SUCCEEDED(hr))
{
// on succesful init of this folder save the cache key
lstrcpy(_szCurrentDir, szParent);
}
}
}
else if (hr != E_OUTOFMEMORY && !fTranslated)
{
_ExcludeFromFutureSearch(szParent);
}
_hrCurrent = hr; // save error state for next time around
if (FAILED(hr))
_ClearFolderState();
}
else
{
hr = _hrCurrent;
}
if (SUCCEEDED(hr))
{
// success implies the state of these variables
ASSERT((NULL != _psfCurrentDir) && (NULL != _pidlFolder) && (_iFolder > 0));
DWORD dwItemID;
GetItemID(iItem, &dwItemID);
LPITEMIDLIST pidl;
hr = SHSimpleIDListFromFindData2(_psfCurrentDir, &fd, &pidl);
if (SUCCEEDED(hr))
{
hr = _pff->AddDataToIDList(pidl, _iFolder, _pidlFolder, DFDF_EXTRADATA, iItem, dwItemID, data[5]->ulVal, ppidl);
ILFree(pidl);
}
}
else
{
// failure implies these should be clear
ASSERT((NULL == _psfCurrentDir) && (NULL == _pidlFolder));
LPITEMIDLIST pidlFull;
if (SUCCEEDED(_StripToParseableName(szTranslatedParent, &pidlFull)))
{
LPCITEMIDLIST pidlChild;
if (SUCCEEDED(SplitIDList(pidlFull, &_pidlFolder, &pidlChild)))
{
hr = _pff->AddFolder(_pidlFolder, TRUE, &_iFolder);
if (SUCCEEDED(hr))
{
hr = _pff->GetFolder(_iFolder, IID_PPV_ARG(IShellFolder, &_psfCurrentDir));
if (SUCCEEDED(hr))
{
hr = _pff->AddDataToIDList(pidlChild, _iFolder, _pidlFolder, DFDF_NONE, 0, 0, 0, ppidl);
if (SUCCEEDED(hr))
{
// on succesful init of this folder save the cache key
lstrcpy(_szCurrentDir, szTranslatedParent);
PathRemoveFileSpec(_szCurrentDir);
}
}
}
}
ILFree(pidlFull);
if (FAILED(hr))
_ClearFolderState();
}
}
}
return hr;
}
HRESULT CContentIndexEnum::GetItemID(UINT iItem, DWORD *puItemID)
{
*puItemID = (UINT)-1;
HRESULT hr = _CacheRowSet(iItem);
if (S_OK == hr)
{
PROPVARIANT* data[1];
hr = _pRowset->GetData(_ahrow[iItem - _ihrowFirst], _hAccessorWorkID, &data);
if (S_OK == hr)
{
// Only one data column so this is easy...
// The ULVal is the thing we are after...
*puItemID = data[0]->ulVal;
}
}
return hr;
}
HRESULT CContentIndexEnum::SortOnColumn(UINT iCol, BOOL fAscending)
{
// Ok We need to generate the Sort String...
return _BuildAndSetCommandTree(iCol, fAscending);
}
HRESULT CContentIndexEnum::SetOwner(IUnknown* punkOwner)
{
// Used to set the docfind folder and from that the filter.
ATOMICRELEASE(_pfilter);
ATOMICRELEASE(_pff);
if (punkOwner)
{
punkOwner->QueryInterface(IID_PPV_ARG(IFindFolder, &_pff));
if (_pff)
_pff->GetFindFilter(&_pfilter);
}
return S_OK;
}
HRESULT CContentIndexEnum::_MapColumns(IUnknown *punk, DBORDINAL cCols,
DBBINDING *pBindings, const DBID *pDbCols,
HACCESSOR &hAccessor)
{
DBORDINAL aMappedColumnIDs[c_cDbCols];
IColumnsInfo *pColumnsInfo;
HRESULT hr = punk->QueryInterface(IID_PPV_ARG(IColumnsInfo, &pColumnsInfo));
if (SUCCEEDED(hr))
{
hr = pColumnsInfo->MapColumnIDs(cCols, pDbCols, aMappedColumnIDs);
if (SUCCEEDED(hr))
{
for (ULONG i = 0; i < cCols; i++)
pBindings[i].iOrdinal = aMappedColumnIDs[i];
IAccessor *pIAccessor;
hr = punk->QueryInterface(IID_PPV_ARG(IAccessor, &pIAccessor));
if (SUCCEEDED(hr))
{
hAccessor = 0;
hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, cCols, pBindings, 0, &hAccessor, 0);
pIAccessor->Release();
}
}
pColumnsInfo->Release();
}
return hr;
}
void CContentIndexEnum::_ReleaseAccessor()
{
IAccessor *pIAccessor;
HRESULT hr = _pRowset->QueryInterface(IID_PPV_ARG(IAccessor, &pIAccessor));
if (SUCCEEDED(hr))
{
if (_hAccessor)
pIAccessor->ReleaseAccessor(_hAccessor, 0);
if (_hAccessorWorkID)
pIAccessor->ReleaseAccessor(_hAccessorWorkID, 0);
pIAccessor->Release();
}
}
HRESULT CContentIndexEnum::_CacheRowSet(UINT iItem)
{
HRESULT hr = S_OK;
if (!_pRowset)
return E_FAIL;
if (!_cRows || !InRange(iItem, _ihrowFirst, _ihrowFirst+(UINT)_cRows-1) || (iItem == (UINT)-1))
{
// Release the last cached element we had.
if (_cRows != 0)
_pRowset->ReleaseRows(ARRAYSIZE(_ahrow), _ahrow, 0, 0, 0);
// See if we are simply releasing our cached data...
_cRows = 0;
_ihrowFirst = (UINT)-1;
if (iItem == (UINT)-1)
return S_OK;
// Ok try to read in the next on...
BYTE bBookMark = (BYTE) DBBMK_FIRST;
HROW *rghRows = (HROW *)_ahrow;
// change this to fetch 100 or so rows at the time -- huge perf improvment
hr = _pRowset->GetRowsAt(0, 0, sizeof(bBookMark), &bBookMark, iItem, ARRAYSIZE(_ahrow), &_cRows, &rghRows);
if (FAILED(hr))
return hr;
_ihrowFirst = iItem;
if ((DB_S_ENDOFROWSET == hr) || (_cRows == 0))
{
if (_cRows == 0)
_ihrowFirst = -1;
else
hr = S_OK; // we got some items and caller expects S_OK so change DB_S_ENDOFROWSET to noerror
}
}
return hr;
}
void CContentIndexEnum::HandleWatchNotify(DBWATCHNOTIFY eChangeReason)
{
// For now we will simply Acknoledge the change...
if (_prwn)
_prwn->OnChange(NULL, eChangeReason);
}
HRESULT CContentIndexEnum::_SetCmdProp(ICommand *pCommand)
{
#define MAX_PROPS 8
DBPROPSET aPropSet[MAX_PROPS];
DBPROP aProp[MAX_PROPS];
ULONG cProps = 0;
HRESULT hr;
// asynchronous query
aProp[cProps].dwPropertyID = DBPROP_IRowsetAsynch;
aProp[cProps].dwOptions = 0;
aProp[cProps].dwStatus = 0;
aProp[cProps].colid = c_dbcolNull;
aProp[cProps].vValue.vt = VT_BOOL;
aProp[cProps].vValue.boolVal = VARIANT_TRUE;
aPropSet[cProps].rgProperties = &aProp[cProps];
aPropSet[cProps].cProperties = 1;
aPropSet[cProps].guidPropertySet = c_guidRowsetProps;
cProps++;
// don't timeout queries
aProp[cProps].dwPropertyID = DBPROP_COMMANDTIMEOUT;
aProp[cProps].dwOptions = DBPROPOPTIONS_SETIFCHEAP;
aProp[cProps].dwStatus = 0;
aProp[cProps].colid = c_dbcolNull;
aProp[cProps].vValue.vt = VT_I4;
aProp[cProps].vValue.lVal = 0;
aPropSet[cProps].rgProperties = &aProp[cProps];
aPropSet[cProps].cProperties = 1;
aPropSet[cProps].guidPropertySet = c_guidRowsetProps;
cProps++;
// We can handle PROPVARIANTs
aProp[cProps].dwPropertyID = DBPROP_USEEXTENDEDDBTYPES;
aProp[cProps].dwOptions = DBPROPOPTIONS_SETIFCHEAP;
aProp[cProps].dwStatus = 0;
aProp[cProps].colid = c_dbcolNull;
aProp[cProps].vValue.vt = VT_BOOL;
aProp[cProps].vValue.boolVal = VARIANT_TRUE;
aPropSet[cProps].rgProperties = &aProp[cProps];
aPropSet[cProps].cProperties = 1;
aPropSet[cProps].guidPropertySet = c_guidQueryExt;
cProps++;
ICommandProperties * pCmdProp = 0;
hr = pCommand->QueryInterface(IID_PPV_ARG(ICommandProperties, &pCmdProp));
if (SUCCEEDED(hr))
{
hr = pCmdProp->SetProperties(cProps, aPropSet);
pCmdProp->Release();
}
return hr;
}
// create the query command string
HRESULT CContentIndexEnum::_BuildAndSetCommandTree(int iCol, BOOL fReverse)
{
LPWSTR pwszRestrictions = NULL;
DWORD dwGQRFlags;
HRESULT hr = _pfilter->GenerateQueryRestrictions(&pwszRestrictions, &dwGQRFlags);
if (SUCCEEDED(hr))
{
ULONG ulDialect;
hr = _pfilter->GetQueryLanguageDialect(&ulDialect);
if (SUCCEEDED(hr))
{
// NOTE: hard coded to our current list of columns
WCHAR wszSort[80]; // use this to sort by different columns...
wszSort[0] = 0;
if ((iCol >= 0) && (iCol < ARRAYSIZE(c_awszColSortNames)) && c_awszColSortNames[iCol])
{
// Sort order is hardcoded for ascending.
StrCpyW(wszSort, c_awszColSortNames[iCol]);
StrCatW(wszSort, L",Path[a],FileName[a]");
}
DBCOMMANDTREE *pTree = NULL;
hr = CITextToFullTreeEx(pwszRestrictions, ulDialect,
L"FileName,Path,Attrib,Size,Write,Rank,WorkID",
wszSort[0] ? wszSort : NULL, 0, &pTree, 0, 0, LOCALE_USER_DEFAULT);
if (FAILED(hr))
{
// Map this to one that I know about
// Note: We will only do this if we require CI else we will try to fallback to old search...
// Note we are running into problems where CI says we are contained in a Catalog even if
// CI process is not running... So try to avoid this if possible
if (dwGQRFlags & GQR_REQUIRES_CI)
hr = MAKE_HRESULT(3, FACILITY_SEARCHCOMMAND, SCEE_CONSTRAINT);
}
if (SUCCEEDED(hr))
{
ICommandTree *pCmdTree;
hr = _pCommand->QueryInterface(IID_PPV_ARG(ICommandTree, &pCmdTree));
if (SUCCEEDED(hr))
{
hr = pCmdTree->SetCommandTree(&pTree, DBCOMMANDREUSE_NONE, FALSE);
pCmdTree->Release();
}
}
}
}
LocalFree((HLOCAL)pwszRestrictions);
return hr;
}
#define cbP (sizeof (PROPVARIANT *))
// [in, out] apwszPaths this is modified
// [in, out] pcPaths
HRESULT CContentIndexEnum::DoQuery(LPWSTR *apwszPaths, UINT *pcPaths)
{
UINT nPaths = *pcPaths;
WCHAR** aScopes = NULL;
WCHAR** aScopesOrig = NULL;
ULONG* aDepths = NULL;
WCHAR** aCatalogs = NULL;
WCHAR** aMachines = NULL;
WCHAR wszPath[MAX_PATH];
LPWSTR pwszPath = wszPath;
LPWSTR pszMachineAlloc = NULL, pszCatalogAlloc = NULL;
LPWSTR pwszMachine, pwszCatalog;
UINT i, iPath = 0;
DWORD dwQueryRestrictions;
// Initiailize all of our query values back to unused
_hAccessor = NULL;
_hAccessorWorkID = NULL;
_pRowset = NULL;
_pRowsetAsync = NULL;
_pCommand = NULL;
// Get array of search paths...
#define MAX_MACHINE_NAME_LEN 32
BOOL fIsCIRunning, fCiIndexed, fCiPermission;
GetCIStatus(&fIsCIRunning, &fCiIndexed, &fCiPermission);
// First pass see if we have anything that make use at all of CI if not lets simply bail and let
// old code walk the list...
HRESULT hr = _pfilter->GenerateQueryRestrictions(NULL, &dwQueryRestrictions);
if (FAILED(hr))
goto Abort;
if ((dwQueryRestrictions & GQR_MAKES_USE_OF_CI) == 0)
{
hr = S_FALSE;
goto Abort;
}
// allocate the arrays that we need to pass to CIMakeICommand and
// the buffers needed for the machine name and catalog name
aDepths = (ULONG*)LocalAlloc(LPTR, nPaths * sizeof(ULONG));
aScopes = (WCHAR**)LocalAlloc(LPTR, nPaths * sizeof(WCHAR*));
aScopesOrig = (WCHAR**)LocalAlloc(LPTR, nPaths * sizeof(WCHAR*));
aCatalogs = (WCHAR**)LocalAlloc(LPTR, nPaths * sizeof(WCHAR*));
aMachines = (WCHAR**)LocalAlloc(LPTR, nPaths * sizeof(WCHAR*));
pszMachineAlloc = pwszMachine = (LPWSTR)LocalAlloc(LPTR, nPaths * MAX_MACHINE_NAME_LEN * sizeof(WCHAR));
pszCatalogAlloc = pwszCatalog = (LPWSTR)LocalAlloc(LPTR, nPaths * MAX_PATH * sizeof(WCHAR));
if (!aDepths || !aScopes || !aScopesOrig || !aCatalogs ||
!aMachines || !pszMachineAlloc || !pszCatalogAlloc)
{
hr = E_OUTOFMEMORY;
goto Abort;
}
// This following loop does two things,
// 1. Check if all the scopes are indexed, if any one scope is not,
// fail the call and we'll do the win32 find.
// 2. Prepare the arrays of parameters that we need to pass to
// CIMakeICommand().
//
// NOTE: Reinerf says this code looks busted for nPaths > 1. See bug 199254 for comments.
for (i = 0; i < nPaths; i++)
{
ULONG cchMachine = MAX_MACHINE_NAME_LEN;
ULONG cchCatalog = MAX_PATH;
WCHAR wszUNCPath[MAX_PATH];
BOOL fRemapped = FALSE;
// if CI is not running we can still do ci queries on a remote drive (if it is indexed)
// so we cannot just bail if ci is not running on user's machine
if (!fIsCIRunning && !PathIsRemote(apwszPaths[i]))
continue; // do grep on this one
hr = LocateCatalogsW(apwszPaths[i], 0, pwszMachine, &cchMachine, pwszCatalog, &cchCatalog);
if (hr != S_OK)
{
// see if by chance this is a network redirected drive. If so we CI does not handle
// these. See if we can remap to UNC path to ask again...
if (!PathIsUNC(apwszPaths[i]))
{
DWORD nLength = ARRAYSIZE(wszUNCPath);
// this api takes TCHAR, but we only compile this part for WINNT...
DWORD dwType = SHWNetGetConnection(apwszPaths[i], wszUNCPath, &nLength);
if ((dwType == NO_ERROR) || (dwType == ERROR_CONNECTION_UNAVAIL))
{
fRemapped = TRUE;
LPWSTR pwsz = PathSkipRootW(apwszPaths[i]);
if (pwsz)
PathAppendW(wszUNCPath, pwsz);
cchMachine = MAX_MACHINE_NAME_LEN; // reset in params
cchCatalog = MAX_PATH;
hr = LocateCatalogsW(wszUNCPath, 0, pwszMachine, &cchMachine, pwszCatalog, &cchCatalog);
}
}
}
if (hr != S_OK)
{
continue; // this one is not indexed.
}
if (S_FALSE == CatalogUptodate(pwszCatalog, pwszMachine))
{
// not up todate
if (dwQueryRestrictions & GQR_REQUIRES_CI)
{
// ci not up to date and we must use it..
// inform the user that results may not be complete
if (!(_grfWarnings & DFW_IGNORE_INDEXNOTCOMPLETE))
{
hr = MAKE_HRESULT(3, FACILITY_SEARCHCOMMAND, SCEE_INDEXNOTCOMPLETE);
goto Abort;
}
//else use ci although index is not complete
}
else
{
// ci is not upto date so just use grep for this drive so user can get
// complete results
pwszCatalog[0] = 0;
pwszMachine[0] = 0;
continue;
}
}
aDepths[iPath] = (_grfFlags & DFOO_INCLUDESUBDIRS) ? QUERY_DEEP : QUERY_SHALLOW;
aScopesOrig[iPath] = apwszPaths[i];
if (fRemapped)
{
aScopes[iPath] = StrDupW(wszUNCPath);
if (aScopes[iPath] == NULL)
{
hr = E_OUTOFMEMORY;
goto Abort;
}
}
else
{
aScopes[iPath] = apwszPaths[i];
}
aCatalogs[iPath] = pwszCatalog;
aMachines[iPath] = pwszMachine;
pwszCatalog += MAX_PATH; // advance the catalog and machine name buffer
pwszMachine += MAX_MACHINE_NAME_LEN;
iPath++; // next item in this list
}
if (iPath == 0)
{
// no catalogs found; - We should check to see if by chance the user specified a query that
// is CI based if so error apapropriately...
hr = (dwQueryRestrictions & GQR_REQUIRES_CI) ? MAKE_HRESULT(3, FACILITY_SEARCHCOMMAND, SCEE_INDEXSEARCH) : S_FALSE;
goto Abort;
}
// Get ICommand.
hr = CIMakeICommand(&_pCommand, iPath, aDepths, aScopes, aCatalogs, aMachines);
if (SUCCEEDED(hr))
{
// create the query command string - Assume default sort...
hr = _BuildAndSetCommandTree(_iColSort, FALSE);
if (SUCCEEDED(hr))
{
if ((dwQueryRestrictions & GQR_REQUIRES_CI) && (nPaths != iPath))
{
// check warning flags to see if we should ignore and continue
if (0 == (_grfWarnings & DFW_IGNORE_CISCOPEMISMATCH))
{
hr = MAKE_HRESULT(3, FACILITY_SEARCHCOMMAND, SCEE_SCOPEMISMATCH);
}
}
if (SUCCEEDED(hr))
{
// Get IRowset.
_SetCmdProp(_pCommand);
hr = _pCommand->Execute(0, IID_IRowsetLocate, 0, 0, (IUnknown **)&_pRowset);
if (SUCCEEDED(hr))
{
// we have the IRowset.
// Real work to get the Accessor
DBBINDING aPropMainCols[c_cDbCols] =
{
{ 0,cbP*0,0,0,0,0,0, DBPART_VALUE, DBMEMOWNER_PROVIDEROWNED, DBPARAMIO_NOTPARAM, 0, 0, DBTYPE_VARIANT|DBTYPE_BYREF, 0, 0 },
{ 0,cbP*1,0,0,0,0,0, DBPART_VALUE, DBMEMOWNER_PROVIDEROWNED, DBPARAMIO_NOTPARAM, 0, 0, DBTYPE_VARIANT|DBTYPE_BYREF, 0, 0 },
{ 0,cbP*2,0,0,0,0,0, DBPART_VALUE, DBMEMOWNER_PROVIDEROWNED, DBPARAMIO_NOTPARAM, 0, 0, DBTYPE_VARIANT|DBTYPE_BYREF, 0, 0 },
{ 0,cbP*3,0,0,0,0,0, DBPART_VALUE, DBMEMOWNER_PROVIDEROWNED, DBPARAMIO_NOTPARAM, 0, 0, DBTYPE_VARIANT|DBTYPE_BYREF, 0, 0 },
{ 0,cbP*4,0,0,0,0,0, DBPART_VALUE, DBMEMOWNER_PROVIDEROWNED, DBPARAMIO_NOTPARAM, 0, 0, DBTYPE_VARIANT|DBTYPE_BYREF, 0, 0 },
{ 0,cbP*5,0,0,0,0,0, DBPART_VALUE, DBMEMOWNER_PROVIDEROWNED, DBPARAMIO_NOTPARAM, 0, 0, DBTYPE_VARIANT|DBTYPE_BYREF, 0, 0 }
};
hr = _MapColumns(_pRowset, c_cDbCols, aPropMainCols, c_aDbCols, _hAccessor);
if (SUCCEEDED(hr))
{
// OK lets also get the accessor for the WorkID...
hr = _MapColumns(_pRowset, ARRAYSIZE(c_aDbWorkIDCols), aPropMainCols, c_aDbWorkIDCols, _hAccessorWorkID);
if (SUCCEEDED(hr))
{
hr = _pRowset->QueryInterface(IID_PPV_ARG(IRowsetAsynch, &_pRowsetAsync));
}
}
}
}
}
}
if (FAILED(hr))
goto Abort;
// If we got here than at least some of our paths are indexed
// we may need to compress the list down of the ones we did not handle...
*pcPaths = (nPaths - iPath); // Let caller know how many we did not process
// need to move all the ones we did not process to the start of the list...
// we always process this list here as we may need to allocate translation lists to be used to
// translate the some UNCS back to the mapped drive the user passed in.
UINT j = 0, iInsert = 0;
iPath--; // make it easy to detect
for (i = 0; i < nPaths; i++)
{
if (aScopesOrig[j] == apwszPaths[i])
{
if (aScopesOrig[j] != aScopes[j])
{
// There is a translation in place.
DFODBET *pdfet = (DFODBET*)LocalAlloc(LPTR, sizeof(*pdfet));
if (pdfet)
{
pdfet->pdfetNext = _pdfetFirst;
_pdfetFirst = pdfet;
pdfet->pwszFrom = aScopes[j];
pdfet->cbFrom = lstrlenW(pdfet->pwszFrom);
pdfet->pwszTo = aScopesOrig[j];
aScopes[j] = aScopesOrig[j]; // Make sure loop below does not delete pwszFrom
apwszPaths[i] = NULL; // Likewise for pswsTo...
}
}
if (apwszPaths[i])
{
LocalFree((HLOCAL)apwszPaths[i]);
apwszPaths[i] = NULL;
}
if (j < iPath)
j++;
}
else
{
apwszPaths[iInsert++] = apwszPaths[i]; // move to right place
}
}
iPath++; // setup to go through cleanupcode...
// Fall through to cleanup code...
Abort:
// Warning... Since a failure return from this function will
// release this class, most all of the allocated items up till the failure should
// be released... Also cleanup any paths we may have allocated...
for (i = 0; i < iPath; i++)
{
if (aScopesOrig[i] != aScopes[i])
LocalFree(aScopes[i]);
}
if (aDepths)
LocalFree(aDepths);
if (aScopes)
LocalFree(aScopes);
if (aScopesOrig)
LocalFree(aScopesOrig);
if (aCatalogs)
LocalFree(aCatalogs);
if (aMachines)
LocalFree(aMachines);
if (pszMachineAlloc)
LocalFree(pszMachineAlloc);
if (pszCatalogAlloc)
LocalFree(pszCatalogAlloc);
return hr;
}
// This is the main external entry point to start a search. This will
// create a new thread to process the
STDAPI_(BOOL) SHFindComputer(LPCITEMIDLIST, LPCITEMIDLIST)
{
IContextMenu *pcm;
HRESULT hr = CoCreateInstance(CLSID_ShellSearchExt, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IContextMenu, &pcm));
if (SUCCEEDED(hr))
{
CMINVOKECOMMANDINFO ici = {0};
ici.cbSize = sizeof(ici);
ici.lpParameters = "{996E1EB1-B524-11d1-9120-00A0C98BA67D}"; // Search Guid of Find Computers
ici.nShow = SW_NORMAL;
hr = pcm->InvokeCommand(&ici);
pcm->Release();
}
return SUCCEEDED(hr);
}
BOOL _IsComputerPidl(LPCITEMIDLIST pidl)
{
CLSID clsid;
if (SUCCEEDED(GetCLSIDFromIDList(pidl, &clsid)))
{
return (IsEqualCLSID(clsid, CLSID_NetworkPlaces)
|| IsEqualCLSID(clsid, CLSID_NetworkRoot)
|| IsEqualCLSID(clsid, CLSID_NetworkDomain));
}
return FALSE;
}
// This is the main external entry point to start a search. This will
// create a new thread to process the
//
STDAPI_(BOOL) SHFindFiles(LPCITEMIDLIST pidlFolder, LPCITEMIDLIST pidlSaveFile)
{
// are we allowed?
if (SHRestricted(REST_NOFIND))
return FALSE;
// We Need a hack to allow Find to work for cases like
// Rest of network and workgroups to map to find computer instead
// This is rather gross, but what the heck. It is also assumed that
// the pidl is of the type that we know about (either File or network)
if (pidlFolder && _IsComputerPidl(pidlFolder))
{
return SHFindComputer(pidlFolder, pidlSaveFile);
}
return RealFindFiles(pidlFolder, pidlSaveFile);
}