andlabs-ui/wintable/accessibility.h

721 lines
20 KiB
C

// 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
// - cells have no children; checkbox cells are themselves the accessible object
// - TODO if we ever add combobox columns, this will need to change somehow
// TODOs:
// - make sure E_POINTER and RPC_E_DISCONNECTED are correct returns for IAccessible
// uncomment this to debug table linked list management
//#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
// 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)
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?
static ULONG STDMETHODCALLTYPE tableAccAddRef(IAccessible *this)
{
TA->refcount++;
// TODO correct?
return TA->refcount;
}
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
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);
}
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);
}
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);
}
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);
}
// 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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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;
}
static HRESULT STDMETHODCALLTYPE tableAccaccNavigate(IAccessible *this, long navDir, VARIANT varStart, VARIANT *pvarEndUpAt)
{
HRESULT hr;
tableAccWhat what;
intptr_t row = -1;
intptr_t column = -1;
if (pvarEndUpAt == NULL)
return E_POINTER;
// TODO set pvarEndUpAt to an invalid value?
if (TA->t == NULL || TA->std == NULL)
return RPC_E_DISCONNECTED;
what = TA->what;
hr = normalizeWhat(TA, varStart, &what);
if (hr != S_OK)
return hr;
switch (what.role) {
case ROLE_SYSTEM_TABLE:
switch (navDir) {
case NAVDIR_FIRSTCHILD:
// TODO header row
if (TA->t->count == 0)
goto nowhere;
row = 0;
goto specificRow;
case NAVDIR_LASTCHILD:
// TODO header row
if (TA->t->count == 0)
goto nowhere;
row = TA->t->count - 1;
goto specificRow;
}
// otherwise, defer to the standard accessible object
return IAccessible_accNavigate(TA->std, navDir, varStart, pvarEndUpAt);
case ROLE_SYSTEM_ROW:
row = what.row;
switch (navDir) {
case NAVDIR_UP:
case NAVDIR_PREVIOUS:
if (row == 0) // can't go up
goto nowhere;
row--;
// row should still be valid because normalizeWhat() returns an error if the current row is no longer valid, and if that's valid, the row above it should also be valid
goto specificRow;
case NAVDIR_DOWN:
case NAVDIR_NEXT:
if (row == TA->t->count - 1) // can't go down
goto nowhere;
row++;
// row should still be valid by the above conjecture
goto specificRow;
case NAVDIR_LEFT:
case NAVDIR_RIGHT:
goto nowhere;
// TODO this doesn't actually exist yet https://msdn.microsoft.com/en-us/library/ms971325 talks about it
// case NAVDIR_PARENT:
// goto tableItself;
case NAVDIR_FIRSTCHILD:
if (TA->t->nColumns == 0)
goto nowhere;
column = 0;
goto specificCell;
case NAVDIR_LASTCHILD:
if (TA->t->nColumns == 0)
goto nowhere;
column = TA->t->nColumns - 1;
goto specificCell;
}
// TODO differentiate between unsupported navigation directions and invalid navigation directions
goto nowhere;
case ROLE_SYSTEM_CELL:
row = what.row;
column = what.column;
switch (navDir) {
case NAVDIR_UP:
if (row == 0) // can't go up
goto nowhere;
row--;
goto specificCell;
case NAVDIR_DOWN:
if (row == TA->t->count - 1) // can't go down
goto nowhere;
row++;
goto specificCell;
case NAVDIR_LEFT:
case NAVDIR_PREVIOUS:
if (column == 0) // can't go left
goto nowhere;
column--;
goto specificCell;
case NAVDIR_RIGHT:
case NAVDIR_NEXT:
if (column == TA->t->nColumns - 1) // can't go right
goto nowhere;
column++;
goto specificCell;
// TODO this doesn't actually exist yet https://msdn.microsoft.com/en-us/library/ms971325 talks about it
// case NAVDIR_PARENT:
// goto specificRow;
case NAVDIR_FIRSTCHILD:
case NAVDIR_LASTCHILD:
goto nowhere;
}
// TODO differentiate between unsupported navigation directions and invalid navigation directions
goto nowhere;
}
// TODO actually do this right
// TODO un-GetLastError() this
panic("impossible blah blah blah TODO write this");
return E_FAIL;
nowhere:
pvarEndUpAt->vt = VT_EMPTY;
return S_FALSE;
tableItself:
pvarEndUpAt->vt = VT_DISPATCH;
pvarEndUpAt->pdispVal = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_TABLE, -1, -1);
return S_OK;
specificRow:
pvarEndUpAt->vt = VT_DISPATCH;
pvarEndUpAt->pdispVal = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_ROW, row, -1);
return S_OK;
specificCell:
pvarEndUpAt->vt = VT_DISPATCH;
pvarEndUpAt->pdispVal = (IDispatch *) newTableAcc(TA->t, ROLE_SYSTEM_CELL, row, column);
return S_OK;
}
// TODO should this ever return parents?
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;
}
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;
}
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);
}
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);
}
static const IAccessibleVtbl tableAccVtbl = {
.QueryInterface = tableAccQueryInterface,
.AddRef = tableAccAddRef,
.Release = tableAccRelease,
.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!
// (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;
}