ciderpress/util/ShellTree.cpp
2007-03-27 17:47:10 +00:00

1566 lines
41 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 == nil)
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 = nil, lpsf2 = nil;
LPITEMIDLIST lpi = nil;
TV_SORTCB tvscb;
LPMALLOC lpMalloc = nil;
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 = nil;
ASSERT(lpi == nil);
} else {
// find the desired special folder
hr = SHGetSpecialFolderLocation(m_hWnd, nFolder, &lpi);
if (FAILED(hr)) {
WMSG0("BUG: could not find requested special folder\n");
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 != nil)
lpsf->Release();
if (lpsf != nil)
lpsf2->Release();
lpMalloc->Free(lpi);
return retval;
}
/*
* Open up and select My Computer.
*/
void
ShellTree::ExpandMyComputer(void)
{
HTREEITEM hItem;
hItem = FindMyComputer();
if (hItem == nil)
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 */
char szBuff[MAX_PATH];
if (Pidl::GetName(lpsf, lpi, SHGDN_NORMAL, szBuff)) {
WMSG2(" Checking '%s' 0x%08lx\n",
szBuff, ulAttrs);
} else {
WMSG1(" Checking <no-name> 0x%08lx\n",
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 == nil) {
/* 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)) {
WMSG0("AddNode failed!\n");
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)) {
WMSG1("Could not get TV '%s'\n", name);
ASSERT(false);
} else if (tvi.cChildren) {
WMSG2("Removing child count (%d) from '%s'\n",
tvi.cChildren, name);
tvi.cChildren = 0;
if (!SetItem(&tvi)) {
WMSG1("Could not set TV '%s'\n", 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();
//WMSG0("FillTreeView DONE\n");
}
/*
* 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 = nil;
TVItemData* lptvid = nil;
char szBuff[MAX_PATH];
LPMALLOC lpMalloc = nil;
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, szBuff)) {
WMSG0("HEY: failed getting friendly name\n");
goto bail; // Error - could not get friendly name.
}
//WMSG2("AddNode '%s' ATTR=0x%08lx\n", 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 = nil;
// 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 = nil;
// 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))
{
WMSG3("COMPARING '%s' to '%s' (res=%d)\n", 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 = nil;
LPITEMIDLIST lpi = nil;
HTREEITEM hParent;
LPMALLOC lpMalloc = nil;
LPENUMIDLIST lpe = nil;
const TVItemData* parentTvid;
TVItemData* newTvid = nil;
HWND hwnd = ::GetParent(m_hWnd);
char szBuff[MAX_PATH];
HTREEITEM hPrev = nil;
BOOL result = false;
CString debugName;
HRESULT hr;
WMSG1("AddFolderAtSelection '%s'\n", name);
// Allocate a shell memory object.
hr = ::SHGetMalloc(&lpMalloc);
if (FAILED(hr))
return FALSE;
hParent = GetSelectedItem();
if (hParent == nil) {
WMSG0("Nothing selected!\n");
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)) {
WMSG1("Could not get TV '%s'\n", debugName);
ASSERT(false);
} else {
HTREEITEM child = GetChildItem(hParent);
if (child == nil && tvi.cChildren) {
WMSG1(" Found unexpanded node, not adding %s\n", name);
result = TRUE;
goto bail;
} else if (child == nil && !tvi.cChildren) {
WMSG1(" Found former leaf node, updating kids in %s\n", debugName);
tvi.cChildren = 1;
if (!SetItem(&tvi)) {
WMSG1("Could not set TV '%s'\n", debugName);
ASSERT(false);
}
result = TRUE;
goto bail;
} else {
ASSERT(child != nil && tvi.cChildren != 0);
WMSG2(" Found expanded branch node '%s', adding new '%s'\n",
debugName, name);
}
}
parentTvid = (TVItemData*)GetItemData(hParent);
ASSERT(parentTvid != nil);
// 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)) {
WMSG0("Glitch: unable to get ShellFolder for selected folder\n");
goto bail;
}
// Get an enumerator for the selected node.
hr = lpsf->EnumObjects(hwnd, SHCONTF_FOLDERS | SHCONTF_INCLUDEHIDDEN,
&lpe);
if (FAILED(hr)) {
WMSG0("Glitch: unable to get enumerator for selected folder\n");
goto bail;
}
// Enumerate throught the list of folder and non-folder objects.
while (S_OK == lpe->Next(1, &lpi, nil)) {
if (Pidl::GetName(lpsf, lpi, SHGDN_NORMAL, szBuff)) {
if (name.CompareNoCase(szBuff) == 0) {
/* match! */
if (!AddNode(lpsf, lpi, parentTvid->lpifq, 0, hParent, &hPrev)) {
WMSG0("AddNode failed!\n");
goto bail;
}
result = TRUE;
break;
}
}
lpMalloc->Free(lpi); //Free the pidl that the shell gave us.
lpi = nil;
}
bail:
if (lpi != nil)
lpMalloc->Free(lpi);
if (lpsf != nil)
lpsf->Release();
if (lpe != nil)
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) {
WMSG0("Already expanded!\n");
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;
char 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) {
WMSG1("Now selected: '%s'\n", szBuff);
} else {
WMSG0("Now selected: <no path>\n");
}
#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;
//WMSG0("TVN_DELETEITEM\n");
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((LPCSTR)"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;
char 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 char* str = path;
int len;
if (str[0] == '\\' && str[1] == '\\') {
*pResultStr = "Can't expand network locations directly.";
return;
}
len = strlen(path);
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 = "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 == nil) {
*pResultStr = "Unable to locate My Computer in tree.";
return;
}
CString drive = pathName.GetDriveOnly();
WMSG1("Searching for drive='%s'\n", drive);
HTREEITEM node = FindDrive(myComputer, drive);
if (node == nil) {
/* unexpected -- couldn't find the drive */
pResultStr->Format("Unable to find drive %s.", 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 == nil) {
/* unexpected -- file doesn't exist */
pResultStr->Format("Unable to find file '%s'.",
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 nil if My Computer wasn't found
* or didn't have any children.
*/
HTREEITEM
ShellTree::FindMyComputer(void)
{
LPSHELLFOLDER desktop = nil;
LPITEMIDLIST myComputerPidl = nil;
LPMALLOC lpMalloc = nil;
HTREEITEM node;
HTREEITEM result = nil;
HRESULT hr;
hr = ::SHGetMalloc(&lpMalloc);
if (FAILED(hr))
return nil;
hr = SHGetDesktopFolder(&desktop);
if (FAILED(hr))
goto bail;
hr = SHGetSpecialFolderLocation(nil, CSIDL_DRIVES, &myComputerPidl);
if (FAILED(hr))
goto bail;
node = GetRootItem();
while (node != nil) {
CString itemText = GetItemText(node);
TVItemData* pData = (TVItemData*) GetItemData(node);
ASSERT(pData != nil);
hr = desktop->CompareIDs(0, myComputerPidl, pData->lpi);
if (SUCCEEDED(hr) && HRESULT_CODE(hr) == 0) {
WMSG1("MATCHED on '%s'\n", itemText);
result = node;
break;
}
node = GetNextSiblingItem(node);
}
if (result != nil && !ItemHasChildren(result)) {
WMSG0("Glitch: My Computer has no children\n");
result = nil;
}
bail:
if (desktop != nil)
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 nil 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 == nil) {
ASSERT(false); // we verified My Computer has kids earlier
return nil;
}
/*
* 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 != nil) {
CString itemText = GetItemText(node);
itemText.MakeUpper();
//WMSG2("COMPARING '%s' vs '%s'\n", udrive, itemText);
if (itemText.Find(udrive) != -1) {
WMSG2("MATCHED '%s' in '%s'\n", udrive, 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)
{
WMSG2("SearchTree node=0x%08lx path='%s'\n",
treeNode, (LPCTSTR) path);
HTREEITEM node;
CString mangle(path);
char* start;
char* end;
/* make a copy of "path" that we can mess with */
start = mangle.GetBuffer(0);
if (start == nil || *start != '\\' || *(start + strlen(start)-1) != '\\')
return nil;
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 = strchr(start, '\\');
if (end == nil) {
ASSERT(false);
return nil;
}
*end = '\0';
while (node != nil) {
CString itemText = GetItemText(node);
//WMSG2("COMPARE '%s' '%s'\n", start, itemText);
if (itemText.CompareNoCase(start) == 0) {
//WMSG2("MATCHED '%s' '%s'\n", itemText, start);
break;
}
node = GetNextSiblingItem(node);
}
if (node == nil) {
WMSG2("NOT FOUND '%s' '%s'\n", (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 ) {
WMSG1("Magic match on '%s'\n", szCompare);
return false;
}
SHGetSpecialFolderPath(NULL,
folder.GetBuffer(MAX_PATH),
CSIDL_DESKTOPDIRECTORY, FALSE );
if( szCompare.Find( folder ) != -1 )
if( szSearchName.Find( szCompare ) == -1 ) {
WMSG4("MAGIC '%s'='%s' and '%s'='%s'\n",
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 ) {
WMSG1("Magic match on '%s'\n", 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 );
WMSG2("Scanning '%s' for drive '%s'\n", 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
WMSG1("Tunnel match '%s' in subnode\n", 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 = nil;
// 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 == nil)
::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