andlabs-ui/wintable/accessibility.h

597 lines
17 KiB
C
Raw Normal View History

// 24 december 2014
// notes:
// - TODO figure out what to do about header
// - a row extends as far right as the right edge of the last cell in the row; anything to the right of that is treated as table space (just like with mouse selection)
// - this has the added effect that hit-testing can only ever return either the table or a cell, never a row
// TODOs:
// - make sure E_POINTER and RPC_E_DISCONNECTED are correct returns for IAccessible
// uncomment this to debug table linked list management
2015-02-15 14:37:16 -06:00
//#define TABLE_DEBUG_LINKEDLIST
// TODO get rid of this
typedef struct tableAccWhat tableAccWhat;
struct tableAccWhat {
LONG role;
intptr_t row;
intptr_t column;
};
struct tableAcc {
const IAccessibleVtbl *vtbl;
ULONG refcount;
struct table *t;
IAccessible *std;
tableAccWhat what;
// the list of currently active accessibility objects is a doubly linked list
struct tableAcc *prev;
struct tableAcc *next;
};
#ifdef TABLE_DEBUG_LINKEDLIST
void list(struct table *t)
{
struct tableAcc *ta;
printf("\n");
if (t->firstAcc == NULL) {
printf("\tempty\n");
return;
}
printf("\t-> ");
for (ta = t->firstAcc; ta != NULL; ta = ta->next)
printf("%p ", ta);
printf("\n\t<- ");
for (ta = t->firstAcc; ta->next != NULL; ta = ta->next)
;
for (; ta != NULL; ta = ta->prev)
printf("%p ", ta);
printf("\n");
}
#endif
// called after each allocation
static struct tableAcc *newTableAcc(struct table *t, LONG role, intptr_t row, intptr_t column);
// common validation for accessibility functions that take varChild
// also normalizes what as if varChild == CHILDID_SELF
static HRESULT normalizeWhat(struct tableAcc *ta, VARIANT varChild, tableAccWhat *what)
{
LONG cid;
if (varChild.vt != VT_I4)
return E_INVALIDARG;
cid = varChild.lVal;
if (cid == CHILDID_SELF)
return S_OK;
cid--;
if (cid < 0)
return E_INVALIDARG;
switch (what->role) {
case ROLE_SYSTEM_TABLE:
// TODO +1?
if (cid >= ta->t->count)
return E_INVALIDARG;
what->role = ROLE_SYSTEM_ROW;
what->row = (intptr_t) cid;
what->column = -1;
break;
case ROLE_SYSTEM_ROW:
case ROLE_SYSTEM_CELL:
// TODO
2015-02-16 03:51:17 -06:00
// TODO also figure out what to do if the current row/cell become invalid (rows being removed, etc.)
break;
}
return S_OK;
}
#define TA ((struct tableAcc *) this)
2014-12-24 19:55:13 -06:00
static HRESULT STDMETHODCALLTYPE tableAccQueryInterface(IAccessible *this, REFIID riid, void **ppvObject)
{
if (ppvObject == NULL)
return E_POINTER;
if (IsEqualIID(riid, &IID_IUnknown) ||
IsEqualIID(riid, &IID_IDispatch) ||
IsEqualIID(riid, &IID_IAccessible)) {
IAccessible_AddRef(this);
*ppvObject = (void *) this;
return S_OK;
}
*ppvObject = NULL;
return E_NOINTERFACE;
}
// TODO use InterlockedIncrement()/InterlockedDecrement() for these?
2014-12-24 19:55:13 -06:00
static ULONG STDMETHODCALLTYPE tableAccAddRef(IAccessible *this)
{
TA->refcount++;
2014-12-25 14:56:06 -06:00
// TODO correct?
return TA->refcount;
}
2014-12-24 19:55:13 -06:00
static ULONG STDMETHODCALLTYPE tableAccRelease(IAccessible *this)
{
TA->refcount--;
if (TA->refcount == 0) {
struct tableAcc *prev, *next;
#ifdef TABLE_DEBUG_LINKEDLIST
if (TA->t != NULL) { printf("before delete:"); list(TA->t); }
#endif
if (TA->t != NULL && TA->t->firstAcc == TA)
TA->t->firstAcc = TA->next;
prev = TA->prev;
next = TA->next;
if (prev != NULL)
prev->next = next;
if (next != NULL)
next->prev = prev;
#ifdef TABLE_DEBUG_LINKEDLIST
if (TA->t != NULL) { printf("after delete:"); list(TA->t); }
#endif
if (TA->std != NULL)
IAccessible_Release(TA->std);
tableFree(TA, "error freeing Table accessibility object");
return 0;
}
return TA->refcount;
}
// IDispatch
// TODO make sure relegating these to the standard accessibility object is harmless
2014-12-24 19:55:13 -06:00
static HRESULT STDMETHODCALLTYPE tableAccGetTypeInfoCount(IAccessible *this, UINT *pctinfo)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_GetTypeInfoCount(TA->std, pctinfo);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccGetTypeInfo(IAccessible *this, UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_GetTypeInfo(TA->std, iTInfo, lcid, ppTInfo);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccGetIDsOfNames(IAccessible *this, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_GetIDsOfNames(TA->std, riid, rgszNames, cNames, lcid, rgDispId);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccInvoke(IAccessible *this, DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_Invoke(TA->std, dispIdMember, riid, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
2014-12-24 19:55:13 -06:00
}
// IAccessible
static HRESULT STDMETHODCALLTYPE tableAccget_accParent(IAccessible *this, IDispatch **ppdispParent)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_get_accParent(TA->std, ppdispParent);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccget_accChildCount(IAccessible *this, long *pcountChildren)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
//TODO
if (pcountChildren == NULL)
return E_POINTER;
*pcountChildren = 0;
return S_OK;
return IAccessible_get_accChildCount(TA->std, pcountChildren);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccget_accChild(IAccessible *this, VARIANT varChild, IDispatch **ppdispChild)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_get_accChild(TA->std, varChild, ppdispChild);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccget_accName(IAccessible *this, VARIANT varChild, BSTR *pszName)
{
printf("get_accName() t %p std %p\n", TA->t, TA->std);
if (TA->t == NULL || TA->std == NULL) {
printf("returning RPC_E_DISCONNECTED\n");
// TODO set values on error
return RPC_E_DISCONNECTED;
}
printf("running main function\n");
//TODO
if (pszName == NULL)
return E_POINTER;
*pszName = SysAllocString(L"accessible table");
return S_OK;
return IAccessible_get_accName(TA->std, varChild, pszName);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccget_accValue(IAccessible *this, VARIANT varChild, BSTR *pszValue)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_get_accValue(TA->std, varChild, pszValue);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccget_accDescription(IAccessible *this, VARIANT varChild, BSTR *pszDescription)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_get_accDescription(TA->std, varChild, pszDescription);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccget_accRole(IAccessible *this, VARIANT varChild, VARIANT *pvarRole)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
//TODO
if (pvarRole == NULL)
return E_POINTER;
pvarRole->vt = VT_I4;
pvarRole->lVal = TA->what.role;
return S_OK;
return IAccessible_get_accRole(TA->std, varChild, pvarRole);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccget_accState(IAccessible *this, VARIANT varChild, VARIANT *pvarState)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_get_accState(TA->std, varChild, pvarState);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccget_accHelp(IAccessible *this, VARIANT varChild, BSTR *pszHelp)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_get_accHelp(TA->std, varChild, pszHelp);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccget_accHelpTopic(IAccessible *this, BSTR *pszHelpFile, VARIANT varChild, long *pidTopic)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_get_accHelpTopic(TA->std, pszHelpFile, varChild, pidTopic);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccget_accKeyboardShortcut(IAccessible *this, VARIANT varChild, BSTR *pszKeyboardShortcut)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_get_accKeyboardShortcut(TA->std, varChild, pszKeyboardShortcut);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccget_accFocus(IAccessible *this, VARIANT *pvarChild)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_get_accFocus(TA->std, pvarChild);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccget_accSelection(IAccessible *this, VARIANT *pvarChildren)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_get_accSelection(TA->std, pvarChildren);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccget_accDefaultAction(IAccessible *this, VARIANT varChild, BSTR *pszDefaultAction)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_get_accDefaultAction(TA->std, varChild, pszDefaultAction);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccaccSelect(IAccessible *this, long flagsSelect, VARIANT varChild)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_accSelect(TA->std, flagsSelect, varChild);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccaccLocation(IAccessible *this, long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varChild)
{
HRESULT hr;
tableAccWhat what;
RECT r;
POINT pt;
struct rowcol rc;
if (pxLeft == NULL || pyTop == NULL || pcxWidth == NULL || pcyHeight == NULL)
return E_POINTER;
// TODO set the out parameters to zero?
if (TA->t == NULL || TA->std == NULL)
return RPC_E_DISCONNECTED;
what = TA->what;
hr = normalizeWhat(TA, varChild, &what);
if (hr != S_OK)
return hr;
switch (what.role) {
case ROLE_SYSTEM_TABLE:
return IAccessible_accLocation(TA->std, pxLeft, pyTop, pcxWidth, pcyHeight, varChild);
case ROLE_SYSTEM_ROW:
// TODO actually write this
return E_FAIL;
case ROLE_SYSTEM_CELL:
rc.row = what.row;
rc.column = what.column;
if (!rowColumnToClientRect(TA->t, rc, &r)) {
// TODO what do we do here?
// TODO we have to return something indicating that the object is off-screen
}
// TODO intersect with client rect?
break;
}
pt.x = r.left;
pt.y = r.top;
if (ClientToScreen(TA->t->hwnd, &pt) == 0)
return HRESULT_FROM_WIN32(GetLastError());
*pxLeft = pt.x;
*pyTop = pt.y;
*pcxWidth = r.right - r.left;
*pcyHeight = r.bottom - r.top;
return S_OK;
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccaccNavigate(IAccessible *this, long navDir, VARIANT varStart, VARIANT *pvarEndUpAt)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_accNavigate(TA->std, navDir, varStart, pvarEndUpAt);
2014-12-24 19:55:13 -06:00
}
2015-02-16 03:51:17 -06:00
// TODO should this ever return parents?
2014-12-24 19:55:13 -06:00
static HRESULT STDMETHODCALLTYPE tableAccaccHitTest(IAccessible *this, long xLeft, long yTop, VARIANT *pvarChild)
{
POINT pt;
struct rowcol rc;
RECT r;
if (pvarChild == NULL)
return E_POINTER;
// TODO set pvarChild to an invalid value?
if (TA->t == NULL || TA->std == NULL)
return RPC_E_DISCONNECTED;
pt.x = xLeft;
pt.y = yTop;
if (ScreenToClient(TA->t->hwnd, &pt) == 0)
return HRESULT_FROM_WIN32(GetLastError());
// first see if the point is even IN the table
if (GetClientRect(TA->t->hwnd, &r) == 0)
return HRESULT_FROM_WIN32(GetLastError());
r.top += TA->t->headerHeight;
if (PtInRect(&r, pt) == 0)
goto outside;
// now see if we're in a cell or in the table
// TODO also handle GetLastError() here
rc = clientCoordToRowColumn(TA->t, pt);
switch (TA->what.role) {
case ROLE_SYSTEM_TABLE:
// either the table or the cell
if (rc.row == -1 || rc.column == -1)
goto self;
goto specificCell;
case ROLE_SYSTEM_ROW:
// a specific cell, but only if in the same row
// TODO actually do we really need these spurious rc.column ==/!= -1 checks?
if (rc.row == TA->what.row) {
if (rc.column == -1)
// TODO de-GetLastError() this
panic("impossible situation TODO write this");
goto specificCell;
}
goto outside;
case ROLE_SYSTEM_CELL:
if (rc.row == TA->what.row && rc.column == TA->what.column)
goto self;
goto outside;
}
// TODO actually do this right
// TODO un-GetLastError() this
panic("impossible blah blah blah TODO write this");
return E_FAIL;
outside:
pvarChild->vt = VT_EMPTY;
return S_FALSE;
self:
pvarChild->vt = VT_I4;
pvarChild->lVal = CHILDID_SELF;
return S_OK;
specificCell:
pvarChild->vt = VT_DISPATCH;
// TODO GetLastError() here too
pvarChild->pdispVal = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_CELL, rc.row, rc.column);
return S_OK;
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccaccDoDefaultAction(IAccessible *this, VARIANT varChild)
{
HRESULT hr;
tableAccWhat what;
if (TA->t == NULL || TA->std == NULL)
return RPC_E_DISCONNECTED;
what = TA->what;
hr = normalizeWhat(TA, varChild, &what);
if (hr != S_OK)
return hr;
if (what.role == ROLE_SYSTEM_CELL)
; // TODO implement this for checkbox cells?
return DISP_E_MEMBERNOTFOUND;
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccput_accName(IAccessible *this, VARIANT varChild, BSTR szName)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_put_accName(TA->std, varChild, szName);
2014-12-24 19:55:13 -06:00
}
static HRESULT STDMETHODCALLTYPE tableAccput_accValue(IAccessible *this, VARIANT varChild, BSTR szValue)
{
if (TA->t == NULL || TA->std == NULL) {
// TODO set values on error
return RPC_E_DISCONNECTED;
}
return IAccessible_put_accValue(TA->std, varChild, szValue);
2014-12-24 19:55:13 -06:00
}
static const IAccessibleVtbl tableAccVtbl = {
.QueryInterface = tableAccQueryInterface,
.AddRef = tableAccAddRef,
.Release = tableAccRelease,
2014-12-24 19:55:13 -06:00
.GetTypeInfoCount = tableAccGetTypeInfoCount,
.GetTypeInfo = tableAccGetTypeInfo,
.GetIDsOfNames = tableAccGetIDsOfNames,
.Invoke = tableAccInvoke,
.get_accParent = tableAccget_accParent,
.get_accChildCount = tableAccget_accChildCount,
.get_accChild = tableAccget_accChild,
.get_accName = tableAccget_accName,
.get_accValue = tableAccget_accValue,
.get_accDescription = tableAccget_accDescription,
.get_accRole = tableAccget_accRole,
.get_accState = tableAccget_accState,
.get_accHelp = tableAccget_accHelp,
.get_accHelpTopic = tableAccget_accHelpTopic,
.get_accKeyboardShortcut = tableAccget_accKeyboardShortcut,
.get_accFocus = tableAccget_accFocus,
.get_accSelection = tableAccget_accSelection,
.get_accDefaultAction = tableAccget_accDefaultAction,
.accSelect = tableAccaccSelect,
.accLocation = tableAccaccLocation,
.accNavigate = tableAccaccNavigate,
.accHitTest = tableAccaccHitTest,
.accDoDefaultAction = tableAccaccDoDefaultAction,
.put_accName = tableAccput_accName,
.put_accValue = tableAccput_accValue,
};
static struct tableAcc *newTableAcc(struct table *t, LONG role, intptr_t row, intptr_t column)
{
struct tableAcc *ta;
HRESULT hr;
IAccessible *std;
ta = (struct tableAcc *) tableAlloc(sizeof (struct tableAcc), "error creating Table accessibility object");
ta->vtbl = &tableAccVtbl;
ta->refcount = 1;
ta->t = t;
// TODO adjust last argument
hr = CreateStdAccessibleObject(t->hwnd, OBJID_CLIENT, &IID_IAccessible, (void **) (&std));
if (hr != S_OK)
// TODO panichresult
panic("error creating standard accessible object for Table");
ta->std = std;
ta->what.role = role;
ta->what.row = row;
ta->what.column = column;
#ifdef TABLE_DEBUG_LINKEDLIST
printf("before add:"); list(t);
#endif
ta->next = t->firstAcc;
if (t->firstAcc != NULL)
t->firstAcc->prev = ta;
t->firstAcc = ta;
#ifdef TABLE_DEBUG_LINKEDLIST
printf("after add:"); list(t);
#endif
return ta;
}
static void invalidateTableAccs(struct table *t)
{
struct tableAcc *ta;
for (ta = t->firstAcc; ta != NULL; ta = ta->next) {
ta->t = NULL;
IAccessible_Release(ta->std);
ta->std = NULL;
}
t->firstAcc = NULL;
}
HANDLER(accessibilityHandler)
{
struct tableAcc *ta;
if (uMsg != WM_GETOBJECT)
return FALSE;
// OBJID_CLIENT evaluates to an expression of type LONG
// the documentation for WM_GETOBJECT says to cast "it" to a DWORD before comparing
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd373624%28v=vs.85%29.aspx casts them both to DWORDs; let's do that
// its two siblings only cast lParam, resulting in an erroneous DWORD to LONG comparison
// The Old New Thing book does not cast anything
// Microsoft's MSAA sample casts lParam to LONG instead!
2015-02-13 15:21:31 -06:00
// (As you can probably tell, the biggest problem with MSAA is that its documentation is ambiguous and/or self-contradictory...)
if (((DWORD) lParam) != ((DWORD) OBJID_CLIENT))
return FALSE;
ta = newTableAcc(t, ROLE_SYSTEM_TABLE, -1, -1);
*lResult = LresultFromObject(&IID_IAccessible, wParam, (LPUNKNOWN) (ta));
// TODO check *lResult
// TODO adjust pointer
IAccessible_Release((IAccessible *) ta);
return TRUE;
}