|
|
#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; }
|