windows-nt/Source/XPSP1/NT/shell/shdocvw/browsext.cpp
2020-09-26 16:20:57 +08:00

1182 lines
36 KiB
C++

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