MGR: Fix bugs in accessibility code for Notices tab

svn path=/trunk/boinc/; revision=21880
This commit is contained in:
Charlie Fenton 2010-07-08 11:55:43 +00:00
parent 195bbb2814
commit 0951733a49
4 changed files with 528 additions and 28 deletions

View File

@ -4954,3 +4954,11 @@ David 6 Jul 2010
html/inc/
translation.inc
Charlie 8 Jul 2010
- MGR: Fix bugs in accessibility code for Notices tab.
clientgui/
NoticeListCtrl.cpp, .h
mac/
MacAccessiblity.cpp

View File

@ -60,7 +60,8 @@ wxAccStatus CNoticeListCtrlAccessible::GetName(int childId, wxString* name)
if (pDoc)
{
strBuffer = wxString(process_client_message(pDoc->notice(childId-1)->title), wxConvUTF8);
strBuffer = wxString(process_client_message(pDoc->notice(childId-1)->title), wxConvUTF8);
strBuffer = StripHTMLTags(strBuffer);
*name = strBuffer.c_str();
}
}
@ -99,25 +100,21 @@ wxAccStatus CNoticeListCtrlAccessible::GetLocation(wxRect& rect, int elementId)
{
// List item
wxSize cCtrlSize = pCtrl->GetClientSize();
int iItemWidth = cCtrlSize.GetWidth();
int iItemHeight = pCtrl->GetTotalClientHeight() / (int)pCtrl->GetItemCount();
// Set the initial control postition to the absolute coords of the upper
// left hand position of the control
rect.SetPosition(pCtrl->GetScreenPosition());
rect.width = iItemWidth - 1;
rect.height = iItemHeight - 1;
rect.width = cCtrlSize.GetWidth() - 1;
rect.height = pCtrl->GetItemHeight(elementId - 1) - 1;
if (1 == elementId)
{
// First child
}
else
{
// Other children
rect.SetTop(rect.GetTop() + ((elementId - 1) * iItemHeight) + 1);
rect.height -= 1;
// Items can have different heights
int firstVisibleItem = (int)pCtrl->GetFirstVisibleLine();
int yOffset = 0;
for (int i=firstVisibleItem; i<(elementId - 1); ++i) {
yOffset += pCtrl->GetItemHeight((size_t)i);
}
rect.SetTop(rect.GetTop() + yOffset);
rect.height -= 1;
return wxACC_OK;
}
// Let the framework handle the other cases.
@ -181,14 +178,29 @@ wxAccStatus CNoticeListCtrlAccessible::GetDescription(int childId, wxString* des
{
CMainDocument* pDoc = wxGetApp().GetDocument();
static wxString strBuffer;
wxDateTime dtBuffer;
wxString strDescription;
wxString strProjectName = wxEmptyString;
wxString strArrivalTime = wxEmptyString;
if (pDoc && (childId != wxACC_SELF))
{
strBuffer = wxEmptyString;
if (pDoc)
{
strBuffer = wxString(process_client_message(pDoc->notice(childId-1)->description.c_str()), wxConvUTF8);
strDescription = wxString(process_client_message(pDoc->notice(childId-1)->description.c_str()), wxConvUTF8);
strProjectName = wxString(pDoc->notice(childId-1)->project_name, wxConvUTF8);
dtBuffer.Set((time_t)pDoc->notice(childId-1)->arrival_time);
strArrivalTime = dtBuffer.Format();
if (strProjectName.IsEmpty()) {
strBuffer.Printf(_("%s; received on %s"), strDescription.c_str(), strArrivalTime.c_str());
} else {
strBuffer.Printf(_("%s; received from %s; on %s"), strDescription.c_str(), strProjectName.c_str(), strArrivalTime.c_str());
}
strBuffer = StripHTMLTags(strBuffer);
*description = strBuffer.c_str();
return wxACC_OK;
}
}
@ -198,6 +210,20 @@ wxAccStatus CNoticeListCtrlAccessible::GetDescription(int childId, wxString* des
}
wxString CNoticeListCtrlAccessible::StripHTMLTags(wxString inBuf) {
wxString outBuf = wxEmptyString;
wxString tempBuf = inBuf;
while (!tempBuf.IsEmpty()) {
outBuf += tempBuf.BeforeFirst(wxT('<'));
tempBuf = tempBuf.AfterFirst(wxT('<'));
if (tempBuf.IsEmpty()) break;
tempBuf = tempBuf.AfterFirst(wxT('>'));
}
return outBuf;
}
#ifndef __WXMAC__
// Navigates from fromId to toId/toObject.
@ -569,14 +595,3 @@ bool CNoticeListCtrl::UpdateUI()
}
return true;
}
/*!
* Return the total height of all the client items.
*/
wxCoord CNoticeListCtrl::GetTotalClientHeight()
{
return EstimateTotalHeight();
}

View File

@ -56,6 +56,8 @@ public:
virtual wxAccStatus GetChildCount(int* childCount);
virtual wxAccStatus DoDefaultAction(int childId);
virtual wxAccStatus GetDescription(int childId, wxString* description);
wxString StripHTMLTags(wxString inBuf);
#ifndef __WXMAC__
virtual wxAccStatus Navigate(wxNavDir navDir, int fromId, int* toId, wxAccessible** toObject);
virtual wxAccStatus GetDefaultAction(int childId, wxString* actionName);
@ -110,7 +112,7 @@ public:
bool UpdateUI();
wxCoord GetTotalClientHeight();
int GetItemHeight(size_t i) { return (int)OnMeasureItem(i); }
private:
#ifdef __WXMAC__

View File

@ -74,6 +74,8 @@ pascal OSStatus BOINCListAccessibilityEventHandler( EventHandlerCallRef inHandle
pascal OSStatus AttachListAccessibilityEventHandler( EventHandlerCallRef inHandlerCallRef,
EventRef inEvent, void* pData);
pascal OSStatus NoticeListAccessibilityEventHandler( EventHandlerCallRef inHandlerCallRef,
EventRef inEvent, void* pData);
static EventTypeSpec myAccessibilityEvents[] = {
{ kEventClassAccessibility, kEventAccessibleGetChildAtPoint },
@ -224,7 +226,7 @@ void CNoticeListCtrlAccessible::SetupMacAccessibilitySupport() {
m_listView = (HIViewRef)pCtrl->GetHandle();
err = HIViewSetEnabled(m_listView, true);
err = InstallHIObjectEventHandler((HIObjectRef)m_listView, NewEventHandlerUPP(AttachListAccessibilityEventHandler),
err = InstallHIObjectEventHandler((HIObjectRef)m_listView, NewEventHandlerUPP(NoticeListAccessibilityEventHandler),
sizeof(myAccessibilityEvents) / sizeof(EventTypeSpec), myAccessibilityEvents,
this, &m_plistAccessibilityEventHandlerRef);
} else {
@ -1719,6 +1721,479 @@ pascal OSStatus AttachListAccessibilityEventHandler( EventHandlerCallRef inHandl
return eventNotHandledErr;
}
pascal OSStatus NoticeListAccessibilityEventHandler( EventHandlerCallRef inHandlerCallRef,
EventRef inEvent, void* pData) {
const UInt32 eventClass = GetEventClass(inEvent);
const UInt32 eventKind = GetEventKind(inEvent);
OSStatus err;
CProjectListCtrlAccessible* pAccessible = (CProjectListCtrlAccessible*)pData;
if (pAccessible == NULL) {
return eventNotHandledErr;
}
CNoticeListCtrl* pCtrl = wxDynamicCast(pAccessible->GetWindow(), CNoticeListCtrl);
if (pCtrl == NULL) {
return eventNotHandledErr;
}
if (eventClass != kEventClassAccessibility) {
return eventNotHandledErr;
}
AXUIElementRef element;
UInt64 inIdentifier = 0;
UInt64 outIdentifier = 0;
SInt32 row = 0;
HIObjectRef obj = NULL;
err = GetEventParameter (inEvent, kEventParamAccessibleObject,
typeCFTypeRef, NULL, sizeof(typeCFTypeRef), NULL, &element);
if (err) return err;
AXUIElementGetIdentifier( element, &inIdentifier );
obj = AXUIElementGetHIObject(element);
row = inIdentifier;
switch (eventKind) {
#pragma mark kEventAccessibleGetChildAtPoint
case kEventAccessibleGetChildAtPoint:
{
CFTypeRef child = NULL;
HIPoint where;
int hitRow;
// Only the whole view can be tested since the parts don't have sub-parts.
if (inIdentifier != 0) {
return noErr;
}
err = GetEventParameter (inEvent, kEventParamMouseLocation,
typeHIPoint, NULL, sizeof(HIPoint), NULL, &where);
if (err) return err;
wxPoint p((int)where.x, (int)where.y);
pCtrl->ScreenToClient(&p.x, &p.y);
err = pAccessible->HitTest(p, &hitRow, NULL);
if (err) {
return eventNotHandledErr;
}
if (hitRow >= 0) {
outIdentifier = hitRow + 1;
child = AXUIElementCreateWithHIObjectAndIdentifier(obj, outIdentifier );
if (child == NULL) {
return eventNotHandledErr;
}
err = SetEventParameter (inEvent, kEventParamAccessibleChild, typeCFTypeRef,
sizeof(typeCFTypeRef), &child);
if (err) {
return eventNotHandledErr;
}
}
return noErr;
}
break;
#pragma mark kEventAccessibleGetFocusedChild
case kEventAccessibleGetFocusedChild:
return noErr;
break;
#pragma mark kEventAccessibleGetAllAttributeNames
case kEventAccessibleGetAllAttributeNames:
{
CFMutableArrayRef namesArray;
err = GetEventParameter (inEvent, kEventParamAccessibleAttributeNames,
typeCFMutableArrayRef, NULL, sizeof(typeCFMutableArrayRef), NULL, &namesArray);
if (err)
return err;
CallNextEventHandler( inHandlerCallRef, inEvent );
if ( inIdentifier == 0 )
{
// Identifier 0 means "the whole view".
// Let accessibility know that this view has children and can
// return a list of them.
CFArrayAppendValue( namesArray, kAXChildrenAttribute );
} else {
// Let accessibility know that this view's children can return description,
// size, position, parent window, top level element and isFocused attributes.
CFArrayAppendValue( namesArray, kAXWindowAttribute );
CFArrayAppendValue( namesArray, kAXTopLevelUIElementAttribute );
CFArrayAppendValue( namesArray, kAXDescriptionAttribute );
CFArrayAppendValue( namesArray, kAXSizeAttribute );
CFArrayAppendValue( namesArray, kAXPositionAttribute );
CFArrayAppendValue( namesArray, kAXTitleAttribute );
CFArrayAppendValue( namesArray, kAXEnabledAttribute );
}
CFArrayAppendValue( namesArray, kAXFocusedAttribute );
CFArrayAppendValue( namesArray, kAXRoleAttribute );
CFArrayAppendValue( namesArray, kAXRoleDescriptionAttribute );
CFArrayAppendValue( namesArray, kAXParentAttribute );
return noErr;
}
break;
#pragma mark kEventAccessibleGetAllParameterizedAttributeNames
case kEventAccessibleGetAllParameterizedAttributeNames:
{
CFMutableArrayRef namesArray;
err = GetEventParameter (inEvent, kEventParamAccessibleAttributeNames,
typeCFMutableArrayRef, NULL, sizeof(typeCFMutableArrayRef), NULL, &namesArray);
if (err) return err;
return noErr;
}
break;
#pragma mark kEventAccessibleGetNamedAttribute
case kEventAccessibleGetNamedAttribute:
{
CFStringRef attribute;
err = GetEventParameter (inEvent, kEventParamAccessibleAttributeName,
typeCFStringRef, NULL, sizeof(typeCFStringRef), NULL, &attribute);
if (err) return err;
if ( CFStringCompare( attribute, kAXFocusedAttribute, 0 ) == kCFCompareEqualTo ) {
// Return whether or not this part is focused.
//TODO: Add kAXFocusedAttribute support?
Boolean focused = false;
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeBoolean, sizeof( focused ), &focused );
return noErr;
} else if ( CFStringCompare( attribute, kAXSizeAttribute, 0 ) == kCFCompareEqualTo ) {
wxRect r;
HISize theSize;
err = pAccessible->GetLocation(r, row);
if (err) {
return eventNotHandledErr;
}
theSize.width = r.width;
theSize.height = r.height;
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeHISize, sizeof( HISize ), &theSize );
return noErr;
} else if ( CFStringCompare( attribute, kAXPositionAttribute, 0 ) == kCFCompareEqualTo ) {
wxRect r;
HIPoint pt;
int x, y;
err = pAccessible->GetLocation(r, row);
if (err) {
return eventNotHandledErr;
}
x = r.x;
y = r.y;
// Now convert to global coordinates
// pCtrl->ClientToScreen(&x, &y);
pt.x = x;
pt.y = y;
SetEventParameter(inEvent, kEventParamAccessibleAttributeValue, typeHIPoint, sizeof(HIPoint), &pt);
return noErr;
}
if ( inIdentifier == 0 ) {
// String compare the incoming attribute name and return the appropriate accessibility
// information as an event parameter.
if ( CFStringCompare( attribute, kAXChildrenAttribute, 0 ) == kCFCompareEqualTo ) {
// Create and return an array of AXUIElements describing the children of this view.
CFMutableArrayRef children;
AXUIElementRef child;
int i, n;
err = pAccessible->GetChildCount(&n);
children = CFArrayCreateMutable( kCFAllocatorDefault, n, &kCFTypeArrayCallBacks );
for ( i = 0; i < n; i++ ) {
outIdentifier = i+1;
child = AXUIElementCreateWithHIObjectAndIdentifier( obj, outIdentifier );
CFArrayAppendValue( children, child );
CFRelease( child );
}
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeCFTypeRef, sizeof( children ), &children );
CFRelease( children );
return noErr;
} else if ( CFStringCompare( attribute, kAXRoleAttribute, 0 ) == kCFCompareEqualTo ) {
// Return a string indicating the role of this view. Using the table role doesn't work.
CFStringRef role = kAXListRole;
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeCFTypeRef, sizeof( role ), &role );
return noErr;
} else if ( CFStringCompare( attribute, kAXRoleDescriptionAttribute, 0 ) == kCFCompareEqualTo ) {
// Return a string indicating the role of this part.
//TODO: specify whether projects or account managers
wxString str;
str = _("list of projects or account managers");
CFStringRef roleDesc = CFStringCreateWithCString(NULL, str.char_str(), kCFStringEncodingUTF8);
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeCFTypeRef, sizeof( roleDesc ), &roleDesc );
CFRelease( roleDesc );
return noErr;
} else if ( CFStringCompare( attribute, kAXParentAttribute, 0 ) == kCFCompareEqualTo ) {
AXUIElementRef parent;
HIViewRef parentView;
parentView = HIViewGetSuperview(pAccessible->m_listView);
parent = AXUIElementCreateWithHIObjectAndIdentifier((HIObjectRef)parentView, 0);
if (parent == NULL) {
return eventNotHandledErr;
}
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeCFTypeRef, sizeof( parent ), &parent );
CFRelease( parent );
return noErr;
} else {
return CallNextEventHandler( inHandlerCallRef, inEvent );
}
} else { // End if ( inIdentifier == 0 )
if ( CFStringCompare( attribute, kAXDescriptionAttribute, 0 ) == kCFCompareEqualTo ) {
wxString str, buf;
int n;
err = pAccessible->GetChildCount(&n);
if (err) {
return eventNotHandledErr;
}
if (pCtrl->IsSelected(row - 1)) {
str.Printf(_("selected row %d of %d; "), row, n);
} else {
str.Printf(_("row %d of %d; "), row, n);
}
err = pAccessible->GetDescription(row, &buf);
if (err) {
return eventNotHandledErr;
}
str += buf;
CFStringRef description = CFStringCreateWithCString(NULL, str.char_str(), kCFStringEncodingUTF8);
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeCFTypeRef, sizeof( description ), &description );
CFRelease( description );
return noErr;
} else if ( CFStringCompare( attribute, kAXParentAttribute, 0 ) == kCFCompareEqualTo ) {
AXUIElementRef parent;
HIViewRef parentView;
parentView = pAccessible->m_listView;
parent = AXUIElementCreateWithHIObjectAndIdentifier((HIObjectRef)parentView, 0);
if (parent == NULL) {
return eventNotHandledErr;
}
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeCFTypeRef, sizeof( parent ), &parent );
CFRelease( parent );
return noErr;
} else if ( CFStringCompare( attribute, kAXSubroleAttribute, 0 ) == kCFCompareEqualTo ) {
return eventNotHandledErr;
} else if ( CFStringCompare( attribute, kAXRoleAttribute, 0 ) == kCFCompareEqualTo ) {
// Return a string indicating the role of this part. The parts of the view behave like
// buttons, so use that system role.
CFStringRef role = kAXStaticTextRole; // kAXRowRole;
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeCFTypeRef, sizeof( role ), &role );
return noErr;
} else if ( CFStringCompare( attribute, kAXRoleDescriptionAttribute, 0 ) == kCFCompareEqualTo ) {
// Return a string describing the role of this part. Use the system description.
CFStringRef roleDesc = HICopyAccessibilityRoleDescription( kAXRowRole, NULL );
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeCFTypeRef, sizeof( roleDesc ), &roleDesc );
CFRelease( roleDesc );
return noErr;
} else if ( CFStringCompare( attribute, kAXWindowAttribute, 0 ) == kCFCompareEqualTo
|| CFStringCompare( attribute, kAXTopLevelUIElementAttribute, 0 ) == kCFCompareEqualTo ) {
// Return the window or top level ui element for this part. They are both the same so re-use the code.
AXUIElementRef windOrTopUI;
WindowRef win = GetControlOwner((HIViewRef)obj);
if (win == NULL) {
return eventNotHandledErr;
}
windOrTopUI = AXUIElementCreateWithHIObjectAndIdentifier( (HIObjectRef)win, 0 );
if (windOrTopUI == NULL) {
return eventNotHandledErr;
}
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeCFTypeRef, sizeof( windOrTopUI ), &windOrTopUI );
CFRelease( windOrTopUI );
return noErr;
} else if ( CFStringCompare( attribute, kAXEnabledAttribute, 0 ) == kCFCompareEqualTo ) {
Boolean enabled = true;
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeBoolean, sizeof( enabled ), &enabled );
return noErr;
//TODO: Add kAXFocusedAttribute support?
#if 0
} else if ( CFStringCompare( attribute, kAXFocusedAttribute, 0 ) == kCFCompareEqualTo ) {
// Return whether or not this part is focused.
Boolean focused = false;
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeBoolean, sizeof( focused ), &focused );
return noErr;
#endif
} else if ( CFStringCompare( attribute, kAXTitleAttribute, 0 ) == kCFCompareEqualTo ) {
// Return the item's text
wxString str;
wxListItem headerItem;
pAccessible->GetName(row, &str);
CFStringRef title = CFStringCreateWithCString(NULL, str.char_str(), kCFStringEncodingUTF8);
SetEventParameter( inEvent, kEventParamAccessibleAttributeValue, typeCFTypeRef, sizeof( title ), &title );
CFRelease( title );
return noErr;
} else {
return eventNotHandledErr;
}
} // End if ( inIdentifier != 0 )
break;
} // End case kEventAccessibleGetNamedAttribute:
#pragma mark kEventAccessibleIsNamedAttributeSettable
case kEventAccessibleIsNamedAttributeSettable:
{
CFStringRef attribute;
Boolean isSettable = false;
err = GetEventParameter (inEvent, kEventParamAccessibleAttributeName,
typeCFStringRef, NULL, sizeof(typeCFStringRef), NULL, &attribute);
if (err) return err;
// The focused attribute is the only settable attribute for this view,
// and it can only be set on part (or subelements), not the whole view.
if (inIdentifier != 0)
{
if ( CFStringCompare( attribute, kAXFocusedAttribute, 0 ) == kCFCompareEqualTo )
{
isSettable = true;
}
}
SetEventParameter( inEvent, kEventParamAccessibleAttributeSettable, typeBoolean, sizeof( Boolean ), &isSettable );
return noErr;
}
break;
#pragma mark kEventAccessibleSetNamedAttribute
case kEventAccessibleSetNamedAttribute:
{
return eventNotHandledErr;
}
break;
#pragma mark kEventAccessibleGetAllActionNames
case kEventAccessibleGetAllActionNames:
{
CFMutableArrayRef array;
err = GetEventParameter (inEvent, kEventParamAccessibleActionNames,
typeCFMutableArrayRef, NULL, sizeof(typeCFMutableArrayRef), NULL, &array);
if (err) return err;
if (inIdentifier != 0) {
CFArrayAppendValue( array, kAXPressAction );
}
return noErr;
}
break;
#pragma mark kEventAccessibleGetNamedActionDescription
case kEventAccessibleGetNamedActionDescription:
{
CFStringRef action;
CFMutableStringRef desc;
CFStringRef selfDesc = NULL;
if (inIdentifier == 0) {
return eventNotHandledErr;
}
err = GetEventParameter (inEvent, kEventParamAccessibleActionName,
typeCFStringRef, NULL, sizeof(typeCFStringRef), NULL, &action);
if (err) return err;
err = GetEventParameter (inEvent, kEventParamAccessibleActionDescription,
typeCFMutableStringRef, NULL, sizeof(typeCFMutableStringRef), NULL, &desc);
if (err) return err;
selfDesc = HICopyAccessibilityActionDescription( action );
CFStringReplaceAll( desc, selfDesc );
CFRelease( selfDesc );
return noErr;
}
break;
#pragma mark kEventAccessiblePerformNamedAction
case kEventAccessiblePerformNamedAction:
{
CFStringRef action;
if (inIdentifier == 0) {
return eventNotHandledErr;
}
err = GetEventParameter (inEvent, kEventParamAccessibleActionName,
typeCFStringRef, NULL, sizeof(typeCFStringRef), NULL, &action);
if (err) return err;
if ( CFStringCompare( action, kAXPressAction, 0 ) != kCFCompareEqualTo ) {
return eventNotHandledErr;
}
err = pAccessible->DoDefaultAction(inIdentifier);
if (err) {
return eventNotHandledErr;
}
return noErr;
}
break;
default:
return eventNotHandledErr;
} // End switch(eventKind)
return eventNotHandledErr;
}
pascal OSStatus SimpleAccessibilityEventHandler( EventHandlerCallRef inHandlerCallRef,
EventRef inEvent, void* pData) {
const UInt32 eventClass = GetEventClass(inEvent);