#include "cabinet.h"
#include "rcids.h"
#include <shguidp.h>

//#define OT_DEBUG

#include "onetree.h"

#ifdef DEBUG
UINT g_cTotalOTBindToFolder = 0;
UINT g_cHitOTBindToFolder = 0;
#define DEBUG_INCL_TOTAL        g_cTotalOTBindToFolder++;
#define DEBUG_INCL_HIT          g_cHitOTBindToFolder++;
#else
#define DEBUG_INCL_TOTAL
#define DEBUG_INCL_HIT
#endif


// We want to use the real ILIsParent in this file
#undef ILIsParent

// these do no checking.
// just convenience macros
//
#define GetNthKid(lpnParent, i)         ((LPOneTreeNode)DPA_GetPtr(lpnParent->hdpaKids, i))
#define GetKidCount(lpnParent)          DPA_GetPtrCount(lpnParent->hdpaKids)
#define NodeHasKids(lpNode)             ((lpNode)->hdpaKids != KIDSUNKNOWN && (lpNode)->hdpaKids != NOKIDS)

int g_nDefOpenSysIndex = -1;
int g_nDefNormalSysIndex = -1;

#define ADDCHILD_FAILED 0   // error, didn't add it
#define ADDCHILD_ADDED   1  // didn't find it, so we added it.
#define ADDCHILD_EXISTED 2  // didn't add, it already existed.

UINT AddChild(IShellFolder *lpsfParent, LPOneTreeNode lpnParent,
                          LPCITEMIDLIST pidl, BOOL bAllowDup,
                          LPOneTreeNode *lplpnKid);

#ifdef DEBUG
void OTValidate(LPOneTreeNode lpNode);
#else
#define OTValidate(lpn)
#endif

//
// BUGBUG: remove the below once SFGAO_BROWSABLE is defined
// in the header files (post NASHVILLE merge).
#ifndef SFGAO_BROWSABLE
#define SFGAO_BROWSABLE 0
#endif

LPOneTreeNode   s_lpnRoot = NULL;
LPSHELLFOLDER   s_pshfRoot = NULL;
LPSHELLFOLDER   s_pshfRootParent = NULL;
LPCITEMIDLIST   s_pidlRoot = NULL;
BOOL            s_bDesktopRoot = FALSE;
HDPA            s_hdpaAppHwnds = NULL;
ULONG           s_uFSRegisterID = 0;
HWND            s_hwndOT = NULL;

#pragma data_seg(".text", "CODE")
const TCHAR      c_szOTClass[] = TEXT("OTClass");
#pragma data_seg()

void _OTGetImageIndex(LPSHELLFOLDER psfParent, LPOneTreeNode lpNode);
void HandleFileSysChange(LONG lNotification, LPITEMIDLIST*lplpidl);
BOOL SearchForKids(HWND hwndOwner, LPOneTreeNode lpnParent, PFileCabinet pfc, BOOL fInteractive);
LRESULT CALLBACK _export OneTreeWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LPOneTreeNode _GetNodeFromIDList(LPCITEMIDLIST pidl, UINT uFlags, HRESULT * phresOut);
void InvalidateImageIndices();


#define WM_OT_FSNOTIFY  (WM_USER + 11)

#ifdef DEBUG
void OTValidate(LPOneTreeNode lpNode)
{
    if (lpNode) {
        Assert(lpNode->dwDebugSig == OTDEBUG_SIG);
        Assert(lpNode->cRef > 0);
    }
}
#endif


//
// OneTree DPA_Search comparison function for LPOneTreeNodes
//

INT CALLBACK OTCompareNodes( LPOneTreeNode lpn1, LPOneTreeNode lpn2, LPOTCompareInfo lpinfo )
{
    HRESULT hres;


    //
    // Compare the two LPOneTreeNodes
    //

    hres = lpinfo->psf->lpVtbl->CompareIDs( lpinfo->psf,
                  0,
                  OTGetFolderID(lpn1),
                  OTGetFolderID(lpn2)
                );

    lpinfo->bFound |= ((INT)ShortFromResult(hres) == 0);
    return (INT)ShortFromResult(hres);
}

//
// OneTree DPA_Search comparison function for LPOneTreeNodes
//

INT CALLBACK OTCompareIDs( LPITEMIDLIST pidl, LPOneTreeNode lpn, LPOTCompareInfo lpinfo )
{
    HRESULT hres;


    //
    // Compare the two LPOneTreeNodes
    //

    hres = lpinfo->psf->lpVtbl->CompareIDs( lpinfo->psf,
                  0,
                  pidl,
                  OTGetFolderID(lpn)
                );

    lpinfo->bFound |= ((INT)ShortFromResult(hres) == 0);
    return (INT)ShortFromResult(hres);
}


LRESULT CALLBACK _export OneTreeWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_OT_FSNOTIFY:
        if (s_hdpaAppHwnds)
        {
            LPSHChangeNotificationLock pshcnl;
            LPITEMIDLIST *ppidl;
            LONG lEvent;

            pshcnl = SHChangeNotification_Lock((HANDLE)wParam, (DWORD)lParam, &ppidl, &lEvent);
            if (pshcnl)
            {
                HandleFileSysChange(lEvent, ppidl);
                SHChangeNotification_Unlock(pshcnl);
            }
        }

        break;

        // debug only.  someone is killing this window and
        // I wanna know who it is!
        case WM_DESTROY:
            // null only in the process of shutting down everything
            if (s_hwndOT) {
                DebugMsg(DM_ERROR, TEXT("***OneTree Window: Someone's murdering us!**"));
                s_hwndOT = NULL;
                SHChangeNotifyDeregister(s_uFSRegisterID);
                if (s_hdpaAppHwnds) {
                    HDPA hdpa = s_hdpaAppHwnds;
                    s_hdpaAppHwnds = NULL;
                    DPA_Destroy(hdpa);
                }
            }


        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    return 0L;
}


void OneTree_Terminate()
{
    if (IsWindow(s_hwndOT))
        DestroyWindow(s_hwndOT);
    s_hwndOT = NULL;

    s_hdpaAppHwnds = NULL;
    s_lpnRoot = NULL;
    OTRelease(s_lpnRoot);

    if (s_pidlRoot)
    {
        SHFree((LPITEMIDLIST)s_pidlRoot);
        s_pidlRoot = NULL;
    }
    if (s_pshfRoot)
    {
        IUnknown_Release(s_pshfRoot);
        s_pshfRoot = NULL;
    }
    if (s_pshfRootParent)
    {
        IUnknown_Release(s_pshfRootParent);
        s_pshfRootParent = NULL;
    }
}

#define GUIDSTR_MAX (1+ 8 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 12 + 1 + 1)

//----------------------------------------------------------------------------
// converts GUID into (...) form without leading identifier; returns
// amount of data copied to lpsz if successful; 0 if buffer too small.

STDAPI_(int)  StringFromGUID2A(REFGUID rguid, LPTSTR lpsz, int cbMax)
{
    if (cbMax < GUIDSTR_MAX)
        return 0;

#pragma data_seg(".text", "CODE")
    wsprintf(lpsz, TEXT("{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}"),
            rguid->Data1, rguid->Data2, rguid->Data3,
            rguid->Data4[0], rguid->Data4[1],
            rguid->Data4[2], rguid->Data4[3],
            rguid->Data4[4], rguid->Data4[5],
            rguid->Data4[6], rguid->Data4[7]);
#pragma data_seg()

    Assert(lstrlen(lpsz) + 1 == GUIDSTR_MAX);
    return GUIDSTR_MAX;
}

//----------------------------------------------------------------------------
BOOL GetTextFromCLSID(const CLSID *pclsid, LPTSTR pszText, int cchText)
{
    HKEY hkeyNew;
    BOOL fRet = FALSE;
    DWORD dwType;
    TCHAR szCLSID[GUIDSTR_MAX+6];
    DWORD cbText = cchText * sizeof(TCHAR);
    VDATEINPUTBUF(pszText, TCHAR, cchText);

#pragma data_seg(DATASEG_READONLY)
    lstrcpy(szCLSID, TEXT("CLSID\\"));
#pragma data_seg()
    StringFromGUID2A(pclsid, szCLSID+6, ARRAYSIZE(szCLSID)-6);
    if (!GetSystemMetrics(SM_CLEANBOOT) && (RegOpenKey(HKEY_CLASSES_ROOT, szCLSID, &hkeyNew) == ERROR_SUCCESS))
    {
        if (RegQueryValueEx(hkeyNew, NULL, 0, &dwType, (LPBYTE) pszText, &cbText) == ERROR_SUCCESS)
        {
            fRet = TRUE;
        }
        RegCloseKey(hkeyNew);
    }
    return fRet;
}

//----------------------------------------------------------------------------
int MapCLSIDToSystemImageListIndex(const CLSID *pclsid)
{
    HKEY hkeyNew;
    DWORD dwType;
    TCHAR sz[MAX_PATH];
    TCHAR szDefIcon[MAX_PATH];
    DWORD cbDefIcon = SIZEOF(szDefIcon);
    int i = 0;

#pragma data_seg(DATASEG_READONLY)
    lstrcpy(sz, TEXT("CLSID\\"));
    StringFromGUID2A(pclsid, sz+6, ARRAYSIZE(sz)-6);
    lstrcat(sz, TEXT("\\DefaultIcon"));
#pragma data_seg()

    if (!GetSystemMetrics(SM_CLEANBOOT) && (RegOpenKey(HKEY_CLASSES_ROOT, sz, &hkeyNew) == ERROR_SUCCESS))
    {
        if (RegQueryValueEx(hkeyNew, NULL, 0, &dwType, (LPBYTE) szDefIcon, &cbDefIcon) == ERROR_SUCCESS)
        {
            i = PathParseIconLocation(szDefIcon);
            i = Shell_GetCachedImageIndex(szDefIcon, i, 0);
            return i;
        }
        RegCloseKey(hkeyNew);
    }

    return 0;
}

UINT AddChildEx(IShellFolder *lpsfParent, LPOneTreeNode lpnParent,
                          LPCITEMIDLIST pidl, BOOL bAllowDup,
                          LPOneTreeNode *lplpnKid, LPCTSTR pszText);

BOOL OneTree_Initialize(const CLSID *pclsid, LPCITEMIDLIST pidlRoot)
{
    WNDCLASS wndclass ;
    SHChangeNotifyEntry fsne = { NULL, TRUE };     // Global & recursive.
    IShellFolder *psfDesktop;
    int iSelectedImage;

    if (!SFCInitialize())
        return FALSE;

    // dummy window for the FSNotify
    wndclass.style         = 0;
    wndclass.lpfnWndProc   = OneTreeWndProc ;
    wndclass.cbClsExtra    = 0 ;
    wndclass.cbWndExtra    = 0 ;
    wndclass.hInstance     = hinstCabinet;
    wndclass.hIcon         = NULL;
    wndclass.hCursor       = NULL;
    wndclass.hbrBackground = NULL;
    wndclass.lpszMenuName  = NULL;
    wndclass.lpszClassName = c_szOTClass;

    if (!RegisterClass (&wndclass))
        return FALSE;

    s_hdpaAppHwnds = NULL;

    s_hwndOT = CreateWindow (c_szOTClass, c_szNULL,
                                WS_OVERLAPPEDWINDOW,
                                CW_USEDEFAULT, CW_USEDEFAULT,
                                CW_USEDEFAULT, CW_USEDEFAULT,
                                NULL, NULL, hinstCabinet, NULL) ;

    if (!s_hwndOT) {
        DebugMsg(DM_ERROR, TEXT("OneTree: failed to create window"));
        return FALSE;
    }

    if (FAILED(ICoCreateInstance(&CLSID_ShellDesktop, &IID_IShellFolder,
        &psfDesktop)))
    {
        DebugMsg(DM_ERROR, TEXT("OneTree: failed to bind to Desktop root"));
        return(FALSE);
    }

    // get the root
    if (pidlRoot)
    {
        LPITEMIDLIST pidlLast;
        // Actually, I just need any error value
        HRESULT hres = (E_OUTOFMEMORY);

        // I want the "global" CreateFromPath, not the Cabinet version
        s_pidlRoot = ILClone(pidlRoot);
        if (!s_pidlRoot)
        {
            DebugMsg(DM_ERROR, TEXT("OneTree: failed to get root IDList"));
            goto Error0;
        }

        pidlLast = ILFindLastID(s_pidlRoot);
        if (pidlLast == s_pidlRoot)
        {
            s_pshfRootParent = psfDesktop;
            IUnknown_AddRef(s_pshfRootParent);
        }
        else
        {
            WORD cbSave;

            cbSave = pidlLast->mkid.cb;
            pidlLast->mkid.cb = 0;
            if (FAILED(psfDesktop->lpVtbl->BindToObject(psfDesktop, s_pidlRoot, NULL,
                &IID_IShellFolder, &s_pshfRootParent)))
            {
                goto Error0;
            }
            pidlLast->mkid.cb = cbSave;
        }


        if (pclsid)
        {
            IPersistFolder *ppf;
            TCHAR szText[MAX_PATH];

            GetTextFromCLSID(pclsid, szText, ARRAYSIZE(szText));
            if (AddChildEx(s_pshfRootParent, NULL, pidlLast, FALSE, &s_lpnRoot, szText)
                != ADDCHILD_ADDED)
            {
                goto Error0;
            }

#ifdef DEBUG
            s_lpnRoot->dwDebugSig = OTDEBUG_SIG;
#endif
            s_lpnRoot->iImage = MapCLSIDToSystemImageListIndex(pclsid);
            s_lpnRoot->iSelectedImage = s_lpnRoot->iImage;

            OTAddRef(s_lpnRoot);

            if (FAILED(ICoCreateInstance(pclsid, &IID_IShellFolder, &s_pshfRoot)))
            {
                DebugMsg(DM_ERROR, TEXT("OneTree: failed to get IShellFolder"));
                goto Error0;
            }

            if (FAILED(IUnknown_QueryInterface(s_pshfRoot, &IID_IPersistFolder,
                &ppf)))
            {
                DebugMsg(DM_ERROR, TEXT("OneTree: failed to get IPersistFolder"));
                goto Error0;
            }

            hres = ppf->lpVtbl->Initialize(ppf, s_pidlRoot);

            IUnknown_Release(ppf);
        }
        else
        {
            if (AddChild(s_pshfRootParent, NULL, pidlLast, FALSE, &s_lpnRoot)
                != ADDCHILD_ADDED)
            {
                goto Error0;
            }
            s_lpnRoot->iImage = SHMapPIDLToSystemImageListIndex(s_pshfRootParent,
                pidlLast, &iSelectedImage);
            s_lpnRoot->iSelectedImage = iSelectedImage;
            OTAddRef(s_lpnRoot);

            hres = s_pshfRootParent->lpVtbl->BindToObject(s_pshfRootParent,
                pidlLast, NULL, &IID_IShellFolder, &s_pshfRoot);
        }

        if (FAILED(hres))
        {
Error0:;
            IUnknown_Release(psfDesktop);

            OneTree_Terminate();
            return(FALSE);
        }

        IUnknown_Release(psfDesktop);
    }
    else
    {
        ITEMIDLIST idl = { 0 } ;

        if (AddChild(NULL, NULL, &idl, FALSE, &s_lpnRoot) != ADDCHILD_ADDED)
        {
            goto Error0;
        }

        s_lpnRoot->iImage = SHMapPIDLToSystemImageListIndex(psfDesktop,
                &idl, &iSelectedImage);
        s_lpnRoot->iSelectedImage = iSelectedImage;

        s_pshfRoot = psfDesktop;
        s_bDesktopRoot = TRUE;
    }

    s_lpnRoot->dwAttribs = SFGAO_HASSUBFOLDER | SFGAO_SHARE | SFGAO_REMOVABLE |SFGAO_FILESYSANCESTOR | SFGAO_FILESYSTEM | SFGAO_CAPABILITYMASK | SFGAO_COMPRESSED;
    s_pshfRoot->lpVtbl->GetAttributesOf(s_pshfRoot, 0, NULL, &s_lpnRoot->dwAttribs);
    s_lpnRoot->dwAttribs &= ~(SFGAO_CANRENAME);

    s_lpnRoot->cChildren = 1;
    s_lpnRoot->hdpaKids = KIDSUNKNOWN;

    fsne.pidl = s_pidlRoot;
    s_uFSRegisterID = SHChangeNotifyRegister(s_hwndOT,
                                        SHCNRF_NewDelivery | SHCNRF_ShellLevel | SHCNRF_InterruptLevel,
                                        SHCNE_ALLEVENTS & ~(SHCNE_FREESPACE|SHCNE_CREATE|SHCNE_DELETE|SHCNE_RENAMEITEM),
                                        WM_OT_FSNOTIFY,
                                        1, &fsne);

    return TRUE;
}


LPITEMIDLIST OTCloneFolderID(LPOneTreeNode lpn)
{
    LPITEMIDLIST pidl = NULL;

    ENTERCRITICAL;
    if (lpn) {
        pidl = ILClone(lpn->pidl);
    }
    LEAVECRITICAL;

    return pidl;
}

LPTSTR OTGetNodeName(LPOneTreeNode lpn, LPTSTR pszText, int cch)
{
    pszText[0] = 0;

    ENTERCRITICAL;
    if (lpn->lpText && lpn->lpText != LPSTR_TEXTCALLBACK) {
        Assert(pszText);
        lstrcpyn(pszText, lpn->lpText, cch);
    }
    LEAVECRITICAL;

    return pszText;
}

BOOL OTILIsEqual(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2)
{
        HRESULT hres;

        // Special-case the root
        if (ILIsEmpty(pidl1) && ILIsEmpty(pidl2))
        {
                return(TRUE);
        }

        hres = s_pshfRoot->lpVtbl->CompareIDs(s_pshfRoot, 0, pidl1, pidl2);
        return (hres==ResultFromShort(0));
}

BOOL OTILIsParent(LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2, BOOL fImmediate)
{
        if (!s_bDesktopRoot)
        {
                // When this was written we could never get here since
                // ILIsParent was only used for the FSNotify stuff, plus this
                // would be way too annoying to rewrite here
                Assert(FALSE);
                return(FALSE);
        }

        return ILIsParent(pidl1, pidl2, fImmediate);
}

HRESULT OTILCreateFromPath(LPCTSTR pszPath, LPITEMIDLIST *ppidl, DWORD *rgfInOut)
{
    WCHAR wszPath[MAX_PATH];
    ULONG pcchEaten;

    if (!pszPath || !*pszPath)
    {
        // We must be referencing the root
        ITEMIDLIST idl = { 0 };

        *ppidl = ILClone(&idl);
        return(*ppidl ? S_OK : (E_OUTOFMEMORY));
    }

    StrToOleStrN(wszPath, ARRAYSIZE(wszPath), pszPath, -1);

    return(s_pshfRoot->lpVtbl->ParseDisplayName(s_pshfRoot,
        NULL, NULL, wszPath, &pcchEaten, ppidl, rgfInOut));
}


LPITEMIDLIST OTPIDLFromPath(LPCTSTR pszPath)
{
        LPITEMIDLIST pidl;

        if (SUCCEEDED(OTILCreateFromPath(pszPath, &pidl, NULL)))
        {
                return(pidl);
        }

        return(NULL);
}


BOOL OTTranslateIDList(LPCITEMIDLIST pidl, LPITEMIDLIST *ppidlLog)
{
        LPITEMIDLIST pidlLog = NULL;

        // Some code depends on this being NULL on failure
        *ppidlLog = NULL;

        if (s_bDesktopRoot)
        {
            // We no longer translate IDLists, since we get a "pre-translated"
            // IDList from ShellExecute.  This allows us to browse into
            // C:\WINDOWS\DESKTOP separately from the "logical" Desktop
            // pidlLog = SHLogILFromFSIL(pidl);
            pidlLog = ILClone(pidl);
        }
        else
        {
                LPCITEMIDLIST pidlRoot;

                if (!ILIsParent(s_pidlRoot, pidl, FALSE))
                {
                        // These guy are not in the same space, so can't open
                        // it
                        return(FALSE);
                }

                // Skip past the root pidl
                for (pidlRoot=s_pidlRoot; !ILIsEmpty(pidlRoot);
                        pidlRoot=_ILNext(pidlRoot))
                {
                        pidl = _ILNext(pidl);
                }

                pidlLog = ILClone(pidl);
        }

        *ppidlLog = pidlLog;

        return(pidlLog != NULL);
}


BOOL OTIsDesktopRoot(void)
{
        return(s_bDesktopRoot);
}


void KillKids(LPOneTreeNode lpNode)
{
    //
    // free this kid  and all its decendants
    //
    if (NodeHasKids(lpNode))
    {
        int i;
        HDPA hdpaKids = lpNode->hdpaKids;

        // null out the hdpaKids var first because debug version
        // make sure that the parent doesn't have a pointe to us (above)
        lpNode->hdpaKids = KIDSUNKNOWN;
        for( i = DPA_GetPtrCount(hdpaKids) - 1; i >= 0 ; i--) {
            OTRelease((LPOneTreeNode)DPA_GetPtr(hdpaKids, i));
        }
        DPA_Destroy(hdpaKids);
    }
}

// returns TRUE if the lpText points somewhere in the pidl
BOOL IsPtrInPidl(LPBYTE p, LPITEMIDLIST pidl)
{
    return (p && (p > (LPBYTE)pidl) && (p <= (((LPBYTE)pidl) + pidl->mkid.cb)));
}

void OTSetNodeName(LPOneTreeNode lpNode, LPTSTR pszName)
{
    if (lpNode->lpText) {
        if (!IsPtrInPidl((LPBYTE)lpNode->lpText, lpNode->pidl)) {
            LocalFree(lpNode->lpText);
        }
    }

    lpNode->lpText = pszName;
}

void OTFreeNodeData(LPOneTreeNode lpNode)
{
    ENTERCRITICAL;
    OTSetNodeName(lpNode, NULL);

    if (lpNode->pidl) {
        ILFree(lpNode->pidl);
        lpNode->pidl = NULL;
    }
    LEAVECRITICAL;
}

void KillNode(LPOneTreeNode lpNode)
{
    OTValidate(lpNode);

//
//  I'm disabling this debug code since we'll hit GPF when the parent
// node is already freed.
//
#if 0
#ifdef DEBUG
    // make sure our parent doesn't have a pointer to us.  This will help
    // (but not ensure) that we're not doing extra OTReleases.
    {
        LPOneTreeNode lpnParent = lpNode->lpnParent;
        int i;
        if (lpnParent && NodeHasKids(lpnParent)) {
            i = GetKidCount(lpnParent);
            while( i--) {
                // this is REALLY REALLY bad if this assert fails.
                // let chee know.
                Assert(GetNthKid(lpnParent, i) != lpNode);
            }
        }
    }

#endif
#endif

    KillKids(lpNode);
    SFCFreeNode(lpNode);

    OTFreeNodeData(lpNode);
    LocalFree(lpNode);
}

void OTRelease(LPOneTreeNode lpNode)
{
    OTValidate(lpNode);

    if (lpNode) {
        Assert(lpNode->cRef > 0);
        Assert((lpNode->cRef > 1) || (lpNode != s_lpnRoot));

        // this should NEVER happen.. but just in case,
        // don't free out the s_lpnRoot while it's still
        // in our global
        if ((lpNode->cRef == 1) && (lpNode == s_lpnRoot))
            return;

        lpNode->cRef--;
        if (lpNode->cRef == 0)
            KillNode(lpNode);
    }
}

LPOneTreeNode OTGetParent(LPOneTreeNode lpNode)
{
    OTValidate(lpNode);

    // sanity check
    if (!lpNode)
        return NULL;

    if (lpNode->lpnParent)
        OTAddRef(lpNode->lpnParent);
    return lpNode->lpnParent;
}

void DoInvalidateAll(LPOneTreeNode lpNode, int iImage)
{
    int i;
    LPOneTreeNode lpnKid ;

    if (lpNode == NULL)
        return;

    OTValidate(lpNode); // this validate just validates  that it's a node...

    if (iImage == -1 ||
        iImage == lpNode->iImage ||
        iImage == lpNode->iSelectedImage) {
        OTInvalidateNode(lpNode);
////////DebugMsg(DM_TRACE, "DoInvalidateAll, found one to invalidate");
    }

    if (NodeHasKids(lpNode)) {
        for (i = GetKidCount(lpNode) - 1; i >= 0; i--) {
            lpnKid = GetNthKid(lpNode, i);
            if (lpnKid) {
                DoInvalidateAll(lpnKid, iImage);
            }
        }
    }
}

//
// traverses the tree starting from lpNode and tries to Release cached
// IShellFolder.
//
void OTSweepFolders(LPOneTreeNode lpNode)
{
    if (NodeHasKids(lpNode)) {
        int i;

        Assert(lpNode);
        OTValidate(lpNode);

        for (i = GetKidCount(lpNode) - 1; i >= 0; i--)
        {
            LPOneTreeNode lpnKid = GetNthKid(lpNode, i);
            if (lpnKid)
            {
                OTSweepFolders(lpnKid);
                SFCFreeNode(lpnKid);
            }
        }
    }
}


void OTActivate()
{
    // BUGBUG:  This should never happen.. but just in case
    if (!s_hwndOT)
    {
        SHChangeNotifyEntry fsne = { s_pidlRoot, TRUE };     // Global & recursive.

        DebugMsg(DM_ERROR, TEXT("OneTree Window got killed... recovering..."));
        s_hwndOT = CreateWindow (c_szOTClass, c_szNULL,
                                WS_OVERLAPPEDWINDOW,
                                CW_USEDEFAULT, CW_USEDEFAULT,
                                CW_USEDEFAULT, CW_USEDEFAULT,
                                NULL, NULL, hinstCabinet, NULL) ;

        s_uFSRegisterID = SHChangeNotifyRegister(s_hwndOT,
                      SHCNRF_NewDelivery | SHCNRF_ShellLevel | SHCNRF_InterruptLevel,
                      SHCNE_ALLEVENTS & ~(SHCNE_CREATE|SHCNE_DELETE),
                      WM_OT_FSNOTIFY,
                      1, &fsne);
    }
}

LPOneTreeNode FindKid(LPSHELLFOLDER psfParent, LPOneTreeNode lpnParent, LPCITEMIDLIST pidlKid, BOOL bValidate, INT * pPos)
{
    HRESULT hres;

    Assert(psfParent);

    // there can be only one...
    Assert(ILIsEmpty(_ILNext(pidlKid)));

    *pPos = 0;
    if (lpnParent->hdpaKids == KIDSUNKNOWN || lpnParent->fInvalid) {
        if (bValidate)
            SearchForKids(NULL, lpnParent, NULL, FALSE);
    }

    //
    // since we might not have validated, we might still have kidsunknow.
    //
    if ((lpnParent->hdpaKids != NOKIDS) && (lpnParent->hdpaKids != KIDSUNKNOWN)) {

        OTCompareInfo info;

        info.psf = psfParent;
        info.bFound = FALSE;
        *pPos = DPA_Search( lpnParent->hdpaKids,
                        (LPVOID)pidlKid,
                        0,
                        (PFNDPACOMPARE)OTCompareIDs,
                        (LPARAM)&info,
                        DPAS_SORTED | DPAS_INSERTAFTER
                       );

        if (info.bFound)
        {
            return (LPOneTreeNode)DPA_FastGetPtr( lpnParent->hdpaKids, *pPos );
        }

    }

    return NULL;
}

// lplpnKidAdded -- this is in/out.  it holds the kid to be added coming in..
// it holds the kid added (or found) coming out
void AdoptKid(LPSHELLFOLDER lpsfParent, LPOneTreeNode* lplpnKidAdded, int iPos, DWORD dwLastChecked)
{
    LPOneTreeNode lpnFind;
    LPOneTreeNode lpnNewKid = *lplpnKidAdded;
    LPOneTreeNode lpnParent = lpnNewKid->lpnParent;

    ENTERCRITICAL;

    if (lpnParent->hdpaKids == KIDSUNKNOWN) {
        OTInvalidateNode(lpnParent);
        lpnParent->hdpaKids = NOKIDS;
    }

    if (lpnParent->hdpaKids == NOKIDS)
    {
        lpnParent->hdpaKids = DPA_Create(0);
        iPos = 0;
    }

    //
    // has anyone added anything to the DPA since
    // we did the initial find?
    if ((iPos != -1 && dwLastChecked == lpnParent->dwLastChanged) ||
        !(lpnFind = FindKid( lpsfParent, lpnParent, lpnNewKid->pidl, FALSE, &iPos))) {

        OTAddRef(lpnNewKid);
        lpnParent->dwLastChanged = GetTickCount();
        DPA_InsertPtr( lpnParent->hdpaKids, iPos, lpnNewKid );

    }
    else
    {
        *lplpnKidAdded = lpnFind;
    }
    LEAVECRITICAL;
}


#ifdef FOR_GEORGEST
void DebugDumpNode(LPOneTreeNode lpn, LPTSTR lpsz)
{
    Assert(lpn->lpText);
    DebugMsg(DM_TRACE, TEXT("ONETREE: %s (%d) %s"), lpsz, lpn, lpn->lpText);
}
#endif


LPTSTR StrRetToStrPtr(STRRET * psr, LPCITEMIDLIST pidl)
{
    LPTSTR psz;

#ifdef UNICODE
    TCHAR szName[MAX_PATH];
    int iLen;

    switch(psr->uType)
    {
    case STRRET_OLESTR:
        lstrcpyn(szName, psr->pOleStr, ARRAYSIZE(szName));
        SHFree(psr->pOleStr);
        break;

    case STRRET_OFFSET:
    {
        LPSTR pszStr = (LPSTR)(((LPBYTE)&pidl->mkid) + psr->uOffset);
        iLen = lstrlenA(pszStr) + 1;
        MultiByteToWideChar(CP_ACP, 0,
                            pszStr, iLen,
                            szName, iLen);
        break;
    }

    case STRRET_CSTR:
        iLen = lstrlenA(psr->cStr) + 1;
        MultiByteToWideChar(CP_ACP, 0,
                            psr->cStr, iLen,
                            szName, iLen);
        break;

    default:
        DebugMsg(DM_ERROR, TEXT("exp - StrRetToStrPtr: Got a type we don't know! %d"), psr->uType);
        return (LPTSTR)c_szNULL;
    }

    psz = LocalAlloc(LPTR, (lstrlen(szName) + 1) * sizeof(TCHAR));
    if (psz) {
        lstrcpy(psz, szName);
    }

#else
    switch (psr->uType)
    {
    case STRRET_OLESTR:
    {
        LPOLESTR pwszDisplayName;

        // Convert from STRRET_OLESTR to STRRET_CSTR
        pwszDisplayName = psr->pOleStr;
        OleStrToStrN(psr->cStr, ARRAYSIZE(psr->cStr),
                     pwszDisplayName, (UINT)-1);
        SHFree(pwszDisplayName);
        psr->uType = STRRET_CSTR;
    }
        // fall through
    case STRRET_CSTR:
        psz = LocalAlloc(LPTR, (lstrlen(psr->cStr) + 1) * sizeof(TCHAR));
        if (psz) {
            lstrcpy(psz, psr->cStr);
        }
        break;


    case STRRET_OFFSET:
        // REVIEW: Do some validation here, instead of asserts!
        Assert(psr->uOffset < pidl->mkid.cb);
        Assert(psr->uOffset >= SIZEOF(pidl->mkid.cb));
        psz = (LPSTR)(((LPBYTE)&pidl->mkid) + psr->uOffset);
        break;
    }
#endif


    return psz;
}

UINT AddChildEx(IShellFolder *lpsfParent, LPOneTreeNode lpnParent,
                          LPCITEMIDLIST pidl, BOOL bAllowDup,
                          LPOneTreeNode *lplpnKid, LPCTSTR pszText)
{
    LPOneTreeNode lpnKid = NULL;
    INT iPos = 0;
    UINT uReturn;
    DWORD dwLastChecked = 0;

    OTValidate(lpnParent);

    // Is it already in the tree?
    if (lpnParent)
    {
        dwLastChecked = lpnParent->dwLastChanged;
        lpnKid = FindKid( lpsfParent, lpnParent, pidl, FALSE, &iPos );

    }

    if (!lpnKid || bAllowDup)
    {
        STRRET srDisplayName;
        HRESULT hres;
        LPITEMIDLIST pidlChild = ILClone(pidl);

        uReturn = ADDCHILD_FAILED; // assume error

        if (!pidlChild)
            goto Error1;

        // First, ask for the display name of this subfolder.
        if (pszText && *pszText)
        {
#ifdef UNICODE
            UINT cchLen = lstrlen(pszText);
            srDisplayName.uType = STRRET_OLESTR;
            srDisplayName.pOleStr = SHAlloc((cchLen + 1) * sizeof(OLECHAR));
            if (srDisplayName.pOleStr == NULL)
            {
                hres = E_OUTOFMEMORY;
            }
            else
            {
                lstrcpyn(srDisplayName.pOleStr, pszText, cchLen + 1);
            }

#else
            srDisplayName.uType = STRRET_CSTR;
            lstrcpyn(srDisplayName.cStr, pszText, ARRAYSIZE(srDisplayName.cStr));

#endif
            hres = S_OK;
        }
        else
        {
            if (lpsfParent)
            {
                    //
                // Note for M7 only:
                //  If we turn off "hide extension", we end up displaying
                // .EXE extension for rooted thing.
                //
                hres = lpsfParent->lpVtbl->GetDisplayNameOf(lpsfParent, pidl,
                    SHGDN_INFOLDER | SHGDN_NORMAL, &srDisplayName);
            }
            else
            {
#ifdef UNICODE
                srDisplayName.uType = STRRET_OLESTR;
                srDisplayName.pOleStr = SHAlloc(MAX_PATH*SIZEOF(TCHAR));
                if (srDisplayName.pOleStr == NULL)
                {
                    hres = E_OUTOFMEMORY;
                }
                else
                {
                    LoadString(hinstCabinet, IDS_DESKTOP, srDisplayName.pOleStr,
                               MAX_PATH);
                    hres = S_OK;
                }
#else
                srDisplayName.uType = STRRET_CSTR;
                LoadString(hinstCabinet, IDS_DESKTOP, srDisplayName.cStr, ARRAYSIZE(srDisplayName.cStr));
                hres = S_OK;
#endif
            }
        }

        if (SUCCEEDED(hres))
        {

            LPOneTreeNode lpnKid = LocalAlloc(LPTR, sizeof(OneTreeNode));


            if (lpnKid)
            {
                // Note that we copy only a mkid. Because we allocate
                // two extra bytes (with 0-init), we'll get a IDL.
                Assert(ILIsEmpty(_ILNext(pidl)));
                lpnKid->pidl = pidlChild;
                lpnKid->lpText = StrRetToStrPtr(&srDisplayName, pidlChild);

#ifdef DEBUG
                lpnKid->dwDebugSig = OTDEBUG_SIG;
#endif

                lpnKid->lpnParent = lpnParent;
                lpnKid->hdpaKids = KIDSUNKNOWN;
                lpnKid->cChildren = (BYTE)I_CHILDRENCALLBACK;
                lpnKid->iImage = (USHORT)I_IMAGECALLBACK;
                lpnKid->iSelectedImage = (USHORT)I_IMAGECALLBACK;
                lpnKid->cRef = 1;
                lpnKid->dwLastChanged = 0;

                DebugDumpNode(lpnKid, TEXT("AdoptingKid"));

                OTInvalidateNode(lpnKid);
                *lplpnKid = lpnKid;
                if (lpnParent)
                {
                    AdoptKid(lpsfParent, lplpnKid, iPos, dwLastChecked);
                    (*lplpnKid)->fMark = 0;
                    // release now..  if adopt kid took it, it did an addref to lpnKid
                    OTRelease( lpnKid );

                    if (lpnKid == *lplpnKid) {
                        uReturn = ADDCHILD_ADDED;
                    } else {
                        uReturn = ADDCHILD_EXISTED;
                    }
                } else {
                    // this needs to return success if there's no parent.
                    // only onetreeinitialize does this, and it needs the return
                    uReturn = ADDCHILD_ADDED;
                }
            }
        } else {
            ILFree(pidlChild);
        }
    }
    else
    {
        // this kid still exists, set fMark to 0 so
        // that we don't kill the kid below
        lpnKid->fMark = 0;

        // didn't add
        *lplpnKid = lpnKid;
        uReturn = ADDCHILD_EXISTED;
    }

Error1:;
#ifdef OT_DEBUG
    DebugMsg(DM_TRACE, TEXT("OneTree: Addchild retuns %d"), uReturn);
#endif
    return uReturn;
}

UINT AddChild(IShellFolder *lpsfParent, LPOneTreeNode lpnParent,
                          LPCITEMIDLIST pidl, BOOL bAllowDup,
                          LPOneTreeNode *lplpnKid)
{
    return AddChildEx(lpsfParent, lpnParent, pidl, bAllowDup, lplpnKid, NULL);
}



// BUGBUG..  One tree reference counting isn't there nor is releasing

//
// Notes:   We can't combine this function with _BindFromJunctoinPoint
//          to simplify the code. We need to avoid allocating szPath[]
//          each time we call OTBindToFolder recursively.
//
HRESULT OTBindToFolderEx(LPOneTreeNode lpNode, LPSHELLFOLDER *ppshf)
{
    HRESULT hres = S_OK;        // assume no error
    *ppshf = NULL;      // assume error

    OTValidate(lpNode);

    if (!IsWindow(s_hwndOT)) {
        s_hwndOT = NULL;
    }

    if (!lpNode)
    {
        return E_FAIL;
    }

    //
    // Special case for the desktop node
    //
    if (lpNode == s_lpnRoot)
    {
        IUnknown_AddRef(s_pshfRoot);
        *ppshf = s_pshfRoot;
        return S_OK;
    }

    return SFCBindToFolder(NULL, lpNode, ppshf);
}

LPSHELLFOLDER OTBindToFolder(LPOneTreeNode lpnd)
{
    LPSHELLFOLDER pshf = NULL;
    HRESULT hres = OTBindToFolderEx(lpnd, &pshf);
    Assert((FAILED(hres) && pshf==NULL) || (SUCCEEDED(hres) && pshf));
    return pshf;
}


HRESULT OTBindToParent(LPOneTreeNode lpnd, LPSHELLFOLDER *ppsf)
{
        HRESULT hres;

        lpnd = OTGetParent(lpnd);

        if (lpnd)
        {
                hres = OTBindToFolderEx(lpnd, ppsf);
                OTRelease(lpnd);
                return(hres);
        }

        // This must be the root item

        if (OTIsDesktopRoot())
        {
                // The desktop has no parent
                return E_INVALIDARG;
        }

        if (!s_pshfRootParent)
        {
                return E_UNEXPECTED;
        }

        IUnknown_AddRef(s_pshfRootParent);
        *ppsf = s_pshfRootParent;
        return S_OK;
}


HRESULT OTRealBindToFolder(LPOneTreeNode lpNode, LPSHELLFOLDER *ppshf)
{
    HRESULT hres;
    LPITEMIDLIST pidl;

    *ppshf = NULL;      // assume error

    pidl = OTCreateIDListFromNode(lpNode);
    if (pidl)
    {
        hres = s_pshfRoot->lpVtbl->BindToObject(s_pshfRoot, pidl, NULL, &IID_IShellFolder, (LPVOID*)ppshf);
        Assert((FAILED(hres) && *ppshf==NULL) || (SUCCEEDED(hres) && *ppshf));
        ILFree(pidl);
    }
    else
    {
        hres = E_OUTOFMEMORY;
    }

    return hres;
}

BOOL OTUpdateNodeName(LPSHELLFOLDER psf, LPOneTreeNode lpNode)
{
    STRRET sr;
    HRESULT hres;
    LPTSTR pszNewName;
    LPCITEMIDLIST pidl;
    BOOL fRet = FALSE;


    pidl = lpNode->pidl;
    if (!pidl)
        goto Bail;


    hres = psf->lpVtbl->GetDisplayNameOf(psf, pidl,
                                         SHGDN_INFOLDER | SHGDN_NORMAL, &sr);
    if (FAILED(hres))
        goto Bail;

    ENTERCRITICAL;
    pszNewName = StrRetToStrPtr(&sr, pidl);
    if (pszNewName) {
        if (!lpNode->lpText || lstrcmp(pszNewName, lpNode->lpText)) {
            OTSetNodeName(lpNode, pszNewName);
        } else {
            // if we didn't set it, we need to free pszNewName
            if (!IsPtrInPidl((LPBYTE)pszNewName, lpNode->pidl)) {
                LocalFree(pszNewName);
            }
        }
    }
    fRet = TRUE;
    LEAVECRITICAL;

Bail:


    return fRet;

}

void ForceNode(LPOneTreeNode lpNode)
{
    LPSHELLFOLDER psfParent;
    DWORD dwAttribs = SFGAO_HASSUBFOLDER | SFGAO_SHARE | SFGAO_REMOVABLE |SFGAO_FILESYSANCESTOR | SFGAO_FILESYSTEM | SFGAO_CAPABILITYMASK | SFGAO_COMPRESSED;

    OTValidate(lpNode);

    if (NULL != (psfParent = OTBindToFolder(lpNode->lpnParent)))
    {

        if (OTUpdateNodeName(psfParent, lpNode)) {

            LPCITEMIDLIST pidl;

            pidl = lpNode->pidl;

            psfParent->lpVtbl->GetAttributesOf(psfParent, 1, &pidl, &dwAttribs);
            lpNode->cChildren = (dwAttribs & SFGAO_HASSUBFOLDER) ?  1 : 0;
            lpNode->dwAttribs = dwAttribs;
            lpNode->fCompressed = (BYTE)(dwAttribs & SFGAO_COMPRESSED ? TRUE : FALSE);

        }

        _OTGetImageIndex(psfParent, lpNode);
        IUnknown_Release(psfParent);
    }
}


void CheckDestroyHDPAKids(LPOneTreeNode lpnParent)
{
    OTValidate(lpnParent);

    //
    // do we have any kids left?
    //
    lpnParent->cChildren = GetKidCount(lpnParent);
    if (!lpnParent->cChildren) {

        lpnParent->cChildren = 0;
        DPA_Destroy(lpnParent->hdpaKids);
        lpnParent->hdpaKids = NOKIDS;

    }
}

//
// nuke all the kids with the ref still set
//
void KillAbandonedKids(LPOneTreeNode lpnParent)
{
    int i;
    LPOneTreeNode lpnKid;

    OTValidate(lpnParent);

    for (i = GetKidCount(lpnParent) - 1; i >= 0; i--) {

        lpnKid = GetNthKid(lpnParent, i);
        if (lpnKid && lpnKid->fMark) {

            // kill de wabbit
            ENTERCRITICAL;
            DPA_DeletePtr(lpnParent->hdpaKids, i);
            lpnParent->dwLastChanged = GetTickCount();
            LEAVECRITICAL;
            OTRelease(lpnKid);
        }
    }

    CheckDestroyHDPAKids(lpnParent);
}

#if 0

UINT OTPeekForAMessage(PFileCabinet pfc)
{
    if (pfc->fShouldClose)
        return PEEK_CLOSE;
    else
        return PeekForAMessage(pfc, pfc->hwndMain, FALSE);
}

#endif

BOOL SearchForKids(HWND hwndOwner, LPOneTreeNode lpnParent, PFileCabinet pfc, BOOL fInteractive)
{
    IShellFolder *lpsfParent;
    LPENUMIDLIST penum;
    LPITEMIDLIST pidl;
    LPOneTreeNode lpnKid;
    BOOL fSuccess = TRUE;
    ULONG celt;
    int iDestroyCount = 0;
    SHELLSTATE ss;

    OTValidate(lpnParent);

    // Have a seat, this could take a while
    lpsfParent = OTBindToFolder(lpnParent);
    if (!lpsfParent)
    {
        Assert(FALSE);
        fSuccess = FALSE;
        goto Error1;
    }

    SHGetSetSettings(&ss, SSF_SHOWALLOBJECTS, FALSE);

    if (FAILED(lpsfParent->lpVtbl->EnumObjects(lpsfParent,
                    fInteractive ? hwndOwner : NULL,
                    ss.fShowAllObjects ? SHCONTF_FOLDERS|SHCONTF_INCLUDEHIDDEN : SHCONTF_FOLDERS,
                    &penum)))
    {
        // Notes: It means the enumeration is either canceled by the
        //  user, or something went wrong.
        //
        // Return the state of lpnParent->hdpaKids to KIDSUNKNOWN.
        //
        KillKids(lpnParent);
        lpnParent->hdpaKids = KIDSUNKNOWN;
        OTInvalidateNode(lpnParent);
        DebugMsg(DM_ERROR, TEXT("ca TR - SearchForKids ISF::EnumObjects failed"));

        fSuccess = FALSE;
        goto Error2;
    }

    // after this point, we're gonna succeed enough that we can turn off the invalid flag.
    lpnParent->fInvalid = 0;

    if (NodeHasKids(lpnParent)) {
        int i;
        for (i = GetKidCount(lpnParent) - 1; i >= 0; i--) {
            lpnKid = GetNthKid(lpnParent, i);
            if (lpnKid) {
                lpnKid->fMark = 1;
            }
        }
    }

    // Build list of all the items

    // Enumerate over all the sub folders within this folder.
    // BUGBUG: Call GetMaxIDSize!
    //
    if (lpnParent->hdpaKids == KIDSUNKNOWN)
        lpnParent->hdpaKids = NOKIDS;


    while (penum->lpVtbl->Next(penum, 1, &pidl, &celt)==S_OK && celt==1)
    {
        AddChild(lpsfParent, lpnParent, pidl, FALSE, &lpnKid);
        SHFree(pidl);

#if 0
        if (pfc) {
            switch (OTPeekForAMessage(pfc)) {
            case PEEK_QUIT:
                Assert(0); // this should never happen

            case PEEK_CLOSE:
                // yes, we see a close message.  Bail!
                OTInvalidateNode(lpnParent);
                PostMessage(pfc->hwndMain, WM_CLOSE, 0, 0);
                goto Punt;
            }
        }
#endif

    }

#if 0
Punt:
#endif

    ENTERCRITICAL;
    if (NodeHasKids(lpnParent))
        KillAbandonedKids(lpnParent);
    else
        lpnParent->cChildren = 0;
    LEAVECRITICAL;

    // Tidyup.
    IUnknown_Release(penum);

  Error2:
    IUnknown_Release(lpsfParent);

  Error1:
    return fSuccess;
}

LPOneTreeNode FindNearestNodeFromIDList(LPCITEMIDLIST pidl, UINT uFlags)
{
    LPOneTreeNode lpnd = NULL;
    LPITEMIDLIST pidlCopy = ILClone(pidl);
    if (pidlCopy)
    {
        while (ILRemoveLastID(pidlCopy))
        {
            uFlags &= ~OTGNF_NEARESTMATCH; // so we won't recurse forever
            lpnd = OTGetNodeFromIDList(pidlCopy, uFlags);
            if (lpnd) {
                OTRelease(lpnd);
                break;
            }
        }
        ILFree(pidlCopy);
    }
    return lpnd;
}


void RelayFileSysChange(LONG lEvent, LPITEMIDLIST pidl, LPITEMIDLIST pidlExtra)
{
    int i;
    HWND hwnd;
    LPNMOTFSEINFO lpnm;

    if (!s_hdpaAppHwnds)
        return;

    lpnm = (LPNMOTFSEINFO)Alloc(SIZEOF(NMOTFSEINFO));
    if (!lpnm)
        return;

    lpnm->nmhdr.code = OTN_FSE;
    lpnm->lEvent = lEvent;
    lpnm->pidl = pidl;
    lpnm->pidlExtra = pidlExtra;

    for (i = DPA_GetPtrCount(s_hdpaAppHwnds) - 1; i >= 0; i--) {
        hwnd = (HWND)DPA_GetPtr(s_hdpaAppHwnds, i);

        if (hwnd) {
            if (!IsWindow(hwnd)) {
                //
                // unregistering will shrink s_hdpaAppHwnds,
                // but we're done with this one anyways
                //
                OTUnregister(hwnd);

            } else {

                // notify them.
                SendMessage(hwnd, CWM_ONETREEFSE, 0, (LPARAM)(LPNMOTFSEINFO)lpnm);
            }
        }
    }
    Free(lpnm);
}

void OTInvalidateRoot()
{
    OTSweepFolders(s_lpnRoot);
    OTInvalidateNode(s_lpnRoot);
}

void OTAbandonKid(LPOneTreeNode lpnParent, LPOneTreeNode lpnKid)
{
    LPSHELLFOLDER psfParent;

    if (lpnParent && lpnKid && NodeHasKids(lpnParent)) {
        LPOneTreeNode lpn;
        int i;

        // invalidate the new node's parent to get any children flags right
        OTInvalidateNode(lpnParent);

        // remove the node from it's parent's list
        psfParent = OTBindToFolder(lpnParent);
        if (psfParent)
        {
            lpn = FindKid( psfParent, lpnParent, lpnKid->pidl, FALSE, &i );

            if (lpn) {
#ifdef OT_DEBUG
                DebugMsg(DM_TRACE, TEXT("OneTree: QuickRename found old node to nuke"));
#endif
                ENTERCRITICAL;
                DPA_DeletePtr(lpnParent->hdpaKids, i);
                lpnParent->dwLastChanged = GetTickCount();
                LEAVECRITICAL;
                OTRelease(lpn);

                CheckDestroyHDPAKids(lpnParent);
            }

            IUnknown_Release(psfParent);

        }

    }
}

// if the RENAMEFOLDER event was a rename within the same
// if the RENAMEFOLDER event was a rename within the same
BOOL TryQuickRename(LPITEMIDLIST pidl, LPITEMIDLIST pidlExtra, BOOL *pfRet)
{
    LPITEMIDLIST pidlClone;
    LPOneTreeNode lpNode;
    LPOneTreeNode lpNodeExtra;
    LPOneTreeNode lpNodeNew;
    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 whole things should be in a critical section from the call of
    // DoHandleFileSysChange

    // this one was deleted
    lpNode = _GetNodeFromIDList(pidl, 0, NULL);
    if (lpNode == s_lpnRoot) {
        OTInvalidateRoot();
        fRet = TRUE;
    } else if (lpNode) {

        // this one was created
        pidlClone = ILClone(pidlExtra);
        if (pidlClone) {
            ILRemoveLastID(pidlClone);

            // if the parent isn't created yet, let's not bother
            lpNodeExtra = _GetNodeFromIDList(pidlClone, 0, NULL);
            ILFree(pidlClone);

            if (lpNodeExtra) {

                lpNodeNew = _GetNodeFromIDList(pidlExtra, OTGNF_TRYADD, NULL);
                if (lpNode == lpNodeNew) {
                    // Same node, lets generate a new one to take care
                    // of simple renames that only change case or other
                    // such things, else the titles and the like will
                    // not be updated...
                    OTAddSubFolder(lpNode->lpnParent, ILFindLastID(pidlExtra), TRUE, &lpNodeNew);
                }

                if (lpNodeNew) {
                    int i;

                    Assert(OTGetParent(lpNodeNew) == lpNodeExtra);
                    Assert((lpNodeNew == lpNode) || (lpNodeNew->hdpaKids == KIDSUNKNOWN));

                    // invalidate the new node's parent to get any children flags right
                    OTInvalidateNode(lpNodeExtra);

                    // Everything's all set.  copy info from lpNode to lpNodeNew
                    // and remove lpNode from it's parent

                    // kidnap lpNode's kids
                    lpNodeNew->cChildren = lpNode->cChildren;
                    lpNodeNew->hdpaKids = lpNode->hdpaKids;
                    lpNodeNew->iImage =  lpNode->iImage;
                    lpNodeNew->iSelectedImage =  lpNode->iSelectedImage;
                    if (NodeHasKids(lpNodeNew)) {
                        for (i = GetKidCount(lpNodeNew) - 1; i >= 0 ; i--) {
                            lpNodeExtra = GetNthKid(lpNodeNew, i);
                            if (lpNodeExtra) {
                                lpNodeExtra->lpnParent = lpNodeNew;
                            }
                        }
                    }

                    //
                    // We need to invalidate all the cached IShellFolder's.
                    //
                    OTSweepFolders(lpNodeNew);
                    SFCFreeNode(lpNodeNew);

                    // now clean up the old node
                    lpNode->cChildren = (BYTE)I_CHILDRENCALLBACK;
                    lpNode->hdpaKids = KIDSUNKNOWN;
                    SFCFreeNode(lpNode);
                    Assert(lpNode->lpnParent); // bad news if we're renaming the root
                    if (lpNode->lpnParent && NodeHasKids(lpNode->lpnParent)) {

                        LPSHELLFOLDER psfParent;
                        LPOneTreeNode lpnParent = lpNode->lpnParent;

                        // invalidate the new node's parent to get any children flags right
                        OTInvalidateNode(lpnParent);

                        // remove the node from it's parent's list
                        psfParent = OTBindToFolder(lpnParent);
                        if (psfParent)
                        {
                            lpNodeExtra = FindKid(psfParent, lpnParent, lpNode->pidl, FALSE, &i );
                            if (lpNodeExtra && lpNodeExtra == lpNode) {
#ifdef OT_DEBUG
                                DebugMsg(DM_TRACE, TEXT("OneTree: QuickRename found old node to nuke"));
#endif
                                ENTERCRITICAL;
                                DPA_DeletePtr(lpnParent->hdpaKids, i);
                                lpnParent->dwLastChanged = GetTickCount();
                                LEAVECRITICAL;
                                OTRelease(lpNode);

                                CheckDestroyHDPAKids(lpnParent);
                            }
                            IUnknown_Release(psfParent);
                        }
                    }

                    fRet = *pfRet = TRUE;
                }
            }
        }
    }
    return fRet;
}


// avoids eating stack
BOOL DoHandleFileSysChange(LONG lNotification, LPITEMIDLIST pidl, LPITEMIDLIST pidlExtra)
{
    LPOneTreeNode lpNode = NULL;
    LPITEMIDLIST pidlClone;
    BOOL fRet = FALSE;

    switch(lNotification)
    {
        case SHCNE_UPDATEITEM:
        {
            // We will rename from ourselves to ourselves, which will force the
            // node to be updated completely (rather than the previous method of
            // just updating ourselves based on a possibly stale pidl)

            BOOL fRenRet;
            if (TryQuickRename(pidl, pidl, &fRenRet))
            {
                fRet = fRenRet;
                break;
            }
            fRet = FALSE;
            break;
        }

        case SHCNE_RENAMEFOLDER:
        {
            BOOL fRenRet;

            // first try to just swap the nodes if it's  true rename (not a move)
            if (TryQuickRename(pidl, pidlExtra, &fRenRet))
            {
                fRet = fRenRet;
                break;
            }

            // Rename is special.  We need to invalidate both
            // the pidl and the pidlExtra. so we call ourselves
            fRet = DoHandleFileSysChange(0, pidlExtra, NULL);

            // Handle the rooted case where this pidl may be NULL!
            if (!pidl)
                break;
        }

        case SHCNE_RMDIR:
            if (ILIsEmpty(pidl)) {
                // we've deleted the desktop dir.
                lpNode = s_lpnRoot;
                OTInvalidateRoot();
                break;
            }
        case 0:
        case SHCNE_MKDIR:
        case SHCNE_DRIVEADD:
        case SHCNE_DRIVEREMOVED:
            if (!pidl)
            {
                break;
            }

            pidlClone = ILClone(pidl);
            if (!pidlClone)
            {
                break;
            }
            ILRemoveLastID(pidlClone);
            lpNode = _GetNodeFromIDList(pidlClone, 0, NULL);
            ILFree(pidlClone);
            break;

    case SHCNE_MEDIAINSERTED:
    case SHCNE_MEDIAREMOVED:
            fRet = TRUE;
            lpNode = _GetNodeFromIDList(pidl, 0, NULL);
            if (lpNode)
                lpNode = lpNode->lpnParent;
            break;

        case SHCNE_DRIVEADDGUI:
            fRet = TRUE;
        case SHCNE_NETSHARE:
        case SHCNE_NETUNSHARE:
        case SHCNE_UPDATEDIR:
            lpNode = _GetNodeFromIDList(pidl, 0, NULL);
            break;

        case SHCNE_SERVERDISCONNECT:
            // nuke all our kids and mark ourselves invalid
            lpNode = _GetNodeFromIDList(pidl, 0, NULL);
            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;
                fRet = TRUE;
            }

    case SHCNE_ASSOCCHANGED:
        fRet = TRUE;
        break;

    case SHCNE_UPDATEIMAGE:
        if (pidl) {
            InvalidateImageIndices();
            DoInvalidateAll(s_lpnRoot, *(int UNALIGNED *)((BYTE*)pidl + 2));
            DebugMsg(DM_TRACE, TEXT(" SHCNE_UPDATEIMAGE :  %d "), *(int UNALIGNED *)((BYTE*)pidl + 2));
            fRet = TRUE;
        }
        break;
    }

    if (lpNode)
    {
        OTInvalidateNode(lpNode);
        fRet = TRUE;
    }

    return fRet;
}


void HandleFileSysChange(LONG lNotification, LPITEMIDLIST*lplpidl)
{
    BOOL fRelay;
    LPITEMIDLIST pidl1 = lplpidl[0];
    LPITEMIDLIST pidl2 = lplpidl[1];

    if (!s_bDesktopRoot)
    {
        // Note that we still call HandleFSChange even if these fail in case
        // we got a rename from our root to somewhere outside our root
        OTTranslateIDList(pidl1, &pidl1);

        if (pidl2)
        {
            OTTranslateIDList(pidl2, &pidl2);
        }
    }

    ENTERCRITICAL;
    fRelay = DoHandleFileSysChange(lNotification, pidl1, pidl2);
    LEAVECRITICAL;

    if (fRelay)
        RelayFileSysChange(lNotification, pidl1, pidl2);

    if (!s_bDesktopRoot)
    {
        if (pidl2)
        {
            ILFree(pidl2);
        }

        if (pidl1)
        {
            ILFree(pidl1);
        }
    }
}

void _OTGetImageIndex(LPSHELLFOLDER psfParent, LPOneTreeNode lpNode)
{
    LPITEMIDLIST pidl;

    OTValidate(lpNode);
    Assert(psfParent);

    pidl = OTCloneFolderID(lpNode);
    if (pidl)
    {
        int iSelectedImage;
        lpNode->iImage = SHMapPIDLToSystemImageListIndex(psfParent,
            pidl, &iSelectedImage);

        lpNode->iSelectedImage = iSelectedImage;
        ILFree(pidl);
    }
}


BOOL OTSubNodeCount(HWND hwndOwner, LPOneTreeNode lpNode, PFileCabinet pfc, UINT* pcnd, BOOL fInteractive)
{
    BOOL fSuccess = TRUE;
    OTValidate(lpNode);
    *pcnd=0;

    if (lpNode)
    {
        if (lpNode->hdpaKids == KIDSUNKNOWN || lpNode->fInvalid)
        {
            fSuccess=SearchForKids(hwndOwner, lpNode, pfc, fInteractive);
        }

        if (NodeHasKids(lpNode))
        {
            *pcnd = GetKidCount(lpNode);
        }
    }
    else
    {
        fSuccess = FALSE;
    }

    return fSuccess;
}

LPOneTreeNode OTGetNthSubNode(HWND hwndOwner, LPOneTreeNode lpnd, UINT i)
{
    UINT cnd;
    OTValidate(lpnd);

    // OTSubNodeCount will validate
    OTSubNodeCount(hwndOwner, lpnd, NULL, &cnd, FALSE);

    if (i >= cnd) {
        return NULL;
    } else {
        LPOneTreeNode lpNode;
        ENTERCRITICAL;
        lpNode = GetNthKid(lpnd, i);
        if (lpNode) OTAddRef(lpNode);
        LEAVECRITICAL;
        return lpNode;
    }
}

BOOL OTIsCompressed(LPOneTreeNode lpNode)
{
    if (lpNode->fInvalid)
        ForceNode(lpNode);

    return lpNode->fCompressed;
}

void OTNodeFillTV_ITEM(LPOneTreeNode lpNode, LPTV_ITEM lpItem)
{
    Assert(lpNode);
    OTValidate(lpNode);

    if (lpNode->fInvalid ||
        lpNode->iImage == (USHORT)I_IMAGECALLBACK ||
        lpNode->iSelectedImage == (USHORT)I_IMAGECALLBACK)
        ForceNode(lpNode);


    if (lpItem->mask & TVIF_TEXT) {
        OTGetNodeName(lpNode, lpItem->pszText, lpItem->cchTextMax);
    }

    if (lpItem->mask & TVIF_CHILDREN) {
        if (lpNode->fInvalid || lpNode->hdpaKids == KIDSUNKNOWN)
            lpItem->cChildren = lpNode->cChildren;
        else if (lpNode->hdpaKids == NOKIDS)
            lpItem->cChildren = 0;
        else
            lpItem->cChildren = GetKidCount(lpNode);
    }

    if (lpItem->mask & TVIF_IMAGE) {
        lpItem->iImage = lpNode->iImage;
    }

    if (lpItem->mask & TVIF_SELECTEDIMAGE) {
        lpItem->iSelectedImage = lpNode->iSelectedImage;
    }
}

BOOL OTHasSubFolders(LPOneTreeNode lpNode)
{
    Assert(lpNode);
    OTValidate(lpNode);

    if (lpNode->fInvalid) {
        ForceNode(lpNode);
    }
    return lpNode->cChildren;
}

void OTRegister(HWND hwnd)
{
    if (!s_hdpaAppHwnds)
        s_hdpaAppHwnds = DPA_Create(0);

    // BUGBUG (DavePl) Check return value of DPA_Create()

    DPA_InsertPtr(s_hdpaAppHwnds, 0, (void *)hwnd);
}

void OTUnregister(HWND hwnd)
{
    int i;
    int iCount;

    if (!s_hdpaAppHwnds)
        return;

    for (iCount = i = DPA_GetPtrCount(s_hdpaAppHwnds) - 1; i >= 0; i--) {
        if (hwnd == (HWND)DPA_GetPtr(s_hdpaAppHwnds, i))
        {
            DPA_DeletePtr(s_hdpaAppHwnds, i);
            return;
        }
    }
}

// this validates that the subpidl is a folder pidl and returns the full (non-simple)
// pidl for it
LPITEMIDLIST OTGetRealFolderIDL(LPOneTreeNode lpnParent, LPCITEMIDLIST pidlSimple)
{
    IShellFolder *lpsfParent;
    HRESULT hres;
    LPITEMIDLIST pidlReal = NULL;

    Assert(_ILNext(pidlSimple)->mkid.cb==0);
    Assert(lpnParent);
    OTValidate(lpnParent);

    hres = OTBindToFolderEx(lpnParent, &lpsfParent);

    // get the pidlReal because we might have a simple pidl
    if (lpsfParent)
    {
        hres = SHGetRealIDL(lpsfParent, pidlSimple, &pidlReal);

        if (SUCCEEDED(hres) && pidlReal)
        {
            DWORD dwAttribs = SFGAO_FOLDER | SFGAO_BROWSABLE;

            hres=lpsfParent->lpVtbl->GetAttributesOf(lpsfParent, 1, &pidlReal, &dwAttribs);
            if (FAILED(hres) || !(dwAttribs & (SFGAO_FOLDER | SFGAO_BROWSABLE)))
            {
                ILFree(pidlReal);
                pidlReal = NULL;
            }
        }

        IUnknown_Release(lpsfParent);
    } else {
        DebugMsg(DM_ERROR, TEXT("Unable to bind to folder %s in OTAddSubFolder"), TEXT("?"));
    }

    return pidlReal;
}

HRESULT OTAddSubFolder(LPOneTreeNode lpNode, LPCITEMIDLIST pidl,
        BOOL fAllowDup, LPOneTreeNode *ppndOut)
{
    LPSHELLFOLDER lpsfParent;
    LPOneTreeNode lpNewNode = NULL;
    HRESULT hres;

    hres = OTBindToFolderEx(lpNode, &lpsfParent);
    if (SUCCEEDED(hres)) {
        LPITEMIDLIST pidlReal = OTGetRealFolderIDL(lpNode, pidl);

        hres = E_FAIL;
        if (pidlReal) {
            if (AddChild(lpsfParent, lpNode, pidlReal, fAllowDup, &lpNewNode) != ADDCHILD_FAILED) {
                hres = NOERROR;
            }
        }
        IUnknown_Release(lpsfParent);
    }

    if (ppndOut) {
        *ppndOut = lpNewNode;
        if (lpNewNode)
            OTAddRef(lpNewNode);
    }

    return hres;
}


void OTGetImageIndex(LPOneTreeNode lpNode, int *lpiImage, int *lpiSelectedImage)
{
    Assert(lpNode);
    OTValidate(lpNode);

    if (lpNode->fInvalid ||
        lpNode->iImage == (USHORT)I_IMAGECALLBACK ||
        lpNode->iSelectedImage == (USHORT)I_IMAGECALLBACK)
    {
        // this gets the sharing info aswell
        ForceNode(lpNode);
    }

    if (lpiImage)
        *lpiImage = lpNode->iImage;

    if (lpiSelectedImage)
        *lpiSelectedImage = lpNode->iSelectedImage;
}

LPOneTreeNode OTGetNodeFromIDListEx(LPCITEMIDLIST pidl, UINT uFlags, HRESULT* phresOut)
{
    LPOneTreeNode lpNode = _GetNodeFromIDList(pidl, uFlags, phresOut);
    if (!lpNode && (uFlags & OTGNF_NEARESTMATCH))
    {
        lpNode = FindNearestNodeFromIDList(pidl, uFlags);
    }

    if (lpNode)
        OTAddRef(lpNode);
    return lpNode;
}

//
// Replacement of OTGetPathFromNode
//
LPITEMIDLIST OTCreateIDListFromNode(LPOneTreeNode lpn)
{
    USHORT uID[1] = { 0 };
    LPITEMIDLIST pidl = ILClone((LPITEMIDLIST)&uID[0]);

    Assert(lpn);

    // Walk backwards to construct the path.
    for (; pidl && (lpn != s_lpnRoot); lpn = lpn->lpnParent)
    {
        LPCITEMIDLIST pidlNext;

        ENTERCRITICAL;
        pidlNext = lpn->pidl;

        if (pidlNext == NULL) {

            LEAVECRITICAL;
            break;
        }

        pidl = ILAppendID(pidl, &pidlNext->mkid, FALSE);
        LEAVECRITICAL;
    }

    return pidl;
}


//
// Replacement of GetNodeFromPath
//
LPOneTreeNode _GetNodeFromIDList(LPCITEMIDLIST pidlFull, UINT uFlags, HRESULT *phresOut)
{
    LPCITEMIDLIST pidl;
    LPOneTreeNode lpNode = s_lpnRoot;
    HRESULT hres = S_OK;

    if (!pidlFull)
    {
        return(NULL);
    }

    //
    // REVIEW: We can eliminate ILCloneFirst by copying each mkid to pidlT.
    //
    for (pidl = pidlFull; !ILIsEmpty(pidl) && lpNode; pidl = _ILNext(pidl))
    {
        LPOneTreeNode lpnParent = lpNode;
        LPITEMIDLIST pidlFirst = ILCloneFirst(pidl);

        OTValidate(lpnParent);
        lpNode = NULL;  // assume error
        if (pidlFirst)
        {
            LPSHELLFOLDER psfParent = OTBindToFolder(lpnParent);
            if (psfParent)
            {
                INT i;

                lpNode = FindKid(psfParent, lpnParent, pidlFirst, uFlags & OTGNF_VALIDATE, &i);

                if (!lpNode && (uFlags & OTGNF_TRYADD))
                {
                    DebugMsg(DM_TRACE, TEXT("Onetree: Unable to Find %s"), (LPTSTR)pidl->mkid.abID);
                    // doing no notify for right now because
                    // several notify handlers call this.
                    // just an efficiency thing
                    hres = OTAddSubFolder(lpnParent, pidlFirst, FALSE, &lpNode);
                    OTRelease(lpNode);
                }
                IUnknown_Release(psfParent);
            }
            ILFree(pidlFirst);
        }
    }

    if (phresOut) {
        DebugMsg(DM_TRACE, TEXT("ca TR - _GetNodeFromIDList returning %x as *phresOut"), hres);
        *phresOut = hres;
    }

    return lpNode;
}

BOOL OTGetDisplayName(LPCITEMIDLIST pidl, LPTSTR pszName)
{
    if (pidl)
    {
        if (s_bDesktopRoot)
        {
            return ILGetDisplayName(pidl, pszName);
        }
        else
        {
            STRRET srName;

            // Not everybody can handle an empty IDList to talk about
            // themselves
            if (ILIsEmpty(pidl))
            {
                return ILGetDisplayName(s_pidlRoot, pszName);
            }

            if (SUCCEEDED(s_pshfRoot->lpVtbl->GetDisplayNameOf(s_pshfRoot, pidl,
                SHGDN_FORPARSING, &srName)))
            {
                StrRetToStrN(pszName, MAX_PATH, &srName, pidl);
                return(TRUE);
            }
        }
    }

    *pszName = 0;
    return FALSE;
}


int CALLBACK OTTreeViewCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
        LPOneTreeNode lpn1 = (LPOneTreeNode)lParam1;
        LPOneTreeNode lpn2 = (LPOneTreeNode)lParam2;
        IShellFolder *psfParent = (IShellFolder *)lParamSort;
        HRESULT hres;

        OTValidate(lpn1);
        OTValidate(lpn2);

    ENTERCRITICAL;
        hres = psfParent->lpVtbl->CompareIDs(psfParent,
                0, OTGetFolderID(lpn1), OTGetFolderID(lpn2));
    LEAVECRITICAL;
        if (FAILED(hres))
        {
                return(0);
        }

        return (short)SCODE_CODE(GetScode(hres));
}


LPITEMIDLIST OTCloneAbsIDList(LPCITEMIDLIST pidlRelToRoot)
{
    if (s_bDesktopRoot)
        return ILClone(pidlRelToRoot);

    return ILCombine(s_pidlRoot, pidlRelToRoot);
}