diff --git a/src/prefs/PrefsDialog.cpp b/src/prefs/PrefsDialog.cpp index 2edc3e1a3..a8bf4ca7d 100644 --- a/src/prefs/PrefsDialog.cpp +++ b/src/prefs/PrefsDialog.cpp @@ -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( 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( 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( 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"));