/*++

Copyright (c) 1995  Microsoft Corporation

Module Name:

    nwshmenu.cxx

Abstract:

    This module implements the IContextMenu member functions necessary to support
    the context menu of NetWare shell extension.

Author:

    Yi-Hsin Sung (yihsins)  25-Oct-1995

--*/


extern "C"
{
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <shellapi.h>
#include <shlobj.h>
#define  DONT_WANT_SHELLDEBUG
#include <shsemip.h>
#include <winnetwk.h>
#include <ntddnwfs.h>
#include "nwshrc.h"
#include "nwwks.h"
#include "nwutil.h"
}

#include "nwshcmn.h"
#include "nwshext.h"

#define MAX_VERB_SIZE          128
#define MAX_SHELL_IDLIST_SIZE  512

BOOL g_cfNetResource = 0;  // Clipboard format
BOOL g_cfIDList = 0;

NWMENUITEM aServerVerbs[]    = { { IDO_VERB_WHOAMI, 0 },
                                 { IDO_VERB_LOGOUT, 0 },
                                 { IDO_VERB_ATTACHAS, 0 },
                                 { 0, 0 } };

NWMENUITEM aDSVerbs[]        = { { IDO_VERB_TREEWHOAMI, 0 },
                                 // { IDO_VERB_SETDEFAULTCONTEXT, 0 },
                                 { 0, 0 } };

NWMENUITEM aDSTreeVerbs[]    = { { IDO_VERB_TREEWHOAMI, 0 },
                                 { 0, 0 } };

NWMENUITEM aGlobalVerbs[]    = { { IDO_VERB_GLOBALWHOAMI, 0 },
                                 { 0, 0 } };

NWMENUITEM aDirectoryVerbs[] = { { IDO_VERB_MAPNETWORKDRIVE, 0 },
                                 { 0, 0 } };


NWMENUITEM aHoodVerbs[]      = { { IDO_VERB_GLOBALWHOAMI, 0 },
                                 { 0, 0 } };


HRESULT
InsertCommandsArray( HMENU hMenu,
                     UINT indexMenu,
                     UINT idCmdFirst,
                     LPNWMENUITEM aVerbs );

UINT
LookupCommand( LPNWMENUITEM aVerbs,
               LPCSTR pszCmd );

UINT
LookupResource( LPNWMENUITEM aVerbs,
                UINT uiResourceOffset );

UINT WINAPI
HIDA_GetIDList( LPIDA hida,
                UINT i,
                LPITEMIDLIST pidlOut,
                UINT cbMax);

//
//  FUNCTION: CNWObjContextMenu::QueryContextMenu(HMENU, UINT, UINT, UINT, UINT)
//
//  PURPOSE: Called by the shell just before the context menu is displayed.
//           This is where you add your specific menu items.
//
//  PARAMETERS:
//    hMenu      - Handle to the context menu
//    indexMenu  - Index of where to begin inserting menu items
//    idCmdFirst - Lowest value for new menu ID's
//    idCmtLast  - Highest value for new menu ID's
//    uFlags     - Specifies the context of the menu event
//
//  RETURN VALUE:
//
//
//  COMMENTS:
//

STDMETHODIMP CNWObjContextMenu::QueryContextMenu( HMENU hMenu,
                                                  UINT indexMenu,
                                                  UINT idCmdFirst,
                                                  UINT idCmdLast,
                                                  UINT uFlags )
{
    HRESULT hres;
    LPNETRESOURCE pNetRes = (LPNETRESOURCE) _buffer;

    if ( !::GetNetResourceFromShell( _pDataObj,
                                     pNetRes,
                                     sizeof( _buffer )))
    {
        // We cannot get the net resource of the selected object.

        // Must return number of menu items we added.
        // Nothing added here.
        return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, FACILITY_NULL, 0 ));
    }

    // First, add a menu separator
    if ( InsertMenu( hMenu, indexMenu, MF_SEPARATOR | MF_BYPOSITION, 0, NULL))
        indexMenu++;

    // Next, add menu items depending on display types
    switch ( pNetRes->dwDisplayType )
    {
        case RESOURCEDISPLAYTYPE_ROOT:
        case RESOURCEDISPLAYTYPE_NETWORK:
            hres = InsertCommandsArray( hMenu, indexMenu,
                                        idCmdFirst, _pIdTable = aGlobalVerbs );

            break;

        case RESOURCEDISPLAYTYPE_TREE:
            hres = InsertCommandsArray( hMenu, indexMenu,
                                        idCmdFirst, _pIdTable = aDSTreeVerbs );
            break;

        case RESOURCEDISPLAYTYPE_NDSCONTAINER:
            hres = InsertCommandsArray( hMenu, indexMenu,
                                        idCmdFirst, _pIdTable = aDSVerbs );
            break;

        case RESOURCEDISPLAYTYPE_SERVER:
        {
            // BUGBUG: Do we need to check if the server name is local
            // and disallow operation???

            hres = InsertCommandsArray( hMenu, indexMenu,
                                        idCmdFirst, _pIdTable = aServerVerbs );

            if (!SUCCEEDED(hres))
                break;

            LPBYTE pBuffer = NULL;
            DWORD  EntriesRead = 0;
            DWORD  ResumeKey = 0;
            WCHAR  szServerName[MAX_PATH + 1];

            NwExtractServerName( pNetRes->lpRemoteName, szServerName );

            // See if we are connected.
            DWORD err = NwGetConnectionStatus( szServerName,
                                               &ResumeKey,
                                               &pBuffer,
                                               &EntriesRead );

            if ( err == NO_ERROR && EntriesRead > 0 )
            {
                PCONN_STATUS pConnStatus = (PCONN_STATUS) pBuffer;

                ASSERT( EntriesRead == 1 );   

                if ( pConnStatus->fPreferred )
                {
                    // This is a implicit preferred server connection
                    // so, don't show the connection and don't let the user
                    // logout of it since rdr doesn't allow it. 
                    ::EnableMenuItem( hMenu,
                                      LookupResource( aServerVerbs,
                                                      IDO_VERB_LOGOUT),
                                      MF_GRAYED | MF_BYCOMMAND);

                }
                else if ( pConnStatus->fNds )
                {
                    BOOL fInDefaultTree = FALSE;

                    err = NwIsServerInDefaultTree( pNetRes->lpRemoteName, &fInDefaultTree );

                    if ( (err == NO_ERROR) && fInDefaultTree )
                    {
                        // NDS connection and in the default tree, disable the Attach As button
                        ::EnableMenuItem( hMenu,
                                          LookupResource( aServerVerbs,
                                                          IDO_VERB_ATTACHAS),
                                          MF_GRAYED | MF_BYCOMMAND );
                    }
                }
            }
            else
            {
                // If we are not attached or if error occurred when getting
                // connection status, then disable the Logout button.
                ::EnableMenuItem( hMenu,
                                  LookupResource( aServerVerbs,
                                                  IDO_VERB_LOGOUT),
                                  MF_GRAYED | MF_BYCOMMAND);
            }

            if ( pBuffer != NULL )
            {
                LocalFree( pBuffer );
                pBuffer = NULL;
            }
            break;
        }

        default:
            // Must return number of menu items we added.
            // Nothing added here.
            hres = ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, FACILITY_NULL, 0 ));
            break;

    }

    return hres;
}

//
//  FUNCTION: CNWObjContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO)
//
//  PURPOSE: Called by the shell after the user has selected on of the
//           menu items that was added in QueryContextMenu().
//
//  PARAMETERS:
//    lpcmi - Pointer to an CMINVOKECOMMANDINFO structure
//
//  RETURN VALUE:
//
//
//  COMMENTS:
//

STDMETHODIMP CNWObjContextMenu::InvokeCommand( LPCMINVOKECOMMANDINFO lpcmi )
{
    HRESULT hres = ResultFromScode(E_INVALIDARG);
    UINT idCmd = LookupCommand( _pIdTable , lpcmi->lpVerb );

    if ( !idCmd )
        return hres;

    LPNETRESOURCE pNetRes = (LPNETRESOURCE) _buffer;

    switch ( idCmd )
    {
        case IDO_VERB_GLOBALWHOAMI:
            hres = NWUIGlobalWhoAmI( lpcmi->hwnd );
            break;

        case IDO_VERB_TREEWHOAMI:
            hres = NWUIWhoAmI( lpcmi->hwnd, pNetRes );
            break;

#if 0
        case IDO_VERB_SETDEFAULTCONTEXT:
            hres = NWUISetDefaultContext( lpcmi->hwnd, pNetRes );
            break;
#endif

        case IDO_VERB_WHOAMI:
            hres = NWUIWhoAmI( lpcmi->hwnd, pNetRes );
            break;

        case IDO_VERB_LOGOUT:
        {
            BOOL fDisconnected = FALSE;
            hres = NWUILogOut( lpcmi->hwnd, pNetRes, &fDisconnected );
            if ( hres == NOERROR && fDisconnected )
            {
                // Logout is successful, need to notify shell

                FORMATETC fmte = { g_cfIDList ? g_cfIDList
                                   : (g_cfIDList=RegisterClipboardFormat( CFSTR_SHELLIDLIST)),
                                   (DVTARGETDEVICE FAR *)NULL,
                                   DVASPECT_CONTENT,
                                   -1,
                                   TYMED_HGLOBAL };
                STGMEDIUM medium;

                hres = _pDataObj->GetData( &fmte, &medium);

                if (SUCCEEDED(hres))
                {
                    // We got pointer to IDList
                    LPIDA pida = (LPIDA)GlobalLock(medium.hGlobal);

                    if ( pida )
                    {
                        BYTE  BufIDList[MAX_SHELL_IDLIST_SIZE];
                        LPITEMIDLIST pidl = (LPITEMIDLIST) BufIDList;

                        if ( pidl )
                        {
                            // Convert IDA to IDList for this call
                            HIDA_GetIDList( pida,
                                            0,              // One object should present
                                            pidl ,
                                            MAX_SHELL_IDLIST_SIZE);

                            // Call SHchangeNotify
                            g_pFuncSHChangeNotify( SHCNE_SERVERDISCONNECT,
                                                   SHCNF_IDLIST,
                                                   pidl,
                                                   NULL);
                        }

                        GlobalUnlock(medium.hGlobal);
                    }
                }

            }
            break;
        }

        case IDO_VERB_ATTACHAS:
            hres = NWUIAttachAs( lpcmi->hwnd, pNetRes );
            break;
    }

    return hres;
}


//
//  FUNCTION: CNWObjContextMenu::GetCommandString( UINT, UINT, UINT FAR *, LPSTR, UINT )
//
//  PURPOSE: Called by the shell after the user has selected on of the
//           menu items that was added in QueryContextMenu().
//
//  PARAMETERS:
//
//  RETURN VALUE:
//
//
//  COMMENTS:
//

STDMETHODIMP CNWObjContextMenu::GetCommandString( UINT idCmd,
                                                  UINT uFlags,
                                                  UINT FAR *reserved,
                                                  LPSTR pszName,
                                                  UINT cchMax )
{
    if ( uFlags == GCS_HELPTEXT && _pIdTable != NULL )
    {
        ::LoadString( ::hmodNW,
                      IDS_VERBS_HELP_BASE + _pIdTable[idCmd].idResourceString,
                      (LPWSTR) pszName,
                      cchMax );

        return NOERROR;
    }

    return E_NOTIMPL;
}

//
//  FUNCTION: CNWFldContextMenu::QueryContextMenu(HMENU, UINT, UINT, UINT, UINT)
//
//  PURPOSE: Called by the shell just before the context menu is displayed.
//           This is where you add your specific menu items.
//
//  PARAMETERS:
//    hMenu      - Handle to the context menu
//    indexMenu  - Index of where to begin inserting menu items
//    idCmdFirst - Lowest value for new menu ID's
//    idCmtLast  - Highest value for new menu ID's
//    uFlags     - Specifies the context of the menu event
//
//  RETURN VALUE:
//
//
//  COMMENTS:
//

STDMETHODIMP CNWFldContextMenu::QueryContextMenu( HMENU hMenu,
                                                  UINT indexMenu,
                                                  UINT idCmdFirst,
                                                  UINT idCmdLast,
                                                  UINT uFlags )
{
    UINT idCmd = idCmdFirst;

    if ( IsNetWareObject() )
    {
        WCHAR szFullPath[MAX_PATH+1];

        if ( GetFSObject( szFullPath, sizeof( szFullPath )) == NOERROR )
        {
            BOOL fUNC = FALSE;

            // Check if the name at least contains the "\\server\share\dir"
            // We need to add "Map Network Drive" menu in this case.
            if (( szFullPath[0] == L'\\') && ( szFullPath[1] == L'\\'))
            {
                LPWSTR pszLastSlash = wcschr( szFullPath + 2, L'\\');
                if ( pszLastSlash )
                    pszLastSlash = wcschr( pszLastSlash+1, L'\\');

                if ( pszLastSlash != NULL )
                    fUNC = TRUE;
            }

            if ( fUNC )
            {
                LPNETRESOURCE pNetRes = (LPNETRESOURCE) _buffer;
                WCHAR szProvider[MAX_PATH+1];

                // Build a net resource that can be used to connect

                // store the provider name first
                wcscpy( szProvider, pNetRes->lpProvider );

                // zero out the memory cause it is filled by IsNetWareObject
                RtlZeroMemory( pNetRes, sizeof(NETRESOURCE));

                pNetRes->dwType = RESOURCETYPE_DISK;
                pNetRes->lpRemoteName = (LPWSTR) ((DWORD)pNetRes + sizeof(NETRESOURCE));
                wcscpy( pNetRes->lpRemoteName, szFullPath );

                pNetRes->lpProvider = (LPWSTR) ((DWORD)pNetRes->lpRemoteName + (wcslen(szFullPath)+1)*sizeof(WCHAR));
                wcscpy( pNetRes->lpProvider, szProvider );

                if ( InsertMenu(hMenu, indexMenu, MF_SEPARATOR | MF_BYPOSITION, 0, NULL))
                {
                    indexMenu++;
                }

                return InsertCommandsArray( hMenu, indexMenu,
                                            idCmdFirst, aDirectoryVerbs );
            }
        }
    }

    // Must return number of menu items we added.
    // Nothing added here.
    return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, FACILITY_NULL, 0 ));

}

//
//  FUNCTION: CNWFldContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO)
//
//  PURPOSE: Called by the shell after the user has selected on of the
//           menu items that was added in QueryContextMenu().
//
//  PARAMETERS:
//    lpcmi - Pointer to an CMINVOKECOMMANDINFO structure
//
//  RETURN VALUE:
//
//
//  COMMENTS:
//

STDMETHODIMP CNWFldContextMenu::InvokeCommand( LPCMINVOKECOMMANDINFO lpcmi )
{
    HRESULT hres = ResultFromScode(E_INVALIDARG);
    UINT idCmd = LookupCommand( aDirectoryVerbs , lpcmi->lpVerb );

    if ( !idCmd )
        return hres;

    LPNETRESOURCE pNetRes = (LPNETRESOURCE) _buffer;

    switch ( idCmd )
    {
        case IDO_VERB_MAPNETWORKDRIVE:
            hres = NWUIMapNetworkDrive( lpcmi->hwnd, pNetRes );
            break;
    }

    return hres;
}


//
//  FUNCTION: CNWFldContextMenu::GetCommandString( UINT, UINT, UINT FAR *, LPSTR, UINT )
//
//  PURPOSE: Called by the shell after the user has selected on of the
//           menu items that was added in QueryContextMenu().
//
//  PARAMETERS:
//
//  RETURN VALUE:
//
//
//  COMMENTS:
//

STDMETHODIMP CNWFldContextMenu::GetCommandString( UINT idCmd,
                                                  UINT uFlags,
                                                  UINT FAR *reserved,
                                                  LPSTR pszName,
                                                  UINT cchMax )
{
    if ( uFlags == GCS_HELPTEXT )
    {
        ::LoadString( ::hmodNW,
                      IDS_VERBS_HELP_BASE + IDO_VERB_MAPNETWORKDRIVE,
                      (LPWSTR) pszName,
                      cchMax );

        return NOERROR;
    }

    return E_NOTIMPL;
}

//
// Method checks if the selected object belongs the netware provider
//
BOOL CNWFldContextMenu::IsNetWareObject( VOID )
{
    LPNETRESOURCE pNetRes = (LPNETRESOURCE) _buffer;

    if ( !::GetNetResourceFromShell( _pDataObj,
                                     pNetRes,
                                     sizeof(_buffer)))
    {
        // Cannot get the NETRESOURCE of the selected object,
        // hence assume that the object is not a NetWare object.
        return FALSE;
    }

    if (  ( pNetRes->lpProvider != NULL )
       && ( _wcsicmp( pNetRes->lpProvider, g_szProviderName ) == 0 )
       )
    {
        return TRUE;
    }

    return FALSE;
}

//
// Method obtains file system name associated with selected shell object
//
HRESULT CNWFldContextMenu::GetFSObject( LPWSTR pszPath, UINT cbMaxPath )
{
    FORMATETC fmte = { CF_HDROP,
                       (DVTARGETDEVICE FAR *) NULL,
                       DVASPECT_CONTENT,
                       -1,
                       TYMED_HGLOBAL };

    STGMEDIUM medium;
    HRESULT hres = _pDataObj->GetData( &fmte, &medium);

    if (SUCCEEDED(hres))
    {
        if ( g_pFuncSHDragQueryFile )
        {
            HDROP hdrop = (HDROP) medium.hGlobal;
            UINT cFiles = (*g_pFuncSHDragQueryFile)( hdrop, (UINT)-1, NULL, 0 );

            (*g_pFuncSHDragQueryFile)( hdrop, 0, pszPath, cbMaxPath );

            ODS(L"CNWFldContextMenu::GetFSObject()\n");
            ODS( pszPath );
            ODS(L"\n");
        }

        //
        // HACK: We are supposed to call ReleaseStgMedium. This is a temporary
        //  hack until OLE 2.01 for Chicago is released.
        //
        if (medium.pUnkForRelease)
        {
            medium.pUnkForRelease->Release();
        }
        else
        {
            GlobalFree(medium.hGlobal);
        }
    }

    return hres;
}


//  FUNCTION: CNWHoodContextMenu::QueryContextMenu(HMENU, UINT, UINT, UINT, UINT)
//
//  PURPOSE: Called by the shell just before the context menu is displayed.
//           This is where you add your specific menu items.
//
//  PARAMETERS:
//    hMenu      - Handle to the context menu
//    indexMenu  - Index of where to begin inserting menu items
//    idCmdFirst - Lowest value for new menu ID's
//    idCmtLast  - Highest value for new menu ID's
//    uFlags     - Specifies the context of the menu event
//
//  RETURN VALUE:
//
//
//  COMMENTS:
//

STDMETHODIMP CNWHoodContextMenu::QueryContextMenu( HMENU hMenu,
                                                   UINT indexMenu,
                                                   UINT idCmdFirst,
                                                   UINT idCmdLast,
                                                   UINT uFlags )
{
    // First, insert a menu separator
    if ( InsertMenu(hMenu, indexMenu, MF_SEPARATOR | MF_BYPOSITION, 0, NULL))
    {
        indexMenu++;
    }

    // Then, insert the verbs
    return InsertCommandsArray( hMenu, indexMenu,
                                idCmdFirst, aHoodVerbs );
}

//
//  FUNCTION: CNWHoodContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO)
//
//  PURPOSE: Called by the shell after the user has selected on of the
//           menu items that was added in QueryContextMenu().
//
//  PARAMETERS:
//    lpcmi - Pointer to an CMINVOKECOMMANDINFO structure
//
//  RETURN VALUE:
//
//
//  COMMENTS:
//

STDMETHODIMP CNWHoodContextMenu::InvokeCommand( LPCMINVOKECOMMANDINFO lpcmi )
{
    HRESULT hres = ResultFromScode(E_INVALIDARG);
    UINT idCmd = LookupCommand( aHoodVerbs , lpcmi->lpVerb );

    if ( !idCmd )
        return hres;

    switch ( idCmd )
    {
        case IDO_VERB_GLOBALWHOAMI:
            hres = NWUIGlobalWhoAmI( lpcmi->hwnd );
            break;
    }

    return hres;
}


//
//  FUNCTION: CNWHoodContextMenu::GetCommandString( UINT, UINT, UINT FAR *, LPSTR, UINT)
//
//  PURPOSE: Called by the shell after the user has selected on of the
//           menu items that was added in QueryContextMenu().
//
//  PARAMETERS:
//
//  RETURN VALUE:
//
//
//  COMMENTS:
//

STDMETHODIMP CNWHoodContextMenu::GetCommandString( UINT idCmd,
                                                   UINT uFlags,
                                                   UINT FAR *reserved,
                                                   LPSTR pszName,
                                                   UINT cchMax )
{
    if ( uFlags == GCS_HELPTEXT )
    {
        ::LoadString( ::hmodNW,
                      IDS_VERBS_HELP_BASE + IDO_VERB_GLOBALWHOAMI,
                      (LPWSTR) pszName,
                      cchMax );

        return NOERROR;
    }

    return E_NOTIMPL;
}

//
// Method gets the NETRESOURCE of the selected object
//
BOOL GetNetResourceFromShell( LPDATAOBJECT  pDataObj,
                              LPNETRESOURCE pNetRes,
                              UINT          dwBufferSize )
{
    FORMATETC fmte = { g_cfNetResource ? g_cfNetResource
                       : (g_cfNetResource=RegisterClipboardFormat(CFSTR_NETRESOURCES)),
                       (DVTARGETDEVICE FAR *) NULL,
                       DVASPECT_CONTENT,
                       -1,
                       TYMED_HGLOBAL };

    STGMEDIUM medium;
    UINT      cItems;

    if ( pNetRes == NULL )
        return FALSE;

    memset( pNetRes, 0, dwBufferSize );

    if ( !g_pFuncSHGetNetResource )   // Not loaded
        return FALSE;

    HRESULT hres = pDataObj->GetData( &fmte, &medium );

    if (!SUCCEEDED(hres))
        return FALSE;

    HNRES hnres = medium.hGlobal;

    // Get the number of selected items
    cItems = (*g_pFuncSHGetNetResource)( hnres, (UINT)-1, NULL, 0);

    if ( cItems == 0 )   // Nothing selected
        return FALSE;

    // Get the NETRESOURCE of the first item
    (*g_pFuncSHGetNetResource)( hnres, 0, pNetRes, dwBufferSize);

#if DBG
    WCHAR szTemp[32];
    wsprintf(szTemp, L"DisplayType = %d\n", pNetRes->dwDisplayType );

    ODS(L"\n**** GetNetResourceFromShell ***\n");
    ODS(pNetRes->lpProvider );
    ODS(L"\n");
    ODS(pNetRes->lpRemoteName );
    ODS(L"\n");
    ODS(szTemp );
    ODS(L"\n\n");
#endif

    //
    // HACK: We are supposed to call ReleaseStgMedium. This is a temporary
    //  hack until OLE 2.01 for Chicago is released.
    //
    if (medium.pUnkForRelease)
    {
        medium.pUnkForRelease->Release();
    }
    else
    {
        GlobalFree(medium.hGlobal);
    }

    return TRUE;
}

//-------------------------------------------------------------------//

HRESULT InsertCommandsArray( HMENU hMenu,
                             UINT  indexMenu,
                             UINT  idCmdFirst,
                             LPNWMENUITEM aVerbs )
{
    UINT    idNewCmdFirst = idCmdFirst;
    WCHAR   szVerb[MAX_VERB_SIZE];

    for ( int i = 0; aVerbs[i].idResourceString ; i++)
    {
        if ( ::LoadString( ::hmodNW,
                           aVerbs[i].idResourceString + IDS_VERBS_BASE,
                           szVerb,
                           sizeof(szVerb)/sizeof(szVerb[0])))
        {
            if (::InsertMenu( hMenu,
                              indexMenu,
                              MF_STRING | MF_BYPOSITION,
                              idNewCmdFirst,
                              szVerb))
            {
                // Add command id to the array
                aVerbs[i].idCommand = idNewCmdFirst;

                // Update command id and index
                idNewCmdFirst++;
                if (indexMenu != (UINT)-1)
                    indexMenu++;
            }
        }
    }

    return ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS,
                           FACILITY_NULL,
                           (USHORT)(idNewCmdFirst-idCmdFirst)));
}

UINT LookupCommand( LPNWMENUITEM aVerbs, LPCSTR pszCmd )
{
    if (HIWORD(pszCmd))
    {
        // Possible that shell will use string commands, but unlikely

        WCHAR szVerb[MAX_VERB_SIZE];
        for ( int i=0; aVerbs[i].idResourceString; i++)
        {
            if ( ::LoadString( ::hmodNW,
                               aVerbs[i].idResourceString + IDS_VERBS_BASE,
                               szVerb,
                               sizeof(szVerb)/sizeof(szVerb[0])))
            {
                if( ::lstrcmpi( (LPCWSTR) pszCmd, szVerb) == 0)
                    return( aVerbs[i].idResourceString);
            }
        }

        return 0;
    }
    else
    {
        return( aVerbs[LOWORD(pszCmd)].idResourceString);
    }
}

UINT LookupResource( LPNWMENUITEM aVerbs, UINT uiResourceOffset )
{
    for ( int i = 0; aVerbs[i].idResourceString; i++ )
    {
        if ( aVerbs[i].idResourceString == uiResourceOffset )
            return aVerbs[i].idCommand;
    }

    return 0;
}

//-------------------------------------------------------------------//

#define _ILSkip(pidl, cb)   ((LPITEMIDLIST)(((BYTE*)(pidl))+cb))
#define _ILNext(pidl)       _ILSkip(pidl, (pidl)->mkid.cb)

#define HIDA_GetPIDLFolder(pida)    (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[0])
#define HIDA_GetPIDLItem(pida, i)   (LPCITEMIDLIST)(((LPBYTE)pida)+(pida)->aoffset[i+1])

static
UINT WINAPI MyILGetSize(LPCITEMIDLIST pidl)
{
    UINT cbTotal = 0;
    if (pidl)
    {
        cbTotal += sizeof(pidl->mkid.cb);   // Null terminator
        while (pidl->mkid.cb)
        {
            cbTotal += pidl->mkid.cb;
            pidl = _ILNext(pidl);
        }
    }

    return cbTotal;
}

UINT WINAPI HIDA_GetIDList( LPIDA hida, UINT i, LPITEMIDLIST pidlOut, UINT cbMax)
{
    LPCITEMIDLIST pidlFolder = HIDA_GetPIDLFolder((LPIDA)hida);
    LPCITEMIDLIST pidlItem   = HIDA_GetPIDLItem((LPIDA)hida, i);

    UINT cbFolder  = MyILGetSize(pidlFolder)-sizeof(USHORT);
    UINT cbItem = MyILGetSize(pidlItem);

    if (cbMax < cbFolder+cbItem)
    {
        if (pidlOut)
            pidlOut->mkid.cb = 0;
    }
    else
    {
        memmove(pidlOut, pidlFolder, cbFolder);
        memmove(((LPBYTE)pidlOut)+cbFolder, pidlItem, cbItem);
    }

    return (cbFolder+cbItem);
}