1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-22 07:28:03 +02:00

Bug1892: VoiceOver on Mac should read category names in Preferences...

... Achieved by implementing a wxAccessible subclass for wxTreeCtrl, but on
Mac only.  Leave be on Windows, just making the new type name an alias.
This commit is contained in:
Paul Licameli 2019-04-27 18:11:00 -04:00
parent 0a0baa39bb
commit 6248226163

View File

@ -78,6 +78,326 @@
#endif
#if wxUSE_ACCESSIBILITY
#ifndef __WXMAC__
// Just an alias
using TreeCtrlAx = WindowAccessible;
#else
// utility functions
namespace {
template< typename Result, typename Fn >
Result VisitItems( const wxTreeCtrl &ctrl, Fn fn )
{
// Do preorder visit of items in the tree until satisfying a test
std::vector< wxTreeItemId > stack;
stack.push_back( ctrl.GetRootItem() );
unsigned position = 0;
while ( !stack.empty() ) {
auto itemId = stack.back();
auto pair = fn( itemId, position );
if ( pair.first )
return pair.second;
wxTreeItemIdValue cookie;
auto childId = ctrl.GetFirstChild( itemId, cookie );
if ( childId )
stack.push_back( childId );
else do {
auto &id = stack.back();
if ( !!( id = ctrl.GetNextSibling( id ) ) )
break;
} while ( stack.pop_back(), !stack.empty() );
++position;
}
return {};
}
unsigned FindItemPosition( const wxTreeCtrl &ctrl, wxTreeItemId id )
{
// Return the 1-based count of the item's position in the pre-order
// visit of the items in the tree (not counting the root item which we
// assume is a dummy that never matches id)
return VisitItems<unsigned>( ctrl,
[=]( wxTreeItemId itemId, unsigned position ){
return std::make_pair( itemId == id, position ); } );
}
wxTreeItemId FindItem( const wxTreeCtrl &ctrl, int nn )
{
// The inverse of the function above
return VisitItems<wxTreeItemId>( ctrl,
[=]( wxTreeItemId itemId, unsigned position ){
return std::make_pair( nn == position, itemId ); } );
}
}
// Define a custom class
class TreeCtrlAx final
: public WindowAccessible
{
public:
TreeCtrlAx(wxTreeCtrl * ctrl);
virtual ~ TreeCtrlAx();
wxAccStatus GetChild(int childId, wxAccessible** child) override;
wxAccStatus GetChildCount(int* childCount) override;
wxAccStatus GetDefaultAction(int childId, wxString *actionName) override;
// Returns the description for this object or a child.
wxAccStatus GetDescription(int childId, wxString *description) override;
// Gets the window with the keyboard focus.
// If childId is 0 and child is NULL, no object in
// this subhierarchy has the focus.
// If this object has the focus, child should be 'this'.
wxAccStatus GetFocus(int *childId, wxAccessible **child) override;
// Returns help text for this object or a child, similar to tooltip text.
wxAccStatus GetHelpText(int childId, wxString *helpText) override;
// Returns the keyboard shortcut for this object or child.
// Return e.g. ALT+K
wxAccStatus GetKeyboardShortcut(int childId, wxString *shortcut) override;
// Returns the rectangle for this object (id = 0) or a child element (id > 0).
// rect is in screen coordinates.
wxAccStatus GetLocation(wxRect& rect, int elementId) override;
// Gets the name of the specified object.
wxAccStatus GetName(int childId, wxString *name) override;
// Returns a role constant.
wxAccStatus GetRole(int childId, wxAccRole *role) override;
// Gets a variant representing the selected children
// of this object.
// Acceptable values:
// - a null variant (IsNull() returns TRUE)
// - a list variant (GetType() == wxT("list"))
// - an integer representing the selected child element,
// or 0 if this object is selected (GetType() == wxT("long"))
// - a "void*" pointer to a wxAccessible child object
//wxAccStatus GetSelections(wxVariant *selections) override;
// leave unimplemented
// Returns a state constant.
wxAccStatus GetState(int childId, long* state) override;
// Returns a localized string representing the value for the object
// or child.
wxAccStatus GetValue(int childId, wxString* strValue) override;
// Navigates from fromId to toId/toObject
// wxAccStatus Navigate(wxNavDir navDir, int fromId, int* toId, wxAccessible** toObject) override;
// Modify focus or selection
wxAccStatus Select(int childId, wxAccSelectionFlags selectFlags) override;
private:
wxTreeCtrl *GetCtrl() { return static_cast<wxTreeCtrl*>( GetWindow() ); }
};
TreeCtrlAx::TreeCtrlAx( wxTreeCtrl *ctrl )
: WindowAccessible{ ctrl }
{
}
TreeCtrlAx::~TreeCtrlAx() = default;
wxAccStatus TreeCtrlAx::GetChild( int childId, wxAccessible** child )
{
if( childId == wxACC_SELF )
{
*child = this;
}
else
{
*child = NULL;
}
return wxACC_OK;
}
wxAccStatus TreeCtrlAx::GetChildCount(int* childCount)
{
auto ctrl = GetCtrl();
if (!ctrl)
return wxACC_FAIL;
*childCount = ctrl->GetCount();
return wxACC_OK;
}
wxAccStatus TreeCtrlAx::GetDefaultAction(int WXUNUSED(childId), wxString* actionName)
{
actionName->clear();
return wxACC_OK;
}
// Returns the description for this object or a child.
wxAccStatus TreeCtrlAx::GetDescription( int WXUNUSED(childId), wxString *description )
{
description->clear();
return wxACC_OK;
}
// This isn't really used yet by wxWidgets as patched by Audacity for
// Mac accessibility, as of Audacity 2.3.2, but here it is anyway, keeping the
// analogy with TrackPanelAx
wxAccStatus TreeCtrlAx::GetFocus( int *childId, wxAccessible **child )
{
auto ctrl = GetCtrl();
if (!ctrl)
return wxACC_FAIL;
auto item = ctrl->GetFocusedItem();
auto id = FindItemPosition( *ctrl, item );
*childId = id;
*child = nullptr;
return wxACC_OK;
}
// Returns help text for this object or a child, similar to tooltip text.
wxAccStatus TreeCtrlAx::GetHelpText( int WXUNUSED(childId), wxString *helpText )
{
helpText->clear();
return wxACC_OK;
}
// Returns the keyboard shortcut for this object or child.
// Return e.g. ALT+K
wxAccStatus TreeCtrlAx::GetKeyboardShortcut( int WXUNUSED(childId), wxString *shortcut )
{
shortcut->clear();
return wxACC_OK;
}
wxAccStatus TreeCtrlAx::GetLocation( wxRect& rect, int elementId )
{
auto ctrl = GetCtrl();
if (!ctrl)
return wxACC_FAIL;
if (elementId == wxACC_SELF)
rect = ctrl->GetRect();
else {
auto item = FindItem( *ctrl, elementId );
if ( !( item && ctrl->GetBoundingRect( item, rect ) ) )
return wxACC_INVALID_ARG;
}
rect.SetPosition( ctrl->GetParent()->ClientToScreen( rect.GetPosition() ) );
return wxACC_OK;
}
wxAccStatus TreeCtrlAx::GetName(int childId, wxString* name)
{
if ( childId == wxACC_SELF )
return WindowAccessible::GetName( childId, name );
else {
auto ctrl = GetCtrl();
if (!ctrl)
return wxACC_FAIL;
auto item = FindItem( *ctrl, childId );
if ( item ) {
*name = ctrl->GetItemText( item );
return wxACC_OK;
}
else
return wxACC_INVALID_ARG;
}
}
wxAccStatus TreeCtrlAx::GetRole( int childId, wxAccRole* role )
{
// Not sure if this correct, but it is analogous with what we use in
// TrackPanel
*role =
childId == wxACC_SELF ? wxROLE_SYSTEM_PANE : wxROLE_SYSTEM_STATICTEXT;
return wxACC_OK;
}
// Returns a state constant.
wxAccStatus TreeCtrlAx::GetState(int childId, long* state)
{
auto ctrl = GetCtrl();
if (!ctrl)
return wxACC_FAIL;
*state = wxACC_STATE_SYSTEM_FOCUSABLE | wxACC_STATE_SYSTEM_SELECTABLE;
if ( childId != wxACC_SELF ) {
auto item = FindItem( *ctrl, childId );
if (item) {
if( item == ctrl->GetFocusedItem() )
*state |= wxACC_STATE_SYSTEM_FOCUSED;
if( item == ctrl->GetSelection() )
*state |= wxACC_STATE_SYSTEM_SELECTED;
}
}
return wxACC_OK;
}
// Returns a localized string representing the value for the object
// or child.
wxAccStatus TreeCtrlAx::GetValue(int childId, wxString* strValue)
{
*strValue = wxString{};
return wxACC_OK;
}
//wxAccStatus TreeCtrlAx::Navigate(
// wxNavDir navDir, int fromId, int* toId, wxAccessible** toObject)
//{
// to do
//}
// Modify focus or selection
wxAccStatus TreeCtrlAx::Select(int childId, wxAccSelectionFlags selectFlags)
{
auto ctrl = GetCtrl();
if (!ctrl)
return wxACC_FAIL;
if (childId != wxACC_SELF) {
int childCount;
GetChildCount( &childCount );
if (childId > childCount)
return wxACC_FAIL;
auto item = FindItem( *ctrl, childId );
if ( item ) {
if (selectFlags == wxACC_SEL_TAKEFOCUS)
ctrl->SetFocusedItem( item );
else if (selectFlags == wxACC_SEL_TAKESELECTION)
ctrl->SelectItem( item );
else
return wxACC_NOT_IMPLEMENTED;
return wxACC_OK;
}
}
return wxACC_NOT_IMPLEMENTED;
}
#endif
#endif
// PrefsPanel might move out into its own file in due ocurse.
PluginPath PrefsPanel::GetPath(){ return BUILTIN_PREFS_PANEL_PREFIX + GetSymbol().Internal(); }
VendorSymbol PrefsPanel::GetVendor(){ return XO("Audacity");}
@ -263,7 +583,8 @@ PrefsDialog::PrefsDialog
mCategories = safenew wxTreebookExt(this, wxID_ANY, mTitlePrefix);
#if wxUSE_ACCESSIBILITY
// so that name can be set on a standard control
mCategories->GetTreeCtrl()->SetAccessible(safenew WindowAccessible(mCategories->GetTreeCtrl()));
mCategories->GetTreeCtrl()->SetAccessible(
safenew TreeCtrlAx(mCategories->GetTreeCtrl()));
#endif
// RJH: Prevent NVDA from reading "treeCtrl"
mCategories->GetTreeCtrl()->SetName(_("Category"));