#include "priv.h" // BUGBUG (lamadio): Conflicts with one defined in winuserp.h #undef WINEVENT_VALID //It's tripping on this... #include "winable.h" #include "apithk.h" #include "resource.h" #include "initguid.h" #include "iaccess.h" #include "mluisupp.h" CAccessible::CAccessible(HMENU hmenu, WORD wID): _hMenu(hmenu), _wID(wID), _cRef(1) { _fState = MB_STATE_TRACK; } CAccessible::CAccessible(IMenuBand* pmb): _cRef(1) { _fState = MB_STATE_MENU; _pmb = pmb; if (_pmb) { _pmb->AddRef(); } } CAccessible::CAccessible(IMenuBand* pmb, int iIndex): _cRef(1) { _fState = MB_STATE_ITEM; _iAccIndex = iIndex; _pmb = pmb; if (_pmb) { _pmb->AddRef(); } } CAccessible::~CAccessible() { ATOMICRELEASE(_pTypeInfo); ATOMICRELEASE(_pInnerAcc); switch (_fState) { case MB_STATE_TRACK: ASSERT(!_hwndMenuWindow || IsWindow(_hwndMenuWindow)); if (_hwndMenuWindow) { // Don't Destroy hmenu. It's part of a larger one... SetMenu(_hwndMenuWindow, NULL); DestroyWindow(_hwndMenuWindow); _hwndMenuWindow = NULL; } break; case MB_STATE_ITEM: ATOMICRELEASE(_pmtbItem); // Fall Through case MB_STATE_MENU: ATOMICRELEASE(_pmtbTop); ATOMICRELEASE(_pmtbBottom); ATOMICRELEASE(_psma); ATOMICRELEASE(_pmb); break; } } HRESULT CAccessible::InitAcc() { HRESULT hres = E_FAIL; if (_fInitialized) return NOERROR; _fInitialized = TRUE; // We're initialized if we fail or not... switch (_fState) { case MB_STATE_TRACK: if (EVAL(_hMenu)) { _hwndMenuWindow = CreateWindow(TEXT("static"), TEXT("MenuWindow"), WS_POPUP, 0, 0, 0, 0, NULL, _hMenu, g_hinst, NULL); if (EVAL(_hwndMenuWindow)) { IAccessible* paccChild1; hres = CreateStdAccessibleObject(_hwndMenuWindow, OBJID_MENU, IID_IAccessible, (void**)&paccChild1); if(SUCCEEDED(hres)) { VARIANT varChild; varChild.vt = VT_I4; varChild.lVal = _wID + 1; //Accesibility is 1 based // In order to get "On par" with the OleAcc's implementation of the HMENU wrapper, // we need to do this twice. Once gets us the IAccessible for the "MenuItem" on the // "Menubar". The second gets us the "Menuitem's" child. This is what we need to emulate // their heirarchy. IDispatch* pdispChild1; hres = paccChild1->get_accChild(varChild, &pdispChild1); // OLEAcc returns a Success code (S_FALSE) while initializing the out param to zero. // Explicitly test this situation. // Does this have a Child? if (hres == S_OK) { // Yes. Look for that child IAccessible* paccChild2; hres = pdispChild1->QueryInterface(IID_IAccessible, (void**)&paccChild2); // Does this have a child? if (hres == S_OK) { // Yep, then we store this guy's child... IDispatch* pdispChild2; varChild.lVal = 1; //Get the first child hres = paccChild2->get_accChild(varChild, &pdispChild2); if (hres == S_OK) { hres = pdispChild2->QueryInterface(IID_IAccessible, (void**)&_pInnerAcc); pdispChild2->Release(); } paccChild2->Release(); } pdispChild1->Release(); } paccChild1->Release(); } } } break; case MB_STATE_ITEM: case MB_STATE_MENU: hres = _pmb->QueryInterface(IID_IShellMenuAcc, (void**)&_psma); if (SUCCEEDED(hres)) { _psma->GetTop(&_pmtbTop); _psma->GetBottom(&_pmtbBottom); } if (_fState == MB_STATE_ITEM) { VARIANT varChild; _GetVariantFromChildIndex(NULL, _iAccIndex, &varChild); if (SUCCEEDED(_GetChildFromVariant(&varChild, &_pmtbItem, &_iIndex))) _idCmd = GetButtonCmd(_pmtbItem->_hwndMB, _iIndex); } break; } return hres; } /*---------------------------------------------------------- Purpose: IUnknown::AddRef method */ STDMETHODIMP_(ULONG) CAccessible::AddRef() { _cRef++; return _cRef; } /*---------------------------------------------------------- Purpose: IUnknown::Release method */ STDMETHODIMP_(ULONG) CAccessible::Release() { ASSERT(_cRef > 0); _cRef--; if (_cRef > 0) return _cRef; delete this; return 0; } STDMETHODIMP CAccessible::QueryInterface(REFIID riid, LPVOID FAR* ppvObj) { static const QITAB qit[] = { QITABENT(CAccessible, IDispatch), QITABENT(CAccessible, IAccessible), QITABENT(CAccessible, IEnumVARIANT), QITABENT(CAccessible, IOleWindow), { 0 }, }; return QISearch(this, qit, riid, ppvObj); } STDMETHODIMP CAccessible::GetTypeInfoCount(UINT FAR* pctinfo) { if (_pInnerAcc) return _pInnerAcc->GetTypeInfoCount(pctinfo); *pctinfo = 1; return NOERROR; } STDMETHODIMP CAccessible::GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo) { *pptinfo = NULL; if (_pInnerAcc) return _pInnerAcc->GetTypeInfo(itinfo, lcid, pptinfo); if (itinfo != 0) return DISP_E_BADINDEX; if (EVAL(_LoadTypeLib())) { *pptinfo = _pTypeInfo; _pTypeInfo->AddRef(); return NOERROR; } else return E_FAIL; } /*---------------------------------------------------------- Purpose: IDispatch::GetIDsOfNames method */ STDMETHODIMP CAccessible::GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, UINT cNames, LCID lcid, DISPID FAR* rgdispid) { if (_pInnerAcc) return _pInnerAcc->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid); if (IID_NULL != riid) return DISP_E_UNKNOWNINTERFACE; if (EVAL(_LoadTypeLib())) { return _pTypeInfo->GetIDsOfNames(rgszNames, cNames, rgdispid); } else return E_FAIL; } /*---------------------------------------------------------- Purpose: IDispatch::Invoke method */ STDMETHODIMP CAccessible::Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pdispparams, VARIANT FAR* pvarResult, EXCEPINFO FAR* pexcepinfo, UINT FAR* puArgErr) { if (_pInnerAcc) return _pInnerAcc->Invoke(dispidMember, riid, lcid, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr); if (IID_NULL != riid) return DISP_E_UNKNOWNINTERFACE; if (EVAL(_LoadTypeLib())) { return _pTypeInfo->Invoke(static_cast(this), dispidMember, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr); } else return E_FAIL; } BOOL CAccessible::_LoadTypeLib() { ITypeLib* pTypeLib; if (_pTypeInfo) return TRUE; if (SUCCEEDED(LoadTypeLib(L"oleacc.dll", &pTypeLib))) { pTypeLib->GetTypeInfoOfGuid(IID_IAccessible, &_pTypeInfo); ATOMICRELEASE(pTypeLib); return TRUE; } return FALSE; } /*---------------------------------------------------------- Purpose: IAccessible::get_accParent method */ STDMETHODIMP CAccessible::get_accParent(IDispatch * FAR* ppdispParent) { HRESULT hres = DISP_E_MEMBERNOTFOUND; switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accParent(ppdispParent); break; case MB_STATE_MENU: { IUnknown* punk; if (SUCCEEDED(_psma->GetParentSite(IID_IUnknown, (void**)&punk))) { IAccessible* pacc; if (SUCCEEDED(IUnknown_QueryService(punk, SID_SMenuBandParent, IID_IAccessible, (void**)&pacc))) { VARIANT varChild = {VT_I4, CHILDID_SELF}; // Init hres = pacc->get_accFocus(&varChild); if (SUCCEEDED(hres)) { hres = pacc->get_accChild(varChild, ppdispParent); } VariantClearLazy(&varChild); pacc->Release(); } else { // Another implementation headache: Accessibility requires // us to return S_FALSE when there is no parent. *ppdispParent = NULL; hres = S_FALSE; } punk->Release(); } return hres; } case MB_STATE_ITEM: // The parent of an item is the menuband itself return IUnknown_QueryService(_psma, SID_SMenuPopup, IID_IDispatch, (void**)ppdispParent); break; } return DISP_E_MEMBERNOTFOUND; } /*---------------------------------------------------------- Purpose: IAccessible::get_accChildCount method */ STDMETHODIMP CAccessible::get_accChildCount(long FAR* pChildCount) { *pChildCount = 0; switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accChildCount(pChildCount); break; case MB_STATE_MENU: { int iTopCount = ToolBar_ButtonCount(_pmtbTop->_hwndMB); int iBottomCount = ToolBar_ButtonCount(_pmtbBottom->_hwndMB); *pChildCount = (_pmtbTop != _pmtbBottom)? iTopCount + iBottomCount : iTopCount; } break; case MB_STATE_ITEM: if (_pmtbItem->v_GetFlags(_idCmd) & SMIF_SUBMENU) *pChildCount = 1; break; } return NOERROR; } /*---------------------------------------------------------- Purpose: IAccessible::get_accChild method */ STDMETHODIMP CAccessible::get_accChild(VARIANT varChildIndex, IDispatch * FAR* ppdispChild) { HRESULT hres = DISP_E_MEMBERNOTFOUND; switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accChild(varChildIndex, ppdispChild); break; case MB_STATE_MENU: { if (varChildIndex.vt == VT_I4 && varChildIndex.lVal == CHILDID_SELF) { // So this is the ONLY menthod that is allowed to fail when something is // unavailable. *ppdispChild = NULL; hres = E_INVALIDARG; } else { int iIndex; // Since it's returing an index, we don't need to test the success case _GetChildFromVariant(&varChildIndex, NULL, &iIndex); hres = _GetAccessibleItem(iIndex, ppdispChild); } } break; case MB_STATE_ITEM: if (_pmtbItem->v_GetFlags(_idCmd) & SMIF_SUBMENU) { VARIANT varChild; hres = _GetVariantFromChildIndex(_pmtbItem->_hwndMB, _iIndex, &varChild); if (SUCCEEDED(hres)) { hres = _psma->GetSubMenu(&varChild, IID_IDispatch, (void**)ppdispChild); } } else hres = E_NOINTERFACE; break; } return hres; } HRESULT CAccessible::_GetAccName(BSTR* pbstr) { IDispatch* pdisp; HRESULT hres = get_accParent(&pdisp); // Get parent can return a success code, but still fail to return a parent. // This interface sucks. if (hres == S_OK) { IAccessible* pacc; hres = pdisp->QueryInterface(IID_IAccessible, (void**)&pacc); if (SUCCEEDED(hres)) { VARIANT varChild; hres = pacc->get_accFocus(&varChild); if (SUCCEEDED(hres)) hres = pacc->get_accName(varChild, pbstr); } } return hres; } /*---------------------------------------------------------- Purpose: IAccessible::get_accName method */ STDMETHODIMP CAccessible::get_accName(VARIANT varChild, BSTR* pszName) { CMenuToolbarBase* pmtb = _pmtbItem; int idCmd = _idCmd; *pszName = NULL; switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accName(varChild, pszName); break; case MB_STATE_MENU: if (varChild.lVal == CHILDID_SELF) { if (_GetAccName(pszName) == S_FALSE) { TCHAR sz[100]; MLLoadString(IDS_ACC_APP, sz, ARRAYSIZE(sz)); *pszName = SysAllocStringT(sz); if (!*pszName) return E_OUTOFMEMORY; } return NOERROR; } else { int iIndex; if (FAILED(_GetChildFromVariant(&varChild, &pmtb, &iIndex))) return DISP_E_MEMBERNOTFOUND; idCmd = GetButtonCmd(pmtb->_hwndMB, iIndex); } // Fall Through case MB_STATE_ITEM: { TCHAR sz[MAX_PATH]; int idString = 0; TBBUTTON tbb; if (ToolBar_GetButton(pmtb->_hwndMB, _iIndex, &tbb) && tbb.fsStyle & BTNS_SEP) { idString = IDS_ACC_SEP; } else if (pmtb->GetChevronID() == _idCmd) { idString = IDS_ACC_CHEVRON; } if (idString != 0) { MLLoadString(idString, sz, ARRAYSIZE(sz)); *pszName = SysAllocStringT(sz); } else { UINT cch = SendMessage(pmtb->_hwndMB, TB_GETBUTTONTEXT, idCmd, NULL); if (cch != 0 && cch < ARRAYSIZE(sz)) { if (SendMessage(pmtb->_hwndMB, TB_GETBUTTONTEXT, idCmd, (LPARAM)sz) > 0) { SHStripMneumonic(sz); *pszName = SysAllocString(sz); } } } if (_fState == MB_STATE_MENU) pmtb->Release(); if (!*pszName) return E_OUTOFMEMORY; return NOERROR; } break; } return DISP_E_MEMBERNOTFOUND; } /*---------------------------------------------------------- Purpose: IAccessible::get_accValue method */ STDMETHODIMP CAccessible::get_accValue(VARIANT varChild, BSTR* pszValue) { switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accValue(varChild, pszValue); break; case MB_STATE_MENU: case MB_STATE_ITEM: // This does not make sense for these. break; } return DISP_E_MEMBERNOTFOUND; } /*---------------------------------------------------------- Purpose: IAccessible::get_accDescription method */ STDMETHODIMP CAccessible::get_accDescription(VARIANT varChild, BSTR FAR* pszDescription) { switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accDescription(varChild, pszDescription); break; case MB_STATE_MENU: if (FAILED(_GetAccName(pszDescription))) { TCHAR sz[100]; MLLoadString(IDS_ACC_APPMB, sz, ARRAYSIZE(sz)); *pszDescription = SysAllocStringT(sz); if (!*pszDescription) return E_OUTOFMEMORY; } break; case MB_STATE_ITEM: return get_accName(varChild, pszDescription); break; } return DISP_E_MEMBERNOTFOUND; } /*---------------------------------------------------------- Purpose: IAccessible::get_accRole method */ STDMETHODIMP CAccessible::get_accRole(VARIANT varChild, VARIANT *pvarRole) { pvarRole->vt = VT_I4; switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accRole(varChild, pvarRole); break; case MB_STATE_MENU: { BOOL fVertical; BOOL fOpen; _psma->GetState(&fVertical, &fOpen); pvarRole->lVal = ( fVertical )? ROLE_SYSTEM_MENUPOPUP : ROLE_SYSTEM_MENUBAR; return NOERROR; } case MB_STATE_ITEM: pvarRole->lVal = ROLE_SYSTEM_MENUITEM; return NOERROR; } return DISP_E_MEMBERNOTFOUND; } /*---------------------------------------------------------- Purpose: IAccessible::get_accState method */ STDMETHODIMP CAccessible::get_accState(VARIANT varChild, VARIANT *pvarState) { switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accState(varChild, pvarState); break; case MB_STATE_MENU: { // All menus can be selected, and given focus. Most will be visible. DWORD dwState = STATE_SYSTEM_FOCUSABLE; BOOL fOpen; BOOL fVertical; _psma->GetState(&fVertical, &fOpen); // Do we have a menu popped up? if (fOpen) { // Yes, then we have focus dwState |= STATE_SYSTEM_FOCUSED; } else if (fVertical) { // If we're a vertical menu without being popped up, then we're invisible. dwState |= STATE_SYSTEM_INVISIBLE; } pvarState->vt = VT_I4; pvarState->lVal = dwState; } return NOERROR; case MB_STATE_ITEM: { DWORD dwAccState = STATE_SYSTEM_FOCUSABLE; int idHotTracked = ToolBar_GetHotItem(_pmtbItem->_hwndMB); DWORD dwState = ToolBar_GetState(_pmtbItem->_hwndMB, _iIndex); if (dwState & TBSTATE_PRESSED) dwAccState |= STATE_SYSTEM_SELECTABLE | STATE_SYSTEM_FOCUSED; if (idHotTracked == _iIndex) dwAccState |= STATE_SYSTEM_HOTTRACKED; pvarState->vt = VT_I4; pvarState->lVal = dwAccState; return NOERROR; } break; } return DISP_E_MEMBERNOTFOUND; } /*---------------------------------------------------------- Purpose: IAccessible::get_accHelp method */ STDMETHODIMP CAccessible::get_accHelp(VARIANT varChild, BSTR* pszHelp) { switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accHelp(varChild, pszHelp); break; case MB_STATE_MENU: case MB_STATE_ITEM: // Not implemented break; } return DISP_E_MEMBERNOTFOUND; } /*---------------------------------------------------------- Purpose: IAccessible::get_accHelpTopic method */ STDMETHODIMP CAccessible::get_accHelpTopic(BSTR* pszHelpFile, VARIANT varChild, long* pidTopic) { switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accHelpTopic(pszHelpFile, varChild, pidTopic); break; case MB_STATE_MENU: case MB_STATE_ITEM: // Not implemented break; } return DISP_E_MEMBERNOTFOUND; } #define CH_PREFIX TEXT('&') TCHAR GetAccelerator(LPCTSTR psz, BOOL bUseDefault) { TCHAR ch = (TCHAR)-1; LPCTSTR pszAccel = psz; // then prefixes are allowed.... see if it has one do { pszAccel = StrChr(pszAccel, CH_PREFIX); if (pszAccel) { pszAccel = CharNext(pszAccel); // handle having && if (*pszAccel != CH_PREFIX) ch = *pszAccel; else pszAccel = CharNext(pszAccel); } } while (pszAccel && (ch == (TCHAR)-1)); if ((ch == (TCHAR)-1) && bUseDefault) { // Since we're unicocde, we don't need to mess with MBCS ch = *psz; } return ch; } /*---------------------------------------------------------- Purpose: IAccessible::get_accKeyboardShortcut method */ STDMETHODIMP CAccessible::get_accKeyboardShortcut(VARIANT varChild, BSTR* pszKeyboardShortcut) { CMenuToolbarBase* pmtb; int iIndex; HRESULT hres = DISP_E_MEMBERNOTFOUND; switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accKeyboardShortcut(varChild, pszKeyboardShortcut); break; case MB_STATE_ITEM: pmtb = _pmtbItem; pmtb->AddRef(); iIndex = _iIndex; goto labelGetaccel; case MB_STATE_MENU: { if (varChild.lVal != CHILDID_SELF) { if (SUCCEEDED(_GetChildFromVariant(&varChild, &pmtb, &iIndex))) { labelGetaccel: TCHAR sz[MAX_PATH]; TCHAR szAccel[100] = TEXT(""); int idCmd = GetButtonCmd(pmtb->_hwndMB, iIndex); if (S_FALSE == _psma->IsEmpty()) { UINT cch = SendMessage(pmtb->_hwndMB, TB_GETBUTTONTEXT, idCmd, NULL); if (cch != 0 && cch < ARRAYSIZE(sz)) { if (SendMessage(pmtb->_hwndMB, TB_GETBUTTONTEXT, idCmd, (LPARAM)sz) > 0) { BOOL fVertical, fOpen; _psma->GetState(&fVertical, &fOpen); if (!fVertical) { MLLoadString(IDS_ACC_ALT, szAccel, ARRAYSIZE(szAccel)); } szAccel[lstrlen(szAccel)] = GetAccelerator(sz, TRUE); szAccel[lstrlen(szAccel)] = TEXT('\0'); hres = S_OK; } } } *pszKeyboardShortcut = SysAllocStringT(szAccel); if (!*pszKeyboardShortcut) hres = E_OUTOFMEMORY; pmtb->Release(); } } } break; } return hres; } /*---------------------------------------------------------- Purpose: IAccessible::get_accFocus method */ STDMETHODIMP CAccessible::get_accFocus(VARIANT FAR * pvarFocusChild) { HRESULT hres = DISP_E_MEMBERNOTFOUND; switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accFocus(pvarFocusChild); break; case MB_STATE_MENU: { pvarFocusChild->vt = VT_I4; pvarFocusChild->lVal = CHILDID_SELF; CMenuToolbarBase* pmtbTracked; _psma->GetTracked(&pmtbTracked); if (pmtbTracked) { int iIndex = ToolBar_GetHotItem(pmtbTracked->_hwndMB); hres = _GetVariantFromChildIndex(pmtbTracked->_hwndMB, iIndex, pvarFocusChild); pmtbTracked->Release(); } } break; case MB_STATE_ITEM: // Not implemented; break; } return hres; } /*---------------------------------------------------------- Purpose: IAccessible::get_accSelection method */ STDMETHODIMP CAccessible::get_accSelection(VARIANT FAR * pvarSelectedChildren) { switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accSelection(pvarSelectedChildren); break; case MB_STATE_MENU: return get_accFocus(pvarSelectedChildren); break; case MB_STATE_ITEM: // Not implemented; break; } return DISP_E_MEMBERNOTFOUND; } /*---------------------------------------------------------- Purpose: IAccessible::get_accDefaultAction method */ STDMETHODIMP CAccessible::get_accDefaultAction(VARIANT varChild, BSTR* pszDefaultAction) { TCHAR sz[MAX_PATH]; switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->get_accDefaultAction(varChild, pszDefaultAction); break; case MB_STATE_MENU: { MLLoadString(IDS_ACC_CLOSE, sz, ARRAYSIZE(sz)); *pszDefaultAction = SysAllocStringT(sz); if (!*pszDefaultAction) return E_OUTOFMEMORY; return NOERROR; } case MB_STATE_ITEM: { if (S_OK == _psma->IsEmpty()) { sz[0] = TEXT('\0'); } else { int iId = (_pmtbItem->v_GetFlags(_idCmd) & SMIF_SUBMENU)? IDS_ACC_OPEN: IDS_ACC_EXEC; MLLoadString(iId, sz, ARRAYSIZE(sz)); } *pszDefaultAction = SysAllocStringT(sz); if (!*pszDefaultAction) return E_OUTOFMEMORY; return NOERROR; } } return DISP_E_MEMBERNOTFOUND; } /*---------------------------------------------------------- Purpose: IAccessible::accSelect method */ STDMETHODIMP CAccessible::accSelect(long flagsSelect, VARIANT varChild) { switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->accSelect(flagsSelect, varChild); break; case MB_STATE_MENU: case MB_STATE_ITEM: break; } return DISP_E_MEMBERNOTFOUND; } /*---------------------------------------------------------- Purpose: IAccessible::accLocation method */ STDMETHODIMP CAccessible::accLocation(long* pxLeft, long* pyTop, long* pcxWidth, long* pcyHeight, VARIANT varChild) { CMenuToolbarBase* pmtb; int iIndex; HRESULT hres = DISP_E_MEMBERNOTFOUND; switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, varChild); break; case MB_STATE_ITEM: pmtb = _pmtbItem; pmtb->AddRef(); iIndex = _iIndex; hres = NOERROR; goto labelGetRect; case MB_STATE_MENU: { RECT rc; if (varChild.vt == VT_I4) { if (varChild.lVal == CHILDID_SELF) { IUnknown* punk; hres = _psma->GetParentSite(IID_IUnknown, (void**)&punk); if (SUCCEEDED(hres)) { IOleWindow* poct; hres = IUnknown_QueryService(punk, SID_SMenuPopup, IID_IOleWindow, (void**)&poct); if (SUCCEEDED(hres)) { HWND hwnd; hres = poct->GetWindow(&hwnd); if (SUCCEEDED(hres)) { // Return the window rect of the menubar. GetWindowRect(hwnd, &rc); } poct->Release(); } punk->Release(); } } else { hres = _GetChildFromVariant(&varChild, &pmtb, &iIndex); if (SUCCEEDED(hres)) { labelGetRect: int idCmd = GetButtonCmd(pmtb->_hwndMB, iIndex); if (!ToolBar_GetRect(pmtb->_hwndMB, idCmd, &rc)) //1 based index hres = E_INVALIDARG; MapWindowPoints(pmtb->_hwndMB, NULL, (LPPOINT)&rc, 2); pmtb->Release(); } } if (SUCCEEDED(hres)) { *pxLeft = rc.left; *pyTop = rc.top; *pcxWidth = rc.right - rc.left; *pcyHeight = rc.bottom - rc.top; } } } break; } return hres; } /*---------------------------------------------------------- Purpose: IAccessible::accNavigate method */ STDMETHODIMP CAccessible::accNavigate(long navDir, VARIANT varStart, VARIANT * pvarEndUpAt) { HRESULT hres = DISP_E_MEMBERNOTFOUND; switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->accNavigate(navDir, varStart, pvarEndUpAt); break; case MB_STATE_MENU: return _Navigate(navDir, varStart, pvarEndUpAt); break; case MB_STATE_ITEM: { VARIANT varChild; _GetVariantFromChildIndex(NULL, _iAccIndex, &varChild); return _Navigate(navDir, varChild, pvarEndUpAt); } } return hres; } /*---------------------------------------------------------- Purpose: IAccessible::accHitTest method */ STDMETHODIMP CAccessible::accHitTest(long xLeft, long yTop, VARIANT * pvarChildAtPoint) { POINT pt = {xLeft, yTop}; switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->accHitTest(xLeft, yTop, pvarChildAtPoint); break; case MB_STATE_MENU: { if (_psma) { int iIndex; HWND hwnd = WindowFromPoint(pt); if (hwnd == _pmtbTop->_hwndMB || hwnd == _pmtbBottom->_hwndMB) { ScreenToClient(hwnd, &pt); iIndex = ToolBar_HitTest(hwnd, &pt); if (iIndex >= 0) { pvarChildAtPoint->vt = VT_DISPATCH; // This call expects the index to be an "Accessible" index which is one based VARIANT varChild; _GetVariantFromChildIndex(hwnd, iIndex, &varChild); //Since this is just returining an index, we don't need to test success _GetChildFromVariant(&varChild, NULL, &iIndex); return _GetAccessibleItem(iIndex, &pvarChildAtPoint->pdispVal); } } // Hmm, must be self pvarChildAtPoint->vt = VT_I4; pvarChildAtPoint->lVal = CHILDID_SELF; return S_OK; } } break; case MB_STATE_ITEM: { RECT rc; MapWindowPoints(NULL, _pmtbItem->_hwndMB, &pt, 1); if (ToolBar_GetRect(_pmtbItem->_hwndMB, _idCmd, &rc) && PtInRect(&rc, pt)) { pvarChildAtPoint->vt = VT_I4; pvarChildAtPoint->lVal = CHILDID_SELF; } else { pvarChildAtPoint->vt = VT_EMPTY; pvarChildAtPoint->lVal = (DWORD)(-1); } return NOERROR; } break; } return DISP_E_MEMBERNOTFOUND; } /*---------------------------------------------------------- Purpose: IAccessible::accDoDefaultAction method */ STDMETHODIMP CAccessible::accDoDefaultAction(VARIANT varChild) { switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->accDoDefaultAction(varChild); break; case MB_STATE_MENU: if (_psma) return _psma->DoDefaultAction(&varChild); break; case MB_STATE_ITEM: if (SendMessage(_pmtbItem->_hwndMB, TB_SETHOTITEM2, _iIndex, HICF_OTHER | HICF_RESELECT | HICF_TOGGLEDROPDOWN)) return NOERROR; } return DISP_E_MEMBERNOTFOUND; } /*---------------------------------------------------------- Purpose: IAccessible::put_accName method */ STDMETHODIMP CAccessible::put_accName(VARIANT varChild, BSTR szName) { switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->put_accName(varChild, szName); break; } return DISP_E_MEMBERNOTFOUND; } /*---------------------------------------------------------- Purpose: IAccessible::put_accValue method */ STDMETHODIMP CAccessible::put_accValue(VARIANT varChild, BSTR pszValue) { switch (_fState) { case MB_STATE_TRACK: if (_pInnerAcc) return _pInnerAcc->put_accValue(varChild, pszValue); break; } return DISP_E_MEMBERNOTFOUND; } HRESULT CAccessible::_Navigate(long navDir, VARIANT varStart, VARIANT * pvarEndUpAt) { ASSERT(pvarEndUpAt); int iIndex = 0; // 1 based index int iTBIndex; HRESULT hres = S_FALSE; TBBUTTONINFO tbInfo; int idCmd; VARIANT varTemp; CMenuToolbarBase* pmtb; BOOL fVertical; BOOL fOpen; tbInfo.cbSize = sizeof(TBBUTTONINFO); pvarEndUpAt->vt = VT_DISPATCH; pvarEndUpAt->pdispVal = NULL; _GetChildFromVariant(&varStart, NULL, &iIndex); _psma->GetState(&fVertical, &fOpen); if (!fVertical) { static const long navMap[] = { NAVDIR_LEFT, // Map to Up NAVDIR_RIGHT, // Map to Down NAVDIR_UP, // Map to Left NAVDIR_DOWN, // Map to Right }; if (IsInRange(navDir, NAVDIR_UP, NAVDIR_RIGHT)) navDir = navMap[navDir - NAVDIR_UP]; } switch (navDir) { case NAVDIR_NEXT: { VARIANT varVert; varVert.vt = VT_BOOL; // For the Vertical case, Next should return an error. // Is this band vertical? // Don't do this for anything but the menu case. if (_fState == MB_STATE_MENU && SUCCEEDED(IUnknown_QueryServiceExec(_psma, SID_SMenuBandParent, &CGID_MenuBand, MBANDCID_ISVERTICAL, 0, NULL, &varVert)) && varVert.boolVal == VARIANT_TRUE) { // Yes. Then punt hres = S_FALSE; break; } // Fall Through } //Fall through case NAVDIR_DOWN: hres = NOERROR; while (SUCCEEDED(hres)) { iIndex++; hres = _GetVariantFromChildIndex(NULL, iIndex, &varTemp); if (SUCCEEDED(hres)) { hres = _GetChildFromVariant(&varTemp, &pmtb, &iTBIndex); if (SUCCEEDED(hres)) { tbInfo.dwMask = TBIF_STATE | TBIF_STYLE; idCmd = GetButtonCmd(pmtb->_hwndMB, iTBIndex); ToolBar_GetButtonInfo(pmtb->_hwndMB, idCmd, &tbInfo); pmtb->Release(); if (!(tbInfo.fsState & TBSTATE_HIDDEN) && !(tbInfo.fsStyle & TBSTYLE_SEP)) { break; } } } } break; case NAVDIR_FIRSTCHILD: if (_fState == MB_STATE_ITEM) { pvarEndUpAt->vt = VT_EMPTY; pvarEndUpAt->pdispVal = NULL; hres = S_FALSE; break; } iIndex = 0; hres = NOERROR; break; case NAVDIR_LASTCHILD: if (_fState == MB_STATE_ITEM) { pvarEndUpAt->vt = VT_EMPTY; pvarEndUpAt->pdispVal = NULL; hres = S_FALSE; break; } iIndex = -1; hres = NOERROR; break; case NAVDIR_LEFT: pvarEndUpAt->vt = VT_DISPATCH; return get_accParent(&pvarEndUpAt->pdispVal); break; case NAVDIR_RIGHT: { CMenuToolbarBase* pmtb = (varStart.lVal & TOOLBAR_MASK)? _pmtbTop : _pmtbBottom; int idCmd = GetButtonCmd(pmtb->_hwndMB, (varStart.lVal & ~TOOLBAR_MASK) - 1); if (pmtb->v_GetFlags(idCmd) & SMIF_SUBMENU) { IMenuPopup* pmp; hres = _psma->GetSubMenu(&varStart, IID_IMenuPopup, (void**)&pmp); if (SUCCEEDED(hres)) { IAccessible* pacc; hres = IUnknown_QueryService(pmp, SID_SMenuBandChild, IID_IAccessible, (void**)&pacc); if (SUCCEEDED(hres)) { hres = pacc->accNavigate(NAVDIR_FIRSTCHILD, varStart, pvarEndUpAt); pacc->Release(); } pmp->Release(); } } return hres; } break; case NAVDIR_PREVIOUS: { VARIANT varVert; varVert.vt = VT_BOOL; // For the Vertical case, Pervious should return an error. // Is this band vertical? // Don't do this for anything but the menu case. if (_fState == MB_STATE_MENU && SUCCEEDED(IUnknown_QueryServiceExec(_psma, SID_SMenuBandParent, &CGID_MenuBand, MBANDCID_ISVERTICAL, 0, NULL, &varVert)) && varVert.boolVal == VARIANT_TRUE) { // Yes. Then punt hres = S_FALSE; break; } // Fall Through } //Fall through case NAVDIR_UP: hres = NOERROR; while (SUCCEEDED(hres)) { iIndex--; hres = _GetVariantFromChildIndex(NULL, iIndex, &varTemp); if (SUCCEEDED(hres)) { hres = _GetChildFromVariant(&varTemp, &pmtb, &iTBIndex); if (SUCCEEDED(hres)) { if (iTBIndex == 0) { hres = S_FALSE; //Don't navigate to self, allow the top bar to get a whack. IUnknown* punk; if (SUCCEEDED(_psma->GetParentSite(IID_IOleCommandTarget, (void**)&punk))) { IOleCommandTarget* poct; if (SUCCEEDED(IUnknown_QueryService(punk, SID_SMenuBandParent, IID_IOleCommandTarget, (void**)&poct))) { VARIANT varVert; varVert.vt = VT_BOOL; if (SUCCEEDED(poct->Exec(&CGID_MenuBand, MBANDCID_ISVERTICAL, 0, NULL, &varVert)) && varVert.boolVal == VARIANT_FALSE) { IAccessible* pacc; if (SUCCEEDED(IUnknown_QueryService(punk, SID_SMenuBandParent, IID_IAccessible, (void**)&pacc))) { VARIANT varChild = {VT_I4, CHILDID_SELF}; hres = pacc->get_accFocus(&varChild); if (SUCCEEDED(hres)) { hres = pacc->get_accChild(varChild, &pvarEndUpAt->pdispVal); } VariantClearLazy(&varChild); pacc->Release(); } } poct->Release(); } punk->Release(); } } // iTBIndex == 0 tbInfo.dwMask = TBIF_STATE | TBIF_STYLE; idCmd = GetButtonCmd(pmtb->_hwndMB, iTBIndex); ToolBar_GetButtonInfo(pmtb->_hwndMB, idCmd, &tbInfo); pmtb->Release(); if (!(tbInfo.fsState & TBSTATE_HIDDEN) && !(tbInfo.fsStyle & TBSTYLE_SEP)) { break; } } } } break; default: hres = E_INVALIDARG; } if (SUCCEEDED(hres) && S_FALSE != hres) hres = _GetAccessibleItem(iIndex, &pvarEndUpAt->pdispVal); return hres; } HRESULT CAccessible::_GetVariantFromChildIndex(HWND hwnd, int iIndex, VARIANT* pvarChild) { // First bit: Top 1, bottom 0 // Rest is index into that toolbar. pvarChild->vt = VT_I4; pvarChild->lVal = iIndex + 1; if (hwnd) { if (hwnd == _pmtbTop->_hwndMB) { pvarChild->lVal |= TOOLBAR_MASK; } } else { // Caller wants us to figure out based on index from top. int iTopCount = ToolBar_ButtonCount(_pmtbTop->_hwndMB); int iBottomCount = ToolBar_ButtonCount(_pmtbBottom->_hwndMB); int iTotalCount = (_pmtbTop != _pmtbBottom)? iTopCount + iBottomCount : iTopCount; if (iIndex < iTopCount) { pvarChild->lVal |= TOOLBAR_MASK; } else { pvarChild->lVal -= iTopCount; } // This works because: // If there are 2 toolbars, the bottom one is represented by top bit clear. // If there is only one, then it doesn't matter if it's top or bottom. // lVal is not zero based.... if (iIndex == -1) pvarChild->lVal = iTotalCount; if (iIndex >= iTotalCount) return E_FAIL; } return NOERROR; } HRESULT CAccessible::_GetChildFromVariant(VARIANT* pvarChild, CMenuToolbarBase** ppmtb, int* piIndex) { ASSERT(_pmtbTop && _pmtbBottom); ASSERT(piIndex); if (ppmtb) *ppmtb = NULL; *piIndex = -1; // Passing a NULL for an HWND returns the index from the beginning of the set. int iAdd = 0; if (pvarChild->vt != VT_I4) return E_FAIL; if (pvarChild->lVal & TOOLBAR_MASK) { if (ppmtb) { *ppmtb = _pmtbTop; } } else { if (ppmtb) { *ppmtb = _pmtbBottom; } else { iAdd = ToolBar_ButtonCount(_pmtbTop->_hwndMB); } } if (ppmtb && *ppmtb) (*ppmtb)->AddRef(); *piIndex = (pvarChild->lVal & ~TOOLBAR_MASK) + iAdd - 1; return (ppmtb && !*ppmtb) ? E_FAIL : S_OK; } HRESULT CAccessible::_GetAccessibleItem(int iIndex, IDispatch** ppdisp) { HRESULT hres = E_OUTOFMEMORY; CAccessible* pacc = new CAccessible(_pmb, iIndex); if (pacc) { hres = pacc->InitAcc(); if (SUCCEEDED(hres)) { hres = pacc->QueryInterface(IID_IDispatch, (void**) ppdisp); } pacc->Release(); } return hres; } // *** IEnumVARIANT methods *** STDMETHODIMP CAccessible::Next(unsigned long celt, VARIANT FAR* rgvar, unsigned long FAR* pceltFetched) { // Picky customer complaint. Check for NULL... if (pceltFetched) *pceltFetched = 1; return _GetVariantFromChildIndex(NULL, _iEnumIndex++, rgvar); } STDMETHODIMP CAccessible::Skip(unsigned long celt) { return E_NOTIMPL; } STDMETHODIMP CAccessible::Reset() { _iEnumIndex = 0; return NOERROR; } STDMETHODIMP CAccessible::Clone(IEnumVARIANT FAR* FAR* ppenum) { return E_NOTIMPL; } // *** IOleWindow methods *** STDMETHODIMP CAccessible::GetWindow(HWND * lphwnd) { *lphwnd = NULL; switch (_fState) { case MB_STATE_TRACK: *lphwnd = _hwndMenuWindow; break; case MB_STATE_ITEM: *lphwnd = _pmtbItem->_hwndMB; break; case MB_STATE_MENU: *lphwnd = _pmtbTop->_hwndMB; break; } if (*lphwnd) return NOERROR; return E_FAIL; } STDMETHODIMP CAccessible::ContextSensitiveHelp(BOOL fEnterMode) { return E_NOTIMPL; }