mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-23 11:33:58 +00:00
f6647b9978
Mostly a bulk conversion of debug messages, primarily with sed: sed -e 's/\(WMSG[0-9]\)\(.*\)\(\\n"\)/LOGI\2"/' This removes the '\n' from the end of the log messages, and sets them all to "info" severity. We want to prefix each line with file/line and/or a timestamp, so it doesn't make sense to have a partial line, and there's no value in embedding the '\n' in every string.
1568 lines
49 KiB
C++
1568 lines
49 KiB
C++
/*
|
|
* CiderPress
|
|
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
|
* See the file LICENSE for distribution terms.
|
|
*/
|
|
/*
|
|
* ShellTree, a TreeCtrl derivative for displaying the Windows shell namespace.
|
|
*/
|
|
#include "StdAfx.h"
|
|
#include "ShellTree.h"
|
|
#include "Pidl.h"
|
|
#include "PathName.h"
|
|
|
|
|
|
/*
|
|
* ==========================================================================
|
|
* ShellTree
|
|
* ==========================================================================
|
|
*/
|
|
|
|
BEGIN_MESSAGE_MAP(ShellTree, CTreeCtrl)
|
|
ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, OnFolderExpanding)
|
|
ON_NOTIFY_REFLECT(TVN_DELETEITEM, OnDeleteShellItem)
|
|
ON_NOTIFY_REFLECT_EX(TVN_SELCHANGED, OnSelectionChange)
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
/*
|
|
* Replace a CTreeCtrl in a dialog box with us. All of the styles are
|
|
* copied from the original dialog window.
|
|
*
|
|
* Returns TRUE on success, FALSE on failure.
|
|
*/
|
|
BOOL
|
|
ShellTree::ReplaceDlgCtrl(CDialog* pDialog, int treeID)
|
|
{
|
|
CWnd* pWnd = pDialog->GetDlgItem(treeID);
|
|
if (pWnd == NULL)
|
|
return FALSE;
|
|
|
|
#if 0
|
|
DWORD styles = pWnd->GetStyle();
|
|
DWORD stylesEx = pWnd->GetExStyle();
|
|
CRect rect;
|
|
pWnd->GetWindowRect(&rect);
|
|
pDialog->ScreenToClient(&rect);
|
|
|
|
pWnd->DestroyWindow();
|
|
CreateEx(stylesEx, WC_TREEVIEW, NULL, styles, rect, pDialog, treeID);
|
|
#endif
|
|
|
|
/* latch on to their window handle */
|
|
Attach(pWnd->m_hWnd);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Populate the tree, starting from "nFolder".
|
|
*
|
|
* Returns TRUE on success, FALSE on failure.
|
|
*/
|
|
BOOL
|
|
ShellTree::PopulateTree(int nFolder)
|
|
{
|
|
LPSHELLFOLDER lpsf = NULL, lpsf2 = NULL;
|
|
LPITEMIDLIST lpi = NULL;
|
|
TV_SORTCB tvscb;
|
|
LPMALLOC lpMalloc = NULL;
|
|
HRESULT hr;
|
|
BOOL retval = FALSE;
|
|
|
|
// Grab a malloc handle.
|
|
hr = ::SHGetMalloc(&lpMalloc);
|
|
if (FAILED(hr))
|
|
return FALSE;
|
|
|
|
// Get a pointer to the desktop folder.
|
|
hr = SHGetDesktopFolder(&lpsf);
|
|
if (FAILED(hr))
|
|
goto bail;
|
|
|
|
// Initialize the tree view to be empty.
|
|
DeleteAllItems();
|
|
|
|
if (nFolder == CSIDL_DESKTOP) {
|
|
// already done
|
|
lpsf2 = lpsf;
|
|
lpsf = NULL;
|
|
ASSERT(lpi == NULL);
|
|
} else {
|
|
// find the desired special folder
|
|
hr = SHGetSpecialFolderLocation(m_hWnd, nFolder, &lpi);
|
|
if (FAILED(hr)) {
|
|
LOGI("BUG: could not find requested special folder");
|
|
goto bail;
|
|
}
|
|
|
|
// bind a ShellFolder to the PIDL.
|
|
hr = lpsf->BindToObject(lpi, 0, IID_IShellFolder, (LPVOID *)&lpsf2);
|
|
if (FAILED(hr))
|
|
goto bail;
|
|
}
|
|
|
|
// fill in the tree starting from this point
|
|
FillTreeView(lpsf2, lpi, TVI_ROOT);
|
|
|
|
// Sort the items in the tree view
|
|
tvscb.hParent = TVI_ROOT;
|
|
tvscb.lParam = 0;
|
|
tvscb.lpfnCompare = TreeViewCompareProc;
|
|
SortChildrenCB(&tvscb);
|
|
|
|
bail:
|
|
if (lpsf != NULL)
|
|
lpsf->Release();
|
|
if (lpsf != NULL)
|
|
lpsf2->Release();
|
|
lpMalloc->Free(lpi);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Open up and select My Computer.
|
|
*/
|
|
void
|
|
ShellTree::ExpandMyComputer(void)
|
|
{
|
|
HTREEITEM hItem;
|
|
hItem = FindMyComputer();
|
|
if (hItem == NULL)
|
|
hItem = GetRootItem();
|
|
Expand(hItem, TVE_EXPAND);
|
|
Select(hItem, TVGN_CARET);
|
|
}
|
|
|
|
|
|
/*
|
|
* Fills a branch of the TreeView control. Given the shell folder (both as
|
|
* a shell folder and the fully-qualified item ID list to it) and the parent
|
|
* item in the tree (TVI_ROOT to start off), add all the kids to the tree.
|
|
*
|
|
* Does not try to add the current entry, as a result of which we don't
|
|
* have a root "Desktop" node that everything is a child of. This is okay.
|
|
*/
|
|
void
|
|
ShellTree::FillTreeView(LPSHELLFOLDER lpsf, LPITEMIDLIST lpifq,
|
|
HTREEITEM hParent)
|
|
{
|
|
CWaitCursor wait;
|
|
HTREEITEM hPrev = NULL; // Previous Item Added.
|
|
LPENUMIDLIST lpe=NULL;
|
|
LPITEMIDLIST lpi=NULL;
|
|
LPMALLOC lpMalloc=NULL;
|
|
ULONG ulFetched;
|
|
HRESULT hr;
|
|
HWND hwnd=::GetParent(m_hWnd);
|
|
bool gotOne = false;
|
|
|
|
// Allocate a shell memory object.
|
|
hr = ::SHGetMalloc(&lpMalloc);
|
|
if (FAILED(hr))
|
|
return;
|
|
|
|
// Get the IEnumIDList object for the given folder.
|
|
hr = lpsf->EnumObjects(hwnd,
|
|
SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN,
|
|
&lpe);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Enumerate throught the list of folder and non-folder objects.
|
|
while (S_OK == lpe->Next(1, &lpi, &ulFetched))
|
|
{
|
|
//Create a fully qualified path to the current item
|
|
//The SH* shell api's take a fully qualified path pidl,
|
|
//(see GetIcon above where I call SHGetFileInfo) whereas the
|
|
//interface methods take a relative path pidl.
|
|
ULONG ulAttrs = SFGAO_HASSUBFOLDER | SFGAO_FOLDER |
|
|
SFGAO_FILESYSANCESTOR | SFGAO_DROPTARGET | SFGAO_HIDDEN;
|
|
bool goodOne;
|
|
|
|
// Determine what type of object we have.
|
|
lpsf->GetAttributesOf(1, (const struct _ITEMIDLIST **)&lpi, &ulAttrs);
|
|
|
|
#if 1
|
|
{ /* DEBUG */
|
|
CString name;
|
|
if (Pidl::GetName(lpsf, lpi, SHGDN_NORMAL, &name)) {
|
|
LOGI(" Checking '%ls' 0x%08lx",
|
|
name, ulAttrs);
|
|
} else {
|
|
LOGI(" Checking <no-name> 0x%08lx",
|
|
ulAttrs);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* (This should be converted to a table and automatically
|
|
* scanned when assertions are enabled.)
|
|
*
|
|
* Win2K folders to show:
|
|
* 'My Computer' 0xb0000100 [at root]
|
|
* 'My Network Places' 0xb0000100 [at root]
|
|
* 'My Documents' 0xb0000100 [at root]
|
|
* 'WIN (C:)' 0xb0000100
|
|
* 'Compact Disc (H:)' 0xb0000100 <-- SFGAO_REMOVABLE not set
|
|
* 'Removable Disk (L:)' 0xb0000100
|
|
* 'Documents and Settings' 0xf0400177
|
|
* 'Entire Network' 0xb0000000
|
|
* 'Microsoft Windows Network' 0xb0000000
|
|
* 'Computers Near Me' 0xa0000100
|
|
* 'ftp.apple.asimov.net' 0xa0000100
|
|
* 'QA-C-Recv on QA' 0xa0000100
|
|
* 'BACKUP' 0xf0400177
|
|
* 'dudley' 0x70400177
|
|
*
|
|
* Win2K files, folders, etc. to hide:
|
|
* 'gVim 6.1' 0x00000100
|
|
* 'Internet Explorer' 0x20000000
|
|
* 'Recycle Bin' 0x20000100 (folder + droptarget)
|
|
* 'Control Panel' 0xa0000000
|
|
* 'Scheduled Tasks on Shiny' 0x20000000
|
|
* 'Add Network Place' 0x00000000
|
|
* 'ColorBars.jpg' 0x40400177
|
|
*
|
|
* Win98 folders to show:
|
|
* 'My Computer' 0xb0000100 [at root]
|
|
* 'My Documents' 0xb0000100 [at root]
|
|
* 'Network Neighborhood' 0xb0000100 [at root]
|
|
* 'WIN98 (C:)' 0xb0000100
|
|
* [C:\]'My Documents' 0xf8000177
|
|
* 'Entire Network' 0xb0000000
|
|
* 'cpt' 0xe0000177
|
|
* 'My eBooks' 0x60000177
|
|
*
|
|
* Win98 folders and stuff to hide:
|
|
* 'Control Panel' 0x20000000
|
|
* 'Printers' 0x20000100 (folder + droptarget)
|
|
* 'Web Folders' 0xa0000000
|
|
* 'BOOTLOG.TXT' 0x40080177
|
|
*
|
|
* Note that Win98 folders on disk don't have FILESYSANCESTOR
|
|
* set. If we check FOLDER && FILESYSTEM (0x60000000), we get
|
|
* anything starting with 6/7/E/F, which appears safe. We
|
|
* need to do additional tests to pick up some of the A/B items
|
|
* that we want while hiding the A items we don't want.
|
|
*
|
|
* FILESYSANCESTOR is 0x10000000, so that plus FOLDER allows
|
|
* 3/7/b/f. These appear to be entirely okay.
|
|
*
|
|
* DROPTARGET is 0x00000100, and HASSUBFOLDER is 0x80000000.
|
|
* Combining with FOLDER yields 0xa00000100, allowing A/B/E/F.
|
|
* The only at-risk is A, but combined with DROPTARGET we
|
|
* seem to screen out all the bad ones.
|
|
*/
|
|
|
|
if (lpifq == NULL) {
|
|
/* dealing with stuff at the root level */
|
|
goodOne = ( (ulAttrs & SFGAO_FOLDER) &&
|
|
(ulAttrs & SFGAO_HASSUBFOLDER) &&
|
|
(ulAttrs & SFGAO_FILESYSANCESTOR) );
|
|
} else {
|
|
/* deeper down, we're picky in different ways */
|
|
bool isFolder = (ulAttrs & SFGAO_FOLDER) != 0;
|
|
bool fileSys = (ulAttrs & SFGAO_FILESYSTEM) != 0;
|
|
bool hasFSAncestor = (ulAttrs & SFGAO_FILESYSANCESTOR) != 0;
|
|
bool dropAndSub = (ulAttrs & SFGAO_DROPTARGET) != 0 &&
|
|
(ulAttrs & SFGAO_HASSUBFOLDER) != 0;
|
|
|
|
goodOne = isFolder &&
|
|
(fileSys || hasFSAncestor || dropAndSub);
|
|
}
|
|
|
|
if (goodOne) {
|
|
gotOne = true;
|
|
|
|
if (!AddNode(lpsf, lpi, lpifq, ulAttrs, hParent, &hPrev)) {
|
|
LOGI("AddNode failed!");
|
|
goto Done;
|
|
}
|
|
}
|
|
|
|
lpMalloc->Free(lpi); //Free the pidl that the shell gave us.
|
|
lpi=0;
|
|
}
|
|
}
|
|
|
|
// Sometimes SFGAO_HASSUBFOLDERS lies, notably in Network Neighborhood.
|
|
// When we actually scan the directory we can update the parent node
|
|
// if it turns out there's nothing underneath.
|
|
if (!gotOne) {
|
|
TVITEM tvi;
|
|
CString name = GetItemText(hParent);
|
|
tvi.hItem = hParent;
|
|
tvi.mask = TVIF_CHILDREN;
|
|
if (!GetItem(&tvi)) {
|
|
LOGI("Could not get TV '%ls'", name);
|
|
ASSERT(false);
|
|
} else if (tvi.cChildren) {
|
|
LOGI("Removing child count (%d) from '%ls'",
|
|
tvi.cChildren, name);
|
|
tvi.cChildren = 0;
|
|
if (!SetItem(&tvi)) {
|
|
LOGI("Could not set TV '%ls'", name);
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
Done:
|
|
if (lpe)
|
|
lpe->Release();
|
|
|
|
//The following 2 if statements will only be TRUE if we got here on an
|
|
//error condition from the "goto" statement. Otherwise, we free this memory
|
|
//at the end of the while loop above.
|
|
if (lpi && lpMalloc)
|
|
lpMalloc->Free(lpi);
|
|
if (lpMalloc)
|
|
lpMalloc->Release();
|
|
|
|
//LOGI("FillTreeView DONE");
|
|
}
|
|
|
|
/*
|
|
* Add a node to the tree.
|
|
*
|
|
* Returns TRUE on success, FALSE on failure.
|
|
*/
|
|
BOOL
|
|
ShellTree::AddNode(LPSHELLFOLDER lpsf, LPITEMIDLIST lpi, LPITEMIDLIST lpifq,
|
|
unsigned long ulAttrs, HTREEITEM hParent, HTREEITEM* phPrev)
|
|
{
|
|
TVITEM tvi;
|
|
TVINSERTSTRUCT tvins;
|
|
LPITEMIDLIST lpifqThisItem = NULL;
|
|
TVItemData* lptvid = NULL;
|
|
WCHAR szBuff[MAX_PATH];
|
|
CString name;
|
|
LPMALLOC lpMalloc = NULL;
|
|
HRESULT hr;
|
|
BOOL result = FALSE;
|
|
|
|
hr = ::SHGetMalloc(&lpMalloc);
|
|
if (FAILED(hr))
|
|
return FALSE;
|
|
|
|
//Now get the friendly name that we'll put in the treeview.
|
|
if (!Pidl::GetName(lpsf, lpi, SHGDN_NORMAL, &name)) {
|
|
LOGI("HEY: failed getting friendly name");
|
|
goto bail; // Error - could not get friendly name.
|
|
}
|
|
wcscpy_s(szBuff, name);
|
|
//LOGI("AddNode '%ls' ATTR=0x%08lx", szBuff, ulAttrs);
|
|
|
|
lptvid = (TVItemData*)lpMalloc->Alloc(sizeof(TVItemData));
|
|
if (!lptvid)
|
|
goto bail;
|
|
|
|
tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE |
|
|
TVIF_PARAM;
|
|
|
|
if (ulAttrs & SFGAO_HASSUBFOLDER) {
|
|
//This item has sub-folders, so let's put the + in the TreeView.
|
|
//The first time the user clicks on the item, we'll populate the
|
|
//sub-folders.
|
|
tvi.mask |= TVIF_CHILDREN;
|
|
tvi.cChildren = 1;
|
|
}
|
|
|
|
tvi.pszText = szBuff;
|
|
tvi.cchTextMax = MAX_PATH; // (not needed for InsertItem)
|
|
|
|
// Allocate a fully-qualified PIDL and stuff it in.
|
|
lpifqThisItem = Pidl::ConcatPidls(lpifq, lpi);
|
|
|
|
// Add the icons.
|
|
GetNormalAndSelectedIcons(lpifqThisItem, &tvi);
|
|
|
|
// Done with lipfqThisItem.
|
|
lptvid->lpifq = lpifqThisItem;
|
|
lpifqThisItem = NULL;
|
|
|
|
// Put in a copy of the relative PIDL.
|
|
lptvid->lpi = Pidl::CopyITEMID(lpMalloc, lpi);
|
|
|
|
// Stuff the parent folder's lpsf in.
|
|
lptvid->lpsfParent = lpsf;
|
|
lpsf->AddRef();
|
|
|
|
// Done with lptvid.
|
|
tvi.lParam = (LPARAM)lptvid;
|
|
lptvid = NULL;
|
|
|
|
// Populate the TreeView Insert Struct
|
|
// The item is the one filled above.
|
|
// Insert it after the last item inserted at this level.
|
|
// And indicate this is a root entry.
|
|
tvins.item = tvi;
|
|
tvins.hInsertAfter = *phPrev;
|
|
tvins.hParent = hParent;
|
|
|
|
// Add the item to the tree
|
|
*phPrev = InsertItem(&tvins);
|
|
|
|
result = TRUE;
|
|
|
|
bail:
|
|
lpMalloc->Release();
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Set the TreeView normal and selected icons for the specified entry.
|
|
*
|
|
* "lpifq" is the fully-qualified PIDL, LPTV_ITEM is an item in the tree.
|
|
*/
|
|
void
|
|
ShellTree::GetNormalAndSelectedIcons(LPITEMIDLIST lpifq,
|
|
LPTV_ITEM lptvitem)
|
|
{
|
|
//Note that we don't check the return value here because if GetIcon()
|
|
//fails, then we're in big trouble...
|
|
|
|
lptvitem->iImage = Pidl::GetItemIcon(lpifq,
|
|
SHGFI_SYSICONINDEX |
|
|
SHGFI_SMALLICON);
|
|
|
|
lptvitem->iSelectedImage = Pidl::GetItemIcon(lpifq,
|
|
SHGFI_SYSICONINDEX |
|
|
SHGFI_SMALLICON |
|
|
SHGFI_OPENICON);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Sort function callback for TreeView SortChildrenCB.
|
|
*/
|
|
int CALLBACK
|
|
ShellTree::TreeViewCompareProc(LPARAM lparam1, LPARAM lparam2, LPARAM)
|
|
{
|
|
TVItemData* lptvid1 = (TVItemData*)lparam1;
|
|
TVItemData* lptvid2 = (TVItemData*)lparam2;
|
|
HRESULT hr;
|
|
|
|
hr = lptvid1->lpsfParent->CompareIDs(0, lptvid1->lpi, lptvid2->lpi);
|
|
|
|
if (FAILED(hr)) {
|
|
ASSERT(false);
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
if (lptvid1->alphaSort && lptvid2->alphaSort) {
|
|
char buf1[MAX_PATH], buf2[MAX_PATH];
|
|
if (Pidl::GetName(lptvid1->lpsfParent, lptvid1->lpi, SHGDN_NORMAL, buf1) &&
|
|
Pidl::GetName(lptvid2->lpsfParent, lptvid2->lpi, SHGDN_NORMAL, buf2))
|
|
{
|
|
LOGI("COMPARING '%s' to '%s' (res=%d)", buf1, buf2,
|
|
(short) HRESULT_CODE(hr));
|
|
return stricmp(buf1, buf2);
|
|
} else {
|
|
ASSERT(false);
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return (short) HRESULT_CODE(hr);
|
|
}
|
|
|
|
|
|
/*
|
|
* Add a new folder to the tree at the currently-selected node. This may
|
|
* not actually add a folder if the new folder is at a point in the tree
|
|
* below where we have already expanded.
|
|
*
|
|
* Returns TRUE on success, or FALSE on failure.
|
|
*/
|
|
BOOL
|
|
ShellTree::AddFolderAtSelection(const CString& name)
|
|
{
|
|
LPSHELLFOLDER lpsf = NULL;
|
|
LPITEMIDLIST lpi = NULL;
|
|
HTREEITEM hParent;
|
|
LPMALLOC lpMalloc = NULL;
|
|
LPENUMIDLIST lpe = NULL;
|
|
const TVItemData* parentTvid;
|
|
TVItemData* newTvid = NULL;
|
|
HWND hwnd = ::GetParent(m_hWnd);
|
|
HTREEITEM hPrev = NULL;
|
|
BOOL result = false;
|
|
CString debugName;
|
|
HRESULT hr;
|
|
|
|
LOGI("AddFolderAtSelection '%ls'", name);
|
|
|
|
// Allocate a shell memory object.
|
|
hr = ::SHGetMalloc(&lpMalloc);
|
|
if (FAILED(hr))
|
|
return FALSE;
|
|
|
|
hParent = GetSelectedItem();
|
|
if (hParent == NULL) {
|
|
LOGI("Nothing selected!");
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* Now we either need to create a new node in an existing tree, or if
|
|
* we haven't expanded the current node yet then we can just let the
|
|
* usual FillTree mechanism do it for us.
|
|
*
|
|
* If the current node is marked as having children, but has no
|
|
* child structures, then it's a folder with sub-folders that hasn't
|
|
* been filled in yet. We don't need to do anything.
|
|
*
|
|
* If the current node doesn't have children, then this is a leaf
|
|
* node that has just become a branch. We update its "#of kids" state
|
|
* and again let the usual mechanisms do their work.
|
|
*
|
|
* If the current node has expanded children, then we need to do the
|
|
* work ourselves. (It's that, or invalidate the entire subtree,
|
|
* which has some UI consequences.)
|
|
*/
|
|
TVITEM tvi;
|
|
debugName = GetItemText(hParent);
|
|
tvi.hItem = hParent;
|
|
tvi.mask = TVIF_CHILDREN;
|
|
if (!GetItem(&tvi)) {
|
|
LOGI("Could not get TV '%ls'", debugName);
|
|
ASSERT(false);
|
|
} else {
|
|
HTREEITEM child = GetChildItem(hParent);
|
|
if (child == NULL && tvi.cChildren) {
|
|
LOGI(" Found unexpanded node, not adding %ls", name);
|
|
result = TRUE;
|
|
goto bail;
|
|
} else if (child == NULL && !tvi.cChildren) {
|
|
LOGI(" Found former leaf node, updating kids in %ls", debugName);
|
|
tvi.cChildren = 1;
|
|
if (!SetItem(&tvi)) {
|
|
LOGI("Could not set TV '%ls'", debugName);
|
|
ASSERT(false);
|
|
}
|
|
result = TRUE;
|
|
goto bail;
|
|
} else {
|
|
ASSERT(child != NULL && tvi.cChildren != 0);
|
|
LOGI(" Found expanded branch node '%ls', adding new '%ls'",
|
|
debugName, name);
|
|
}
|
|
}
|
|
|
|
|
|
parentTvid = (TVItemData*)GetItemData(hParent);
|
|
ASSERT(parentTvid != NULL);
|
|
|
|
// Get a handle to the ShellFolder for the currently selected node.
|
|
hr = parentTvid->lpsfParent->BindToObject(parentTvid->lpi,
|
|
0, IID_IShellFolder, (LPVOID *)&lpsf);
|
|
if (FAILED(hr)) {
|
|
LOGI("Glitch: unable to get ShellFolder for selected folder");
|
|
goto bail;
|
|
}
|
|
|
|
// Get an enumerator for the selected node.
|
|
hr = lpsf->EnumObjects(hwnd, SHCONTF_FOLDERS | SHCONTF_INCLUDEHIDDEN,
|
|
&lpe);
|
|
if (FAILED(hr)) {
|
|
LOGI("Glitch: unable to get enumerator for selected folder");
|
|
goto bail;
|
|
}
|
|
|
|
// Enumerate throught the list of folder and non-folder objects.
|
|
while (S_OK == lpe->Next(1, &lpi, NULL)) {
|
|
CString pidlName;
|
|
if (Pidl::GetName(lpsf, lpi, SHGDN_NORMAL, &pidlName)) {
|
|
if (name.CompareNoCase(pidlName) == 0) {
|
|
/* match! */
|
|
if (!AddNode(lpsf, lpi, parentTvid->lpifq, 0, hParent, &hPrev)) {
|
|
LOGI("AddNode failed!");
|
|
goto bail;
|
|
}
|
|
result = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
lpMalloc->Free(lpi); //Free the pidl that the shell gave us.
|
|
lpi = NULL;
|
|
}
|
|
|
|
bail:
|
|
if (lpi != NULL)
|
|
lpMalloc->Free(lpi);
|
|
if (lpsf != NULL)
|
|
lpsf->Release();
|
|
if (lpe != NULL)
|
|
lpe->Release();
|
|
lpMalloc->Release();
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* Respond to TVN_ITEMEXPANDING message.
|
|
*
|
|
* If the subtree hasn't been expanded yet, dig in.
|
|
*/
|
|
void ShellTree::OnFolderExpanding(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
TVItemData* lptvid; //Long pointer to TreeView item data
|
|
HRESULT hr;
|
|
LPSHELLFOLDER lpsf2=NULL;
|
|
TV_SORTCB tvscb;
|
|
|
|
NM_TREEVIEW* pnmtv = (NM_TREEVIEW*)pNMHDR;
|
|
if (pnmtv->itemNew.state & TVIS_EXPANDEDONCE) {
|
|
LOGI("Already expanded!");
|
|
return;
|
|
}
|
|
|
|
lptvid = (TVItemData*)pnmtv->itemNew.lParam;
|
|
if (lptvid) {
|
|
hr = lptvid->lpsfParent->BindToObject(lptvid->lpi,
|
|
0, IID_IShellFolder,(LPVOID *)&lpsf2);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
FillTreeView(lpsf2,
|
|
lptvid->lpifq,
|
|
pnmtv->itemNew.hItem);
|
|
}
|
|
|
|
tvscb.hParent = pnmtv->itemNew.hItem;
|
|
tvscb.lParam = 0;
|
|
tvscb.lpfnCompare = TreeViewCompareProc;
|
|
|
|
SortChildrenCB(&tvscb);
|
|
}
|
|
|
|
*pResult = 0;
|
|
}
|
|
|
|
#if 0
|
|
/****************************************************************************
|
|
*
|
|
* FUNCTION: GetContextMenu(NMHDR* pNMHDR, LRESULT* pResult)
|
|
*
|
|
* PURPOSE: Diplays a popup menu for the folder selected. Pass the
|
|
* parameters from Rclick() to this function.
|
|
*
|
|
* MESSAGEMAP: NM_RCLICK;
|
|
*
|
|
****************************************************************************/
|
|
void ShellTree::GetContextMenu(NMHDR*, LRESULT* pResult)
|
|
{
|
|
POINT pt;
|
|
TVItemData* lptvid; //Long pointer to TreeView item data
|
|
TV_HITTESTINFO tvhti;
|
|
TV_ITEM tvi;
|
|
|
|
::GetCursorPos((LPPOINT)&pt);
|
|
ScreenToClient(&pt);
|
|
tvhti.pt=pt;
|
|
HitTest(&tvhti);
|
|
SelectItem(tvhti.hItem);
|
|
if (tvhti.flags & (TVHT_ONITEMLABEL|TVHT_ONITEMICON))
|
|
{
|
|
ClientToScreen(&pt);
|
|
tvi.mask=TVIF_PARAM;
|
|
tvi.hItem=tvhti.hItem;
|
|
|
|
if (!GetItem(&tvi)){
|
|
return;
|
|
}
|
|
|
|
lptvid = (TVItemData*)tvi.lParam;
|
|
|
|
Pidl::DoTheMenuThing(::GetParent(m_hWnd),
|
|
lptvid->lpsfParent, lptvid->lpi, &pt);
|
|
}
|
|
|
|
*pResult = 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Respond to TVN_SELCHANGED notification.
|
|
*/
|
|
BOOL
|
|
ShellTree::OnSelectionChange(NMHDR* pnmh, LRESULT* pResult)
|
|
{
|
|
fFolderPathValid = OnFolderSelected(pnmh, pResult, fFolderPath);
|
|
*pResult = 0;
|
|
return FALSE; // allow window parent to handle notification
|
|
}
|
|
|
|
/*
|
|
* This does the bulk of the work when the selection changes.
|
|
*
|
|
* The filesystem path (if any) to the object is placed in "szFolderPath".
|
|
*/
|
|
BOOL
|
|
ShellTree::OnFolderSelected(NMHDR* pNMHDR, LRESULT* pResult,
|
|
CString &szFolderPath)
|
|
{
|
|
TVItemData* lptvid;
|
|
LPSHELLFOLDER lpsf2=NULL;
|
|
WCHAR szBuff[MAX_PATH];
|
|
HRESULT hr;
|
|
BOOL bRet=false;
|
|
HTREEITEM hItem=NULL;
|
|
|
|
hItem = GetSelectedItem();
|
|
if (hItem) {
|
|
lptvid = (TVItemData*)GetItemData(hItem);
|
|
|
|
if (lptvid && lptvid->lpsfParent && lptvid->lpi)
|
|
{
|
|
hr = lptvid->lpsfParent->BindToObject(lptvid->lpi,
|
|
0, IID_IShellFolder, (LPVOID *)&lpsf2);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
ULONG ulAttrs = SFGAO_FILESYSTEM;
|
|
|
|
// Determine what type of object we have.
|
|
lptvid->lpsfParent->GetAttributesOf(1,
|
|
(const struct _ITEMIDLIST **)&lptvid->lpi, &ulAttrs);
|
|
|
|
if (ulAttrs & (SFGAO_FILESYSTEM)) {
|
|
if (SHGetPathFromIDList(lptvid->lpifq, szBuff)){
|
|
szFolderPath = szBuff;
|
|
bRet = true;
|
|
}
|
|
}
|
|
|
|
if (bRet) {
|
|
LOGI("Now selected: '%ls'", szBuff);
|
|
} else {
|
|
LOGI("Now selected: <no path>");
|
|
}
|
|
|
|
#if 0
|
|
// If we're expanding into new territory, load the
|
|
// sub-tree. [This makes it expand things that aren't
|
|
// necessarily going to be opened, which is very bad for
|
|
// empty floppy and CD-ROM drives. Makes little sense.]
|
|
TV_SORTCB tvscb;
|
|
NM_TREEVIEW* pnmtv = (NM_TREEVIEW*)pNMHDR;
|
|
if ((pnmtv->itemNew.cChildren == 1) &&
|
|
!(pnmtv->itemNew.state & TVIS_EXPANDEDONCE))
|
|
{
|
|
FillTreeView(lpsf2, lptvid->lpifq, pnmtv->itemNew.hItem);
|
|
|
|
tvscb.hParent = pnmtv->itemNew.hItem;
|
|
tvscb.lParam = 0;
|
|
tvscb.lpfnCompare = TreeViewCompareProc;
|
|
SortChildrenCB(&tvscb);
|
|
|
|
pnmtv->itemNew.state |= TVIS_EXPANDEDONCE;
|
|
pnmtv->itemNew.stateMask |= TVIS_EXPANDEDONCE;
|
|
pnmtv->itemNew.mask |= TVIF_STATE;
|
|
SetItem(&pnmtv->itemNew);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
if(lpsf2)
|
|
lpsf2->Release();
|
|
|
|
}
|
|
*pResult = 0;
|
|
return bRet;
|
|
}
|
|
|
|
/*
|
|
* Handle TVN_DELETEITEM notification by cleaning up our stuff.
|
|
*/
|
|
void
|
|
ShellTree::OnDeleteShellItem(NMHDR* pNMHDR, LRESULT* pResult)
|
|
{
|
|
TVItemData* lptvid=NULL;
|
|
HRESULT hr;
|
|
LPMALLOC lpMalloc;
|
|
|
|
//LOGI("TVN_DELETEITEM");
|
|
|
|
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
|
|
|
|
//Let's free the memory for the TreeView item data...
|
|
hr = SHGetMalloc(&lpMalloc);
|
|
if (FAILED(hr))
|
|
return;
|
|
|
|
lptvid = (TVItemData*)pNMTreeView->itemOld.lParam;
|
|
lptvid->lpsfParent->Release();
|
|
lpMalloc->Free(lptvid->lpi);
|
|
lpMalloc->Free(lptvid->lpifq);
|
|
lpMalloc->Free(lptvid);
|
|
lpMalloc->Release();
|
|
|
|
*pResult = 0;
|
|
}
|
|
|
|
/*
|
|
* Gets a handle to the system image list (by just grabbing whatever is
|
|
* in place for C:\) and makes it available to the tree control.
|
|
*
|
|
* The image list should NOT be deleted.
|
|
*/
|
|
void
|
|
ShellTree::EnableImages()
|
|
{
|
|
// Get the handle to the system image list, for our icons
|
|
HIMAGELIST hImageList;
|
|
SHFILEINFO sfi;
|
|
|
|
hImageList = (HIMAGELIST)SHGetFileInfo(L"C:\\",
|
|
0, &sfi, sizeof(SHFILEINFO),
|
|
SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
|
|
|
|
// Attach ImageList to TreeView
|
|
if (hImageList)
|
|
::SendMessage(m_hWnd, TVM_SETIMAGELIST, (WPARAM) TVSIL_NORMAL,
|
|
(LPARAM)hImageList);
|
|
}
|
|
|
|
/****************************************************************************
|
|
*
|
|
* FUNCTION: GetSelectedFolderPath(CString &szFolderPath)
|
|
*
|
|
* PURPOSE: Retrieves the path of the currently selected string.
|
|
* Pass a CString object that will hold the folder path.
|
|
* If the path is not in the filesystem(eg MyComputer)
|
|
* or none is selected it returns false.
|
|
*
|
|
* MESSAGEMAP: NONE
|
|
*
|
|
****************************************************************************/
|
|
BOOL ShellTree::GetSelectedFolderPath(CString &szFolderPath)
|
|
{
|
|
TVItemData* lptvid; //Long pointer to TreeView item data
|
|
LPSHELLFOLDER lpsf2=NULL;
|
|
WCHAR szBuff[MAX_PATH];
|
|
HTREEITEM hItem=NULL;
|
|
HRESULT hr;
|
|
BOOL bRet=false;
|
|
|
|
hItem = GetSelectedItem();
|
|
if(hItem)
|
|
{
|
|
lptvid = (TVItemData*)GetItemData(hItem);
|
|
|
|
if (lptvid && lptvid->lpsfParent && lptvid->lpi)
|
|
{
|
|
hr = lptvid->lpsfParent->BindToObject(lptvid->lpi,
|
|
0, IID_IShellFolder,(LPVOID *)&lpsf2);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ULONG ulAttrs = SFGAO_FILESYSTEM;
|
|
|
|
// Determine what type of object we have.
|
|
lptvid->lpsfParent->GetAttributesOf(1,
|
|
(const struct _ITEMIDLIST **)&lptvid->lpi, &ulAttrs);
|
|
|
|
if (ulAttrs & (SFGAO_FILESYSTEM))
|
|
{
|
|
if(SHGetPathFromIDList(lptvid->lpifq, szBuff)){
|
|
szFolderPath = szBuff;
|
|
bRet = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
if(lpsf2)
|
|
lpsf2->Release();
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
/****************************************************************************
|
|
*
|
|
* FUNCTION: GetParentShellFolder(HTREEITEM folderNode)
|
|
*
|
|
* PURPOSE: Retrieves the pointer to the ISHELLFOLDER interface
|
|
* of the tree node passed as the paramter.
|
|
*
|
|
* MESSAGEMAP: NONE
|
|
*
|
|
****************************************************************************/
|
|
LPSHELLFOLDER ShellTree::GetParentShellFolder(HTREEITEM folderNode)
|
|
{
|
|
TVItemData* lptvid; //Long pointer to TreeView item data
|
|
|
|
lptvid = (TVItemData*)GetItemData(folderNode);
|
|
if (lptvid)
|
|
return lptvid->lpsfParent;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
*
|
|
* FUNCTION: GetRelativeIDLIST(HTREEITEM folderNode)
|
|
*
|
|
* PURPOSE: Retrieves the Pointer to an ITEMIDLIST structure that
|
|
* identifies the subfolder relative to its parent folder.
|
|
* see GetParentShellFolder();
|
|
*
|
|
* MESSAGEMAP: NONE
|
|
*
|
|
****************************************************************************/
|
|
LPITEMIDLIST ShellTree::GetRelativeIDLIST(HTREEITEM folderNode)
|
|
{
|
|
TVItemData* lptvid; //Long pointer to TreeView item data
|
|
|
|
lptvid = (TVItemData*)GetItemData(folderNode);
|
|
if (lptvid)
|
|
return lptvid->lpifq;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/****************************************************************************
|
|
*
|
|
* FUNCTION: GetFullyQualifiedIDLIST(HTREEITEM folderNode)
|
|
*
|
|
* PURPOSE: Retrieves the Pointer to an ITEMIDLIST
|
|
* structure that identifies the subfolder relative to the
|
|
* desktop. This is a fully qualified Item Identifier
|
|
*
|
|
* MESSAGEMAP: NONE
|
|
*
|
|
****************************************************************************/
|
|
LPITEMIDLIST ShellTree::GetFullyQualifiedID(HTREEITEM folderNode)
|
|
{
|
|
TVItemData* lptvid; //Long pointer to TreeView item data
|
|
|
|
lptvid = (TVItemData*)GetItemData(folderNode);
|
|
if (lptvid)
|
|
return lptvid->lpifq;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Tunnel into the tree, finding the node that corresponds to the
|
|
* requested pathname.
|
|
*
|
|
* Sets "resultMsg" to a non-empty string on error.
|
|
*/
|
|
void
|
|
ShellTree::TunnelTree(CString path, CString* pResultStr)
|
|
{
|
|
const WCHAR* str = path;
|
|
int len;
|
|
|
|
if (str[0] == '\\' && str[1] == '\\') {
|
|
*pResultStr = "Can't expand network locations directly.";
|
|
return;
|
|
}
|
|
len = path.GetLength();
|
|
if (len < 1) {
|
|
*pResultStr = "You must enter a folder name.";
|
|
return;
|
|
}
|
|
|
|
/* make sure it ends in \ so splitpath knows it's a directory */
|
|
if (path[len-1] != '\\')
|
|
path += '\\';
|
|
|
|
/* if it doesn't exist, there's not much point in searching for it */
|
|
PathName pathName(path);
|
|
if (!pathName.Exists()) {
|
|
*pResultStr = L"Folder not found.";
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Find the folder that corresponds to "My Computer", and then scan
|
|
* it for the drive letter.
|
|
*/
|
|
HTREEITEM myComputer = FindMyComputer();
|
|
if (myComputer == NULL) {
|
|
*pResultStr = L"Unable to locate My Computer in tree.";
|
|
return;
|
|
}
|
|
|
|
CString drive = pathName.GetDriveOnly();
|
|
LOGI("Searching for drive='%ls'", drive);
|
|
|
|
HTREEITEM node = FindDrive(myComputer, drive);
|
|
if (node == NULL) {
|
|
/* unexpected -- couldn't find the drive */
|
|
pResultStr->Format(L"Unable to find drive %ls.", drive);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* We've got the node for the drive. Now we just need to walk
|
|
* through the tree one level at a time, comparing the name in
|
|
* the tree against our pathname component.
|
|
*/
|
|
node = SearchTree(node, pathName.GetPathOnly());
|
|
|
|
if (node == NULL) {
|
|
/* unexpected -- file doesn't exist */
|
|
pResultStr->Format(L"Unable to find file '%ls'.",
|
|
(LPCWSTR) pathName.GetPathOnly());
|
|
} else {
|
|
Select(node, TVGN_CARET);
|
|
EnsureVisible(node);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find the tree entry that corresponds to "My Computer".
|
|
*
|
|
* This is hampered somewhat by the absence of a way to compare two
|
|
* shell folders for equality. The PIDL compare function is meant for
|
|
* sorting only (at least as far as it has been documented), and the My
|
|
* Computer "folder" has no path to examine.
|
|
*
|
|
* It helps greatly to assume that My Computer is right under Desktop.
|
|
* If it moved, or if we started the tree somewhere other than right at
|
|
* the desktop, we'd have to recursively search the tree.
|
|
*
|
|
* Returns a handle to the tree item, or NULL if My Computer wasn't found
|
|
* or didn't have any children.
|
|
*/
|
|
HTREEITEM
|
|
ShellTree::FindMyComputer(void)
|
|
{
|
|
LPSHELLFOLDER desktop = NULL;
|
|
LPITEMIDLIST myComputerPidl = NULL;
|
|
LPMALLOC lpMalloc = NULL;
|
|
HTREEITEM node;
|
|
HTREEITEM result = NULL;
|
|
HRESULT hr;
|
|
|
|
hr = ::SHGetMalloc(&lpMalloc);
|
|
if (FAILED(hr))
|
|
return NULL;
|
|
|
|
hr = SHGetDesktopFolder(&desktop);
|
|
if (FAILED(hr))
|
|
goto bail;
|
|
|
|
hr = SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, &myComputerPidl);
|
|
if (FAILED(hr))
|
|
goto bail;
|
|
|
|
node = GetRootItem();
|
|
while (node != NULL) {
|
|
CString itemText = GetItemText(node);
|
|
TVItemData* pData = (TVItemData*) GetItemData(node);
|
|
ASSERT(pData != NULL);
|
|
|
|
hr = desktop->CompareIDs(0, myComputerPidl, pData->lpi);
|
|
if (SUCCEEDED(hr) && HRESULT_CODE(hr) == 0) {
|
|
LOGI("MATCHED on '%ls'", itemText);
|
|
result = node;
|
|
break;
|
|
}
|
|
node = GetNextSiblingItem(node);
|
|
}
|
|
|
|
if (result != NULL && !ItemHasChildren(result)) {
|
|
LOGI("Glitch: My Computer has no children");
|
|
result = NULL;
|
|
}
|
|
|
|
bail:
|
|
if (desktop != NULL)
|
|
desktop->Release();
|
|
lpMalloc->Free(myComputerPidl);
|
|
lpMalloc->Release();
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Given a pointer to the My Computer node in the tree, find the node
|
|
* corresponding to the requested drive (which should be of the form
|
|
* "C:").
|
|
*
|
|
* Returns a pointer to the drive's node on success, or NULL on failure.
|
|
*/
|
|
HTREEITEM
|
|
ShellTree::FindDrive(HTREEITEM myComputer, const CString& drive)
|
|
{
|
|
CString udrive;
|
|
|
|
/* expand & scan */
|
|
Expand(myComputer, TVE_EXPAND);
|
|
|
|
HTREEITEM node;
|
|
node = GetChildItem(myComputer);
|
|
if (node == NULL) {
|
|
ASSERT(false); // we verified My Computer has kids earlier
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Look for the drive letter. It's buried amongst other fluff, so
|
|
* we have to rely on Windows preventing the use of a ":" anywhere
|
|
* else in the string to avoid false-positives.
|
|
*
|
|
* We *might* be able to assume it looks like "(C:)", but that's
|
|
* probably unwise.
|
|
*/
|
|
udrive = drive;
|
|
udrive.MakeUpper();
|
|
while (node != NULL) {
|
|
CString itemText = GetItemText(node);
|
|
itemText.MakeUpper();
|
|
|
|
//LOGI("COMPARING '%ls' vs '%ls'", (LPCWSTR) udrive, (LPCWSTR) itemText);
|
|
if (itemText.Find(udrive) != -1) {
|
|
LOGI("MATCHED '%ls' in '%ls'", (LPCWSTR) udrive, (LPCWSTR) itemText);
|
|
break;
|
|
}
|
|
node = GetNextSiblingItem(node);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
/*
|
|
* Given a path, search a subtree following the components.
|
|
*
|
|
* Pass in the tree's root (it's children will be searched for a
|
|
* match with the first path component) and the path to look for
|
|
* (which must start and end with '\\').
|
|
*/
|
|
HTREEITEM
|
|
ShellTree::SearchTree(HTREEITEM treeNode, const CString& path)
|
|
{
|
|
LOGI("SearchTree node=0x%08lx path='%ls'",
|
|
treeNode, (LPCTSTR) path);
|
|
|
|
HTREEITEM node;
|
|
CString mangle(path);
|
|
WCHAR* start;
|
|
WCHAR* end;
|
|
|
|
/* make a copy of "path" that we can mess with */
|
|
start = mangle.GetBuffer(0);
|
|
if (start == NULL || *start != '\\' || *(start + wcslen(start)-1) != '\\')
|
|
return NULL;
|
|
start++;
|
|
|
|
node = treeNode;
|
|
while (*start != '\0') {
|
|
/* grab first node in next level down */
|
|
Expand(node, TVE_EXPAND); // need to fill in the tree
|
|
node = GetChildItem(node);
|
|
|
|
end = wcschr(start, '\\');
|
|
if (end == NULL) {
|
|
ASSERT(false);
|
|
return NULL;
|
|
}
|
|
*end = '\0';
|
|
|
|
while (node != NULL) {
|
|
CString itemText = GetItemText(node);
|
|
|
|
//LOGI("COMPARE '%s' '%s'", start, itemText);
|
|
if (itemText.CompareNoCase(start) == 0) {
|
|
//LOGI("MATCHED '%s' '%s'", itemText, start);
|
|
break;
|
|
}
|
|
|
|
node = GetNextSiblingItem(node);
|
|
}
|
|
if (node == NULL) {
|
|
LOGI("NOT FOUND '%ls' '%ls'", (LPCTSTR) path, start);
|
|
break;
|
|
}
|
|
|
|
start = end+1;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
#ifdef USE_OLD
|
|
|
|
/****************************************************************************
|
|
*
|
|
* FUNCTION: SearchTree( HTREEITEM treeNode,
|
|
* CString szSearchName )
|
|
*
|
|
* PURPOSE: Too crude to explain, just use it
|
|
*
|
|
* WARNING: Only works if you use the default PopulateTree()
|
|
* Not guaranteed to work on any future or existing
|
|
* version of windows. Use with caution. Pretty much
|
|
* ok if you're using on local drives
|
|
*
|
|
****************************************************************************/
|
|
bool ShellTree::SearchTree(HTREEITEM treeNode,
|
|
CString szSearchName,
|
|
FindAttribs attr)
|
|
{
|
|
TVItemData* lptvid; //Long pointer to TreeView item data
|
|
LPSHELLFOLDER lpsf2=NULL;
|
|
char drive[_MAX_DRIVE];
|
|
char dir[_MAX_DIR];
|
|
char fname[_MAX_FNAME];
|
|
char ext[_MAX_EXT];
|
|
bool bRet=false;
|
|
HRESULT hr;
|
|
CString szCompare;
|
|
|
|
szSearchName.MakeUpper();
|
|
while(treeNode && bRet==false)
|
|
{
|
|
lptvid=(TVItemData*)GetItemData(treeNode);
|
|
if (lptvid && lptvid->lpsfParent && lptvid->lpi)
|
|
{
|
|
hr=lptvid->lpsfParent->BindToObject(lptvid->lpi,
|
|
0,IID_IShellFolder,(LPVOID *)&lpsf2);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ULONG ulAttrs = SFGAO_FILESYSTEM;
|
|
lptvid->lpsfParent->GetAttributesOf(1,
|
|
(const struct _ITEMIDLIST **)&lptvid->lpi, &ulAttrs);
|
|
if (ulAttrs & (SFGAO_FILESYSTEM))
|
|
{
|
|
if(SHGetPathFromIDList(lptvid->lpifq,
|
|
szCompare.GetBuffer(MAX_PATH)))
|
|
{
|
|
switch(attr)
|
|
{
|
|
case type_drive:
|
|
_splitpath(szCompare,drive,dir,fname,ext);
|
|
szCompare=drive;
|
|
break;
|
|
case type_folder:
|
|
szCompare = GetItemText(treeNode);
|
|
break;
|
|
}
|
|
szCompare.MakeUpper();
|
|
if(szCompare == szSearchName)
|
|
{
|
|
EnsureVisible(treeNode);
|
|
SelectItem(treeNode);
|
|
bRet=true;
|
|
}
|
|
}
|
|
}
|
|
lpsf2->Release();
|
|
}
|
|
}
|
|
treeNode = GetNextSiblingItem(treeNode);
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
/****************************************************************************
|
|
*
|
|
* FUNCTION: TunnelTree(CString szFindPath)
|
|
*
|
|
* PURPOSE: Too crude to explain, just use it
|
|
*
|
|
* WARNING: Only works if you use the default PopulateTree()
|
|
* Not guaranteed to work on any future or existing
|
|
* version of windows. Use with caution. Pretty much
|
|
* ok if you're using on local drives
|
|
*
|
|
****************************************************************************/
|
|
void ShellTree::TunnelTree(CString szFindPath)
|
|
{
|
|
HTREEITEM subNode = GetRootItem();
|
|
CString szPathHop;
|
|
char drive[_MAX_DRIVE];
|
|
char dir[_MAX_DIR];
|
|
char fname[_MAX_FNAME];
|
|
char ext[_MAX_EXT];
|
|
char delimiter[]="\\";
|
|
|
|
PathName checkPath(szFindPath);
|
|
if(!checkPath.Exists())
|
|
{
|
|
MessageBox(szFindPath,"Folder not found",MB_ICONERROR);
|
|
return;
|
|
}
|
|
|
|
if(szFindPath.ReverseFind('\\') != szFindPath.GetLength()-1)
|
|
{
|
|
szFindPath += "\\";
|
|
}
|
|
|
|
_splitpath(szFindPath,drive,dir,fname,ext);
|
|
|
|
//search the drive first
|
|
szPathHop=drive;
|
|
subNode=GetChildItem(subNode);
|
|
if(subNode)
|
|
{
|
|
if(SearchTree(subNode,szPathHop, ShellTree::type_drive))
|
|
{
|
|
//break down subfolders and search
|
|
char *p=strtok(dir,delimiter);
|
|
while(p)
|
|
{
|
|
subNode = GetSelectedItem();
|
|
subNode = GetChildItem(subNode);
|
|
if(SearchTree(subNode,p,ShellTree::type_folder))
|
|
p=strtok(NULL,delimiter);
|
|
else
|
|
p=NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#endif
|
|
#ifdef USE_NEW
|
|
// new version for Win2K
|
|
|
|
/****************************************************************************
|
|
*
|
|
* FUNCTION: SearchTree( HTREEITEM treeNode,
|
|
* CString szSearchName )
|
|
*
|
|
* PURPOSE: Too crude to explain, just use it
|
|
*
|
|
* WARNING: Only works if you use the default PopulateTree()
|
|
* Not guaranteed to work on any future or existing
|
|
* version of windows. Use with caution. Pretty much
|
|
* ok if you're using on local drives
|
|
*
|
|
****************************************************************************/
|
|
bool ShellTree::SearchTree(HTREEITEM treeNode,
|
|
CString szSearchName,
|
|
FindAttribs attr)
|
|
{
|
|
TVItemData* lptvid; //Long pointer to TreeView item data
|
|
LPSHELLFOLDER lpsf2=NULL;
|
|
char drive[_MAX_DRIVE];
|
|
char dir[_MAX_DIR];
|
|
char fname[_MAX_FNAME];
|
|
char ext[_MAX_EXT];
|
|
bool bRet=false;
|
|
HRESULT hr;
|
|
CString szCompare;
|
|
|
|
szSearchName.MakeUpper();
|
|
while(treeNode && bRet==false)
|
|
{
|
|
lptvid=(TVItemData*)GetItemData(treeNode);
|
|
if (lptvid && lptvid->lpsfParent && lptvid->lpi)
|
|
{
|
|
hr=lptvid->lpsfParent->BindToObject(lptvid->lpi,
|
|
0,IID_IShellFolder,(LPVOID *)&lpsf2);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ULONG ulAttrs = SFGAO_FILESYSTEM;
|
|
lptvid->lpsfParent->GetAttributesOf(1, (const struct _ITEMIDLIST **)&lptvid->lpi, &ulAttrs);
|
|
if (ulAttrs & (SFGAO_FILESYSTEM))
|
|
{
|
|
if(SHGetPathFromIDList(lptvid->lpifq,
|
|
szCompare.GetBuffer(MAX_PATH)))
|
|
{
|
|
CString folder;
|
|
|
|
SHGetSpecialFolderPath(NULL,
|
|
folder.GetBuffer(MAX_PATH),
|
|
CSIDL_COMMON_DESKTOPDIRECTORY, FALSE);
|
|
if( szCompare.Find( folder ) != -1 )
|
|
if( szSearchName.Find( szCompare ) == -1 ) {
|
|
LOGI("Magic match on '%s'", szCompare);
|
|
return false;
|
|
}
|
|
|
|
SHGetSpecialFolderPath(NULL,
|
|
folder.GetBuffer(MAX_PATH),
|
|
CSIDL_DESKTOPDIRECTORY, FALSE );
|
|
if( szCompare.Find( folder ) != -1 )
|
|
if( szSearchName.Find( szCompare ) == -1 ) {
|
|
LOGI("MAGIC '%s'='%s' and '%s'='%s'",
|
|
szCompare, folder, szSearchName, szCompare);
|
|
return false;
|
|
}
|
|
|
|
SHGetSpecialFolderPath(NULL,
|
|
folder.GetBuffer(MAX_PATH),
|
|
CSIDL_PERSONAL, FALSE );
|
|
if( szCompare.Find( folder ) != -1 )
|
|
if( szSearchName.Find( szCompare ) == -1 ) {
|
|
LOGI("Magic match on '%s'", szCompare);
|
|
return false;
|
|
}
|
|
|
|
switch(attr) {
|
|
case type_drive:
|
|
_splitpath(szCompare,drive,dir,fname,ext);
|
|
szCompare = drive;
|
|
break;
|
|
case type_folder:
|
|
szCompare = GetItemText(treeNode);
|
|
break;
|
|
}
|
|
szCompare.MakeUpper();
|
|
if(szCompare == szSearchName)
|
|
{
|
|
EnsureVisible(treeNode);
|
|
SelectItem(treeNode);
|
|
bRet=true;
|
|
}
|
|
}
|
|
}
|
|
lpsf2->Release();
|
|
}
|
|
}
|
|
treeNode = GetNextSiblingItem(treeNode);
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
/****************************************************************************
|
|
*
|
|
* FUNCTION: TunnelTree(CString szFindPath)
|
|
*
|
|
* PURPOSE: Too crude to explain, just use it
|
|
*
|
|
* WARNING: Only works if you use the default PopulateTree()
|
|
* Not guaranteed to work on any future or existing
|
|
* version of windows. Use with caution. Pretty much
|
|
* ok if you're using on local drives
|
|
*
|
|
****************************************************************************/
|
|
void ShellTree::TunnelTree(CString szFindPath)
|
|
{
|
|
HTREEITEM subNode = GetRootItem();
|
|
CString szPathHop;
|
|
char drive[_MAX_DRIVE];
|
|
char dir[_MAX_DIR];
|
|
char fname[_MAX_FNAME];
|
|
char ext[_MAX_EXT];
|
|
char delimiter[]="\\";
|
|
|
|
PathName checkPath(szFindPath);
|
|
if(!checkPath.Exists()) {
|
|
MessageBox(szFindPath,"Folder not found",MB_ICONERROR);
|
|
return;
|
|
}
|
|
|
|
if(szFindPath.ReverseFind('\\') != szFindPath.GetLength()-1) {
|
|
szFindPath += "\\";
|
|
}
|
|
|
|
_splitpath(szFindPath, drive, dir, fname, ext);
|
|
|
|
HTREEITEM root = subNode;
|
|
//search the drive first
|
|
szPathHop=drive;
|
|
do {
|
|
CString currItem = GetItemText( root );
|
|
LOGI("Scanning '%s' for drive '%s'", currItem, szPathHop);
|
|
if (ItemHasChildren(root))
|
|
{
|
|
Expand(root, TVE_EXPAND);
|
|
subNode = GetChildItem(root);
|
|
if(subNode)
|
|
{
|
|
if(SearchTree(subNode, szPathHop, ShellTree::type_drive))
|
|
{
|
|
// we have a match on the drive; SearchTree will have
|
|
// left it as the selected item
|
|
LOGI("Tunnel match '%s' in subnode", szPathHop);
|
|
|
|
// break down subfolders and search
|
|
char* p = strtok(dir, delimiter);
|
|
while (p) {
|
|
subNode = GetSelectedItem();
|
|
subNode = GetChildItem(subNode);
|
|
if(SearchTree(subNode, p, ShellTree::type_folder))
|
|
p=strtok(NULL,delimiter);
|
|
else
|
|
p=NULL;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
Expand(root, TVE_COLLAPSE);
|
|
}
|
|
|
|
root = GetNextSiblingItem( root );
|
|
} while( root );
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
#if 0 // quick test
|
|
LPMALLOC g_pMalloc = NULL;
|
|
|
|
// Main_OnBrowse - browses for a program folder.
|
|
// hwnd - handle to the application's main window.
|
|
//
|
|
// Uses the global variable g_pMalloc, which is assumed to point
|
|
// to the shell's IMalloc interface.
|
|
void Main_OnBrowse(HWND hwnd)
|
|
{
|
|
BROWSEINFO bi;
|
|
LPSTR lpBuffer;
|
|
LPITEMIDLIST pidlPrograms; // PIDL for Programs folder
|
|
LPITEMIDLIST pidlBrowse; // PIDL selected by user
|
|
|
|
if (g_pMalloc == NULL)
|
|
::SHGetMalloc(&g_pMalloc);
|
|
|
|
// Allocate a buffer to receive browse information.
|
|
if ((lpBuffer = (LPSTR) g_pMalloc->Alloc(
|
|
MAX_PATH)) == NULL)
|
|
return;
|
|
|
|
// Get the PIDL for the Programs folder.
|
|
if (!SUCCEEDED(SHGetSpecialFolderLocation(
|
|
hwnd, CSIDL_PROGRAMS, &pidlPrograms))) {
|
|
g_pMalloc->Free(lpBuffer);
|
|
return;
|
|
}
|
|
|
|
// Fill in the BROWSEINFO structure.
|
|
bi.hwndOwner = hwnd;
|
|
bi.pidlRoot = pidlPrograms;
|
|
bi.pszDisplayName = lpBuffer;
|
|
bi.lpszTitle = "Choose a Program Group";
|
|
bi.ulFlags = 0;
|
|
bi.lpfn = NULL;
|
|
bi.lParam = 0;
|
|
|
|
// Browse for a folder and return its PIDL.
|
|
pidlBrowse = SHBrowseForFolder(&bi);
|
|
if (pidlBrowse != NULL) {
|
|
|
|
// Show the display name, title, and file system path.
|
|
MessageBox(hwnd, lpBuffer, "Display name", MB_OK);
|
|
if (SHGetPathFromIDList(pidlBrowse, lpBuffer))
|
|
SetWindowText(hwnd, lpBuffer);
|
|
|
|
// Free the PIDL returned by SHBrowseForFolder.
|
|
g_pMalloc->Free(pidlBrowse);
|
|
}
|
|
|
|
// Clean up.
|
|
g_pMalloc->Free(pidlPrograms);
|
|
g_pMalloc->Free(lpBuffer);
|
|
}
|
|
#endif
|