#include "priv.h"
#include "browsext.h"
#include "tbext.h"
#include <winreg.h>     // For the registry walking
#include "dochost.h"
#include "resource.h"
#include <mluisupp.h>
#include <tb_ids.h>


// {DFEED31E-78ED-11d2-86BA-00C04F8EEA99}
EXTERN_C const IID IID_IToolbarExt = 
{ 0xdfeed31e, 0x78ed, 0x11d2, { 0x86, 0xba, 0x0, 0xc0, 0x4f, 0x8e, 0xea, 0x99 } };

// {D82B85D0-78F4-11d2-86BA-00C04F8EEA99}
EXTERN_C const CLSID CLSID_PrivBrowsExtCommands =
{ 0xd82b85d0, 0x78f4, 0x11d2, { 0x86, 0xba, 0x0, 0xc0, 0x4f, 0x8e, 0xea, 0x99 } };

const TCHAR c_szHelpMenu[]  = TEXT("help");

//+-------------------------------------------------------------------------
// Creates and instance of CBrowserExtension
//--------------------------------------------------------------------------
HRESULT CBrowserExtension_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
{
    *ppunk = NULL;
    CBrowserExtension* p = new CBrowserExtension();
    if (p)
    {
        *ppunk = SAFECAST(p, IToolbarExt*);
        return S_OK;
    }

    return E_OUTOFMEMORY;
}

CBrowserExtension::CBrowserExtension()
:   _cRef(1),
    _uStringIndex((UINT)-1),
    _uiImageIndex((UINT)-1)
{
    ASSERT(_pISB == NULL);
    ASSERT(_hdpa == NULL);
    ASSERT(_nExtButtons == 0);
    ASSERT(_fStringInit == FALSE);
    ASSERT(_fImageInit == FALSE);
}

CBrowserExtension::~CBrowserExtension(void)
{
    if (_pISB)
        _pISB->Release();

    if (_hdpa)
    {
        _FreeItems();
        DPA_Destroy(_hdpa);
        _hdpa = NULL;
    }

    _ReleaseImageLists(_uiImageIndex);
}

// *** IUnknown methods ***

HRESULT CBrowserExtension::QueryInterface(REFIID riid, void ** ppvObj)
{
    static const QITAB qit[] = {
        QITABENT(CBrowserExtension, IToolbarExt),
        QITABENT(CBrowserExtension, IObjectWithSite),
        QITABENT(CBrowserExtension, IOleCommandTarget),
        { 0 },
    };

    return QISearch(this, qit, riid, ppvObj);
}

STDMETHODIMP_(ULONG) CBrowserExtension::AddRef()
{
    return InterlockedIncrement(&_cRef);
}

STDMETHODIMP_(ULONG) CBrowserExtension::Release()
{
    if (InterlockedDecrement(&_cRef) == 0)
    {
        delete this;
        return 0;
    }
    return _cRef;
}

// IToolbarExt interface functions
HRESULT CBrowserExtension::SetSite(IUnknown* pUnkSite)
{
    HRESULT hr = S_OK;

    ATOMICRELEASE(_pISB);

    if (pUnkSite)
    {
        hr = pUnkSite->QueryInterface(IID_IShellBrowser, (LPVOID*)&_pISB);
    }

    // See if we need to init ourselves
    if (NULL == _hdpa)
    {
        // Real construction happens here
        HRESULT hr2 = Update();

        ASSERT(SUCCEEDED(hr2));
    }
    else
    {
        // Update the site for each button/menu extension
        for (int i = 0; i < DPA_GetPtrCount(_hdpa); i++)
        {
            ExtensionItem* pItem = (ExtensionItem*)DPA_GetPtr(_hdpa, i);
            IUnknown_SetSite(pItem->pIBE, _pISB);
        }
    }
    return hr;
}

STDMETHODIMP CBrowserExtension::GetSite(REFIID riid, void ** ppvSite)
{
    HRESULT hr = S_OK;
    *ppvSite = NULL;

    if (_pISB)
    {
        hr = _pISB->QueryInterface(riid, ppvSite);
    }
    return hr;
}

HRESULT CBrowserExtension::GetNumButtons(UINT* pButtons)
{
    ASSERT(pButtons);
    *pButtons = _nExtButtons;
    return S_OK;
}

HRESULT CBrowserExtension::InitButtons(IExplorerToolbar* pxtb, UINT* puStringIndex, const GUID* pguidCommandGroup)
{
    ASSERT(pxtb);

    UINT uiSize;
    pxtb->GetBitmapSize(&uiSize);
    int cx = LOWORD(uiSize);

    // Get the image lists for the current button size and screen resolution
    CImageList* pimlDef;
    CImageList* pimlHot;
    UINT uiImageIndexOld = _uiImageIndex;
    _uiImageIndex = _GetImageLists(&pimlDef, &pimlHot, cx < 20);
    pxtb->SetImageList(pguidCommandGroup, *pimlDef, *pimlHot, NULL);

    // Free the previously used image list
    _ReleaseImageLists(uiImageIndexOld);

    // Add the button text to the toolbar
    if (_uStringIndex == (UINT)-1)
    {
        LRESULT iAddResult = 0; // result of adding the string buffer to the toolbar string list
        HRESULT hr = pxtb->AddString(pguidCommandGroup, MLGetHinst(), IDS_BROWSER_TB_LABELS, &iAddResult);
        _uStringIndex = (UINT)iAddResult;
        _AddCustomStringsToBuffer(pxtb, pguidCommandGroup);
    }

    *puStringIndex = _uStringIndex;
    return S_OK;
}

CBrowserExtension::ExtensionItem* CBrowserExtension::_FindItem(REFGUID rguid)
{
    ExtensionItem* pFound = NULL;
    if (NULL != _hdpa)
    {
        for (int i = 0; i < DPA_GetPtrCount(_hdpa); i++)
        {
            ExtensionItem* pItem = (ExtensionItem*)DPA_GetPtr(_hdpa, i);

            if (pItem && IsEqualGUID(pItem->guid, rguid))
            {
                pFound = pItem;
                break;
            }
        }
    }
    return pFound;
}

void CBrowserExtension::_AddItem(HKEY hkeyExtensions, LPCWSTR pszGuidItem, REFGUID rguidItem)
{
    // Create the dpa used to store our items
    if (NULL == _hdpa)
    {
        _hdpa = DPA_Create(5);
        if (NULL == _hdpa)
        {
            return;
        }
    }

    HKEY hkeyThisExtension;

    if (RegOpenKeyEx(hkeyExtensions, pszGuidItem, 0, KEY_READ, &hkeyThisExtension) == ERROR_SUCCESS)
    {
        // Get the clsid of the object
        WCHAR szCLSID[64];
        ULONG cbCLSID = SIZEOF(szCLSID);
        CLSID clsidCustomButton;

        if (SUCCEEDED(RegQueryValueEx(hkeyThisExtension, TEXT("clsid"), NULL, NULL, (unsigned char *)&szCLSID, &cbCLSID)) &&
            SUCCEEDED(CLSIDFromString(szCLSID, &clsidCustomButton)))
        {
            IBrowserExtension * pibeTemp;

            // Check for our internal object.  Note that our CoCreateInctance wrapper
            // compares to the address of the global clsid, so we want to use the global
            // guid.
            const CLSID* pclsid = &clsidCustomButton;
            if (IsEqualGUID(clsidCustomButton, CLSID_ToolbarExtExec))
            {
                pclsid = &CLSID_ToolbarExtExec;
            }
            else if (IsEqualGUID(clsidCustomButton, CLSID_ToolbarExtBand))
            {
                pclsid = &CLSID_ToolbarExtBand;
            }

            // Create the extension object
            if (SUCCEEDED(CoCreateInstance(*pclsid, NULL, CLSCTX_INPROC_SERVER,
                                 IID_IBrowserExtension, (void **)&pibeTemp)))
            {
                if (SUCCEEDED(pibeTemp->Init(rguidItem)))
                {
                    // Add this item to our array
                    ExtensionItem* pItem = new ExtensionItem;
                    if (pItem)
                    {
                        if (DPA_AppendPtr(_hdpa, pItem) != -1)
                        {
                            VARIANTARG varArg;

                            pItem->idCmd = _GetCmdIdFromClsid(pszGuidItem);
                            pItem->pIBE = pibeTemp;
                            pItem->guid = rguidItem;
                            pibeTemp->AddRef();

                            // See if it's a button
                            if (SUCCEEDED(pibeTemp->GetProperty(TBEX_BUTTONTEXT, NULL)))
                            {
                                _nExtButtons++;
                                pItem->fButton = TRUE;

                                // See if the button default to visible on the toolbar
                                if (SUCCEEDED(pibeTemp->GetProperty(TBEX_DEFAULTVISIBLE, &varArg)))
                                {
                                    ASSERT(varArg.vt == VT_BOOL);
                                    pItem->fVisible = (varArg.boolVal == VARIANT_TRUE);
                                }
                            }

                            // set the target menu
                            
                            pItem->idmMenu = 0;
                            
                            if (SUCCEEDED(pibeTemp->GetProperty(TMEX_MENUTEXT, NULL)))
                            {
                                
                                if (SUCCEEDED(pibeTemp->GetProperty(TMEX_CUSTOM_MENU, &varArg)))
                                {
                                    ASSERT(varArg.vt == VT_BSTR);
                                    ASSERT(IS_VALID_STRING_PTR(varArg.bstrVal, -1));
    
                                    if (!StrCmpNI(varArg.bstrVal, c_szHelpMenu, ARRAYSIZE(c_szHelpMenu)))
                                    {
                                        pItem->idmMenu = FCIDM_MENU_HELP;
                                    }
    
                                    VariantClear(&varArg);
                                }

                                if (pItem->idmMenu == 0)
                                {
                                    pItem->idmMenu = FCIDM_MENU_TOOLS;
                                }
                            }

                            // Pass the site to the object
                            IUnknown_SetSite(pibeTemp, _pISB);
                        }
                        else
                        {
                            delete pItem;
                        }
                    }
                }

                // This will free pibeTemp if we didn't store it away
                pibeTemp->Release();
            }
        }
        RegCloseKey(hkeyThisExtension);
    }
}


//
// All real construction happens here.  In theory this function can be called upon a SysINIChange to update our
// custom toolbar cached information.  This has not been tested.  This opens the Extensions section of the registry
// enumerates all of the subkeys.  Attempts to CoCreate each one.  Upon successful CoCreation it calls
// IObjectWithSite::SetSite(IShellBrowser), if it is implemented.  Next IBrowserExtension::Init is called.  Finally,
// IBrowserExtension::GetProperty(TBEX_BUTTONTEXT, NULL) is called looking for a S_OK to insure that the control in
// question is a Toolbar Button (as opposed to a tools menu item, or...)
//
HRESULT CBrowserExtension::Update()
{
    WCHAR szItemGuid[64];    // sufficient for {clsid}
    DWORD cbItemGuid;
    GUID guidItem;
    HRESULT hr = S_OK;

    // Free previous items
    _nExtButtons = 0;
    _nExtToolsMenuItems = 0;
    _FreeItems();

    // First add extensions from HKCU
    HKEY hkeyExtensions;
    if (RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Internet Explorer\\Extensions"), 0,
                     KEY_READ, &hkeyExtensions) == ERROR_SUCCESS)
    {
        cbItemGuid = sizeof(szItemGuid);
        for (int iKey = 0;
             RegEnumKeyEx(hkeyExtensions, iKey, szItemGuid, &cbItemGuid, NULL, NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS;
             iKey++)
        {
            if (SUCCEEDED(CLSIDFromString(szItemGuid, &guidItem)))
            {
                _AddItem(hkeyExtensions, szItemGuid, guidItem);
            }
            cbItemGuid = sizeof(szItemGuid);
        }

        RegCloseKey(hkeyExtensions);
    }

    // Next add any unique items from HKLM
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Internet Explorer\\Extensions"), 0,
                     KEY_READ, &hkeyExtensions) == ERROR_SUCCESS)
    {
        cbItemGuid = sizeof(szItemGuid);
        for (int iKey = 0;
             RegEnumKeyEx(hkeyExtensions, iKey, szItemGuid, &cbItemGuid, NULL, NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS;
             iKey++)
        {
            if (SUCCEEDED(CLSIDFromString(szItemGuid, &guidItem)))
            {
                if (_FindItem(guidItem) == NULL)
                {
                    _AddItem(hkeyExtensions, szItemGuid, guidItem);
                }
            }
            cbItemGuid = sizeof(szItemGuid);
        }

        RegCloseKey(hkeyExtensions);
    }

    return hr;
}

//
// This takes a TBBUTTON[] and fills in the Custom Buttons.  A couple of usage points:
// (1) The caller should allocate a TBBUTTON[] big enough for NUM_STD_BUTTONS + GetNumExtButtons()
//     Then they should copy the standard buttons into the array, and pass the pointer to the remainder
//     of the array here.
// (2) This function should *by design* never be called before AddCustomImagesToImageList and
//     AddCustomStringsToBuffer have both been called.  An attempt to do so in DEBUG mode will hit
//     a break point.
//
HRESULT CBrowserExtension::GetButtons(TBBUTTON * tbArr, int nNumButtons, BOOL fInit)
{
    ASSERT(_fStringInit && _fImageInit);

    if (_hdpa)
    {
        ASSERT(nNumButtons == _nExtButtons);
        ASSERT(tbArr != NULL)
        int iBtn = 0;

        for (int i = 0; i < DPA_GetPtrCount(_hdpa); i++)
        {
            ExtensionItem* pItem = (ExtensionItem*)DPA_GetPtr(_hdpa, i);
            if (!pItem->fButton)
                continue;

            // We use the MAKELONG(n, 1) to insure that we are using the alternate image list.
            tbArr[iBtn].iBitmap    = MAKELONG(pItem->iImageID, 1);
            tbArr[iBtn].idCommand  = pItem->idCmd;
            tbArr[iBtn].fsState    = TBSTATE_ENABLED;
            tbArr[iBtn].fsStyle    = BTNS_BUTTON;
            tbArr[iBtn].dwData     = 0;
            tbArr[iBtn].iString    = pItem->iStringID;

            //
            // Default to hidden during initialization so that it defaults to the left well
            // of the the customize dialog (defaults off the toolbar)
            //
            if (fInit && !pItem->fVisible)
            {
                tbArr[iBtn].fsState = TBSTATE_HIDDEN;
            }

            ++iBtn;
        }
    }
    return S_OK;
}

//
// This function takes the ImageLists for hot and normal icons and adds the appropriate icon to each
// list for each custom toolbar button.  The resultant ImageID is then stored in our _rgExtensionItem struct
// so that the IDs can be placed in a TBBUTTON[] when AddExtButtonsTBArray is called.
//
HRESULT CBrowserExtension::_AddCustomImagesToImageList(CImageList& rimlNormal, CImageList& rimlHot, BOOL fSmallIcons)
{
#ifdef DEBUG
    _fImageInit = TRUE;
#endif DEBUG

    if (rimlNormal.HasImages() && rimlHot.HasImages() && NULL != _hdpa)
    {
        for (int i = 0; i < DPA_GetPtrCount(_hdpa); i++)
        {
            ExtensionItem* pItem = (ExtensionItem*)DPA_GetPtr(_hdpa, i);
            if (!pItem->fButton)
                continue;

            VARIANTARG varArg;

            pItem->iImageID = rimlNormal.GetImageIndex(pItem->guid);
            if (-1 == pItem->iImageID &&
                SUCCEEDED(pItem->pIBE->GetProperty((fSmallIcons ? TBEX_GRAYICONSM : TBEX_GRAYICON), &varArg)))
            {
                if (varArg.vt == VT_BYREF)
                {
                    pItem->iImageID = rimlNormal.AddIcon((HICON)varArg.byref, pItem->guid);
                }
                else if (varArg.vt == VT_I4)
                {
                    // It's one of our built-in images
                    pItem->iImageID = varArg.lVal;
                }
                else
                {
                    TraceMsg(TF_ALWAYS, "Button doesn't have an image associated");
                }
            }

            int iHot = rimlHot.GetImageIndex(pItem->guid);
            if (-1 == iHot &&
                SUCCEEDED(pItem->pIBE->GetProperty((fSmallIcons ? TBEX_HOTICONSM : TBEX_HOTICON), &varArg)))
            {
                if (varArg.vt == VT_BYREF)
                {
                    iHot = rimlHot.AddIcon((HICON)varArg.byref, pItem->guid);
                }
                else if (varArg.vt == VT_I4)
                {
                    // It's one of our built-in images
                    iHot = varArg.lVal;
                }
                else
                {
                    TraceMsg(TF_ALWAYS, "Button doesn't have an image associated");
                }
            }

            if (iHot!=pItem->iImageID)
            {
                TraceMsg(TF_ALWAYS, "ButtonExtension: iHot doesn't match iImageID");
            }
        }
    }

    return S_OK;
}

//
// This function takes the StringList and adds the caption (ToolbarText) for each of the custom toolbar buttons
// to it.  The resultant StringID is then stored in our _rgExtensionItem struct so that the ID can be placed in
// a TBBUTTON[] when AddExtButtonsTBArray is called.
//
HRESULT CBrowserExtension::_AddCustomStringsToBuffer(IExplorerToolbar * pxtb, const GUID* pguidCommandGroup)
{
#ifdef DEBUG
    _fStringInit = TRUE;
#endif DEBUG

    if (NULL != _hdpa)
    {
        for (int i = 0; i < DPA_GetPtrCount(_hdpa); i++)
        {
            ExtensionItem* pItem = (ExtensionItem*)DPA_GetPtr(_hdpa, i);
            if (!pItem->fButton)
                continue;

            VARIANTARG varArg;

            if (SUCCEEDED(pItem->pIBE->GetProperty(TBEX_BUTTONTEXT, &varArg)))
            {
                // We need to double-null terminate the string!
                WCHAR szBuf[70];    // should be ample for button text!
                ZeroInit(szBuf, sizeof(szBuf));
                StrNCpy(szBuf, varArg.bstrVal, ARRAYSIZE(szBuf) - 2);
                LRESULT iResult;

                if (SUCCEEDED(pxtb->AddString(pguidCommandGroup, 0, (LPARAM)szBuf, &iResult)))
                {
                    pItem->iStringID = (SHORT)iResult;
                }

                VariantClear(&varArg);
            }
        }
    }

    return S_OK;
}

int CBrowserExtension::_GetCmdIdFromClsid(LPCWSTR pszGuid)
{
    DWORD dwDisposition;
    HRESULT hr = S_OK;
    int nReturn = DVIDM_MENUEXT_FIRST; 

    HKEY hkeyExtensionMapping;
    if (RegCreateKeyEx(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Internet Explorer\\Extensions\\CmdMapping"), 0, NULL, 0,
                       KEY_READ | KEY_WRITE, NULL, &hkeyExtensionMapping, &dwDisposition) == ERROR_SUCCESS)
    {
        DWORD dwType = REG_DWORD, dwData, cbData = sizeof(dwData);
        
        if ( (SHQueryValueEx(hkeyExtensionMapping, pszGuid, NULL, &dwType, &dwData, &cbData) == ERROR_SUCCESS) &&
             (dwType == REG_DWORD) )
        {
            //the item has a mapping
            nReturn = dwData;
        }
        else
        {
            //it's a new item, get and store the next available id in the default value of the Mapping key
            if ( (SHQueryValueEx(hkeyExtensionMapping, L"NextId", NULL, &dwType, &dwData, &cbData) != ERROR_SUCCESS) ||
                 (dwType != REG_DWORD) )
            {
                dwData = DVIDM_MENUEXT_FIRST;
            }
            nReturn = dwData;

            dwType = REG_DWORD;
            cbData = sizeof(dwData);
            EVAL(SHSetValueW(hkeyExtensionMapping, NULL, pszGuid, dwType, &dwData, cbData) == ERROR_SUCCESS);

            dwData++;
            ASSERT(dwData < DVIDM_MENUEXT_LAST); //ugh, we've used up our whole range. we need to look for holes.
            EVAL(SHSetValueW(hkeyExtensionMapping, NULL, L"NextId", dwType, &dwData, cbData) == ERROR_SUCCESS);
        }
        RegCloseKey(hkeyExtensionMapping);
    }

    return nReturn;
}

int CBrowserExtension::_GetIdpaFromCmdId(int nCmdId)
{
    if (NULL != _hdpa)
    {
        for (int i = 0; i < DPA_GetPtrCount(_hdpa); i++)
        {
            ExtensionItem* pItem = (ExtensionItem*)DPA_GetPtr(_hdpa, i);
            if (pItem->idCmd == nCmdId)
                return i;
        }
    }
    return -1;
}

// *** IOleCommandTarget methods ***

HRESULT CBrowserExtension::Exec(const GUID *pguidCmdGroup, DWORD nCmdID,
    DWORD nCmdexecopt, VARIANTARG *pvarargIn, VARIANTARG *pvarargOut)
{
    if (!pguidCmdGroup)
        return E_INVALIDARG;

    if (IsEqualGUID(*pguidCmdGroup, CLSID_ToolbarExtButtons))
    {
        int iCmd = _GetIdpaFromCmdId(nCmdID);

        if (iCmd >= 0 && iCmd < DPA_GetPtrCount(_hdpa))
        {
            ExtensionItem* pItem = (ExtensionItem*)DPA_GetPtr(_hdpa, iCmd);
            if (pItem)
                return IUnknown_Exec(pItem->pIBE, NULL, 0, 0, NULL, NULL);
        }
    }
    else if (IsEqualGUID(*pguidCmdGroup, CLSID_PrivBrowsExtCommands))
    {
        switch (nCmdID)
        {
        case PBEC_GETSTRINGINDEX:
            if (pvarargIn && pvarargIn->vt == VT_I4)
            {
                pvarargIn->lVal = _uStringIndex;
                return S_OK;
            }
            break;
        }
    }

    return E_FAIL;
}

HRESULT CBrowserExtension::QueryStatus(const GUID *pguidCmdGroup,
    ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT *pcmdtext)
{
    if (!pguidCmdGroup)
        return E_INVALIDARG;

    if (IsEqualGUID(*pguidCmdGroup, CLSID_ToolbarExtButtons))
    {
        for (ULONG i = 0; i < cCmds; i++)
        {
            int iCmd = _GetIdpaFromCmdId(rgCmds[i].cmdID);

            if (iCmd >= 0 && iCmd < DPA_GetPtrCount(_hdpa))
            {
                ExtensionItem* pItem = (ExtensionItem*)DPA_GetPtr(_hdpa, iCmd);
                if (pItem)
                {
                    // I don't think this has ever worked.  The command id
                    // isn't the same as the one we use in Exec.
                    IUnknown_QueryStatus(pItem->pIBE, pguidCmdGroup, 1, &rgCmds[i], pcmdtext);
                }
            }
        }

        return S_OK;
    }

    return E_FAIL;
}

//
// This function is a helper for the destructor.  It is also called by Update() so that if we are ever asked
// to Update() we first kill all of our cached information and then we go to the registry...
//

void CBrowserExtension::_FreeItems(void)
{
    if (_hdpa)
    {
        for (int i = DPA_GetPtrCount(_hdpa) - 1; i >= 0; --i)
        {
            ExtensionItem* pItem = (ExtensionItem*)DPA_DeletePtr(_hdpa, i);

            IUnknown_SetSite(pItem->pIBE, NULL);
            pItem->pIBE->Release();
            delete pItem;
        }
    }
}

// this help function is used to isolate the menu-specific
// processing. after using this helper to fill out the BROWSEXT_MENU_INFO
// struct, the OnCustomizableMenuPopup is able to do menu-inspecific
// processing.

HRESULT
CBrowserExtension::_GetCustomMenuInfo(HMENU hMenuParent, HMENU hMenu, BROWSEXT_MENU_INFO * pMI)
{
    HRESULT hr;

    RIP(IS_VALID_HANDLE(hMenuParent, MENU));
    RIP(IS_VALID_HANDLE(hMenu, MENU));
    RIP(IS_VALID_WRITE_PTR(pMI, BROWSEXT_MENU_INFO *));

    hr = E_FAIL;
    pMI->idmMenu = 0;

    // set idmMenu, idmPlaceholder, and idmModMarker to values
    // reflecting whichever menu's popup we're currently handling

    if (GetMenuFromID(hMenuParent, FCIDM_MENU_HELP) == hMenu)
    {
        pMI->idmMenu = FCIDM_MENU_HELP;
        pMI->idmPlaceholder = FCIDM_HELP_EXT_PLACEHOLDER;
        pMI->idmModMarker = FCIDM_HELP_EXT_MOD_MARKER;
    }
    else if (GetMenuFromID(hMenuParent, FCIDM_MENU_TOOLS) == hMenu)
    {
        pMI->idmMenu = FCIDM_MENU_TOOLS;
        pMI->idmPlaceholder = FCIDM_TOOLS_EXT_PLACEHOLDER;
        pMI->idmModMarker = FCIDM_TOOLS_EXT_MOD_MARKER;
    }

    // set iInsert. using a constant insertion index
    // instead of always inserting by command at
    // the placeholder makes it easier later when
    // we have to stick in the final separator to
    // isolate the custom item group.

    if (pMI->idmMenu != 0)
    {
        int i;
        int cItems;

        cItems = GetMenuItemCount(hMenu);

        for (i = 0; i < cItems; i++)
        {
            MENUITEMINFO    mii;
            BOOL            f;

            mii.cbSize = sizeof(mii);
            mii.fMask = MIIM_ID;

            f = GetMenuItemInfo(hMenu, i, TRUE, &mii);

            if (f && mii.wID == pMI->idmPlaceholder)
            {
                pMI->iInsert = i;
                hr = S_OK;
                break;
            }
        }
    }

    return hr;
}

// note, this popup handler can't easily tell whether an item
// has been removed from the DPA. if you remove any items from the
// DPA it is your responsibility to delete them from the menu
// also, if they live on a menu

HRESULT CBrowserExtension::OnCustomizableMenuPopup(HMENU hMenuParent, HMENU hMenu)
{
    HRESULT             hr;
    BROWSEXT_MENU_INFO  menuInfo;

    RIP(IS_VALID_HANDLE(hMenu, MENU));

    hr = _GetCustomMenuInfo(hMenuParent, hMenu, &menuInfo);
    if (SUCCEEDED(hr) && _hdpa != NULL)
    {
        BOOL    fItemInserted;
        UINT    cItems;
        UINT    i;

        ASSERT(IS_VALID_HANDLE(_hdpa, DPA));

        fItemInserted = FALSE;

        // check each extension object we currently have
        // to see whether any of them should go into this
        // menu

        cItems = (UINT)DPA_GetPtrCount(_hdpa);

        for (i = 0; i < cItems; i++)
        {
            ExtensionItem * pItem;

            pItem = (ExtensionItem *)DPA_GetPtr(_hdpa, i);
            ASSERT(IS_VALID_READ_PTR(pItem, ExtensionItem));

            // does this item go into the menu we're currently
            // customizing?

            if (pItem->idmMenu == menuInfo.idmMenu)
            {
                MENUITEMINFO        mii;
                IOleCommandTarget * pOCT;

                mii.fMask   = MIIM_ID;
                mii.wID     = pItem->idCmd;
                mii.cbSize  = sizeof(mii);

                // set the MENUITEMINFO's state information, if applicable

                ASSERT(IS_VALID_CODE_PTR(pItem->pIBE, IBrowserExtension));

                hr = pItem->pIBE->QueryInterface(IID_IOleCommandTarget, (void **)&pOCT);
                if (SUCCEEDED(hr))
                {
                    OLECMD oleCmd = {OLECMDID_OPEN,};

                    ASSERT(IS_VALID_CODE_PTR(pOCT, IOleCommandTarget));

                    hr = pOCT->QueryStatus(NULL, 1, &oleCmd, NULL);
                    if (SUCCEEDED(hr))
                    {
                        mii.fMask |= MIIM_STATE;
                        mii.fState = 0;

                        // enabled state

                        if (oleCmd.cmdf & OLECMDF_ENABLED)
                        {
                            mii.fState |= MFS_ENABLED;
                        }
                        else
                        {
                            mii.fState |= MFS_DISABLED;
                        }

                        // checked state

                        if (oleCmd.cmdf & OLECMDF_LATCHED)
                        {
                            mii.fState |= MFS_CHECKED;
                        }
                        else
                        {
                            mii.fState |= MFS_UNCHECKED;
                        }
                    }

                    pOCT->Release();
                }

                // get the menu text.
                // this changing is an unlikely scenario, but if we're truly
                // supporting dynamic customization, then we need to allow for
                // this possibility.

                VARIANTARG  varArg;

                hr = pItem->pIBE->GetProperty(TMEX_MENUTEXT, &varArg);
                if (SUCCEEDED(hr))
                {
                    BOOL    fItemExists;

                    ASSERT(varArg.vt == VT_BSTR);
                    ASSERT(IS_VALID_STRING_PTR(varArg.bstrVal, -1));

                    fItemExists = GetMenuItemInfo(hMenu, mii.wID, FALSE, &mii);

                    mii.fMask |= MIIM_TYPE;
                    mii.fType = MFT_STRING;
                    mii.cch = SysStringLen(varArg.bstrVal);
                    mii.dwTypeData = varArg.bstrVal;

                    if (fItemExists)
                    {
                        // update the old item using current info

                        SetMenuItemInfo(hMenu, mii.wID, FALSE, &mii);
                    }
                    else
                    {
                        // create a new item using current info

                        if (InsertMenuItem(hMenu, menuInfo.iInsert, TRUE, &mii))
                        {
                            fItemInserted = TRUE;
                        }
                    }

                    VariantClear(&varArg);
                }

            }
        }

        if (fItemInserted)
        {
            MENUITEMINFO    mii;
            BOOL            fModMarkerExists;

            // since we made an insertion, we need to insert
            // a separator, but only if we didn't do it already

            mii.cbSize = sizeof(mii);
            mii.fMask = 0;

            fModMarkerExists = GetMenuItemInfo(hMenu, menuInfo.idmModMarker, FALSE, &mii);

            if (!fModMarkerExists)
            {
                mii.fMask = MIIM_ID | MIIM_TYPE;
                mii.wID = menuInfo.idmModMarker;
                mii.fType = MFT_SEPARATOR;

                InsertMenuItem(hMenu, menuInfo.iInsert, TRUE, &mii);
            }
        }

        // the only thing that is guaranteed to be a complete failure
        // if if we failed to get the info for the menu doing the popup.
        // otherwise, despite the possibility that any particular insertion
        // attempt might have failed, there are potentially many custom
        // items. though some might fail, some might succeed. in either
        // we'll return overall success, because we successfully did the
        // best we could with the items that were present.
        // at least we didn't crash :)

        hr = S_OK;
    }

    return hr;
}

HRESULT CBrowserExtension::OnMenuSelect(UINT nCmdID)
{
    VARIANT varArg;
    HRESULT hr = E_FAIL;

    // We better have stored our menu extensions if we are at this point
    ASSERT(_hdpa != NULL);
    int i = _GetIdpaFromCmdId(nCmdID);
    if (i >= 0 && i < DPA_GetPtrCount(_hdpa))
    {
        ExtensionItem* pItem = (ExtensionItem*)DPA_GetPtr(_hdpa, i);
        ASSERT(pItem->idmMenu != 0);

        hr = pItem->pIBE->GetProperty(TMEX_STATUSBARTEXT, &varArg);
        if (SUCCEEDED(hr))
        {
            if (varArg.vt == VT_BSTR)
            {
                // Set the Status Bar Text
                if (_pISB)
                {
                    _pISB->SetStatusTextSB(varArg.bstrVal);
                }
            }

            VariantClear(&varArg);
            hr = S_OK;
        }
    }
    return hr;
}

// Create an image list for the Cut/Copy/Paste buttons
CBrowserExtension::CImageCache CBrowserExtension::_rgImages[3];

//
// Get the image list for the toolbar. These image lists are shared between instances so
// the caller must call _ReturnImageLists when finished with them.  The index returned from this
// functions is passed to _ReturnImageLists.
//
UINT CBrowserExtension::_GetImageLists(CImageList** ppimlDef, CImageList** ppimlHot, BOOL fSmall)
{
    COLORREF crMask = RGB( 255, 0, 255 );
    BOOL bUseNewIcons = !SHUseClassicToolbarGlyphs();

    //
    // Get the index into our image cache
    //   16 color 16x16 (small)
    //   16 color 20x20
    //   256 color 20x20
    //
    int i = fSmall ? 0 : 1;
    if (!fSmall && SHGetCurColorRes() > 8)
        ++i;

    int cx = fSmall ? 16 : 20;

    if (!fSmall && bUseNewIcons)
    {
        cx = 24;
    }

    //
    // Create the images if necessary
    //
    ENTERCRITICAL;

    if (_rgImages[0].uiResDef == 0)
    {

        _rgImages[1].uiResDef = IDB_CLASSIC_IETOOLBAR;
        _rgImages[1].uiResHot = IDB_CLASSIC_IETOOLBARHOT;
        _rgImages[1].bShell32 = FALSE;


        if (bUseNewIcons)
        {
            _rgImages[0].uiResDef = IDB_TB_EXT_DEF_16;
            _rgImages[0].uiResHot = IDB_TB_EXT_HOT_16;
            _rgImages[0].bShell32 = TRUE;
            _rgImages[2].uiResDef = IDB_TB_EXT_DEF_24;
            _rgImages[2].uiResHot = IDB_TB_EXT_HOT_24;
            _rgImages[2].bShell32 = TRUE;
        }
        else
        {
            _rgImages[0].uiResDef = IDB_CLASSIC_IETOOLBAR16;
            _rgImages[0].uiResHot = IDB_CLASSIC_IETOOLBARHOT16;
            _rgImages[0].bShell32 = FALSE;
            _rgImages[2].uiResDef = IDB_CLASSIC_IETOOLBARHICOLOR;
            _rgImages[2].uiResHot = IDB_CLASSIC_IETOOLBARHOTHICOLOR;
            _rgImages[2].bShell32 = FALSE;
        }
    }

    if (!_rgImages[i].imlDef.HasImages())
    {
        _rgImages[i].imlDef = ImageList_LoadImage(_rgImages[i].bShell32 ? GetModuleHandle(TEXT("shell32.dll")) : HINST_THISDLL,
                                           MAKEINTRESOURCE(_rgImages[i].uiResDef),
                                           cx, 0, crMask,
                                           IMAGE_BITMAP, LR_CREATEDIBSECTION);
    }

    if (!_rgImages[i].imlHot.HasImages())
    {
        _rgImages[i].imlHot = ImageList_LoadImage(_rgImages[i].bShell32 ? GetModuleHandle(TEXT("shell32.dll")) : HINST_THISDLL,
                                           MAKEINTRESOURCE(_rgImages[i].uiResHot),
                                           cx, 0, crMask,
                                           IMAGE_BITMAP, LR_CREATEDIBSECTION);
    }

    //
    // Add the custom buttons to our image lists
    //
    _AddCustomImagesToImageList(_rgImages[i].imlDef, _rgImages[i].imlHot, fSmall);

    ++_rgImages[i].cUsage;

    *ppimlDef = &_rgImages[i].imlDef;
    *ppimlHot = &_rgImages[i].imlHot;
    LEAVECRITICAL;

    return i;
}

//
// Called when the imagelist indicated by uiIndex is not longer used by this instance
//
void CBrowserExtension::_ReleaseImageLists(UINT uiIndex)
{
    if (uiIndex >= ARRAYSIZE(_rgImages))
    {
        return;
    }

    ENTERCRITICAL;

    ASSERT(_rgImages[uiIndex].cUsage >= 1);

    // If the image lists are no longer used, we can free them
    if (--_rgImages[uiIndex].cUsage == 0)
    {
        _rgImages[uiIndex].imlDef.FreeImages();
        _rgImages[uiIndex].imlHot.FreeImages();
    }
    LEAVECRITICAL;
}

//+-------------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------------
CImageList::CImageList(HIMAGELIST himl)
:   _himl(himl)
{
    ASSERT(_hdpa == NULL);
}

//+-------------------------------------------------------------------------
// Destructor
//--------------------------------------------------------------------------
CImageList::~CImageList()
{
    FreeImages();
}

//+-------------------------------------------------------------------------
// Frees an association item from our dpa
//--------------------------------------------------------------------------
int CImageList::_DPADestroyCallback(LPVOID p, LPVOID d)
{
    delete (ImageAssoc*)p;
    return 1;
}

//+-------------------------------------------------------------------------
// Frees our image list and inex associations
//--------------------------------------------------------------------------
void CImageList::FreeImages()
{
    if (_hdpa)
    {
        DPA_DestroyCallback(_hdpa, _DPADestroyCallback, 0);
        _hdpa = NULL;
    }
    if (_himl)
    {
        ImageList_Destroy(_himl);
        _himl = NULL;
    }
}

//+-------------------------------------------------------------------------
// Updates the image list
//--------------------------------------------------------------------------
CImageList& CImageList::operator=(HIMAGELIST himl)
{
    if (himl != _himl)
    {
        FreeImages();
        _himl = himl;
    }
    return *this;
}

//+-------------------------------------------------------------------------
// Returns the index of the images associated with rguid.  Returns -1 if not
// found.
//--------------------------------------------------------------------------
int CImageList::GetImageIndex(REFGUID rguid)
{
    int iIndex = -1;

    if (_hdpa)
    {
        ASSERT(_himl);

        for (int i=0; i < DPA_GetPtrCount(_hdpa); ++i)
        {
            ImageAssoc* pAssoc = (ImageAssoc*)DPA_GetPtr(_hdpa, i);
            if (IsEqualGUID(pAssoc->guid, rguid))
            {
                return pAssoc->iImage;
            }
        }
    }
    return iIndex;
}

//+-------------------------------------------------------------------------
// Adds the icon to the image list and returns the index.  If the image is
// already present, the existing index is returned.  Returns -1 on failure.
//--------------------------------------------------------------------------
int CImageList::AddIcon(HICON hicon, REFGUID rguid)
{
    ASSERT(hicon != NULL);

    // First see is we have already added this image
    int iIndex = GetImageIndex(rguid);
    if (iIndex == -1)
    {
        // Make sure we have a dpa to store our items
        if (NULL == _hdpa)
        {
            _hdpa = DPA_Create(5);
        }

        if (_hdpa && _himl)
        {
            // Add the icon to our image list
            iIndex = ImageList_AddIcon(_himl, hicon);
            if (iIndex != -1)
            {
                ImageAssoc* pAssoc = new ImageAssoc;
                if (pAssoc)
                {
                    pAssoc->guid = rguid;
                    pAssoc->iImage = iIndex;
                    DPA_AppendPtr(_hdpa, pAssoc);
                }
            }
        }
    }
    return iIndex;
}
