1473 lines
38 KiB
C
1473 lines
38 KiB
C
|
#define NOSHELLDEBUG // don't take shell versions of this
|
||
|
|
||
|
// todo:
|
||
|
//
|
||
|
// "New Folder" capability
|
||
|
//
|
||
|
// dlb click on folders should expand/collapse
|
||
|
//
|
||
|
// delayed keyboard selection so keyboard navigation does not generate a sel change
|
||
|
//
|
||
|
// drag and drop image drawing (not just OLE cursors)
|
||
|
//
|
||
|
// name space change notification. hook up notify handler
|
||
|
//
|
||
|
// default keyboard accelerators (F2 = Rename, etc)
|
||
|
//
|
||
|
// Partial expanded nodes in the tree "Tree Down" (TVIS_EXPANDPARTIAL) for net cases
|
||
|
//
|
||
|
// Programbility:
|
||
|
// notifies - sel changed, node expanded, verb executed, etc.
|
||
|
// cmds - do verb, get item, etc.
|
||
|
|
||
|
#include <windows.h>
|
||
|
#include <shlobj.h>
|
||
|
#include <shlobjp.h> // for SHChangeNotifyReigster
|
||
|
|
||
|
#include "idlist.h"
|
||
|
|
||
|
#include "nsc.h"
|
||
|
#include "dropsrc.h"
|
||
|
#include "common.h"
|
||
|
|
||
|
#define ID_CONTROL 100
|
||
|
|
||
|
#define WM_CHANGENOTIFY (WM_USER + 11)
|
||
|
|
||
|
#define SHCNE_FOLDERS \
|
||
|
(SHCNE_MKDIR | SHCNE_RMDIR | \
|
||
|
SHCNE_MEDIAINSERTED | SHCNE_MEDIAREMOVED | \
|
||
|
SHCNE_DRIVEREMOVED | SHCNE_DRIVEADD | \
|
||
|
SHCNE_RENAMEFOLDER | \
|
||
|
SHCNE_UPDATEDIR | \
|
||
|
SHCNE_UPDATEITEM | \
|
||
|
SHCNE_SERVERDISCONNECT | \
|
||
|
SHCNE_UPDATEIMAGE | \
|
||
|
SHCNE_DRIVEADDGUI)
|
||
|
|
||
|
#define SHCNE_ITEMS (SHCNE_CREATE | SHCNE_DELETE | SHCNE_RENAMEITEM | SHCNE_ASSOCCHANGED)
|
||
|
|
||
|
|
||
|
void _RegisterNotify(NSC *pns, BOOL fReRegister)
|
||
|
{
|
||
|
if (pns->nChangeNotifyID)
|
||
|
{
|
||
|
SHChangeNotifyDeregister(pns->nChangeNotifyID);
|
||
|
pns->nChangeNotifyID = 0;
|
||
|
}
|
||
|
|
||
|
if (fReRegister)
|
||
|
{
|
||
|
SHChangeNotifyEntry fsne;
|
||
|
|
||
|
fsne.pidl = pns->pidlRoot;
|
||
|
fsne.fRecursive = TRUE;
|
||
|
|
||
|
pns->nChangeNotifyID = SHChangeNotifyRegister(pns->hwnd,
|
||
|
// SHCNRF_NewDelivery |
|
||
|
SHCNRF_ShellLevel | SHCNRF_InterruptLevel,
|
||
|
pns->style & NSS_SHOWNONFOLDERS ? (SHCNE_FOLDERS | SHCNE_ITEMS) : (SHCNE_FOLDERS),
|
||
|
WM_CHANGENOTIFY, 1, &fsne);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
LRESULT _OnNCCreate(HWND hwnd, LPCREATESTRUCT pcs)
|
||
|
{
|
||
|
NSC *pns = LocalAlloc(LPTR, sizeof(NSC));
|
||
|
if (pns)
|
||
|
{
|
||
|
pns->hwnd = hwnd;
|
||
|
pns->hwndParent = pcs->hwndParent;
|
||
|
pns->style = pcs->style;
|
||
|
pns->id = (UINT)pcs->hMenu;
|
||
|
|
||
|
// remove border styles from our window that we propogated to
|
||
|
// our child control
|
||
|
|
||
|
SetWindowLong(hwnd, GWL_STYLE, pcs->style & ~WS_BORDER);
|
||
|
SetWindowLong(hwnd, GWL_EXSTYLE, pcs->dwExStyle & ~WS_EX_CLIENTEDGE);
|
||
|
|
||
|
pns->hwndTree = CreateWindowEx(pcs->dwExStyle, WC_TREEVIEW, NULL,
|
||
|
(pcs->style & 0xFFFF0000) | (WS_CHILD | TVS_HASBUTTONS | TVS_EDITLABELS | TVS_SHOWSELALWAYS), // TVS_HASLINES |
|
||
|
pcs->x, pcs->y, pcs->cx, pcs->cy,
|
||
|
hwnd, (HMENU)ID_CONTROL, pcs->hInstance, NULL);
|
||
|
if (pns->hwndTree)
|
||
|
{
|
||
|
SHFILEINFO sfi;
|
||
|
HIMAGELIST himl = (HIMAGELIST)SHGetFileInfo("C:\\", 0, &sfi, sizeof(SHFILEINFO), SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
|
||
|
|
||
|
SetWindowLong(hwnd, 0, (LONG)pns);
|
||
|
|
||
|
TreeView_SetImageList(pns->hwndTree, himl, TVSIL_NORMAL);
|
||
|
|
||
|
if (pns->style & NSS_DROPTARGET)
|
||
|
CTreeDropTarget_Register(pns);
|
||
|
|
||
|
return TRUE; // success
|
||
|
}
|
||
|
LocalFree(pns);
|
||
|
}
|
||
|
DebugMsg(DM_ERROR, "Failing NameSpaceControl create");
|
||
|
|
||
|
return FALSE; // fail the create
|
||
|
}
|
||
|
|
||
|
void _ReleaseCachedShellFolder(NSC *pns)
|
||
|
{
|
||
|
if (pns->psfCache)
|
||
|
{
|
||
|
Release(pns->psfCache);
|
||
|
|
||
|
pns->psfCache = NULL;
|
||
|
pns->htiCache = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void _ReleaseRootFolder(NSC *pns)
|
||
|
{
|
||
|
if (pns->psfRoot)
|
||
|
{
|
||
|
Release(pns->psfRoot);
|
||
|
pns->psfRoot = NULL;
|
||
|
|
||
|
if (pns->pidlRoot)
|
||
|
{
|
||
|
ILFree(pns->pidlRoot);
|
||
|
pns->pidlRoot = NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void _OnNCDestroy(NSC *pns)
|
||
|
{
|
||
|
_ReleaseRootFolder(pns);
|
||
|
|
||
|
Assert(pns->pidlRoot == NULL);
|
||
|
|
||
|
// ILFree(pns->pidlRoot);
|
||
|
// pns->pidlRoot = NULL;
|
||
|
|
||
|
_ReleaseCachedShellFolder(pns);
|
||
|
|
||
|
if (pns->style & NSS_DROPTARGET)
|
||
|
CTreeDropTarget_Revoke(pns);
|
||
|
|
||
|
_RegisterNotify(pns, FALSE);
|
||
|
|
||
|
|
||
|
LocalFree(pns);
|
||
|
}
|
||
|
|
||
|
// builds a fully qualified IDLIST from a given tree node by walking up the tree
|
||
|
// be sure to free this when you are done!
|
||
|
|
||
|
LPITEMIDLIST _GetFullIDList(HWND hwndTree, HTREEITEM hti)
|
||
|
{
|
||
|
LPITEMIDLIST pidl;
|
||
|
TV_ITEM tvi;
|
||
|
|
||
|
Assert(hti);
|
||
|
|
||
|
// now lets get the information about the item
|
||
|
tvi.mask = TVIF_PARAM | TVIF_HANDLE;
|
||
|
tvi.hItem = hti;
|
||
|
if (!TreeView_GetItem(hwndTree, &tvi))
|
||
|
{
|
||
|
DebugMsg(DM_ERROR, "bogus tree item passed");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
pidl = ILClone((LPITEMIDLIST)tvi.lParam);
|
||
|
|
||
|
// Now walk up parents.
|
||
|
while ((tvi.hItem = TreeView_GetParent(hwndTree, tvi.hItem)) && pidl)
|
||
|
{
|
||
|
LPITEMIDLIST pidlT;
|
||
|
|
||
|
if (!TreeView_GetItem(hwndTree, &tvi))
|
||
|
return pidl; // will assume I messed up...
|
||
|
|
||
|
pidlT = ILCombine((LPITEMIDLIST)tvi.lParam, pidl);
|
||
|
|
||
|
ILFree(pidl);
|
||
|
|
||
|
pidl = pidlT;
|
||
|
|
||
|
}
|
||
|
return pidl;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
pitem->flags;
|
||
|
pitem->hitem;
|
||
|
pitem->psf;
|
||
|
pitem->pidl;
|
||
|
pitem->dwAttributes;
|
||
|
*/
|
||
|
|
||
|
BOOL _GetItem(NSC *pns, NSC_ITEMINFO *pitem)
|
||
|
{
|
||
|
// BUGBUG: validate pitem->hitem
|
||
|
|
||
|
if (pitem->flags & NSIF_HITEM)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
if (pitem->flags & NSIF_FOLDER)
|
||
|
{
|
||
|
Assert(!(pitem->flags & NSIF_PARENTFOLDER)); // should be exclusive
|
||
|
|
||
|
}
|
||
|
else if (pitem->flags & NSIF_PARENTFOLDER)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
if (pitem->flags & (NSIF_IDLIST | NSIF_FULLIDLIST))
|
||
|
{
|
||
|
pitem->pidl = NULL;
|
||
|
|
||
|
if (pitem->flags & NSIF_FULLIDLIST)
|
||
|
pitem->pidl = _GetFullIDList(pns->hwndTree, (HTREEITEM)pitem->hitem);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TV_ITEM tvi;
|
||
|
|
||
|
tvi.mask = TVIF_PARAM | TVIF_HANDLE;
|
||
|
tvi.hItem = (HTREEITEM)pitem->hitem;
|
||
|
if (TreeView_GetItem(pns->hwndTree, &tvi))
|
||
|
{
|
||
|
pitem->pidl = (LPITEMIDLIST)tvi.lParam;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (pitem->flags & NSIF_ATTRIBUTES)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
BOOL _SetItemNotify(NSC *pns, NSI_FLAGS flags)
|
||
|
{
|
||
|
// NS_NOTIFY nsn;
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
// Some helper functions for processing the dialog
|
||
|
|
||
|
HTREEITEM _AddItemToTree(HWND hwndTree, HTREEITEM htiParent, LPCITEMIDLIST pidl, int cChildren)
|
||
|
{
|
||
|
TV_INSERTSTRUCT tii;
|
||
|
|
||
|
// Initialize item to add with callback for everything
|
||
|
tii.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_CHILDREN;
|
||
|
tii.hParent = htiParent;
|
||
|
tii.hInsertAfter = TVI_FIRST;
|
||
|
tii.item.iImage = I_IMAGECALLBACK;
|
||
|
tii.item.iSelectedImage = I_IMAGECALLBACK;
|
||
|
tii.item.pszText = LPSTR_TEXTCALLBACK; //
|
||
|
tii.item.cChildren = cChildren; // Assume it has children
|
||
|
tii.item.lParam = (LPARAM)pidl;
|
||
|
|
||
|
return TreeView_InsertItem(hwndTree, &tii);
|
||
|
}
|
||
|
|
||
|
void _ExpandTree(NSC *pns, HTREEITEM htiRoot, int iDepth)
|
||
|
{
|
||
|
HTREEITEM hti;
|
||
|
|
||
|
if (iDepth == 0)
|
||
|
return;
|
||
|
|
||
|
TreeView_Expand(pns->hwndTree, htiRoot, TVE_EXPAND);
|
||
|
|
||
|
if (iDepth == 1)
|
||
|
return; // avoid useless loop
|
||
|
|
||
|
// recurse to children, expanding them
|
||
|
for (hti = TreeView_GetChild(pns->hwndTree, htiRoot); hti; hti = TreeView_GetNextSibling(pns->hwndTree, hti))
|
||
|
{
|
||
|
_ExpandTree(pns, hti, iDepth - 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// set the root of the name space control.
|
||
|
//
|
||
|
// in:
|
||
|
// pidlRoot NULL means the desktop
|
||
|
// HIWORD 0 -> LOWORD == ID of special folder (CSIDL_* values)
|
||
|
|
||
|
BOOL _OnSetRoot(NSC *pns, NSC_SETROOT *psr)
|
||
|
{
|
||
|
_ReleaseRootFolder(pns);
|
||
|
|
||
|
if (psr->psf)
|
||
|
{
|
||
|
pns->psfRoot = psr->psf;
|
||
|
}
|
||
|
else if (FAILED(SHGetDesktopFolder(&pns->psfRoot)))
|
||
|
{
|
||
|
DebugMsg(DM_ERROR, "Failed to get desktop folder");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
AddRef(pns->psfRoot); // we hang on to this
|
||
|
|
||
|
// HIWORD/LOWORD stuff is to support pidl IDs instead of full pidl here
|
||
|
if (HIWORD(psr->pidlRoot))
|
||
|
pns->pidlRoot = ILClone(psr->pidlRoot);
|
||
|
else
|
||
|
{
|
||
|
Assert(psr->psf == NULL); // special folders are only valid for desktop shell folder
|
||
|
|
||
|
SHGetSpecialFolderLocation(NULL, LOWORD(psr->pidlRoot) ? LOWORD(psr->pidlRoot) : CSIDL_DESKTOP, &pns->pidlRoot);
|
||
|
}
|
||
|
|
||
|
if (pns->pidlRoot)
|
||
|
{
|
||
|
HTREEITEM htiRoot = _AddItemToTree(pns->hwndTree, TVI_ROOT, pns->pidlRoot, 1);
|
||
|
if (htiRoot)
|
||
|
{
|
||
|
_ExpandTree(pns, htiRoot, psr->iExpandDepth);
|
||
|
|
||
|
TreeView_SelectItem(pns->hwndTree, htiRoot);
|
||
|
|
||
|
_RegisterNotify(pns, TRUE);
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
DebugMsg(DM_ERROR, "set root failed");
|
||
|
|
||
|
_ReleaseRootFolder(pns);
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
// cache the shell folder for a given tree item
|
||
|
// in:
|
||
|
// hti tree node to cache shell folder for. this my be
|
||
|
// NULL indicating the root item.
|
||
|
//
|
||
|
|
||
|
BOOL _CacheShellFolder(NSC *pns, HTREEITEM hti)
|
||
|
{
|
||
|
// in the cache?
|
||
|
if ((hti != pns->htiCache) || (pns->psfCache == NULL))
|
||
|
{
|
||
|
// cache miss, do the work
|
||
|
LPITEMIDLIST pidl;
|
||
|
|
||
|
_ReleaseCachedShellFolder(pns);
|
||
|
|
||
|
if (hti)
|
||
|
pidl = _GetFullIDList(pns->hwndTree, hti);
|
||
|
else
|
||
|
{
|
||
|
// root item...
|
||
|
pidl = ILClone(pns->pidlRoot);
|
||
|
if (pidl && pidl->mkid.cb != 0)
|
||
|
{
|
||
|
ILRemoveLastID(pidl);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (pidl)
|
||
|
{
|
||
|
// special case for root of evil...
|
||
|
if (pidl->mkid.cb == 0)
|
||
|
{
|
||
|
pns->psfCache = pns->psfRoot;
|
||
|
AddRef(pns->psfCache); // to match ref count
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pns->psfRoot->lpVtbl->BindToObject(pns->psfRoot, pidl, NULL, &IID_IShellFolder, &pns->psfCache);
|
||
|
}
|
||
|
|
||
|
ILFree(pidl);
|
||
|
|
||
|
if (pns->psfCache)
|
||
|
{
|
||
|
pns->htiCache = hti; // this is for the cache match
|
||
|
return TRUE;
|
||
|
}
|
||
|
else
|
||
|
DebugMsg(DM_ERROR, "failed to get cached shell folder");
|
||
|
}
|
||
|
else
|
||
|
DebugMsg(DM_ERROR, "failed to get create PIDL for cached shell folder");
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
// pidlItem is typically a relative pidl, except in the case of the root where
|
||
|
// it can be a fully qualified pidl
|
||
|
|
||
|
LPITEMIDLIST _CacheParentShellFolder(NSC *pns, HTREEITEM hti, LPITEMIDLIST pidl)
|
||
|
{
|
||
|
Assert(hti);
|
||
|
|
||
|
if (_CacheShellFolder(pns, TreeView_GetParent(pns->hwndTree, hti)))
|
||
|
{
|
||
|
if (pidl == NULL)
|
||
|
{
|
||
|
TV_ITEM tvi;
|
||
|
tvi.mask = TVIF_PARAM | TVIF_HANDLE;
|
||
|
tvi.hItem = hti;
|
||
|
if (!TreeView_GetItem(pns->hwndTree, &tvi))
|
||
|
return NULL;
|
||
|
|
||
|
pidl = (LPITEMIDLIST)tvi.lParam;
|
||
|
}
|
||
|
|
||
|
return ILFindLastID(pidl);
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
int CALLBACK _TreeCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
|
||
|
{
|
||
|
IShellFolder *psf = (IShellFolder *)lParamSort;
|
||
|
HRESULT hres = psf->lpVtbl->CompareIDs(psf, 0, (LPITEMIDLIST)lParam1, (LPITEMIDLIST)lParam2);
|
||
|
|
||
|
Assert(SUCCEEDED(hres));
|
||
|
|
||
|
return (short)SCODE_CODE(hres);
|
||
|
}
|
||
|
|
||
|
void _Sort(NSC *pns, HTREEITEM hti, IShellFolder *psf)
|
||
|
{
|
||
|
TV_SORTCB scb;
|
||
|
|
||
|
scb.hParent = hti;
|
||
|
scb.lpfnCompare = _TreeCompare;
|
||
|
|
||
|
scb.lParam = (LPARAM)psf;
|
||
|
TreeView_SortChildrenCB(pns->hwndTree, &scb, FALSE);
|
||
|
}
|
||
|
|
||
|
// filter function... let clients filter what gets added here
|
||
|
|
||
|
BOOL _ShouldAdd(NSC *pns, LPCITEMIDLIST pidl)
|
||
|
{
|
||
|
#if 0
|
||
|
//
|
||
|
// We need to special case here in the netcase where we onlyu
|
||
|
// browse down to workgroups...
|
||
|
//
|
||
|
//
|
||
|
// Here is where I also need to special case to not go below
|
||
|
// workgroups when the appropriate option is set.
|
||
|
//
|
||
|
bType = SIL_GetType(pidl);
|
||
|
if ((pns->ulFlags & BIF_DONTGOBELOWDOMAIN) && (bType & SHID_NET))
|
||
|
{
|
||
|
switch (bType & (SHID_NET | SHID_INGROUPMASK))
|
||
|
{
|
||
|
case SHID_NET_SERVER:
|
||
|
ILFree(pidl); // Dont want to add this one
|
||
|
continue; // Try the next one
|
||
|
case SHID_NET_DOMAIN:
|
||
|
cChildren = 0; // Force to not have children;
|
||
|
}
|
||
|
}
|
||
|
else if ((pns->ulFlags & BIF_BROWSEFORCOMPUTER) && (bType & SHID_NET))
|
||
|
{
|
||
|
if ((bType & (SHID_NET | SHID_INGROUPMASK)) == SHID_NET_SERVER)
|
||
|
cChildren = 0; // Don't expand below it...
|
||
|
}
|
||
|
else if (fPrinterTest)
|
||
|
{
|
||
|
// Special case when we are only allowing printers.
|
||
|
// for now I will simply key on the fact that it is non-FS.
|
||
|
ULONG ulAttr = SFGAO_FILESYSTEM;
|
||
|
|
||
|
psf->lpVtbl->GetAttributesOf(psf, 1, &pidl, &ulAttr);
|
||
|
|
||
|
if ((ulAttr & SFGAO_FILESYSTEM)== 0)
|
||
|
{
|
||
|
cChildren = 0; // Force to not have children;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ILFree(pidl); // Dont want to add this one
|
||
|
continue; // Try the next one
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
// send notify up to partent to let them filter
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
BOOL _OnItemExpanding(NSC *pns, NM_TREEVIEW *pnm)
|
||
|
{
|
||
|
IShellFolder *psf;
|
||
|
IEnumIDList *penum; // Enumerator in use.
|
||
|
DWORD grfFlags = SHCONTF_FOLDERS;
|
||
|
int cAdded = 0;
|
||
|
|
||
|
if ((pnm->action != TVE_EXPAND) || (pnm->itemNew.state & TVIS_EXPANDEDONCE))
|
||
|
return FALSE;
|
||
|
|
||
|
Assert(pnm->itemNew.hItem);
|
||
|
|
||
|
if (!_CacheShellFolder(pns, pnm->itemNew.hItem))
|
||
|
return FALSE;
|
||
|
|
||
|
psf = pns->psfCache;
|
||
|
AddRef(psf); // hang on as adding items may change the cached psfCache
|
||
|
|
||
|
#if 0
|
||
|
// Need to do a couple of special cases here to allow us to
|
||
|
// browse for a network printer. In this case if we are at server
|
||
|
// level we then need to change what we search for non folders when
|
||
|
// we are the level of a server.
|
||
|
if (pns->ulFlags & BIF_BROWSEFORPRINTER)
|
||
|
{
|
||
|
grfFlags = SHCONTF_FOLDERS | SHCONTF_NETPRINTERSRCH;
|
||
|
pidl = ILFindLastID(pidlToExpand);
|
||
|
bType = SIL_GetType(pidl);
|
||
|
fPrinterTest = ((bType & (SHID_NET|SHID_INGROUPMASK))==SHID_NET_SERVER);
|
||
|
if (fPrinterTest)
|
||
|
grfFlags |= SHCONTF_NONFOLDERS;
|
||
|
}
|
||
|
else
|
||
|
#endif
|
||
|
|
||
|
if (pns->style & NSS_SHOWNONFOLDERS)
|
||
|
grfFlags |= SHCONTF_NONFOLDERS;
|
||
|
|
||
|
if (pns->style & NSS_SHOWHIDDEN)
|
||
|
grfFlags |= SHCONTF_INCLUDEHIDDEN;
|
||
|
|
||
|
// passing NULL hwnd makes the enum not put up UI. this is what we want
|
||
|
// in auto-expand cases
|
||
|
|
||
|
if (SUCCEEDED(psf->lpVtbl->EnumObjects(psf,
|
||
|
pns->fAutoExpanding ? NULL : pns->hwnd, grfFlags, &penum)))
|
||
|
{
|
||
|
UINT celt;
|
||
|
LPITEMIDLIST pidl;
|
||
|
|
||
|
while (penum->lpVtbl->Next(penum, 1, &pidl, &celt) == S_OK && celt == 1)
|
||
|
{
|
||
|
int cChildren = I_CHILDRENCALLBACK; // Do call back for children
|
||
|
|
||
|
if (_ShouldAdd(pns, pidl))
|
||
|
{
|
||
|
_AddItemToTree(pns->hwndTree, pnm->itemNew.hItem, pidl, cChildren);
|
||
|
cAdded++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ILFree(pidl);
|
||
|
}
|
||
|
}
|
||
|
ReleaseLast(penum);
|
||
|
|
||
|
_Sort(pns, pnm->itemNew.hItem, psf);
|
||
|
}
|
||
|
|
||
|
Release(psf);
|
||
|
|
||
|
// If we did not add anything we should update this item to let
|
||
|
// the user know something happened.
|
||
|
//
|
||
|
if (cAdded == 0)
|
||
|
{
|
||
|
TV_ITEM tvi;
|
||
|
tvi.mask = TVIF_CHILDREN | TVIF_HANDLE; // only change the number of children
|
||
|
tvi.hItem = pnm->itemNew.hItem;
|
||
|
tvi.cChildren = 0;
|
||
|
|
||
|
TreeView_SetItem(pns->hwndTree, &tvi);
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
void _OnDeleteItem(NSC *pns, NM_TREEVIEW *pnm)
|
||
|
{
|
||
|
ILFree((LPITEMIDLIST)pnm->itemOld.lParam);
|
||
|
}
|
||
|
|
||
|
void _GetIconIndex(NSC *pns, LPITEMIDLIST pidl, ULONG ulAttrs, TVITEM *pitem)
|
||
|
{
|
||
|
IShellIcon *psi;
|
||
|
|
||
|
if (SUCCEEDED(QueryInterface(pns->psfCache, &IID_IShellIcon, &psi)))
|
||
|
{
|
||
|
if (psi->lpVtbl->GetIconOf(psi, pidl, 0, &pitem->iImage) == S_OK)
|
||
|
{
|
||
|
if (!(ulAttrs & SFGAO_FOLDER) || FAILED(psi->lpVtbl->GetIconOf(psi, pidl, GIL_OPENICON, &pitem->iSelectedImage)))
|
||
|
{
|
||
|
pitem->iSelectedImage = pitem->iImage;
|
||
|
}
|
||
|
|
||
|
Release(psi);
|
||
|
return;
|
||
|
}
|
||
|
Release(psi);
|
||
|
}
|
||
|
|
||
|
{
|
||
|
// slow way...
|
||
|
|
||
|
LPITEMIDLIST pidlFull = _GetFullIDList(pns->hwndTree, pitem->hItem);
|
||
|
if (pidlFull)
|
||
|
{
|
||
|
SHFILEINFO sfi;
|
||
|
|
||
|
SHGetFileInfo((LPCSTR)pidlFull, 0, &sfi, sizeof(SHFILEINFO), SHGFI_PIDL | SHGFI_SYSICONINDEX); // | SHGFI_SMALLICON
|
||
|
pitem->iImage = sfi.iIcon;
|
||
|
|
||
|
if (!(ulAttrs & SFGAO_FOLDER))
|
||
|
pitem->iSelectedImage = pitem->iImage;
|
||
|
else
|
||
|
{
|
||
|
SHGetFileInfo((LPCSTR)pidlFull, 0, &sfi, sizeof(SHFILEINFO), SHGFI_PIDL | SHGFI_OPENICON | SHGFI_SYSICONINDEX);
|
||
|
pitem->iSelectedImage = sfi.iIcon;
|
||
|
}
|
||
|
|
||
|
ILFree(pidlFull);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
int _GetChildren(NSC *pns, IShellFolder *psf, LPCITEMIDLIST pidl, ULONG ulAttrs)
|
||
|
{
|
||
|
int cChildren = 0; // assume none
|
||
|
|
||
|
if (ulAttrs & SFGAO_FOLDER)
|
||
|
{
|
||
|
if (pns->style & NSS_SHOWNONFOLDERS)
|
||
|
{
|
||
|
// there is no SFGAO_ bit that includes non folders so we need to enum
|
||
|
IShellFolder *psfItem;
|
||
|
if (SUCCEEDED(psf->lpVtbl->BindToObject(psf, pidl, NULL, &IID_IShellFolder, &psfItem)))
|
||
|
{
|
||
|
// if we are showing non folders we have to do an enum to peek down at items below
|
||
|
IEnumIDList *penum;
|
||
|
DWORD grfFlags = SHCONTF_FOLDERS | SHCONTF_NONFOLDERS;
|
||
|
|
||
|
if (pns->style & NSS_SHOWHIDDEN)
|
||
|
grfFlags |= SHCONTF_INCLUDEHIDDEN;
|
||
|
|
||
|
if (SUCCEEDED(psfItem->lpVtbl->EnumObjects(psfItem, NULL, grfFlags, &penum)))
|
||
|
{
|
||
|
UINT celt;
|
||
|
LPITEMIDLIST pidlTemp;
|
||
|
|
||
|
if (penum->lpVtbl->Next(penum, 1, &pidlTemp, &celt) == S_OK && celt == 1)
|
||
|
{
|
||
|
ILFree(pidlTemp);
|
||
|
cChildren = 1;
|
||
|
}
|
||
|
Release(penum);
|
||
|
}
|
||
|
Release(psfItem);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// if just folders we can peek at the attributes
|
||
|
ULONG ulAttrs = SFGAO_HASSUBFOLDER;
|
||
|
psf->lpVtbl->GetAttributesOf(psf, 1, &pidl, &ulAttrs);
|
||
|
|
||
|
cChildren = (ulAttrs & SFGAO_HASSUBFOLDER) ? 1 : 0;
|
||
|
}
|
||
|
}
|
||
|
return cChildren;
|
||
|
}
|
||
|
|
||
|
void _OnGetDisplayInfo(NSC *pns, TV_DISPINFO *pnm)
|
||
|
{
|
||
|
LPITEMIDLIST pidl = _CacheParentShellFolder(pns, pnm->item.hItem, (LPITEMIDLIST)pnm->item.lParam);
|
||
|
|
||
|
Assert(pidl);
|
||
|
|
||
|
if (pidl == NULL)
|
||
|
return;
|
||
|
|
||
|
Assert(pns->psfCache);
|
||
|
|
||
|
Assert(pnm->item.mask & (TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT | TVIF_CHILDREN));
|
||
|
|
||
|
if (pnm->item.mask & TVIF_TEXT)
|
||
|
{
|
||
|
STRRET str;
|
||
|
pns->psfCache->lpVtbl->GetDisplayNameOf(pns->psfCache, pidl, SHGDN_INFOLDER, &str);
|
||
|
|
||
|
StrRetToStrN(pnm->item.pszText, pnm->item.cchTextMax, &str, pidl);
|
||
|
}
|
||
|
|
||
|
// make sure we set the attributes for those flags that need them
|
||
|
if (pnm->item.mask & (TVIF_CHILDREN | TVIF_IMAGE | TVIF_SELECTEDIMAGE))
|
||
|
{
|
||
|
ULONG ulAttrs = SFGAO_FOLDER;
|
||
|
pns->psfCache->lpVtbl->GetAttributesOf(pns->psfCache, 1, &pidl, &ulAttrs);
|
||
|
|
||
|
// Also see if this guy has any child folders
|
||
|
if (pnm->item.mask & TVIF_CHILDREN)
|
||
|
{
|
||
|
pnm->item.cChildren = _GetChildren(pns, pns->psfCache, pidl, ulAttrs);
|
||
|
}
|
||
|
|
||
|
if (pnm->item.mask & (TVIF_IMAGE | TVIF_SELECTEDIMAGE))
|
||
|
{
|
||
|
// We now need to map the item into the right image index.
|
||
|
_GetIconIndex(pns, pidl, ulAttrs, &pnm->item);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// force the treeview to store this so we don't get called back again
|
||
|
pnm->item.mask |= TVIF_DI_SETITEM;
|
||
|
}
|
||
|
|
||
|
// send up the sel changed to let clients enable/disable buttons, etc.
|
||
|
|
||
|
void _OnSelChanged(NSC *pns, LPNM_TREEVIEW pnm)
|
||
|
{
|
||
|
|
||
|
#if 0
|
||
|
LPITEMIDLIST pidl;
|
||
|
ULONG ulAttrs = SFGAO_FILESYSTEM;
|
||
|
BYTE bType;
|
||
|
|
||
|
// We only need to do anything if we only want to return File system
|
||
|
// level objects.
|
||
|
if ((pns->ulFlags & (BIF_RETURNONLYFSDIRS | BIF_RETURNFSANCESTORS | BIF_BROWSEFORPRINTER | BIF_BROWSEFORCOMPUTER)) == 0)
|
||
|
goto NotifySelChange;
|
||
|
|
||
|
// We need to get the attributes of this object...
|
||
|
if (_CacheParentShellFolder(pns, pnm->itemNew.hItem, (LPITEMIDLIST)pnm->itemNew.lParam))
|
||
|
{
|
||
|
BOOL fEnable = TRUE;
|
||
|
|
||
|
bType = SIL_GetType(pidl);
|
||
|
if ((pns->ulFlags & (BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS)) != 0)
|
||
|
{
|
||
|
int i;
|
||
|
// if this is the root pidl, then do a get attribs on 0
|
||
|
// so that we'll get the attributes on the root, rather than
|
||
|
// random returned values returned by FSFolder
|
||
|
if (ILIsEmpty(pidl)) {
|
||
|
i = 0;
|
||
|
} else
|
||
|
i = 1;
|
||
|
|
||
|
pns->psfCache->lpVtbl->GetAttributesOf(pns->psfCache,
|
||
|
i, &pidl, &ulAttrs);
|
||
|
|
||
|
fEnable = (((ulAttrs & SFGAO_FILESYSTEM) && (pns->ulFlags & BIF_RETURNONLYFSDIRS)) ||
|
||
|
((ulAttrs & SFGAO_FILESYSANCESTOR) && (pns->ulFlags & BIF_RETURNFSANCESTORS))) ||
|
||
|
((bType & (SHID_NET | SHID_INGROUPMASK)) == SHID_NET_SERVER);
|
||
|
}
|
||
|
else if ((pns->ulFlags & BIF_BROWSEFORCOMPUTER) != 0)
|
||
|
{
|
||
|
fEnable = ((bType & (SHID_NET | SHID_INGROUPMASK)) == SHID_NET_SERVER);
|
||
|
}
|
||
|
else if ((pns->ulFlags & BIF_BROWSEFORPRINTER) != 0)
|
||
|
{
|
||
|
// Printers are of type Share and usage Print...
|
||
|
fEnable = ((bType & (SHID_NET | SHID_INGROUPMASK)) == SHID_NET_SHARE);
|
||
|
}
|
||
|
|
||
|
EnableWindow(GetDlgItem(pns->hwnd, IDOK), fEnable);
|
||
|
}
|
||
|
|
||
|
NotifySelChange:
|
||
|
if (pns->lpfn)
|
||
|
{
|
||
|
pidl = _GetFullIDList(pns->hwndTree, pnm->itemNew.hItem);
|
||
|
BFSFCallback(pns, BFFM_SELCHANGED, (LPARAM)pidl);
|
||
|
ILFree(pidl);
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
const char c_szCut[] = "cut";
|
||
|
const char c_szRename[] = "rename";
|
||
|
|
||
|
LRESULT _ContextMenu(NSC *pns, short x, short y)
|
||
|
{
|
||
|
HTREEITEM hti;
|
||
|
POINT ptPopup; // in screen coordinate
|
||
|
|
||
|
if (x == -1 && y == -1)
|
||
|
{
|
||
|
// Keyboard-driven: Get the popup position from the selected item.
|
||
|
hti = TreeView_GetSelection(pns->hwndTree);
|
||
|
if (hti)
|
||
|
{
|
||
|
RECT rc;
|
||
|
//
|
||
|
// Note that TV_GetItemRect returns it in client coordinate!
|
||
|
//
|
||
|
TreeView_GetItemRect(pns->hwndTree, hti, &rc, TRUE);
|
||
|
ptPopup.x = (rc.left + rc.right) / 2;
|
||
|
ptPopup.y = (rc.top + rc.bottom) / 2;
|
||
|
MapWindowPoints(pns->hwndTree, HWND_DESKTOP, &ptPopup, 1);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TV_HITTESTINFO tvht;
|
||
|
|
||
|
// Mouse-driven: Pick the treeitem from the position.
|
||
|
ptPopup.x = x;
|
||
|
ptPopup.y = y;
|
||
|
|
||
|
tvht.pt = ptPopup;
|
||
|
ScreenToClient(pns->hwndTree, &tvht.pt);
|
||
|
|
||
|
hti = TreeView_HitTest(pns->hwndTree, &tvht);
|
||
|
}
|
||
|
|
||
|
if (hti)
|
||
|
{
|
||
|
LPCITEMIDLIST pidl = _CacheParentShellFolder(pns, hti, NULL);
|
||
|
if (pidl)
|
||
|
{
|
||
|
IContextMenu *pcm;
|
||
|
|
||
|
TreeView_SelectDropTarget(pns->hwndTree, hti);
|
||
|
|
||
|
if (SUCCEEDED(pns->psfCache->lpVtbl->GetUIObjectOf(pns->psfCache, pns->hwnd, 1, &pidl, &IID_IContextMenu, NULL, &pcm)))
|
||
|
{
|
||
|
HMENU hmenu = CreatePopupMenu();
|
||
|
if (hmenu)
|
||
|
{
|
||
|
UINT idCmd;
|
||
|
|
||
|
pns->pcm = pcm; // for IContextMenu2 code
|
||
|
|
||
|
pcm->lpVtbl->QueryContextMenu(pcm, hmenu, 0, 1, 0x7fff,
|
||
|
CMF_EXPLORE | CMF_CANRENAME);
|
||
|
|
||
|
// use pns->hwnd so menu msgs go there and I can forward them
|
||
|
// using IContextMenu2 so "Sent To" works
|
||
|
|
||
|
idCmd = TrackPopupMenu(hmenu,
|
||
|
TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN,
|
||
|
ptPopup.x, ptPopup.y, 0, pns->hwnd, NULL);
|
||
|
|
||
|
if (idCmd)
|
||
|
{
|
||
|
char szCommandString[64];
|
||
|
BOOL fHandled = FALSE;
|
||
|
BOOL fCutting = FALSE;
|
||
|
|
||
|
// We need to special case the rename command
|
||
|
if (SUCCEEDED(pcm->lpVtbl->GetCommandString(pcm, idCmd - 1,
|
||
|
0, NULL, szCommandString, sizeof(szCommandString))))
|
||
|
{
|
||
|
if (lstrcmpi(szCommandString, c_szRename)==0)
|
||
|
{
|
||
|
TreeView_EditLabel(pns->hwndTree, hti);
|
||
|
fHandled = TRUE;
|
||
|
}
|
||
|
else if (!lstrcmpi(szCommandString, c_szCut))
|
||
|
{
|
||
|
fCutting = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!fHandled)
|
||
|
{
|
||
|
CMINVOKECOMMANDINFO ici = {
|
||
|
sizeof(CMINVOKECOMMANDINFO),
|
||
|
0L,
|
||
|
pns->hwndTree,
|
||
|
MAKEINTRESOURCE(idCmd - 1),
|
||
|
NULL, NULL,
|
||
|
SW_NORMAL,
|
||
|
};
|
||
|
|
||
|
HRESULT hres = pcm->lpVtbl->InvokeCommand(pcm, &ici);
|
||
|
if (fCutting && SUCCEEDED(hres))
|
||
|
{
|
||
|
TV_ITEM tvi;
|
||
|
tvi.mask = TVIF_STATE;
|
||
|
tvi.stateMask = TVIS_CUT;
|
||
|
tvi.state = TVIS_CUT;
|
||
|
tvi.hItem = hti;
|
||
|
TreeView_SetItem(pns->hwndTree, &tvi);
|
||
|
|
||
|
// pns->hwndNextViewer = SetClipboardViewer(pns->hwndTree);
|
||
|
// pns->htiCut = hti;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
DestroyMenu(hmenu);
|
||
|
pns->pcm = NULL;
|
||
|
}
|
||
|
ReleaseLast(pcm);
|
||
|
}
|
||
|
TreeView_SelectDropTarget(pns->hwndTree, NULL);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
LRESULT _OnBeginLabelEdit(NSC *pns, TV_DISPINFO *ptvdi)
|
||
|
{
|
||
|
BOOL fCantRename = TRUE;
|
||
|
|
||
|
LPCITEMIDLIST pidl = _CacheParentShellFolder(pns, ptvdi->item.hItem, NULL);
|
||
|
if (pidl)
|
||
|
{
|
||
|
DWORD dwAttribs = SFGAO_CANRENAME;
|
||
|
pns->psfCache->lpVtbl->GetAttributesOf(pns->psfCache, 1, &pidl, &dwAttribs);
|
||
|
if (dwAttribs & SFGAO_CANRENAME)
|
||
|
fCantRename = FALSE;
|
||
|
}
|
||
|
|
||
|
if (fCantRename)
|
||
|
MessageBeep(0);
|
||
|
|
||
|
return fCantRename;
|
||
|
}
|
||
|
|
||
|
LRESULT _OnEndLabelEdit(NSC *pns, TV_DISPINFO *ptvdi)
|
||
|
{
|
||
|
LPCITEMIDLIST pidl;
|
||
|
|
||
|
// See if the user cancelled
|
||
|
if (ptvdi->item.pszText == NULL)
|
||
|
return TRUE; // Nothing to do here.
|
||
|
|
||
|
Assert(ptvdi->item.hItem);
|
||
|
|
||
|
pidl = _CacheParentShellFolder(pns, ptvdi->item.hItem, NULL);
|
||
|
if (pidl)
|
||
|
{
|
||
|
UINT cch = lstrlen(ptvdi->item.pszText)+1;
|
||
|
LPOLESTR pwsz = (LPOLESTR)LocalAlloc(LPTR, cch * sizeof(WCHAR));
|
||
|
if (pwsz)
|
||
|
{
|
||
|
StrToOleStrN(pwsz, cch, ptvdi->item.pszText, -1);
|
||
|
|
||
|
if (SUCCEEDED(pns->psfCache->lpVtbl->SetNameOf(pns->psfCache, pns->hwnd, pidl, pwsz, 0, NULL)))
|
||
|
{
|
||
|
// SHChangeNotifyHandleEvents();
|
||
|
|
||
|
// NOTES: pidl is no longer valid here.
|
||
|
|
||
|
//
|
||
|
// Set the handle to NULL in the notification to let
|
||
|
// the system know that the pointer is probably not
|
||
|
// valid anymore.
|
||
|
//
|
||
|
ptvdi->item.hItem = NULL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SendMessage(pns->hwndTree, TVM_EDITLABEL, (WPARAM)ptvdi->item.pszText, (LPARAM)ptvdi->item.hItem);
|
||
|
}
|
||
|
LocalFree((HLOCAL)pwsz);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0; // We always return 0, "we handled it".
|
||
|
}
|
||
|
|
||
|
void _OnBeginDrag(NSC *pns, NM_TREEVIEW *pnmhdr)
|
||
|
{
|
||
|
LPCITEMIDLIST pidl = _CacheParentShellFolder(pns, pnmhdr->itemNew.hItem, NULL);
|
||
|
if (pidl)
|
||
|
{
|
||
|
DWORD dwEffect = DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK;
|
||
|
|
||
|
pns->psfCache->lpVtbl->GetAttributesOf(pns->psfCache, 1, &pidl, &dwEffect);
|
||
|
|
||
|
dwEffect &= DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK;
|
||
|
|
||
|
if (dwEffect)
|
||
|
{
|
||
|
IDataObject *pdtobj;
|
||
|
HRESULT hres = pns->psfCache->lpVtbl->GetUIObjectOf(pns->psfCache, pns->hwnd, 1, &pidl, &IID_IDataObject, NULL, &pdtobj);
|
||
|
|
||
|
if (SUCCEEDED(hres))
|
||
|
{
|
||
|
hres = OleInitialize(NULL);
|
||
|
|
||
|
if (SUCCEEDED(hres))
|
||
|
{
|
||
|
IDropSource *pdsrc;
|
||
|
|
||
|
if (SUCCEEDED(CDropSource_CreateInstance(&pdsrc)))
|
||
|
{
|
||
|
DWORD dwRet;
|
||
|
|
||
|
pns->htiDragging = pnmhdr->itemNew.hItem;
|
||
|
|
||
|
DoDragDrop(pdtobj, pdsrc, dwEffect, &dwRet);
|
||
|
|
||
|
pns->htiDragging = NULL;
|
||
|
|
||
|
DebugMsg(DM_TRACE, "DoDragDrop returns dwRet: %d", dwRet);
|
||
|
|
||
|
Release(pdsrc);
|
||
|
}
|
||
|
OleUninitialize();
|
||
|
}
|
||
|
#if 0
|
||
|
HIMAGELIST himlDrag = TreeView_CreateDragImage(pns->hwndTree, pnmhdr->itemNew.hItem);
|
||
|
if (himlDrag)
|
||
|
{
|
||
|
if (DAD_SetDragImage(himlDrag, NULL))
|
||
|
{
|
||
|
SHDoDragDrop(hwndOwner, pdtobj, NULL, dwEffect, &dwEffect);
|
||
|
|
||
|
DAD_SetDragImage((HIMAGELIST)-1, NULL);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DebugMsg(DM_TRACE, "sh ER - Tree_OnBeginDrag DAD_SetDragImage failed");
|
||
|
Assert(0);
|
||
|
}
|
||
|
ImageList_Destroy(himlDrag);
|
||
|
}
|
||
|
#endif
|
||
|
ReleaseLast(pdtobj);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void _InvokeContextMenu(IShellFolder *psf, LPCITEMIDLIST pidl, HWND hwnd, LPCSTR pszVerb)
|
||
|
{
|
||
|
IContextMenu *pcm;
|
||
|
if (SUCCEEDED(psf->lpVtbl->GetUIObjectOf(psf, hwnd, 1, &pidl, &IID_IContextMenu, NULL, &pcm)))
|
||
|
{
|
||
|
HMENU hmenu = CreatePopupMenu();
|
||
|
if (hmenu)
|
||
|
{
|
||
|
pcm->lpVtbl->QueryContextMenu(pcm, hmenu, 0, 1, 255, pszVerb ? 0 : CMF_DEFAULTONLY);
|
||
|
if (pszVerb == NULL)
|
||
|
pszVerb = MAKEINTRESOURCE(GetMenuDefaultItem(hmenu, MF_BYCOMMAND, 0) - 1);
|
||
|
if (pszVerb)
|
||
|
{
|
||
|
CMINVOKECOMMANDINFOEX ici = {
|
||
|
sizeof(CMINVOKECOMMANDINFOEX),
|
||
|
0L,
|
||
|
hwnd,
|
||
|
pszVerb,
|
||
|
NULL, NULL,
|
||
|
SW_NORMAL,
|
||
|
};
|
||
|
|
||
|
pcm->lpVtbl->InvokeCommand(pcm, (LPCMINVOKECOMMANDINFO)&ici);
|
||
|
}
|
||
|
DestroyMenu(hmenu);
|
||
|
}
|
||
|
Release(pcm);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void _DoVerb(NSC *pns, HTREEITEM hti, LPCSTR pszVerb)
|
||
|
{
|
||
|
hti = hti ? hti : TreeView_GetSelection(pns->hwndTree);
|
||
|
if (hti)
|
||
|
{
|
||
|
LPCITEMIDLIST pidl = _CacheParentShellFolder(pns, hti, NULL);
|
||
|
if (pidl)
|
||
|
{
|
||
|
_InvokeContextMenu(pns->psfCache, pidl, pns->hwnd, pszVerb);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BOOL _DoDlbClick(NSC *pns)
|
||
|
{
|
||
|
HTREEITEM hti = TreeView_GetSelection(pns->hwndTree);
|
||
|
if (hti)
|
||
|
{
|
||
|
LPCITEMIDLIST pidl = _CacheParentShellFolder(pns, hti, NULL);
|
||
|
if (pidl)
|
||
|
{
|
||
|
ULONG ulAttrs = SFGAO_FOLDER;
|
||
|
pns->psfCache->lpVtbl->GetAttributesOf(pns->psfCache, 1, &pidl, &ulAttrs);
|
||
|
|
||
|
if (ulAttrs & SFGAO_FOLDER)
|
||
|
return FALSE; // do default action (expand/collapse)
|
||
|
|
||
|
_InvokeContextMenu(pns->psfCache, pidl, pns->hwnd, NULL);
|
||
|
}
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
HTREEITEM _GetNodeFromIDList(LPITEMIDLIST pidl)
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
BOOL TryQuickRename(LPITEMIDLIST pidl, LPITEMIDLIST pidlExtra)
|
||
|
{
|
||
|
#if 0
|
||
|
LPOneTreeNode lpnSrc;
|
||
|
BOOL fRet = FALSE;
|
||
|
|
||
|
// This can happen when a folder is moved from a "rooted" Explorer outside
|
||
|
// of the root
|
||
|
if (!pidl || !pidlExtra)
|
||
|
return FALSE;
|
||
|
|
||
|
// this one was deleted
|
||
|
lpnSrc = _GetNodeFromIDList(pidl, 0);
|
||
|
if (!lpnSrc)
|
||
|
return FALSE;
|
||
|
|
||
|
if (lpnSrc == s_lpnRoot)
|
||
|
{
|
||
|
OTInvalidateRoot();
|
||
|
return TRUE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// this one was created
|
||
|
LPITEMIDLIST pidlClone = ILClone(pidlExtra);
|
||
|
if (pidlClone)
|
||
|
{
|
||
|
LPOneTreeNode lpnDestParent;
|
||
|
|
||
|
ILRemoveLastID(pidlClone);
|
||
|
|
||
|
// if the parent isn't created yet, let's not bother
|
||
|
lpnDestParent = _GetNodeFromIDList(pidlClone, 0);
|
||
|
ILFree(pidlClone);
|
||
|
|
||
|
if (lpnDestParent)
|
||
|
{
|
||
|
LPITEMIDLIST pidlLast = OTGetRealFolderIDL(lpnDestParent, ILFindLastID(pidlExtra));
|
||
|
if (pidlLast)
|
||
|
{
|
||
|
LPSHELLFOLDER psf = OTBindToFolder(lpnDestParent);
|
||
|
if (psf)
|
||
|
{
|
||
|
OTAddRef(lpnSrc); // addref because AdoptKid doesn't and OTAbandonKid releases
|
||
|
|
||
|
// remove lpnSrc from its parent's list.
|
||
|
OTAbandonKid(lpnSrc->lpnParent, lpnSrc);
|
||
|
|
||
|
// invalidate the new node's parent to get any children flags right
|
||
|
OTInvalidateNode(lpnDestParent);
|
||
|
|
||
|
// free any cached folders
|
||
|
OTSweepFolders(lpnSrc);
|
||
|
SFCFreeNode(lpnSrc);
|
||
|
|
||
|
OTFreeNodeData(lpnSrc);
|
||
|
lpnSrc->pidl = pidlLast;
|
||
|
lpnSrc->lpnParent = lpnDestParent;
|
||
|
OTUpdateNodeName(psf, lpnSrc);
|
||
|
AdoptKid(lpnDestParent, lpnSrc);
|
||
|
|
||
|
fRet = TRUE;
|
||
|
|
||
|
IUnknown_Release(psf);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ILFree(pidlLast);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return fRet;
|
||
|
#else
|
||
|
return FALSE;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void _DoChangeNotify(NSC *pns, LONG lEvent, LPITEMIDLIST pidl, LPITEMIDLIST pidlExtra)
|
||
|
{
|
||
|
#if 0
|
||
|
switch(lEvent)
|
||
|
{
|
||
|
case SHCNE_RENAMEFOLDER:
|
||
|
// first try to just swap the nodes if it's true rename (not a move)
|
||
|
if (!TryQuickRename(pidl, pidlExtra))
|
||
|
{
|
||
|
// Rename is special. We need to invalidate both
|
||
|
// the pidl and the pidlExtra. so we call ourselves
|
||
|
_DoHandleChangeNotify(pns, 0, pidlExtra, NULL);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SHCNE_RMDIR:
|
||
|
if (ILIsEmpty(pidl)) {
|
||
|
// we've deleted the desktop dir.
|
||
|
lpNode = s_lpnRoot;
|
||
|
OTInvalidateRoot();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// Sitemaps are "inserted" items. We need the ability to remove them.
|
||
|
// Unless the "fInserted" flag is reset, we can not remove them.
|
||
|
if(lpNode = _GetNodeFromIDList(pidl, 0))
|
||
|
{
|
||
|
if(lpNode->fInserted)
|
||
|
lpNode->fInserted = FALSE;
|
||
|
lpNode = OTGetParent(lpNode);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case 0:
|
||
|
case SHCNE_MKDIR:
|
||
|
case SHCNE_DRIVEADD:
|
||
|
case SHCNE_DRIVEREMOVED:
|
||
|
if (pidl)
|
||
|
{
|
||
|
LPITEMIDLIST pidlClone = ILClone(pidl);
|
||
|
if (pidlClone)
|
||
|
{
|
||
|
ILRemoveLastID(pidlClone);
|
||
|
lpNode = _GetNodeFromIDList(pidlClone, 0);
|
||
|
ILFree(pidlClone);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SHCNE_MEDIAINSERTED:
|
||
|
case SHCNE_MEDIAREMOVED:
|
||
|
lpNode = _GetNodeFromIDList(pidl, 0);
|
||
|
if (lpNode)
|
||
|
lpNode = lpNode->lpnParent;
|
||
|
break;
|
||
|
|
||
|
case SHCNE_DRIVEADDGUI:
|
||
|
case SHCNE_UPDATEITEM:
|
||
|
case SHCNE_NETSHARE:
|
||
|
case SHCNE_NETUNSHARE:
|
||
|
case SHCNE_UPDATEDIR:
|
||
|
lpNode = _GetNodeFromIDList(pidl, 0);
|
||
|
break;
|
||
|
|
||
|
case SHCNE_SERVERDISCONNECT:
|
||
|
// nuke all our kids and mark ourselves invalid
|
||
|
lpNode = _GetNodeFromIDList(pidl, 0);
|
||
|
if (lpNode && NodeHasKids(lpNode))
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = GetKidCount(lpNode) -1; i >= 0; i--) {
|
||
|
OTRelease(GetNthKid(lpNode, i));
|
||
|
}
|
||
|
DPA_Destroy(lpNode->hdpaKids);
|
||
|
lpNode->hdpaKids = KIDSUNKNOWN;
|
||
|
OTInvalidateNode(lpNode);
|
||
|
SFCFreeNode(lpNode);
|
||
|
} else {
|
||
|
lpNode = NULL;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case SHCNE_ASSOCCHANGED:
|
||
|
break;
|
||
|
|
||
|
case SHCNE_UPDATEIMAGE:
|
||
|
if (pidl) {
|
||
|
InvalidateImageIndices();
|
||
|
DoInvalidateAll(s_lpnRoot, *(int UNALIGNED *)((BYTE*)pidl + 2));
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
void _OnChangeNotify(NSC *pns, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
LPITEMIDLIST *ppidl;
|
||
|
LONG lEvent;
|
||
|
LPSHChangeNotificationLock pshcnl;
|
||
|
|
||
|
pshcnl = SHChangeNotification_Lock((HANDLE)wParam, (DWORD)lParam, &ppidl, &lEvent);
|
||
|
if (pshcnl)
|
||
|
{
|
||
|
_DoChangeNotify(pns, lEvent, ppidl[0], ppidl[1]);
|
||
|
|
||
|
SHChangeNotification_Unlock(pshcnl);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
LRESULT CALLBACK NameSpaceWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
NSC *pns = (NSC *)GetWindowLong(hwnd, 0);
|
||
|
|
||
|
switch (uMsg) {
|
||
|
case WM_NCCREATE:
|
||
|
Assert(pns == NULL);
|
||
|
return _OnNCCreate(hwnd, (LPCREATESTRUCT)lParam);
|
||
|
|
||
|
case WM_NCDESTROY:
|
||
|
if (pns)
|
||
|
_OnNCDestroy(pns);
|
||
|
break;
|
||
|
|
||
|
case WM_SIZE:
|
||
|
if (pns->hwndTree)
|
||
|
MoveWindow(pns->hwndTree, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
|
||
|
break;
|
||
|
|
||
|
case WM_STYLECHANGED:
|
||
|
if (pns && wParam == GWL_STYLE)
|
||
|
pns->style = ((LPSTYLESTRUCT)lParam)->styleNew;
|
||
|
break;
|
||
|
|
||
|
case WM_SETFOCUS:
|
||
|
if (pns && pns->hwndTree)
|
||
|
SetFocus(pns->hwndTree);
|
||
|
break;
|
||
|
|
||
|
case WM_SETFONT:
|
||
|
case WM_GETFONT:
|
||
|
if (pns && pns->hwndTree)
|
||
|
return SendMessage(pns->hwndTree, uMsg, wParam, lParam);
|
||
|
break;
|
||
|
|
||
|
case WM_NOTIFY:
|
||
|
Assert(((NMHDR *)lParam)->idFrom == ID_CONTROL);
|
||
|
|
||
|
switch (((NMHDR *)lParam)->code) {
|
||
|
|
||
|
// we track this through WM_CONTEXTMENU
|
||
|
// case NM_RCLICK:
|
||
|
|
||
|
case NM_RETURN:
|
||
|
case NM_DBLCLK:
|
||
|
return _DoDlbClick(pns);
|
||
|
|
||
|
case TVN_GETDISPINFO:
|
||
|
_OnGetDisplayInfo(pns, (TV_DISPINFO *)lParam);
|
||
|
break;
|
||
|
|
||
|
case TVN_ITEMEXPANDING:
|
||
|
SetCursor(LoadCursor(NULL, IDC_WAIT));
|
||
|
_OnItemExpanding(pns, (LPNM_TREEVIEW)lParam);
|
||
|
break;
|
||
|
|
||
|
case TVN_ITEMEXPANDED:
|
||
|
SetCursor(LoadCursor(NULL, IDC_ARROW));
|
||
|
break;
|
||
|
|
||
|
case TVN_DELETEITEM:
|
||
|
_OnDeleteItem(pns, (LPNM_TREEVIEW)lParam);
|
||
|
break;
|
||
|
|
||
|
case TVN_SELCHANGED:
|
||
|
_OnSelChanged(pns, (LPNM_TREEVIEW)lParam);
|
||
|
break;
|
||
|
|
||
|
case TVN_BEGINLABELEDIT:
|
||
|
return _OnBeginLabelEdit(pns, (TV_DISPINFO *)lParam);
|
||
|
|
||
|
case TVN_ENDLABELEDIT:
|
||
|
return _OnEndLabelEdit(pns, (TV_DISPINFO *)lParam);
|
||
|
|
||
|
case TVN_BEGINDRAG:
|
||
|
case TVN_BEGINRDRAG:
|
||
|
_OnBeginDrag(pns, (NM_TREEVIEW *)lParam);
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_CONTEXTMENU:
|
||
|
_ContextMenu(pns, (short)LOWORD(lParam), (short)HIWORD(lParam));
|
||
|
break;
|
||
|
|
||
|
case WM_INITMENUPOPUP:
|
||
|
case WM_DRAWITEM:
|
||
|
case WM_MEASUREITEM:
|
||
|
if (pns->pcm)
|
||
|
{
|
||
|
IContextMenu2 *pcm2;
|
||
|
if (SUCCEEDED(QueryInterface(pns->pcm, &IID_IContextMenu2, &pcm2)))
|
||
|
{
|
||
|
pcm2->lpVtbl->HandleMenuMsg(pcm2, uMsg, wParam, lParam);
|
||
|
Release(pcm2);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case WM_CHANGENOTIFY:
|
||
|
#define ppidl ((LPITEMIDLIST *)wParam)
|
||
|
_DoChangeNotify(pns, (LONG)lParam, ppidl[0], ppidl[1]);
|
||
|
break;
|
||
|
|
||
|
case NSM_SETROOT:
|
||
|
return _OnSetRoot(pns, (NSC_SETROOT *)lParam);
|
||
|
|
||
|
case NSM_GETIDLIST:
|
||
|
if (wParam)
|
||
|
return (LRESULT)_GetFullIDList(pns->hwndTree, (HTREEITEM)lParam);
|
||
|
else
|
||
|
{
|
||
|
TV_ITEM tvi;
|
||
|
|
||
|
// now lets get the information about the item
|
||
|
tvi.mask = TVIF_PARAM | TVIF_HANDLE;
|
||
|
tvi.hItem = (HTREEITEM)lParam;
|
||
|
if (!TreeView_GetItem(pns->hwndTree, &tvi))
|
||
|
return 0;
|
||
|
|
||
|
return (LRESULT)tvi.lParam; // relative PIDL
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case NSM_DOVERB:
|
||
|
_DoVerb(pns, NULL, (LPCSTR)lParam);
|
||
|
return TRUE;
|
||
|
|
||
|
default:
|
||
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
const char c_szNameSpaceClass[] = NAME_SPACE_CLASS;
|
||
|
|
||
|
BOOL NameSpace_RegisterClass(HINSTANCE hinst)
|
||
|
{
|
||
|
WNDCLASS wc;
|
||
|
|
||
|
InitCommonControls();
|
||
|
|
||
|
if (!GetClassInfo(hinst, c_szNameSpaceClass, &wc))
|
||
|
{
|
||
|
wc.lpfnWndProc = NameSpaceWndProc;
|
||
|
wc.hCursor = NULL;
|
||
|
wc.hIcon = NULL;
|
||
|
wc.lpszMenuName = NULL;
|
||
|
wc.hInstance = hinst;
|
||
|
wc.lpszClassName = c_szNameSpaceClass;
|
||
|
wc.hbrBackground = NULL;
|
||
|
wc.style = 0;
|
||
|
wc.cbWndExtra = sizeof(NSC *);
|
||
|
wc.cbClsExtra = 0;
|
||
|
|
||
|
return RegisterClass(&wc);
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|