561 lines
15 KiB
C++
561 lines
15 KiB
C++
// 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));
|
|
}
|
|
|