// Copyright (c) 1996-1999 Microsoft Corporation // -------------------------------------------------------------------------- // // BUTTON.CPP // // This file has the implementation of the button client // // BOGUS: In theory, just override get_accRole() and get_accState(). // In reality, have to also override other things, mainly for the Start // button. // // Implements: // get_accChildCount // get_accChild // get_accName // get_accRole // get_accState // get_accDefaultAction // get_accKeyboardShortcut // accNavigate // accDoDefaultAction // Next // Skip // // -------------------------------------------------------------------------- #include "oleacc_p.h" #include "default.h" #include "ctors.h" #include "window.h" #include "client.h" #include "button.h" #include "menu.h" // because start button has a child that is a menu. // SendMessageTimeout timeout of 1/4 sec - should be enough time for a // program to respond, but short enough to not annoy the user if we do // block that long. // // See comments in CButton::DoDefaultAction for details on how this is // used and why it is needed. #define SENDMESSAGE_TIMEOUT 250 // -------------------------------------------------------------------------- // // CreateButtonClient() // // -------------------------------------------------------------------------- HRESULT CreateButtonClient(HWND hwnd, long idChildCur, REFIID riid, void** ppvButtonC) { CButton * pbutton; HRESULT hr; InitPv(ppvButtonC); pbutton = new CButton(hwnd, idChildCur); if (! pbutton) return(E_OUTOFMEMORY); hr = pbutton->QueryInterface(riid, ppvButtonC); if (!SUCCEEDED(hr)) delete pbutton; return(hr); } // -------------------------------------------------------------------------- // // CButton::CButton() // // -------------------------------------------------------------------------- CButton::CButton(HWND hwnd, LONG idChildCur) : CClient( CLASS_ButtonClient ) { Initialize(hwnd, idChildCur); } // -------------------------------------------------------------------------- // // SetupChildren() // // -------------------------------------------------------------------------- void CButton::SetupChildren(void) { HWND hwndFocus; HWND hwndChild; if (!InTheShell(m_hwnd, SHELL_TRAY)) { m_cChildren = 0; return; } // check to see if the start button has focus and a menu is shown. if so, // then there is one child hwndFocus = MyGetFocus(); if (m_hwnd == hwndFocus) { hwndChild = FindWindow (TEXT("#32768"),NULL); if (IsWindowVisible(hwndChild)) m_cChildren = 1; } } // -------------------------------------------------------------------------- // // CButton::get_accName() // // HACK for start button. // // -------------------------------------------------------------------------- STDMETHODIMP CButton::get_accName(VARIANT varChild, BSTR* pszName) { InitPv(pszName); if (!ValidateChild(&varChild)) return(E_INVALIDARG); if (!InTheShell(m_hwnd, SHELL_TRAY)) return(CClient::get_accName(varChild, pszName)); return(HrCreateString(STR_STARTBUTTON, pszName)); } // -------------------------------------------------------------------------- // // CButton::get_accKeyboardShortcut() // // HACK for start button // // -------------------------------------------------------------------------- STDMETHODIMP CButton::get_accKeyboardShortcut(VARIANT varChild, BSTR* pszShortcut) { InitPv(pszShortcut); if (!ValidateChild(&varChild)) return(E_INVALIDARG); if (!InTheShell(m_hwnd, SHELL_TRAY)) return(CClient::get_accKeyboardShortcut(varChild, pszShortcut)); return(HrCreateString(STR_STARTBUTTON_SHORTCUT, pszShortcut)); } // -------------------------------------------------------------------------- // // CButton::get_accChildCount() // // HACK for start button // // -------------------------------------------------------------------------- STDMETHODIMP CButton::get_accChildCount(long *pcCount) { SetupChildren(); *pcCount = m_cChildren; return(S_OK); } // -------------------------------------------------------------------------- // // CButton::get_accChild() // // HACK for start button. If the menu is visible then we'll give that // back, otherwise we'll just fall back on CClient // // -------------------------------------------------------------------------- STDMETHODIMP CButton::get_accChild(VARIANT varChild, IDispatch ** ppdispChild) { HWND hwndChild; InitPv(ppdispChild); if (!InTheShell(m_hwnd, SHELL_TRAY)) return(CClient::get_accChild(varChild,ppdispChild)); SetupChildren(); if (m_cChildren > 0) { hwndChild = FindWindow (TEXT("#32768"),NULL); if (IsWindowVisible(hwndChild)) { return (CreateMenuPopupWindow (hwndChild,0L,IID_IDispatch,(void **)ppdispChild)); } } return S_FALSE; } // -------------------------------------------------------------------------- // // CButton::accNavigate() // // HACK for start button // // -------------------------------------------------------------------------- STDMETHODIMP CButton::accNavigate(long dwNavDir, VARIANT varStart, VARIANT * pvarEnd) { HWND hwndChild; HWND hwndNext; InitPvar(pvarEnd); // // Validate--this accepts an HWND id. // if (!ValidateHwnd(&varStart) || !ValidateNavDir(dwNavDir, varStart.lVal)) return(E_INVALIDARG); if (!InTheShell(m_hwnd, SHELL_TRAY)) return(CClient::accNavigate(dwNavDir,varStart,pvarEnd)); // so this is only for the Start button. // We want to find the menu that is lowest in the z order SetupChildren(); if ((m_cChildren > 0) && (dwNavDir == NAVDIR_FIRSTCHILD || dwNavDir == NAVDIR_LASTCHILD)) { hwndChild = FindWindow(TEXT("#32768"),NULL); if (!hwndChild) return(S_FALSE); for( ; ; ) { hwndNext = FindWindowEx(NULL,hwndChild,TEXT("#32768"),NULL); if (hwndNext && IsWindowVisible(hwndNext)) hwndChild = hwndNext; else break; } if (IsWindowVisible(hwndChild)) return(GetWindowObject(hwndChild, pvarEnd)); } return(CClient::accNavigate(dwNavDir,varStart,pvarEnd)); } // -------------------------------------------------------------------------- // // CButton::get_accRole() // // -------------------------------------------------------------------------- STDMETHODIMP CButton::get_accRole(VARIANT varChild, VARIANT *pvarRole) { long lStyle; InitPvar(pvarRole); // // Validate parameters // if (! ValidateChild(&varChild)) return(E_INVALIDARG); pvarRole->vt = VT_I4; // // Get window style // lStyle = GetWindowLong(m_hwnd, GWL_STYLE); switch (lStyle & BS_TYPEMASK) { default: pvarRole->lVal = ROLE_SYSTEM_PUSHBUTTON; break; case BS_CHECKBOX: case BS_AUTOCHECKBOX: case BS_3STATE: case BS_AUTO3STATE: pvarRole->lVal = ROLE_SYSTEM_CHECKBUTTON; break; case BS_RADIOBUTTON: case BS_AUTORADIOBUTTON: pvarRole->lVal = ROLE_SYSTEM_RADIOBUTTON; break; case BS_GROUPBOX: pvarRole->lVal = ROLE_SYSTEM_GROUPING; break; } return(S_OK); } // -------------------------------------------------------------------------- // // CButton::get_accState() // // -------------------------------------------------------------------------- STDMETHODIMP CButton::get_accState(VARIANT varChild, VARIANT *pvarState) { LRESULT lResult; HRESULT hr; InitPvar(pvarState); // // Validate parameters && get window client state. // hr = CClient::get_accState(varChild, pvarState); if (!SUCCEEDED(hr)) return(hr); Assert(pvarState->vt == VT_I4); lResult = SendMessage(m_hwnd, BM_GETSTATE, 0, 0); if (lResult & BST_PUSHED) pvarState->lVal |= STATE_SYSTEM_PRESSED; if (lResult & BST_CHECKED) pvarState->lVal |= STATE_SYSTEM_CHECKED; if (lResult & BST_INDETERMINATE) pvarState->lVal |= STATE_SYSTEM_MIXED; if ((GetWindowLong(m_hwnd, GWL_STYLE) & BS_TYPEMASK) == BS_DEFPUSHBUTTON) pvarState->lVal |= STATE_SYSTEM_DEFAULT; return(S_OK); } // -------------------------------------------------------------------------- // // CButton::get_accDefaultAction() // // This is the button's name if it is a push button and not disabled. // // -------------------------------------------------------------------------- STDMETHODIMP CButton::get_accDefaultAction(VARIANT varChild, BSTR* pszDefAction) { long lStyle; InitPv(pszDefAction); // // Validate. // if (!ValidateChild(&varChild)) return(E_INVALIDARG); lStyle = GetWindowLong(m_hwnd, GWL_STYLE); if (lStyle & WS_DISABLED) return(S_FALSE); switch (lStyle & BS_TYPEMASK) { case BS_PUSHBUTTON: case BS_DEFPUSHBUTTON: case BS_PUSHBOX: case BS_OWNERDRAW: case BS_USERBUTTON: // Pushing a push button is the default return(HrCreateString(STR_BUTTON_PUSH, pszDefAction)); case BS_CHECKBOX: case BS_AUTOCHECKBOX: // Toggling a checkbox is the default if (SendMessage(m_hwnd, BM_GETSTATE, 0, 0) & BST_CHECKED) return(HrCreateString(STR_BUTTON_UNCHECK, pszDefAction)); else return(HrCreateString(STR_BUTTON_CHECK, pszDefAction)); break; case BS_RADIOBUTTON: case BS_AUTORADIOBUTTON: // Checking a radio button is the default return(HrCreateString(STR_BUTTON_CHECK, pszDefAction)); case BS_3STATE: case BS_AUTO3STATE: switch (SendMessage(m_hwnd, BM_GETCHECK, 0, 0)) { case 0: return(HrCreateString(STR_BUTTON_CHECK, pszDefAction)); case 1: return(HrCreateString(STR_BUTTON_HALFCHECK, pszDefAction)); default: return(HrCreateString(STR_BUTTON_UNCHECK, pszDefAction)); } break; } return(E_NOT_APPLICABLE); } // -------------------------------------------------------------------------- // // CButton::accDoDefaultAction() // // -------------------------------------------------------------------------- STDMETHODIMP CButton::accDoDefaultAction(VARIANT varChild) { long lStyle; // // Validate // if (!ValidateChild(&varChild)) return(E_INVALIDARG); lStyle = GetWindowLong(m_hwnd, GWL_STYLE); if (lStyle & WS_DISABLED) return(S_FALSE); switch (lStyle & BS_TYPEMASK) { case BS_PUSHBUTTON: case BS_DEFPUSHBUTTON: if (InTheShell(m_hwnd, SHELL_TRAY)) { // // You can't just click the start button; it won't do // anything if the tray isn't active except take focus // PostMessage(m_hwnd, WM_SYSCOMMAND, SC_TASKLIST, 0L); break; } // FALL THRU case BS_PUSHBOX: case BS_OWNERDRAW: case BS_USERBUTTON: case BS_CHECKBOX: case BS_AUTOCHECKBOX: case BS_RADIOBUTTON: case BS_AUTORADIOBUTTON: case BS_3STATE: case BS_AUTO3STATE: // This used to be a PostMessage, but that hung powerpoint on 9x. // (ADG#186) // // PPT has a PeekMessage-based message loop. The first time around, // they don't filter messages and use PM_NOREMOVE, and get the // message. The second time, they filter on the message that they've // just got, and use PM_REMOVE to remove it this time. However, on // 9x, this failed - so the message remained in the queue, and got // processed over and over again... // // Seems to be a 9x-only quirk - messages are fine, but // BM_CLICK and a couple of other BM_ messages (GETSTATE?) have // this problem. // // Anyhow, using a SendMessage variant instead of PostMessage // bypasses the message queue, so it's not a problem. The Timeout // version is used to stop oleacc clients blocking if the target // takes a long time to do its stuff (eg. if it starts a modal // dialog). //So using PostMessage on 9x can hang powerpoint, but using SendMessageTimeout on NT can cause the report dialog to hang. #ifdef NTONLYBUILD PostMessage( m_hwnd, BM_CLICK, 0, 0 ); #else DWORD_PTR dwResult; SendMessageTimeout( m_hwnd, BM_CLICK, 0, 0, SMTO_NORMAL, SENDMESSAGE_TIMEOUT, & dwResult ); #endif return S_OK; } return E_NOT_APPLICABLE; } // -------------------------------------------------------------------------- // // CButton::Next() // // // -------------------------------------------------------------------------- STDMETHODIMP CButton::Next(ULONG celt, VARIANT *rgvar, ULONG* pceltFetched) { HWND hwndChild; VARIANT* pvar; long cFetched; HRESULT hr; if (!InTheShell(m_hwnd, SHELL_TRAY)) return(CClient::Next(celt,rgvar,pceltFetched)); // Can be NULL if (pceltFetched) *pceltFetched = 0; pvar = rgvar; cFetched = 0; SetupChildren(); // we only ever have 1 child if (m_idChildCur > 1) return (S_FALSE); // we only have one child if we have the focus and the menu // is visible hwndChild = FindWindow(TEXT("#32768"),NULL); if (!hwndChild) return(S_FALSE); if (IsWindowVisible(hwndChild)) { hr = GetWindowObject(hwndChild, pvar); if (SUCCEEDED(hr)) { ++pvar; ++cFetched; } } // // Advance the current position // m_idChildCur = 1; // // Fill in the number fetched // if (pceltFetched) *pceltFetched += cFetched; // // Return S_FALSE if we grabbed fewer items than requested // return((cFetched < (long)celt) ? S_FALSE : S_OK); } // -------------------------------------------------------------------------- // // CButton::Skip() // // -------------------------------------------------------------------------- STDMETHODIMP CButton::Skip(ULONG celt) { if (!InTheShell (m_hwnd,SHELL_TRAY)) return (CClient::Skip(celt)); return (CAccessible::Skip(celt)); }