//////////////////////////////////////////////////////////////////////////// // File: TBExt.cpp (toolbar extension classes) // Author: Karim Farouki // // We define here three classes: // (1) CToolbarExt a base class that takes care of the // button work for our custom extensions // (2) CToolbarExtBand the object which deals with custom // buttons that plug into bands // (3) CToolbarExtExec the object which deals with custom // buttons (or tools menu items) that exec stuff. // // The latter two are derived from the former #include "priv.h" #include #include "tbext.h" ////////////////////////////// // Class CToolbarExt // // This is the base class from which CToolbarExtBand and CToolbarExtExec // both inherit. It takes care of all the ToolbarButton specific stuff // like lazy loading the appropriate icons, and keeping track of the button // text. // Constructor / Destructor // CToolbarExt::CToolbarExt() : _cRef(1) { ASSERT(_hIcon == NULL); ASSERT(_hIconSm == NULL); ASSERT(_hHotIcon == NULL); ASSERT(_hHotIconSm == NULL); ASSERT(_bstrButtonText == NULL); ASSERT(_bstrToolTip == NULL); ASSERT(_hkeyThisExtension == NULL); ASSERT(_hkeyCurrentLang == NULL); ASSERT(_pisb == NULL); DllAddRef(); } // Destructor // CToolbarExt::~CToolbarExt() { if (_pisb) _pisb->Release(); if (_bstrButtonText) SysFreeString(_bstrButtonText); if (_bstrToolTip) SysFreeString(_bstrToolTip); if (_hIcon) DestroyIcon(_hIcon); if (_hIconSm) DestroyIcon(_hIconSm); if (_hHotIcon) DestroyIcon(_hHotIcon); if (_hHotIconSm) DestroyIcon(_hHotIconSm); if (_hkeyThisExtension) RegCloseKey(_hkeyThisExtension); if (_hkeyCurrentLang) RegCloseKey(_hkeyCurrentLang); DllRelease(); } // IUnknown implementation // STDMETHODIMP CToolbarExt::QueryInterface(const IID& iid, void** ppv) { if (iid == IID_IUnknown) *ppv = static_cast(this); else if (iid == IID_IBrowserExtension) *ppv = static_cast(this); else if (iid == IID_IOleCommandTarget) *ppv = static_cast(this); else if (iid == IID_IObjectWithSite) *ppv = static_cast(this); else { *ppv = NULL; return E_NOINTERFACE; } reinterpret_cast(*ppv)->AddRef(); return S_OK; } STDMETHODIMP_(ULONG) CToolbarExt::AddRef() { return InterlockedIncrement(&_cRef); } STDMETHODIMP_(ULONG) CToolbarExt::Release() { if (InterlockedDecrement(&_cRef) == 0) { delete this; return 0; } return _cRef; } // IBrowserExtension::Init Implementation. We'll read the ButtonText here but wait on the icons until // a specific variant of the icon is requested. STDMETHODIMP CToolbarExt::Init(REFGUID rguid) { HRESULT hr = S_OK; LPOLESTR pszGUID; if (SUCCEEDED(StringFromCLSID(rguid, &pszGUID))) { //Open the extension reg key associated with this guid WCHAR szKey[MAX_PATH]; StrCpyN(szKey, TEXT("Software\\Microsoft\\Internet Explorer\\Extensions\\"), ARRAYSIZE(szKey)); StrCatBuff(szKey, pszGUID, ARRAYSIZE(szKey)); // We will keep _hkeyThisExtension around... it will be closed in the destructor! if (RegOpenKeyEx(HKEY_CURRENT_USER, szKey, 0, KEY_READ, &_hkeyThisExtension) == ERROR_SUCCESS || RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, KEY_READ, &_hkeyThisExtension) == ERROR_SUCCESS) { // See if there is a subkey for the current language LANGID langid = MLGetUILanguage(); WCHAR szBuff[MAX_PATH]; wnsprintf(szBuff, ARRAYSIZE(szBuff), L"Lang%04x", langid); RegOpenKeyEx(_hkeyThisExtension, szBuff, 0, KEY_READ, &_hkeyCurrentLang); // Now get the button text _RegReadString(_hkeyThisExtension, TEXT("ButtonText"), &_bstrButtonText); } CoTaskMemFree(pszGUID); } if (!_bstrButtonText) hr = E_FAIL; return hr; } // // Gets the icon closest to the desired size from an .ico file or from the // resource in a .dll of .exe file // HICON CToolbarExt::_ExtractIcon ( LPWSTR pszPath, // file to get icon from int resid, // resource id (0 if unused) int cx, // desired icon width int cy // desired icon height ) { HICON hIcon = NULL; WCHAR szPath[MAX_PATH]; SHExpandEnvironmentStrings(pszPath, szPath, ARRAYSIZE(szPath)); // If no resource id, assume it's an ico file if (resid == 0) { hIcon = (HICON)LoadImage(0, szPath, IMAGE_ICON, cx, cy, LR_LOADFROMFILE); } // Otherwise, see if it's a resouce if (hIcon == NULL) { HINSTANCE hInst = LoadLibraryEx(szPath, NULL, LOAD_LIBRARY_AS_DATAFILE); if (hInst) { hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(resid), IMAGE_ICON, cx, cy, LR_DEFAULTCOLOR); FreeLibrary(hInst); } } return hIcon; } // // Returns the desired icon in pvarProperty // HRESULT CToolbarExt::_GetIcon ( LPCWSTR pszIcon, // Name of icon value in registry int nWidth, // icon width int nHeight, // icon height HICON& rhIcon, // location to cached icon VARIANTARG * pvarProperty // used for return icon ) { HRESULT hr = S_OK; if (pvarProperty) { if (rhIcon == NULL) { BSTR bstrIconName; if (_RegReadString(_hkeyThisExtension, pszIcon, &bstrIconName, TRUE)) { // Parse entry such as "file.ext,1" to get the icon index int nIconIndex = PathParseIconLocation(bstrIconName); // If the entry was ",#" then it's an index into our built-in button bitmap if (*bstrIconName == L'\0') { pvarProperty->vt = VT_I4; pvarProperty->lVal = nIconIndex; SysFreeString(bstrIconName); return hr; } else { rhIcon = _ExtractIcon(bstrIconName, nIconIndex, nWidth, nHeight); } SysFreeString(bstrIconName); } } if (rhIcon) { pvarProperty->vt = VT_BYREF; pvarProperty->byref = rhIcon; } else { VariantInit(pvarProperty); } } return hr; } // // Implementation of IBrowserExtension::GetProperty(). There are two important points here: // (1) We are lazy loading the appropriate icons. This way if the user never goes into small icon // mode we never create the images... // (2) If we are called with a NULL pvarProperty then we must still return S_OK if the iPropID // is for a property that we support and E_NOTIMPL if we do not. This is why the if (pvarProperty) // check is done for each case rather tan outside the case block. This behavior is important // for CBrowserExtension::Update() who passes in a NULL pvarProperty but still is trying to determine // what kind of extension this is! // STDMETHODIMP CToolbarExt::GetProperty(SHORT iPropID, VARIANTARG * pvarProperty) { HRESULT hr = S_OK; if (pvarProperty) VariantInit(pvarProperty); // in case of failure switch (iPropID) { case TBEX_BUTTONTEXT: if (pvarProperty) { pvarProperty->bstrVal = SysAllocString(_bstrButtonText); if (pvarProperty->bstrVal) { pvarProperty->vt = VT_BSTR; } else { hr = E_OUTOFMEMORY; } } break; case TBEX_GRAYICON: // For Whistler, we now use a 24 x 24 icons if (SHUseClassicToolbarGlyphs()) { hr = _GetIcon(TEXT("Icon"), 20, 20, _hIcon, pvarProperty); } else { hr = _GetIcon(TEXT("Icon"), 24, 24, _hIcon, pvarProperty); } break; case TBEX_GRAYICONSM: hr = _GetIcon(TEXT("Icon"), 16, 16, _hIconSm, pvarProperty); break; case TBEX_HOTICON: // For Whistler, we now use a 24 x 24 icons if (SHUseClassicToolbarGlyphs()) { hr = _GetIcon(TEXT("HotIcon"), 20, 20, _hHotIcon, pvarProperty); } else { hr = _GetIcon(TEXT("HotIcon"), 24, 24, _hHotIcon, pvarProperty); } break; case TBEX_HOTICONSM: hr = _GetIcon(TEXT("HotIcon"), 16, 16, _hHotIconSm, pvarProperty); break; case TBEX_DEFAULTVISIBLE: if (pvarProperty) { BOOL fVisible = _RegGetBoolValue(L"Default Visible", FALSE); pvarProperty->vt = VT_BOOL; pvarProperty->boolVal = fVisible ? VARIANT_TRUE : VARIANT_FALSE; } break; default: hr = E_NOTIMPL; } return hr; } // // IOleCommandTarget Implementation // STDMETHODIMP CToolbarExt::QueryStatus(const GUID* pguidCmdGroup, ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT* pCmdText) { HRESULT hr = OLECMDERR_E_UNKNOWNGROUP; if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CLSID_ToolbarExtButtons)) { // Default to all commands enabled for (ULONG i = 0; i < cCmds; i++) { // if (prgCmds[i].cmdID == 1) // Execing this object is supported and can be done at this point rgCmds[i].cmdf = OLECMDF_ENABLED | OLECMDF_SUPPORTED; // else // prgCmds[i].cmdf = 0; } hr = S_OK; } // Return an empty pCmdText if (pCmdText != NULL) { pCmdText->cwActual = 0; } return hr; } // // IObjectWithSite Implementation // STDMETHODIMP CToolbarExt::SetSite(IUnknown* pUnkSite) { if (_pisb != NULL) { _pisb->Release(); _pisb = NULL; } if (pUnkSite) pUnkSite->QueryInterface(IID_IShellBrowser, (void **)&_pisb); return S_OK; } STDMETHODIMP CToolbarExt::GetSite(REFIID riid, void ** ppvSite) { return E_NOTIMPL; } BOOL CToolbarExt::_RegGetBoolValue ( LPCWSTR pszPropName, BOOL fDefault ) { WCHAR szData[MAX_PATH]; DWORD cbData = SIZEOF(szData); if ((_hkeyCurrentLang && RegQueryValueEx(_hkeyCurrentLang, pszPropName, NULL, NULL, (unsigned char *)szData, &cbData) == ERROR_SUCCESS) || (_hkeyThisExtension && RegQueryValueEx(_hkeyThisExtension, pszPropName, NULL, NULL, (unsigned char *)szData, &cbData) == ERROR_SUCCESS)) { if ((0 == StrCmpI(L"TRUE", szData)) || (0 == StrCmpI(L"YES", szData))) { fDefault = TRUE; // We read TRUE from the registry. } else if ((0 == StrCmpI(L"FALSE", szData)) || (0 == StrCmpI(L"NO", szData))) { fDefault = FALSE; // We read TRUE from the registry. } } return fDefault; } // Private Helper Functions // // shlwapi has some similar function; however, they all insist on reopening and closing the key in question // with each read. It is explicitly suggested that we use our own helper if we are caching the key... BOOL CToolbarExt::_RegReadString ( HKEY hkeyThisExtension, LPCWSTR pszPropName, BSTR * pbstrProp, BOOL fExpand // = FALSE, Expand Environment strings ) { WCHAR szData[MAX_PATH]; *pbstrProp = NULL; BOOL fSuccess = FALSE; // First try the optional location for localized content if (_hkeyCurrentLang) { if (SUCCEEDED(SHLoadRegUIString(_hkeyCurrentLang, pszPropName, szData, ARRAYSIZE(szData)))) { fSuccess = TRUE; } } // Next try default location if (!fSuccess && _hkeyThisExtension) { if (SUCCEEDED(SHLoadRegUIString(hkeyThisExtension, pszPropName, szData, ARRAYSIZE(szData)))) { fSuccess = TRUE; } } if (fSuccess) { LPWSTR psz = szData; WCHAR szExpand[MAX_PATH]; if (fExpand) { SHExpandEnvironmentStrings(szData, szExpand, ARRAYSIZE(szExpand)); psz = szExpand; } *pbstrProp = SysAllocString(psz); } return (NULL != *pbstrProp); } /////////////////////////////////////////////////////////// // Class CToolbarExtBand // // This class adds to the base functionality of CToolbarExt // by storing the CLSID for a registered band, and displaying that // band upon execution of IOleCommandTarget::Exec // // STDAPI CToolbarExtBand_CreateInstance( IUnknown * punkOuter, IUnknown ** ppunk, LPCOBJECTINFO poi ) { HRESULT hr = S_OK; *ppunk = NULL; CToolbarExtBand * lpTEB = new CToolbarExtBand(); if (lpTEB == NULL) hr = E_OUTOFMEMORY; else *ppunk = SAFECAST(lpTEB, IBrowserExtension *); return hr; } // Constructor / Destructor // CToolbarExtBand::CToolbarExtBand() { ASSERT(_cRef == 1); ASSERT(_bBandState == FALSE); ASSERT(_bstrBandCLSID == NULL); } // Destructor // CToolbarExtBand::~CToolbarExtBand() { if (_bstrBandCLSID) SysFreeString(_bstrBandCLSID); } // IBrowserExtension::Init() We pass the majroity of the work on to the base class, then we load // the BandCLSID and cache it. STDMETHODIMP CToolbarExtBand::Init(REFGUID rguid) { HRESULT hr = CToolbarExt::Init(rguid); _RegReadString(_hkeyThisExtension, TEXT("BandCLSID"), &_bstrBandCLSID); if (!(_bstrButtonText && _bstrBandCLSID)) hr = E_FAIL; return hr; } STDMETHODIMP CToolbarExtBand::QueryStatus ( const GUID * pguidCmdGroup, ULONG cCmds, OLECMD prgCmds[], OLECMDTEXT * pCmdText ) { HRESULT hr = OLECMDERR_E_UNKNOWNGROUP; if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CLSID_ToolbarExtButtons)) { VARIANT varClsid; // Default to all commands enabled for (ULONG i = 0; i < cCmds; i++) { varClsid.vt = VT_BSTR; varClsid.bstrVal = _bstrBandCLSID; prgCmds[i].cmdf = OLECMDF_ENABLED | OLECMDF_SUPPORTED; hr = IUnknown_Exec(_pisb, &CGID_ShellDocView, SHDVID_ISBROWSERBARVISIBLE, 0, &varClsid, NULL); if (S_OK == hr) { prgCmds[i].cmdf |= OLECMDF_LATCHED; } } hr = S_OK; } return hr; } // Take the pIShellBrowser (obtained from IObjectWithSite::SetSite()) and disply the band STDMETHODIMP CToolbarExtBand::Exec( const GUID * pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANT * pvaIn, VARIANT * pvaOut ) { HRESULT hr = E_FAIL; if (_pisb) { VARIANT varClsid; varClsid.vt = VT_BSTR; varClsid.bstrVal = _bstrBandCLSID; _bBandState = !_bBandState; IUnknown_Exec(_pisb, &CGID_ShellDocView, SHDVID_SHOWBROWSERBAR, _bBandState, &varClsid, NULL); hr = S_OK; } return hr; } /////////////////////////////////////////////////////////////////////// // Class CToolbarExtExec // // Expands on the base class by adding support for tools menu plug-ins. // An instance of this class can be a button OR a menu OR BOTH. It also // keeps track of a BSTR which it ShellExecutes in its IOleCommandTarget::Exec() // STDAPI CToolbarExtExec_CreateInstance( IUnknown * punkOuter, IUnknown ** ppunk, LPCOBJECTINFO poi ) { HRESULT hr = S_OK; *ppunk = NULL; CToolbarExtExec * lpTEE = new CToolbarExtExec(); if (lpTEE == NULL) hr = E_OUTOFMEMORY; else *ppunk = SAFECAST(lpTEE, IBrowserExtension *); return hr; } CToolbarExtExec::CToolbarExtExec() { ASSERT(_cRef == 1); ASSERT(_bstrToolTip == NULL); ASSERT(_bstrExec == NULL); ASSERT(_bstrScript == NULL); ASSERT(_bstrMenuText == NULL); ASSERT(_bstrMenuCustomize == NULL); ASSERT(_bstrMenuStatusBar == NULL); ASSERT(_punkExt == NULL); } CToolbarExtExec::~CToolbarExtExec() { if (_bstrToolTip) SysFreeString(_bstrToolTip); if (_bstrExec) SysFreeString(_bstrExec); if (_bstrScript) SysFreeString(_bstrScript); if (_bstrMenuText) SysFreeString(_bstrMenuText); if (_bstrMenuCustomize) SysFreeString(_bstrMenuCustomize); if (_bstrMenuStatusBar) SysFreeString(_bstrMenuStatusBar); if (_punkExt) _punkExt->Release(); } // Pass on the work for the toolbar button intiaztion to the base class then determine the object // type and initialize the menu information if necessary... STDMETHODIMP CToolbarExtExec::Init(REFGUID rguid) { HRESULT hr = CToolbarExt::Init(rguid); // If the baseclass initialization went OK, then we have a working button if (hr == S_OK) _bButton = TRUE; // Get app and/or script to execute (optional) _RegReadString(_hkeyThisExtension, TEXT("Exec"), &_bstrExec, TRUE); _RegReadString(_hkeyThisExtension, TEXT("Script"), &_bstrScript, TRUE); // See if we have a menu item if (_RegReadString(_hkeyThisExtension, TEXT("MenuText"), &_bstrMenuText)) { _RegReadString(_hkeyThisExtension, TEXT("MenuCustomize"), &_bstrMenuCustomize); _RegReadString(_hkeyThisExtension, TEXT("MenuStatusBar"), &_bstrMenuStatusBar); _bMenuItem = TRUE; } if (_bMenuItem || _bButton) { hr = S_OK; } return hr; } // It we're a button try passing the work on to the base class, if that doesn't cut it we'll // check the menu stuff... STDMETHODIMP CToolbarExtExec::GetProperty(SHORT iPropID, VARIANTARG * pvarProperty) { HRESULT hr = S_OK; BOOL fImple = FALSE; if (_bButton) { // If The generic button's getproperty returns S_OK then our job here is done if (CToolbarExt::GetProperty(iPropID, pvarProperty) == S_OK) fImple = TRUE; } if (_bMenuItem && !fImple) { fImple = TRUE; if (pvarProperty) VariantInit(pvarProperty); switch (iPropID) { case TMEX_CUSTOM_MENU: { if (pvarProperty) { pvarProperty->bstrVal = SysAllocString(_bstrMenuCustomize); if (pvarProperty->bstrVal) { pvarProperty->vt = VT_BSTR; } else { hr = E_OUTOFMEMORY; } } } break; case TMEX_MENUTEXT: if (pvarProperty) { pvarProperty->bstrVal = SysAllocString(_bstrMenuText); if (pvarProperty->bstrVal) { pvarProperty->vt = VT_BSTR; } else { hr = E_OUTOFMEMORY; } } break; case TMEX_STATUSBARTEXT: if (pvarProperty) { pvarProperty->bstrVal = SysAllocString(_bstrMenuStatusBar); if (pvarProperty->bstrVal) { pvarProperty->vt = VT_BSTR; } else { hr = E_OUTOFMEMORY; } } break; default: fImple = FALSE; } } if (!fImple) hr = E_NOTIMPL; return hr; } STDMETHODIMP CToolbarExtExec::SetSite(IUnknown* punkSite) { // Give the external object our site IUnknown_SetSite(_punkExt, punkSite); // Call base class return CToolbarExt::SetSite(punkSite); } STDMETHODIMP CToolbarExtExec::QueryStatus(const GUID * pguidCmdGroup, ULONG cCmds, OLECMD rgCmds[], OLECMDTEXT * pCmdText) { HRESULT hr = S_OK; // Pass query to external object if it exists IOleCommandTarget* pCmd; if (_punkExt && SUCCEEDED(_punkExt->QueryInterface(IID_IOleCommandTarget, (LPVOID*)&pCmd))) { hr = pCmd->QueryStatus(pguidCmdGroup, cCmds, rgCmds, pCmdText); pCmd->Release(); } else { // Let base class handle this hr = CToolbarExt::QueryStatus(pguidCmdGroup, cCmds, rgCmds, pCmdText); } return hr; } // Shell execute the _bstrExec STDMETHODIMP CToolbarExtExec::Exec( const GUID * pguidCmdGroup, DWORD nCmdId, DWORD nCmdexecopt, VARIANT * pvaIn, VARIANT * pvaOut ) { HRESULT hr = S_OK; // // The first time this is called, we lazy instantiate an external object if // one is registered.. This object can JIT in components and provide a // command target. // if (!_bExecCalled) { // We only do this once _bExecCalled = TRUE; BSTR bstrExtCLSID; if (_RegReadString(_hkeyThisExtension, TEXT("clsidExtension"), &bstrExtCLSID)) { // We have an extension clsid, so create the object. This gives the object an oportunity // to jit in code when its button or menu is invoked. CLSID clsidExt; if (CLSIDFromString(bstrExtCLSID, &clsidExt) == S_OK) { if (SUCCEEDED(CoCreateInstance(clsidExt, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void **)&_punkExt))) { // Give the object our site (optional) IUnknown_SetSite(_punkExt, _pisb); } } SysFreeString(bstrExtCLSID); } } // Pass command to external object if it exists IOleCommandTarget* pCmd; if (_punkExt && SUCCEEDED(_punkExt->QueryInterface(IID_IOleCommandTarget, (LPVOID*)&pCmd))) { hr = pCmd->Exec(pguidCmdGroup, nCmdId, nCmdexecopt, pvaIn, pvaOut); pCmd->Release(); } // Run a script if one was specified if(_bstrScript && _pisb) { IOleCommandTarget *poct = NULL; VARIANT varArg; varArg.vt = VT_BSTR; varArg.bstrVal = _bstrScript; hr = _pisb->QueryInterface(IID_IOleCommandTarget, (LPVOID *)&poct); if (SUCCEEDED(hr)) { // Tell MSHTML to execute the script hr = poct->Exec(&CGID_MSHTML, IDM_RUNURLSCRIPT, 0, &varArg, NULL); poct->Release(); } } // Launch executable if one was specified if (_bstrExec) { SHELLEXECUTEINFO sei = { 0 }; sei.cbSize = sizeof(sei); sei.lpFile = _bstrExec; sei.nShow = SW_SHOWNORMAL; // We are using ShellExecuteEx over ShellExecute because the Unicode version of ShellExecute // is bogus on 95/98 if (ShellExecuteExW(&sei) == FALSE) hr = E_FAIL; } return hr; }