#include "priv.h" #include "browmenu.h" #include "resource.h" #include "uemapp.h" #include "mluisupp.h" #include #include "legacy.h" #define UEM_NEWITEMCOUNT 2 // Exported by shdocvw STDAPI GetLinkInfo(IShellFolder* psf, LPCITEMIDLIST pidlItem, BOOL* pfAvailable, BOOL* pfSticky); #define REG_STR_MAIN TEXT("SOFTWARE\\Microsoft\\Internet Explorer\\Main") BOOL AreIntelliMenusEnbaled() { // This is only garenteed to work on version 5 shell because the session // incrementer is located in the tray if (GetUIVersion() >= 5) { DWORD dwRest = SHRestricted(REST_INTELLIMENUS); if (dwRest != RESTOPT_INTELLIMENUS_USER) return (dwRest == RESTOPT_INTELLIMENUS_ENABLED); return SHRegGetBoolUSValue(REG_STR_MAIN, TEXT("FavIntelliMenus"), FALSE, FALSE); // Don't ignore HKCU, Disable Menus by default } else return FALSE; } CFavoritesCallback::CFavoritesCallback() : _cRef(1) { _fOffline = BOOLIFY(SHIsGlobalOffline()); } CFavoritesCallback::~CFavoritesCallback() { ASSERT(_punkSite == NULL); ASSERT(_psmFavCache == NULL); } /*---------------------------------------------------------- Purpose: IUnknown::QueryInterface method */ STDMETHODIMP CFavoritesCallback::QueryInterface (REFIID riid, LPVOID * ppvObj) { static const QITAB qit[] = { QITABENT(CFavoritesCallback, IShellMenuCallback), QITABENT(CFavoritesCallback, IObjectWithSite), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } /*---------------------------------------------------------- Purpose: IUnknown::AddRef method */ STDMETHODIMP_(ULONG) CFavoritesCallback::AddRef () { return ++_cRef; } /*---------------------------------------------------------- Purpose: IUnknown::Release method */ STDMETHODIMP_(ULONG) CFavoritesCallback::Release() { ASSERT(_cRef > 0); _cRef--; if( _cRef > 0) return _cRef; delete this; return 0; } /*---------------------------------------------------------- Purpose: IObjectWithSite::SetSite method */ STDMETHODIMP CFavoritesCallback::SetSite(IUnknown* punk) { ATOMICRELEASE(_punkSite); _punkSite = punk; if (_punkSite) { _punkSite->AddRef(); } else if (_psmFavCache) { // Since the top level menu is being destroyed, they are removing // our site. We should cleanup. DWORD dwFlags; UINT uId; UINT uIdA; _psmFavCache->GetMenuInfo(NULL, &uId, &uIdA, &dwFlags); // Tell menuband we're no longer caching it. We need to do this so ClowseDW // cleans up the menus. dwFlags &= ~SMINIT_CACHED; _psmFavCache->Initialize(NULL, uId, uIdA, dwFlags); IDeskBand* pdesk; if (SUCCEEDED(_psmFavCache->QueryInterface(IID_IDeskBand, (LPVOID*)&pdesk))) { pdesk->CloseDW(0); pdesk->Release(); } ATOMICRELEASE(_psmFavCache); } return NOERROR; } /*---------------------------------------------------------- Purpose: IShellMenuCallback::CallbackSM method */ STDMETHODIMP CFavoritesCallback::CallbackSM(LPSMDATA psmd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HRESULT hres = S_FALSE; switch (uMsg) { case SMC_INITMENU: hres = _Init(psmd->hmenu, psmd->uIdParent, psmd->punk); break; case SMC_EXITMENU: hres = _Exit(); break; case SMC_CREATE: if (psmd->uIdParent == FCIDM_MENU_FAVORITES) _fExpandoMenus = AreIntelliMenusEnbaled(); break; case SMC_DEMOTE: hres = _Demote(psmd); break; case SMC_PROMOTE: hres = _Promote(psmd); break; case SMC_NEWITEM: hres = _HandleNew(psmd); break; case SMC_SFEXEC: hres = SHNavigateToFavorite(psmd->psf, psmd->pidlItem, _punkSite, SBSP_DEFBROWSER | SBSP_DEFMODE); break; case SMC_GETINFO: hres = _GetHmenuInfo(psmd->hmenu, psmd->uId, (SMINFO*)lParam); break; case SMC_SFSELECTITEM: hres = _SelectItem(psmd->pidlFolder, psmd->pidlItem); break; case SMC_GETOBJECT: hres = _GetObject(psmd, (GUID)*((GUID*)wParam), (void**)lParam); break; case SMC_DEFAULTICON: hres = _GetDefaultIcon((LPTSTR)wParam, (int*)lParam); break; case SMC_GETSFINFO: hres = _GetSFInfo(psmd, (SMINFO*)lParam); break; case SMC_SHCHANGENOTIFY: { PSMCSHCHANGENOTIFYSTRUCT pshf = (PSMCSHCHANGENOTIFYSTRUCT)lParam; hres = _ProcessChangeNotify(psmd, pshf->lEvent, pshf->pidl1, pshf->pidl2); } break; case SMC_REFRESH: _fExpandoMenus = AreIntelliMenusEnbaled(); break; case SMC_CHEVRONGETTIP: hres = _GetTip((LPTSTR)wParam, (LPTSTR)lParam); break; case SMC_CHEVRONEXPAND: { if (_fShowingTip) { LPTSTR pszExpanded = TEXT("NO"); SHRegSetUSValue(REG_STR_MAIN, TEXT("FavChevron"), REG_SZ, pszExpanded, lstrlen(pszExpanded) * sizeof(TCHAR), SHREGSET_FORCE_HKCU); } _fShowingTip = FALSE; hres = S_OK; } break; case SMC_DISPLAYCHEVRONTIP: // Should we show the tip? _fShowingTip = SHRegGetBoolUSValue(REG_STR_MAIN, TEXT("FavChevron"), FALSE, TRUE); // Default to YES. if (_fShowingTip) { hres = S_OK; } break; case SMC_SFDDRESTRICTED: hres = _AllowDrop((IDataObject*)wParam, (HWND)lParam) ? S_FALSE : S_OK; break; } return hres; } HRESULT CFavoritesCallback::_Init(HMENU hMenu, UINT uIdParent, IUnknown* punk) { #ifdef DEBUG if (GetAsyncKeyState(VK_SHIFT) < 0) { UEMFireEvent(&UEMIID_BROWSER, UEME_CTLSESSION, UEMF_XEVENT, TRUE, -1); } #endif HRESULT hres = S_FALSE; if (SUCCEEDED(IUnknown_QueryServiceExec(_punkSite, SID_STopLevelBrowser, &CGID_MenuBand, MBANDCID_ENTERMENU, 0, NULL, NULL))) hres = S_OK; // Only do this for the favorites dropdown. This was causing // the chevron menu to be invalidated before it was created. This caused some // resize problems because the metrics were unavailable. if (uIdParent == FCIDM_MENU_FAVORITES) { // If we switched between online and offline, we need to re-init the menu BOOL fOffline = BOOLIFY(SHIsGlobalOffline()); if (fOffline ^ _fOffline || _fRefresh) { _fOffline = fOffline; IShellMenu* psm; if (SUCCEEDED(punk->QueryInterface(IID_IShellMenu, (void**)&psm))) { psm->InvalidateItem(NULL, SMINV_REFRESH); psm->Release(); } _fRefresh = FALSE; } } return hres; } HRESULT CFavoritesCallback::_Exit() { HRESULT hr = IUnknown_QueryServiceExec(_punkSite, SID_STopLevelBrowser, &CGID_MenuBand, MBANDCID_EXITMENU, 0, NULL, NULL); return SUCCEEDED(hr) ? S_OK : S_FALSE; } HRESULT CFavoritesCallback::_GetHmenuInfo(HMENU hMenu, UINT uId, SMINFO* psminfo) { if (uId == FCIDM_MENU_FAVORITES) { if (psminfo->dwMask & SMIM_FLAGS) psminfo->dwFlags |= SMIF_DROPCASCADE; } else { if (psminfo->dwMask & SMIM_FLAGS) psminfo->dwFlags |= SMIF_TRACKPOPUP; } // No item has icons if (psminfo->dwMask & SMIM_ICON) psminfo->iIcon = -1; return S_OK; } HRESULT CFavoritesCallback::_GetSFInfo(SMDATA* psmd, SMINFO* psminfo) { BOOL fAvailable; // // If we are offline and the item is not available, we set the // SMIF_ALTSTATE so that the menu item is greyed // if (psminfo->dwMask & SMIM_FLAGS) { if (_fOffline && SUCCEEDED(GetLinkInfo(psmd->psf, psmd->pidlItem, &fAvailable, NULL)) && fAvailable == FALSE) { // Not available, so grey the item psminfo->dwFlags |= SMIF_ALTSTATE; } if (_fExpandoMenus) psminfo->dwFlags |= _GetDemote(psmd); } return S_OK; } HRESULT CFavoritesCallback::_SelectItem(LPCITEMIDLIST pidlFolder, LPCITEMIDLIST pidl) { HRESULT hres = S_FALSE; LPITEMIDLIST pidlFull = ILCombine(pidlFolder, pidl); if (pidlFull) { VARIANTARG vargIn; hres = InitVariantFromIDList(&vargIn, pidlFull); if (SUCCEEDED(hres)) { hres = IUnknown_QueryServiceExec(_punkSite, SID_SMenuBandHandler, &CGID_MenuBandHandler, MBHANDCID_PIDLSELECT, 0, &vargIn, NULL); VariantClearLazy(&vargIn); } ILFree(pidlFull); } return hres; } void CFavoritesCallback::_RefreshItem(HMENU hmenu, int idCmd, IShellMenu* psm) { SMDATA smd; smd.dwMask = SMDM_HMENU; smd.hmenu = hmenu; smd.uId = idCmd; psm->InvalidateItem(&smd, SMINV_ID | SMINV_REFRESH); } HRESULT CFavoritesCallback::_GetObject(LPSMDATA psmd, REFIID riid, void** ppvOut) { HRESULT hres = S_FALSE; *ppvOut = NULL; if (IsEqualIID(IID_IShellMenu, riid)) { if (psmd->uId == FCIDM_MENU_FAVORITES) { // Do we have a cached Favorites menu? if (_psmFavCache) { // Yes we do, return it _psmFavCache->AddRef(); *ppvOut = (LPVOID)_psmFavCache; hres = S_OK; } else { // Nope; We need to create one... hres = CoCreateInstance(CLSID_MenuBand, NULL, CLSCTX_INPROC, IID_IShellMenu, (void**)&_psmFavCache); if (SUCCEEDED(hres)) { HMENU hmenu = NULL; HWND hwnd; _psmFavCache->Initialize(this, FCIDM_MENU_FAVORITES, ANCESTORDEFAULT, SMINIT_CACHED | SMINIT_VERTICAL); // We need to grab the Top HMENU portion of the Favorites menu from the current band IShellMenu* psm; if (SUCCEEDED(psmd->punk->QueryInterface(IID_IShellMenu, (LPVOID*)&psm))) { psm->GetMenu(&hmenu, &hwnd, NULL); hmenu = GetSubMenu(hmenu, GetMenuPosFromID(hmenu, FCIDM_MENU_FAVORITES)); // Delete the placeholder item (there to keep the separator from getting // lost during shbrowse menu merging, which deletes trailing separators). int iPos = GetMenuPosFromID(hmenu, FCIDM_FAVPLACEHOLDER); if (iPos >= 0) DeleteMenu(hmenu, iPos, MF_BYPOSITION); psm->Release(); } if (hmenu) { hres = _psmFavCache->SetMenu(hmenu, hwnd, SMSET_TOP | SMSET_DONTOWN); } LPITEMIDLIST pidlFav; if (SUCCEEDED(hres) && SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_FAVORITES, &pidlFav))) { IShellFolder* psf; if (SUCCEEDED(IEBindToObject(pidlFav, &psf))) { HKEY hMenuKey; DWORD dwDisp; RegCreateKeyEx(HKEY_CURRENT_USER, STRREG_FAVORITES, NULL, NULL, REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, &hMenuKey, &dwDisp); hres = _psmFavCache->SetShellFolder(psf, pidlFav, hMenuKey, SMSET_BOTTOM | SMSET_USEBKICONEXTRACTION | SMSET_HASEXPANDABLEFOLDERS); psf->Release(); } ILFree(pidlFav); } if (SUCCEEDED(hres)) { _psmFavCache->AddRef(); // We're caching this. *ppvOut = _psmFavCache; } } } } } else if (IsEqualIID(IID_IShellMenuCallback, riid)) { IShellMenuCallback* psmcb = (IShellMenuCallback*) new CFavoritesCallback; if (psmcb) { *ppvOut = (LPVOID)psmcb; hres = S_OK; } } return hres; } // Short circuit the looking up of a default icon. We're going to assume that all of them // are URLs, even folders, for the sake of speed. It gives the user feedback directly, then // we asyncronously render the real icons. HRESULT CFavoritesCallback::_GetDefaultIcon(TCHAR* psz, int* piIndex) { HRESULT hr; DWORD cchSize = MAX_PATH; if (SUCCEEDED(hr = AssocQueryString(0, ASSOCSTR_DEFAULTICON, TEXT("InternetShortcut"), NULL, psz, &cchSize))) *piIndex = PathParseIconLocation(psz); return hr; } DWORD CFavoritesCallback::_GetDemote(SMDATA* psmd) { UEMINFO uei; DWORD dwFlags = 0; if (_fExpandoMenus) { uei.cbSize = SIZEOF(uei); uei.dwMask = UEIM_HIT; if (SUCCEEDED(UEMQueryEvent(&UEMIID_BROWSER, UEME_RUNPIDL, (WPARAM)psmd->psf, (LPARAM)psmd->pidlItem, &uei))) { if (uei.cHit == 0) { dwFlags |= SMIF_DEMOTED; } } } return dwFlags; } HRESULT CFavoritesCallback::_Demote(LPSMDATA psmd) { HRESULT hres = S_FALSE; if (_fExpandoMenus) { UEMINFO uei; uei.cbSize = SIZEOF(uei); uei.dwMask = UEIM_HIT; uei.cHit = 0; hres = UEMSetEvent(&UEMIID_BROWSER, UEME_RUNPIDL, (WPARAM)psmd->psf, (LPARAM)psmd->pidlItem, &uei); } return hres; } HRESULT CFavoritesCallback::_Promote(LPSMDATA psmd) { if (_fExpandoMenus) { UEMFireEvent(&UEMIID_BROWSER, UEME_RUNPIDL, UEMF_XEVENT, (WPARAM)psmd->psf, (LPARAM)psmd->pidlItem); } return S_OK; } HRESULT CFavoritesCallback::_HandleNew(LPSMDATA psmd) { HRESULT hres = S_FALSE; if (_fExpandoMenus) { UEMINFO uei; uei.cbSize = SIZEOF(uei); uei.dwMask = UEIM_HIT; uei.cHit = UEM_NEWITEMCOUNT; hres = UEMSetEvent(&UEMIID_BROWSER, UEME_RUNPIDL, (WPARAM)psmd->psf, (LPARAM)psmd->pidlItem, &uei); } return hres; } HRESULT CFavoritesCallback::_GetTip(LPTSTR pstrTitle, LPTSTR pstrTip) { MLLoadString(IDS_CHEVRONTIPTITLE, pstrTitle, MAX_PATH); MLLoadString(IDS_CHEVRONTIP, pstrTip, MAX_PATH); // Why would this fail? if (EVAL(pstrTitle[0] != TEXT('\0') && pstrTip[0] != TEXT('\0'))) return S_OK; return S_FALSE; } // There is a duplicate of this helper in shell32\unicpp\startmnu.cpp // When modifying this, rev that one as well. void UEMRenamePidl(const GUID *pguidGrp1, IShellFolder* psf1, LPCITEMIDLIST pidl1, const GUID *pguidGrp2, IShellFolder* psf2, LPCITEMIDLIST pidl2) { UEMINFO uei; uei.cbSize = SIZEOF(uei); uei.dwMask = UEIM_HIT | UEIM_FILETIME; if (SUCCEEDED(UEMQueryEvent(pguidGrp1, UEME_RUNPIDL, (WPARAM)psf1, (LPARAM)pidl1, &uei)) && uei.cHit > 0) { UEMSetEvent(pguidGrp2, UEME_RUNPIDL, (WPARAM)psf2, (LPARAM)pidl2, &uei); uei.cHit = 0; UEMSetEvent(pguidGrp1, UEME_RUNPIDL, (WPARAM)psf1, (LPARAM)pidl1, &uei); } } // There is a duplicate of this helper in shell32\unicpp\startmnu.cpp // When modifying this, rev that one as well. void UEMDeletePidl(const GUID *pguidGrp, IShellFolder* psf, LPCITEMIDLIST pidl) { UEMINFO uei; uei.cbSize = SIZEOF(uei); uei.dwMask = UEIM_HIT; uei.cHit = 0; UEMSetEvent(pguidGrp, UEME_RUNPIDL, (WPARAM)psf, (LPARAM)pidl, &uei); } HRESULT CFavoritesCallback::_ProcessChangeNotify(SMDATA* psmd, LONG lEvent, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) { switch (lEvent) { case SHCNE_RENAMEFOLDER: case SHCNE_RENAMEITEM: { LPITEMIDLIST pidlFavorites; if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_FAVORITES, &pidlFavorites))) { if (ILIsParent(pidlFavorites, pidl1, FALSE)) { IShellFolder* psfFrom; LPCITEMIDLIST pidlFrom; if (SUCCEEDED(IEBindToParentFolder(pidl1, &psfFrom, &pidlFrom))) { if (ILIsParent(pidlFavorites, pidl2, FALSE)) { IShellFolder* psfTo; LPCITEMIDLIST pidlTo; if (SUCCEEDED(IEBindToParentFolder(pidl2, &psfTo, &pidlTo))) { // Then we need to rename it UEMRenamePidl(&UEMIID_BROWSER, psfFrom, pidlFrom, &UEMIID_BROWSER, psfTo, pidlTo); psfTo->Release(); } } else { // Otherwise, we delete it. UEMDeletePidl(&UEMIID_BROWSER, psfFrom, pidlFrom); } psfFrom->Release(); } } ILFree(pidlFavorites); } } break; case SHCNE_DELETE: case SHCNE_RMDIR: { IShellFolder* psf; LPCITEMIDLIST pidl; if (SUCCEEDED(IEBindToParentFolder(pidl1, &psf, &pidl))) { UEMDeletePidl(&UEMIID_BROWSER, psf, pidl); psf->Release(); } } break; case SHCNE_CREATE: case SHCNE_MKDIR: { IShellFolder* psf; LPCITEMIDLIST pidl; if (SUCCEEDED(IEBindToParentFolder(pidl1, &psf, &pidl))) { UEMINFO uei; uei.cbSize = SIZEOF(uei); uei.dwMask = UEIM_HIT; uei.cHit = UEM_NEWITEMCOUNT; UEMSetEvent(&UEMIID_BROWSER, UEME_RUNPIDL, (WPARAM)psf, (LPARAM)pidl, &uei); } } break; case SHCNE_EXTENDED_EVENT: { // We get this event when we are offline and the cache was changed. // We need to refresh the favorites menu when we next show it so the // correct items are greyed. SHChangeDWORDAsIDList UNALIGNED * pdwidl = (SHChangeDWORDAsIDList UNALIGNED *)pidl1; int iEvent = pdwidl->dwItem1; if (iEvent == SHCNEE_WININETCHANGED && (pdwidl->dwItem2 & (CACHE_NOTIFY_ADD_URL | CACHE_NOTIFY_DELETE_URL | CACHE_NOTIFY_DELETE_ALL | CACHE_NOTIFY_URL_SET_STICKY | CACHE_NOTIFY_URL_UNSET_STICKY))) { _fRefresh = TRUE; } } break; } return S_FALSE; } // // _Disallow drop returns S_OK if the drop shold not be allowed. S_FALSE if // the drop should be allowed. // BOOL CFavoritesCallback::_AllowDrop(IDataObject* pIDataObject, HWND hwnd) { ASSERT(NULL == hwnd || IsWindow(hwnd)); BOOL fRet = True; // Allow drop. if (hwnd && pIDataObject) { LPITEMIDLIST pidl; if (SUCCEEDED(SHPidlFromDataObject(pIDataObject, &pidl, NULL, 0))) { fRet = IEIsLinkSafe(hwnd, pidl, ILS_ADDTOFAV); ILFree(pidl); } } return fRet; }