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


