#include "shellprv.h" #include "tbmenu.h" #include "isfband.h" #include "isfmenu.h" #include "mluisupp.h" #define SMFORWARD(x) if (!_psm) { return E_FAIL; } else return _psm->x class CTrackShellMenu : public ITrackShellMenu, public IShellMenu2, public IObjectWithSite, public IServiceProvider { public: // *** IUnknown *** virtual STDMETHODIMP QueryInterface(REFIID riid, LPVOID * ppvObj); virtual STDMETHODIMP_(ULONG) AddRef(void); virtual STDMETHODIMP_(ULONG) Release(void); // *** IShellMenu methods *** virtual STDMETHODIMP Initialize(IShellMenuCallback* psmc, UINT uId, UINT uIdAncestor, DWORD dwFlags); virtual STDMETHODIMP GetMenuInfo(IShellMenuCallback** ppsmc, UINT* puId, UINT* puIdAncestor, DWORD* pdwFlags); virtual STDMETHODIMP SetShellFolder(IShellFolder* psf, LPCITEMIDLIST pidlFolder, HKEY hkey, DWORD dwFlags); virtual STDMETHODIMP GetShellFolder(DWORD* pdwFlags, LPITEMIDLIST* ppidl, REFIID riid, void** ppvObj); virtual STDMETHODIMP SetMenu(HMENU hmenu, HWND hwnd, DWORD dwFlags); virtual STDMETHODIMP GetMenu(HMENU* phmenu, HWND* phwnd, DWORD* pdwFlags); virtual STDMETHODIMP InvalidateItem(LPSMDATA psmd, DWORD dwFlags); virtual STDMETHODIMP GetState(LPSMDATA psmd); virtual STDMETHODIMP SetMenuToolbar(IUnknown* punk, DWORD dwFlags); // *** ITrackShellMenu methods *** virtual STDMETHODIMP SetObscured(HWND hwndTB, IUnknown* punkBand, DWORD dwSMSetFlags); virtual STDMETHODIMP Popup(HWND hwnd, POINTL *ppt, RECTL *prcExclude, DWORD dwFlags); // *** IObjectWithSite methods *** virtual STDMETHODIMP SetSite(IUnknown* punkSite); virtual STDMETHODIMP GetSite(REFIID ridd, void** ppvObj) { *ppvObj = NULL; return E_NOTIMPL; }; // *** IServiceProvider methods *** virtual STDMETHODIMP QueryService(REFGUID guidService, REFIID riid, void **ppvObj); // *** IShellMenu2 methods *** virtual STDMETHODIMP GetSubMenu(UINT idCmd, REFIID riid, void **ppvObj); virtual STDMETHODIMP SetToolbar(HWND hwnd, DWORD dwFlags); virtual STDMETHODIMP SetMinWidth(int cxMenu); virtual STDMETHODIMP SetNoBorder(BOOL fNoBorder); virtual STDMETHODIMP SetTheme(LPCWSTR pszTheme); CTrackShellMenu(); private: virtual ~CTrackShellMenu(); IShellMenu* _psmClient; IShellMenu* _psm; IShellMenu2* _psm2; IUnknown* _punkSite; int _cRef; HMENU _hmenu; BITBOOL _fDestroyTopLevel : 1; }; typedef struct { WNDPROC pfnOriginal; IMenuBand* pmb; } MENUHOOK; #define SZ_MENUHOOKPROP TEXT("MenuHookProp") LRESULT CALLBACK MenuHookWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { MENUHOOK* pmh = (MENUHOOK*)GetProp(hwnd, SZ_MENUHOOKPROP); if (pmh) { MSG msg; LRESULT lres; msg.hwnd = hwnd; msg.message = uMsg; msg.wParam = wParam; msg.lParam = lParam; if (pmh->pmb->TranslateMenuMessage(&msg, &lres) == S_OK) return lres; wParam = msg.wParam; lParam = msg.lParam; return CallWindowProc(pmh->pfnOriginal, hwnd, uMsg, wParam, lParam); } return 0; } HRESULT HookMenuWindow(HWND hwnd, IMenuBand* pmb) { HRESULT hres = E_FAIL; ASSERT(IsWindow(hwnd)); // make sure we haven't already hooked this window if (GetProp(hwnd, SZ_MENUHOOKPROP) == NULL) { MENUHOOK* pmh = new MENUHOOK; if (pmh) { pmh->pfnOriginal = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_WNDPROC); pmh->pmb = pmb; SetProp(hwnd, SZ_MENUHOOKPROP, pmh); SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)MenuHookWndProc); hres = S_OK; } } return hres; } void UnHookMenuWindow(HWND hwnd) { MENUHOOK* pmh = (MENUHOOK*)GetProp(hwnd, SZ_MENUHOOKPROP); if (pmh) { SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR) pmh->pfnOriginal); SetProp(hwnd, SZ_MENUHOOKPROP, NULL); delete pmh; } } // This class is here to implement a "Menu Filter". We need this because the old style of // implementing obscured Menus does not work because user munges the WM_INITMENUPOPUP information // based on the relative position within the HMENU. So here we keep that information, we just hide the item. class CShellMenuCallbackWrapper : public IShellMenuCallback, public CObjectWithSite { int _cRef; IShellMenuCallback* _psmc; HWND _hwnd; RECT _rcTB; ~CShellMenuCallbackWrapper() { ATOMICRELEASE(_psmc); } public: CShellMenuCallbackWrapper(HWND hwnd, IShellMenuCallback* psmc) : _cRef(1) { _psmc = psmc; if (_psmc) _psmc->AddRef(); _hwnd = hwnd; GetClientRect(_hwnd, &_rcTB); } // *** IUnknown methods *** STDMETHODIMP QueryInterface (REFIID riid, LPVOID * ppvObj) { static const QITAB qit[] = { QITABENT(CShellMenuCallbackWrapper, IShellMenuCallback), QITABENT(CShellMenuCallbackWrapper, IObjectWithSite), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } STDMETHODIMP_(ULONG) AddRef() { _cRef++; return _cRef; } STDMETHODIMP_(ULONG) Release() { ASSERT(_cRef > 0); _cRef--; if (_cRef > 0) return _cRef; delete this; return 0; } // *** CObjectWithSite methods (override)*** STDMETHODIMP SetSite(IUnknown* punk) { IUnknown_SetSite(_psmc, punk); return S_OK; } STDMETHODIMP GetSite(REFIID riid, void** ppObj) { return IUnknown_GetSite(_psmc, riid, ppObj); } // *** IShellMenuCallback methods *** STDMETHODIMP CallbackSM(LPSMDATA psmd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HRESULT hres = S_FALSE; if (_psmc) hres = _psmc->CallbackSM(psmd, uMsg, wParam, lParam); if (uMsg == SMC_GETINFO) { SMINFO* psminfo = (SMINFO*)lParam; int iPos = (int)SendMessage(_hwnd, TB_COMMANDTOINDEX, psmd->uId, 0); if (psminfo->dwMask & SMIM_FLAGS && iPos >= 0 && !SHIsButtonObscured(_hwnd, &_rcTB, iPos)) { psminfo->dwFlags |= SMIF_HIDDEN; hres = S_OK; } } return hres; } }; STDAPI CTrackShellMenu_CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv) { HRESULT hres = E_OUTOFMEMORY; CTrackShellMenu* pObj = new CTrackShellMenu(); if (pObj) { hres = pObj->QueryInterface(riid, ppv); pObj->Release(); } return hres; } CTrackShellMenu::CTrackShellMenu() : _cRef(1) { if (SUCCEEDED(CoCreateInstance(CLSID_MenuBand, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellMenu, &_psm)))) { _psm->QueryInterface(IID_PPV_ARG(IShellMenu2, &_psm2)); } } CTrackShellMenu::~CTrackShellMenu() { ATOMICRELEASE(_psm2); ATOMICRELEASE(_psm); ATOMICRELEASE(_psmClient); ASSERT(!_punkSite); // else someone neglected to call matching SetSite(NULL) } ULONG CTrackShellMenu::AddRef() { _cRef++; return _cRef; } ULONG CTrackShellMenu::Release() { ASSERT(_cRef > 0); _cRef--; if (_cRef > 0) return _cRef; delete this; return 0; } HRESULT CTrackShellMenu::QueryInterface(REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENTMULTI(CTrackShellMenu, IShellMenu, ITrackShellMenu), QITABENT(CTrackShellMenu, ITrackShellMenu), QITABENT(CTrackShellMenu, IShellMenu2), QITABENT(CTrackShellMenu, IObjectWithSite), QITABENT(CTrackShellMenu, IServiceProvider), { 0 }, }; HRESULT hres = QISearch(this, qit, riid, ppvObj); return hres; } // *** IServiceProvider methods *** HRESULT CTrackShellMenu::QueryService(REFGUID guidService, REFIID riid, void **ppvObj) { return IUnknown_QueryService(_psm, guidService, riid, ppvObj); } // *** IShellMenu methods *** STDMETHODIMP CTrackShellMenu::Initialize(IShellMenuCallback* psmc, UINT uId, UINT uIdAncestor, DWORD dwFlags) { SMFORWARD(Initialize(psmc, uId, uIdAncestor, dwFlags)); } STDMETHODIMP CTrackShellMenu::GetMenuInfo(IShellMenuCallback** ppsmc, UINT* puId, UINT* puIdAncestor, DWORD* pdwFlags) { SMFORWARD(GetMenuInfo(ppsmc, puId, puIdAncestor, pdwFlags)); } STDMETHODIMP CTrackShellMenu::SetShellFolder(IShellFolder* psf, LPCITEMIDLIST pidlFolder, HKEY hkey, DWORD dwFlags) { SMFORWARD(SetShellFolder(psf, pidlFolder, hkey, dwFlags)); } STDMETHODIMP CTrackShellMenu::GetShellFolder(DWORD* pdwFlags, LPITEMIDLIST* ppidl, REFIID riid, void** ppvObj) { SMFORWARD(GetShellFolder(pdwFlags, ppidl, riid, ppvObj)); } STDMETHODIMP CTrackShellMenu::SetMenu(HMENU hmenu, HWND hwnd, DWORD dwFlags) { SMFORWARD(SetMenu(hmenu, hwnd, dwFlags)); } STDMETHODIMP CTrackShellMenu::GetMenu(HMENU* phmenu, HWND* phwnd, DWORD* pdwFlags) { SMFORWARD(GetMenu(phmenu, phwnd, pdwFlags)); } STDMETHODIMP CTrackShellMenu::InvalidateItem(LPSMDATA psmd, DWORD dwFlags) { SMFORWARD(InvalidateItem(psmd, dwFlags)); } STDMETHODIMP CTrackShellMenu::GetState(LPSMDATA psmd) { SMFORWARD(GetState(psmd)); } STDMETHODIMP CTrackShellMenu::SetMenuToolbar(IUnknown* punk, DWORD dwFlags) { SMFORWARD(SetMenuToolbar(punk, dwFlags)); } STDMETHODIMP CTrackShellMenu::GetSubMenu(UINT idCmd, REFIID riid, void **ppvObj) { if (_psm2) { return _psm2->GetSubMenu(idCmd, riid, ppvObj); } else { return E_NOTIMPL; } } STDMETHODIMP CTrackShellMenu::SetToolbar([in] HWND hwnd, [in] DWORD dwFlags) { if (_psm2) { return _psm2->SetToolbar(hwnd, dwFlags); } else { return E_NOTIMPL; } } STDMETHODIMP CTrackShellMenu::SetMinWidth([in] int cxMenu) { if (_psm2) { return _psm2->SetMinWidth(cxMenu); } else { return E_NOTIMPL; } } STDMETHODIMP CTrackShellMenu::SetNoBorder([in] BOOL fNoBorder) { if (_psm2) { return _psm2->SetNoBorder(fNoBorder); } else { return E_NOTIMPL; } } STDMETHODIMP CTrackShellMenu::SetTheme([in] LPCWSTR pszTheme) { if (_psm2) { return _psm2->SetTheme(pszTheme); } else { return E_NOTIMPL; } } // *** ITrackShellMenu methods *** HRESULT CTrackShellMenu::SetObscured(HWND hwndTB, IUnknown* punkBand, DWORD dwSMSetFlags) { HRESULT hr = E_OUTOFMEMORY; // Make sure we created the Inner Shell Menu if (!_psm) return hr; if (punkBand && SUCCEEDED(punkBand->QueryInterface(IID_PPV_ARG(IShellMenu, &_psmClient)))) { UINT uId, uIdAncestor; DWORD dwFlags; IShellMenuCallback* psmcb; hr = _psmClient->GetMenuInfo(&psmcb, &uId, &uIdAncestor, &dwFlags); if (SUCCEEDED(hr)) { IShellMenuCallback* psmcbClone = NULL; if (psmcb) { if (S_FALSE == psmcb->CallbackSM(NULL, SMC_GETOBJECT, (WPARAM)&IID_IShellMenuCallback, (LPARAM)(LPVOID*)&psmcbClone)) { psmcbClone = psmcb; psmcbClone->AddRef(); } } dwFlags &= ~SMINIT_HORIZONTAL; CShellMenuCallbackWrapper* psmcw = new CShellMenuCallbackWrapper(hwndTB, psmcbClone); // We want the bands to think it is: // Top level - because it has no menuband parent // Vertical - because it's not a menubar dwFlags |= SMINIT_TOPLEVEL | SMINIT_VERTICAL; hr = _psm->Initialize(psmcw, uId, ANCESTORDEFAULT, dwFlags); if (SUCCEEDED(hr)) { HWND hwndOwner; HMENU hmenuObscured; hr = _psmClient->GetMenu(&hmenuObscured, &hwndOwner, NULL); if (SUCCEEDED(hr)) { hr = _psm->SetMenu(hmenuObscured, hwndOwner, dwSMSetFlags | SMSET_DONTOWN); // Menuband takes ownership; } } if (psmcb) psmcb->Release(); if (psmcbClone) psmcbClone->Release(); if (psmcw) psmcw->Release(); } } else { IShellMenu2 *psm2; hr = _psm->QueryInterface(IID_PPV_ARG(IShellMenu2, &psm2)); if (SUCCEEDED(hr)) { hr = psm2->SetToolbar(hwndTB, dwSMSetFlags); psm2->Release(); } } return hr; } HRESULT CTrackShellMenu::Popup(HWND hwnd, POINTL *ppt, RECTL *prcExclude, DWORD dwFlags) { IMenuBand* pmb; HRESULT hres = E_INVALIDARG; if (!_psm) return hres; hres = _psm->QueryInterface(IID_PPV_ARG(IMenuBand, &pmb)); if (FAILED(hres)) return hres; HWND hwndParent = GetTopLevelAncestor(hwnd); // Did the user set a menu into the Shell Menu? HWND hwndSubclassed = NULL; GetMenu(NULL, &hwndSubclassed, NULL); if (hwndSubclassed == NULL) { // No; We need to artificially set one so that the message filtering and stuff works SetMenu(NULL, hwndParent, 0); } SetForegroundWindow(hwndParent); IMenuPopup* pmp; hres = CoCreateInstance(CLSID_MenuDeskBar, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IMenuPopup, &pmp)); if (SUCCEEDED(hres)) { IBandSite* pbs; hres = CoCreateInstance(CLSID_MenuBandSite, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IBandSite, &pbs)); if (SUCCEEDED(hres)) { hres = pmp->SetClient(pbs); if (SUCCEEDED(hres)) { IDeskBand* pdb; hres = _psm->QueryInterface(IID_PPV_ARG(IDeskBand, &pdb)); if (SUCCEEDED(hres)) { hres = pbs->AddBand(pdb); pdb->Release(); } } pbs->Release(); } // If we've got a site ourselves, have MenuDeskBar use that. if (_punkSite) IUnknown_SetSite(pmp, _punkSite); if (SUCCEEDED(hres)) { CMBMsgFilter* pmf = GetMessageFilter(); void* pvContext = GetMessageFilter()->GetContext(); hres = HookMenuWindow(hwndParent, pmb); if (SUCCEEDED(hres)) { // This collapses any modal menus before we proceed. When switching between // Chevron menus, we need to collapse the previous menu. Refer to the comment // at the function definition. pmf->ForceModalCollapse(); pmp->Popup(ppt, (LPRECTL)prcExclude, dwFlags); pmf->SetModal(TRUE); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { HRESULT hres = pmb->IsMenuMessage(&msg); if (hres == E_FAIL) { // menuband says it's time to pack up and go home. // re-post this message so that it gets handled after // we've cleaned up the menu (avoid re-entrancy issues & // let rebar restore state of chevron button to unpressed) PostMessage(msg.hwnd, msg.message, msg.wParam, msg.lParam); break; } else if (hres != S_OK) { // menuband didn't handle this one TranslateMessage(&msg); DispatchMessage(&msg); } } hres = S_OK; UnHookMenuWindow(hwndParent); // We cannot change the context when modal, so unset the modal flag so that we can undo the context block. pmf->SetModal(FALSE); pmf->SetContext(pvContext, TRUE); } pmb->Release(); } if (_psmClient) { // This is to fix a bug where if there is a cached ISHellMenu in the submenu, // and you share the callback (For example, Broweser menu callback and the // favorites menu being shared between the browser bar and the chevron menu) // when on menu collapsed, we were destroying the sub menu by doing a set site. // since we no longer do the set site on the sub menu, we need a way to say "Reset // your parent". and this is the best way. IUnknown_Exec(_psmClient, &CGID_MenuBand, MBANDCID_REFRESH, 0, NULL, NULL); } // This call is required regardless of whether we had a _punkSite above; // MenuDeskBar does its cleanup on SetSite(NULL). IUnknown_SetSite(pmp, NULL); pmp->Release(); } return hres; } // *** IObjectWithSite methods *** HRESULT CTrackShellMenu::SetSite(IUnknown* punkSite) { ASSERT(NULL == punkSite || IS_VALID_CODE_PTR(punkSite, IUnknown)); ATOMICRELEASE(_punkSite); _punkSite = punkSite; if (punkSite) punkSite->AddRef(); return S_OK; } BOOL IsISFBand(IUnknown* punk) { OLECMD rgCmds[] = { ISFBID_PRIVATEID, 0, }; IUnknown_QueryStatus(punk, &CGID_ISFBand, SIZEOF(rgCmds), rgCmds, NULL); return BOOLIFY(rgCmds[0].cmdf); } HRESULT DoISFBandStuff(CTrackShellMenu* ptsm, IUnknown* punk) { HRESULT hr = E_INVALIDARG; if (punk && ptsm) { IShellFolderBand* psfb; hr = punk->QueryInterface(IID_PPV_ARG(IShellFolderBand, &psfb)); if (SUCCEEDED(hr)) { BANDINFOSFB bi; bi.dwMask = ISFB_MASK_IDLIST | ISFB_MASK_SHELLFOLDER; hr = psfb->GetBandInfoSFB(&bi); if (SUCCEEDED(hr)) { CISFMenuCallback* pCallback = new CISFMenuCallback(); if (pCallback) { hr = pCallback->Initialize(punk); if (SUCCEEDED(hr)) { ptsm->Initialize(SAFECAST(pCallback, IShellMenuCallback*), 0, ANCESTORDEFAULT, SMINIT_VERTICAL | SMINIT_TOPLEVEL); hr = ptsm->SetShellFolder(bi.psf, bi.pidl, NULL, SMSET_COLLAPSEONEMPTY); } pCallback->Release(); } else { hr = E_OUTOFMEMORY; } bi.psf->Release(); ILFree(bi.pidl); } psfb->Release(); } } return hr; } // An API for internal use. HRESULT ToolbarMenu_Popup(HWND hwnd, LPRECT prc, IUnknown* punk, HWND hwndTB, int idMenu, DWORD dwFlags) { HRESULT hres = E_OUTOFMEMORY; CTrackShellMenu* ptsm = new CTrackShellMenu(); if (ptsm) { hres = S_OK; if (IsISFBand(punk)) { hres = DoISFBandStuff(ptsm, punk); } else if (hwndTB) { ptsm->Initialize(NULL, 0, ANCESTORDEFAULT, SMINIT_TOPLEVEL | SMINIT_VERTICAL | SMINIT_RESTRICT_DRAGDROP); hres = ptsm->SetObscured(hwndTB, punk, SMSET_TOP); } IUnknown* punkSite; if (SUCCEEDED(IUnknown_GetSite(punk, IID_PPV_ARG(IUnknown, &punkSite)))) ptsm->SetSite(punkSite); HMENU hmenu = idMenu ? SHLoadMenuPopup(HINST_THISDLL, idMenu) : NULL; if (SUCCEEDED(hres) && hmenu) hres = ptsm->SetMenu(hmenu, hwnd, SMSET_BOTTOM); if (SUCCEEDED(hres)) { DWORD dwPopupFlags = MPPF_BOTTOM; // select first/last menu item if specified if (dwFlags == DBPC_SELECTFIRST) { dwPopupFlags |= MPPF_INITIALSELECT; } else if (dwFlags == DBPC_SELECTLAST) { dwPopupFlags |= MPPF_FINALSELECT; } else if (dwFlags != 0) { VARIANT var; var.vt = VT_I4; var.lVal = dwFlags; IUnknown* punk; if (SUCCEEDED(ptsm->QueryInterface(IID_PPV_ARG(IUnknown, &punk)))) { IUnknown_QueryServiceExec(punk, SID_SMenuBandChild, &CGID_MenuBand, MBANDCID_SELECTITEM, 0, &var, NULL); punk->Release(); } } POINTL ptPop = {prc->left, prc->bottom}; hres = ptsm->Popup(hwnd, &ptPop, (LPRECTL)prc, dwPopupFlags); } ptsm->SetSite(NULL); ptsm->Release(); } return hres; }