2025-11-28 00:35:46 +09:00

866 lines
20 KiB
C++

/*************************************************************************************************
* Description: Implementation of the accessible object.
*
* See EntryPoint.cpp for a full description of this sample.
*
*
* Copyright (C) Microsoft Corporation. All rights reserved.
*
* This source code is intended only as a supplement to Microsoft
* Development Tools and/or on-line documentation. See these other
* materials for detailed information regarding Microsoft code samples.
*
* THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
* PARTICULAR PURPOSE.
*
*************************************************************************************************/
#include "AccServer.h"
AccServer::AccServer(HWND hwnd, CustomListControl* pOwnerControl):
m_pControl(pOwnerControl), m_hwnd(hwnd), m_refCount(1), m_enumCount(0)
{
// Create a standard window-based IAccessible object to handle default actions.
CreateStdAccessibleObject(hwnd, OBJID_CLIENT, IID_PPV_ARGS(&m_pStdAccessibleObject));
}
AccServer::~AccServer()
{
if (m_pStdAccessibleObject != NULL)
{
m_pStdAccessibleObject->Release();
}
}
// Set the state of the control.
//
void AccServer::SetControlIsAlive(bool alive)
{
m_controlIsAlive = alive;
}
// IUnknown methods.
//
IFACEMETHODIMP_(ULONG) AccServer::AddRef()
{
return ++m_refCount;
}
IFACEMETHODIMP_(ULONG) AccServer::Release()
{
if (--m_refCount <= 0)
{
delete this;
return 0;
}
return m_refCount;
}
IFACEMETHODIMP AccServer::QueryInterface(REFIID riid, void** ppInterface)
{
if (riid == __uuidof(IUnknown))
{
*ppInterface = static_cast<IUnknown*>(static_cast<IAccessible*>(this));
}
else if (riid == __uuidof(IAccessible))
{
*ppInterface = static_cast<IAccessible*>(this);
}
else if (riid == __uuidof(IDispatch))
{
*ppInterface = static_cast<IDispatch*>(this);
}
else if (riid == __uuidof(IEnumVARIANT))
{
*ppInterface = static_cast<IEnumVARIANT*>(this);
}
else
{
*ppInterface = NULL;
return E_NOINTERFACE;
}
(static_cast<IUnknown*>(*ppInterface))->AddRef();
return S_OK;
}
// IDispatch methods.
// Under Oleacc.dll v. 2, these don't have to be implemented.
// However, COM requires that any out parameters be cleared.
IFACEMETHODIMP AccServer::GetTypeInfoCount(UINT* pctinfo)
{
*pctinfo = 0;
return E_NOTIMPL;
}
IFACEMETHODIMP AccServer::GetTypeInfo(UINT /*itinfo*/, LCID /*lcid*/, ITypeInfo** pptinfo)
{
*pptinfo = NULL;
return E_NOTIMPL;
}
IFACEMETHODIMP AccServer::GetIDsOfNames(REFIID /*riid*/, OLECHAR** rgszNames, UINT /*cNames*/,
LCID /*lcid*/, DISPID* rgdispid)
{
*rgszNames = NULL;
*rgdispid = 0;
return E_NOTIMPL;
}
IFACEMETHODIMP AccServer::Invoke(DISPID /*dispidMember*/, REFIID /*riid*/, LCID /*lcid*/, WORD /*wFlags*/,
DISPPARAMS* pdispparams, VARIANT* pvarResult,
EXCEPINFO* pexcepinfo, UINT* puArgErr)
{
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
pdispparams = NULL;
pvarResult->vt = VT_EMPTY;
pexcepinfo = NULL;
puArgErr = NULL;
return E_NOTIMPL;
}
// IEnumVARIANT methods
IFACEMETHODIMP AccServer::Next(
ULONG celt, // Number of elements to return.
VARIANT *rgVar, // Array of returned elements.
ULONG *pCeltFetched) // Number actually returned.
{
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
ULONG childCount = static_cast<ULONG>(m_pControl->GetCount());
if (pCeltFetched != NULL)
{
*pCeltFetched = 0;
}
if (!rgVar)
{
return E_INVALIDARG;
}
ULONG fetched = 0;
for (ULONG x = 0; x < celt; x++)
{
if (++m_enumCount <= childCount)
{
rgVar[x].vt = VT_I4;
// Return the index, because in this example the list members always have sequential
// child IDs. In most cases, the value would be a nonsequential ID or an IDispatch pointer.
rgVar[x].lVal = m_enumCount;
fetched++;
}
else
{
break;
}
}
if (pCeltFetched != NULL)
{
*pCeltFetched = fetched;
}
//
// Return S_FALSE if we grabbed fewer items than requested
//
return((fetched < celt) ? S_FALSE : S_OK);
}
IFACEMETHODIMP AccServer::Skip(ULONG celt)
{
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
ULONG childCount = static_cast<ULONG>(m_pControl->GetCount());
m_enumCount += celt;
if (m_enumCount > childCount)
{
m_enumCount = childCount;
}
//
// We return S_FALSE if at the end
//
return((m_enumCount >= childCount) ? S_FALSE : S_OK);
}
IFACEMETHODIMP AccServer::Reset()
{
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
m_enumCount = 0;
return S_OK;
}
IFACEMETHODIMP AccServer::Clone(IEnumVARIANT **ppEnum)
{
*ppEnum = NULL;
AccServer* pAcc = new (std::nothrow) AccServer(m_hwnd, m_pControl);
HRESULT hr = (pAcc != NULL) ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
pAcc->m_enumCount = m_enumCount;
*ppEnum = pAcc;
}
return hr;
}
// IAccessible methods.
// Gets the parent object.
//
IFACEMETHODIMP AccServer::get_accParent(
IDispatch **ppdispParent)
{
*ppdispParent = NULL;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
return m_pStdAccessibleObject->get_accParent(ppdispParent);
}
// Gets the count of child objects or elements.
//
IFACEMETHODIMP AccServer::get_accChildCount(
long *pcountChildren)
{
*pcountChildren = 0;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
*pcountChildren = m_pControl->GetCount();
return S_OK;
}
// Gets a child object. Because the list items in this control are elements,
// not objects, returns S_FALSE.
//
IFACEMETHODIMP AccServer::get_accChild(
VARIANT varChild,
IDispatch **ppdispChild)
{
*ppdispChild = NULL;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
if ((varChild.vt != VT_I4) || (varChild.lVal > m_pControl->GetCount()))
{
return E_INVALIDARG;
}
return S_FALSE;
}
// Get the name of the control or one of its children.
IFACEMETHODIMP AccServer::get_accName(
VARIANT varChild,
BSTR *pszName)
{
*pszName = NULL;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
if ((varChild.vt != VT_I4) || (varChild.lVal > m_pControl->GetCount()))
{
*pszName = NULL;
return E_INVALIDARG;
}
// For the control itself, let the standard accessible object return the name
// assigned by the application. This is either the "caption" property or, if
// there is no caption, the text of any label.
if (varChild.lVal == CHILDID_SELF)
{
return m_pStdAccessibleObject->get_accName(varChild, pszName);
}
else
{
LISTITERATOR item = m_pControl->GetItemAt(varChild.lVal - 1);
CustomListControlItem* pItem = static_cast<CustomListControlItem*>(*item);
*pszName = SysAllocString(pItem->GetName());
if (!pszName)
{
return E_OUTOFMEMORY;
}
}
return S_OK;
}
// Get the value of the control or one of its children.
// Not implemented for a list box.
IFACEMETHODIMP AccServer::get_accValue(
VARIANT /*varChild*/,
BSTR *pszValue)
{
*pszValue = NULL;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
return DISP_E_MEMBERNOTFOUND;
}
// Get the description of the control or one of its children.
// Note that the descriptive strings given here are not typical;
// see Description Property in the documentation for information
// about when this property should be supported.
IFACEMETHODIMP AccServer::get_accDescription(
VARIANT varChild,
BSTR *pszDescription)
{
*pszDescription = NULL;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
if ((varChild.vt != VT_I4) || (varChild.lVal > m_pControl->GetCount()))
{
return E_INVALIDARG;
}
if (varChild.lVal == CHILDID_SELF)
{
*pszDescription = SysAllocString(L"List of contacts.");
}
else
{
*pszDescription = SysAllocString(L"A contact.");
}
return S_OK;
}
// Get the role of the control or one of its children.
IFACEMETHODIMP AccServer::get_accRole(
VARIANT varChild,
VARIANT *pvarRole)
{
pvarRole->vt = VT_EMPTY;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
if ((varChild.vt != VT_I4) || (varChild.lVal > m_pControl->GetCount()))
{
pvarRole->vt = VT_EMPTY;
return E_INVALIDARG;
}
pvarRole->vt = VT_I4;
if (varChild.lVal == CHILDID_SELF)
{
pvarRole->lVal = ROLE_SYSTEM_LIST;
}
else
{
pvarRole->lVal = ROLE_SYSTEM_LISTITEM;
}
return S_OK;
}
// Gets the state of the control or one of its children.
IFACEMETHODIMP AccServer::get_accState(
VARIANT varChild,
VARIANT *pvarState)
{
pvarState->vt = VT_EMPTY;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
if ((varChild.vt != VT_I4) || (varChild.lVal > m_pControl->GetCount()))
{
pvarState->vt = VT_EMPTY;
return E_INVALIDARG;
}
if (varChild.lVal == CHILDID_SELF)
{
return m_pStdAccessibleObject->get_accState(varChild, pvarState);
}
else // For list items.
{
DWORD flags = STATE_SYSTEM_SELECTABLE | STATE_SYSTEM_FOCUSABLE;
int index = static_cast<int>(varChild.lVal - 1);
if (index == m_pControl->GetSelectedIndex())
{
flags |= STATE_SYSTEM_SELECTED;
if (GetFocus() == m_hwnd)
{
flags |= STATE_SYSTEM_FOCUSED;
}
}
pvarState->vt = VT_I4;
pvarState->lVal = flags;
}
return S_OK;
}
// Get a help string for the control or one of its children.
// For simplicity, the string is not localized.
IFACEMETHODIMP AccServer::get_accHelp(
VARIANT varChild,
BSTR *pszHelp)
{
*pszHelp = NULL;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
if ((varChild.vt != VT_I4) || (varChild.lVal > m_pControl->GetCount()))
{
*pszHelp = NULL;
return E_INVALIDARG;
}
if (varChild.lVal == CHILDID_SELF)
{
*pszHelp = SysAllocString(L"Contact list.");
}
else
{
int index = static_cast<int>(varChild.lVal - 1);
LISTITERATOR item = m_pControl->GetItemAt(index);
CustomListControlItem* pItem = (CustomListControlItem*)(*item);
if (pItem->GetStatus() == Status_Online)
{
*pszHelp = SysAllocString(L"Online contact.");
}
else
{
*pszHelp = SysAllocString(L"Offline contact.");
}
}
return S_OK;
}
// Get a help file for the control or one of its children.
//
IFACEMETHODIMP AccServer::get_accHelpTopic(
BSTR *pszHelpFile,
VARIANT /*varChild*/,
long * /*pidTopic*/)
{
*pszHelpFile = NULL;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
return S_FALSE;
}
// Get a keyboard shortcut for the control.
//
IFACEMETHODIMP AccServer::get_accKeyboardShortcut(
VARIANT varChild,
BSTR *pszKeyboardShortcut)
{
*pszKeyboardShortcut = NULL;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
return m_pStdAccessibleObject->get_accKeyboardShortcut(varChild, pszKeyboardShortcut);
}
// Get the element that has the keyboard focus.
IFACEMETHODIMP AccServer::get_accFocus(VARIANT *pvarChild)
{
pvarChild->vt = VT_EMPTY;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
HRESULT hr = m_pStdAccessibleObject->get_accFocus(pvarChild);
// If the HWND does not have the focus, the variant type is set to VT_EMPTY.
if ((pvarChild->vt != VT_I4) || (FAILED(hr)))
{
return hr;
}
else
{
int index = m_pControl->GetSelectedIndex();
if (index < 0)
{
pvarChild->lVal = CHILDID_SELF;
}
else
{
// Convert to 1-based index for child ID.
pvarChild->lVal = index + 1;
}
}
return S_OK;
}
// Get the index of the selected child.
//
IFACEMETHODIMP AccServer::get_accSelection(VARIANT *pvarChildren)
{
pvarChildren->vt = VT_EMPTY;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
int childID = m_pControl->GetSelectedIndex() + 1; // Convert from 0-based.
if (childID <= 0)
{
pvarChildren->vt = VT_EMPTY;
}
else
{
pvarChildren->vt = VT_I4;
pvarChildren->lVal = childID;
}
return S_OK;
}
// Get a description of the default action.
IFACEMETHODIMP AccServer::get_accDefaultAction(
VARIANT varChild,
BSTR *pszDefaultAction)
{
*pszDefaultAction = NULL;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
if ((varChild.vt != VT_I4) || (varChild.lVal > m_pControl->GetCount()))
{
*pszDefaultAction = NULL;
return E_INVALIDARG;
}
if (varChild.lVal == CHILDID_SELF)
{
*pszDefaultAction = NULL;
return DISP_E_MEMBERNOTFOUND;
}
else
{
*pszDefaultAction = SysAllocString(L"Double-click");
}
return S_OK;
}
// Select an item. Allow only single selection.
IFACEMETHODIMP AccServer::accSelect(
long flagsSelect, VARIANT varChild)
{
// Check parameters. We don't support the following:
// SELFLAG_NONE
// SELFLAG_ADDSELECTION
// SELFLAG_REMOVESELECTION
// SELFLAG_EXTENDSELECTION
// SELFLAG_VALID
DWORD allowedFlags = SELFLAG_TAKEFOCUS | SELFLAG_TAKESELECTION;
if ((flagsSelect | allowedFlags) != allowedFlags)
{
return E_INVALIDARG;
}
if ((varChild.vt != VT_I4) || (varChild.lVal > m_pControl->GetCount()))
{
return E_INVALIDARG;
}
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
// Move the focus to the list box.
SetFocus(m_hwnd);
// Move the selection if called on to do so.
if (((flagsSelect & (SELFLAG_TAKESELECTION | SELFLAG_TAKEFOCUS)) != 0)
&& (varChild.lVal != CHILDID_SELF))
{
int selection = static_cast<int>(varChild.lVal) - 1;
m_pControl->SelectItem(selection);
}
return S_OK;
}
// Get the location of the control or the list item.
IFACEMETHODIMP AccServer::accLocation(
long *pxLeft,
long *pyTop,
long *pcxWidth,
long *pcyHeight,
VARIANT varChild)
{
*pxLeft = 0;
*pyTop = 0;
*pcxWidth = 0;
*pcyHeight = 0;
if ((varChild.vt != VT_I4) || (varChild.lVal > m_pControl->GetCount()))
{
return E_INVALIDARG;
}
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
if (varChild.lVal == CHILDID_SELF)
{
return m_pStdAccessibleObject->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, varChild);
}
else
{
RECT rect;
if (m_pControl->GetItemScreenRect(varChild.lVal - 1, &rect) == FALSE)
{
return E_INVALIDARG;
}
else
{
*pxLeft = rect.left;
*pyTop = rect.top;
*pcxWidth = rect.right - rect.left;
*pcyHeight = rect.bottom - rect.top;
return S_OK;
}
}
}
// Navigate through the tree.
IFACEMETHODIMP AccServer::accNavigate(
long navDir,
VARIANT varStart,
VARIANT *pvarEndUpAt)
{
// Default value.
pvarEndUpAt->vt = VT_EMPTY;
if ((varStart.vt != VT_I4) || (varStart.lVal > m_pControl->GetCount()))
{
return E_INVALIDARG;
}
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
switch (navDir)
{
case NAVDIR_FIRSTCHILD:
if ((varStart.lVal == CHILDID_SELF) && (m_pControl->GetCount() > 0))
{
pvarEndUpAt->vt = VT_I4;
pvarEndUpAt->lVal = 1;
}
else
{
return S_FALSE;
}
break;
case NAVDIR_LASTCHILD:
if ((varStart.lVal == CHILDID_SELF) && (m_pControl->GetCount() > 0))
{
pvarEndUpAt->vt = VT_I4;
pvarEndUpAt->lVal = m_pControl->GetCount();
}
else
{
return S_FALSE;
}
break;
case NAVDIR_NEXT:
case NAVDIR_DOWN:
if (varStart.lVal != CHILDID_SELF)
{
pvarEndUpAt->vt = VT_I4;
pvarEndUpAt->lVal = varStart.lVal + 1;
// Out of range.
if (pvarEndUpAt->lVal > m_pControl->GetCount())
{
pvarEndUpAt->vt = VT_EMPTY;
return S_FALSE;
}
}
else // Call through to method on standard container.
{
return m_pStdAccessibleObject->accNavigate(navDir, varStart, pvarEndUpAt);
}
break;
case NAVDIR_PREVIOUS:
case NAVDIR_UP:
if (varStart.lVal != CHILDID_SELF)
{
pvarEndUpAt->vt = VT_I4;
pvarEndUpAt->lVal = varStart.lVal - 1;
// Out of range.
if (pvarEndUpAt->lVal < 1)
{
pvarEndUpAt->vt = VT_EMPTY;
return S_FALSE;
}
}
else // Call through to method on standard container.
{
return m_pStdAccessibleObject->accNavigate(navDir, varStart, pvarEndUpAt);
}
break;
// Unsupported directions.
case NAVDIR_LEFT:
case NAVDIR_RIGHT:
if (varStart.lVal == CHILDID_SELF)
{
return m_pStdAccessibleObject->accNavigate(navDir, varStart, pvarEndUpAt);
}
else
{
pvarEndUpAt->vt = VT_EMPTY;
return S_FALSE;
}
break;
}
return S_OK;
}
IFACEMETHODIMP AccServer::accHitTest(
long xLeft,
long yTop,
VARIANT *pvarChild)
{
pvarChild->vt = VT_EMPTY;
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
// Does the control window contain the point?
// Note: don't use WindowFromPoint, as it may return a transparent window such as
// a group box.
RECT controlRect;
GetWindowRect(m_hwnd, &controlRect);
BOOL inWindow = ((xLeft >= controlRect.left) && (xLeft <= controlRect.right)
&& (yTop >= controlRect.top) && (yTop <= controlRect.bottom));
// Not in our window.
if (!inWindow)
{
return S_FALSE;
}
else // In our window; return list item, or self if in blank space.
{
pvarChild->vt = VT_I4;
POINT pt;
pt.x = xLeft;
pt.y = yTop;
ScreenToClient(m_hwnd, &pt);
int index = m_pControl->IndexFromY(pt.y);
if (index >= 0)
{
pvarChild->lVal = index + 1;
}
else
{
pvarChild->lVal = CHILDID_SELF;
}
return S_OK;
}
}
IFACEMETHODIMP AccServer::accDoDefaultAction(
VARIANT varChild)
{
if ((varChild.vt != VT_I4) || (varChild.lVal > m_pControl->GetCount()))
{
return E_INVALIDARG;
}
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
if (varChild.lVal != CHILDID_SELF)
{
// Because our sample action is to open a dialog box (thus blocking),
// do it indirectly. First select the item.
if (SUCCEEDED(accSelect(SELFLAG_TAKESELECTION, varChild)))
{
PostMessage(m_hwnd, CUSTOMLB_DEFERDOUBLECLICK, 0, 0);
}
}
return S_OK;
}
IFACEMETHODIMP AccServer::put_accName(
VARIANT /*varChild*/,
BSTR /*szName*/)
{
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
return E_NOTIMPL;
}
IFACEMETHODIMP AccServer::put_accValue(
VARIANT /*varChild*/,
BSTR /*szValue*/)
{
if (!m_controlIsAlive)
{
return RPC_E_DISCONNECTED;
}
return E_NOTIMPL;
}