mirror of
https://github.com/wxWidgets/wxWidgets.git
synced 2026-10-06 07:00:16 +08:00
Add accessibility support for wxStatusBar in wxGTK and wxOSX
Only wxMSW uses the native wxStatusBar which is recognized by screen readers, while wxGTK and wxOSX use the generic version which is just a window and so was not recognized by them as being what it is at all. Add generic (but private for now) SetAccessibleElements() function implemented for both of them and use it from wxStatusBarGeneric to let Orca/VoiceOver know about the status bar fields and, in wxGTK, about its role. Assisted-by: Claude Opus 5.5 Co-authored-by: Quin Gillespie <trypsynth@gmail.com>
This commit is contained in:
co-authored by
Quin Gillespie
parent
ac4cbd0c09
commit
29aa2bbd4c
@@ -0,0 +1,68 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Name: wx/private/access.h
|
||||
// Purpose: Private accessibility helpers used by the generic controls.
|
||||
// Author: Vadim Zeitlin
|
||||
// Created: 2026-09-30
|
||||
// Copyright: (c) 2026 Vadim Zeitlin <vadim@wxwidgets.org>
|
||||
// Licence: wxWindows licence
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _WX_PRIVATE_ACCESS_H_
|
||||
#define _WX_PRIVATE_ACCESS_H_
|
||||
|
||||
#include "wx/gdicmn.h"
|
||||
#include "wx/string.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
class WXDLLIMPEXP_FWD_CORE wxWindow;
|
||||
|
||||
namespace wxPrivate
|
||||
{
|
||||
|
||||
// Part of a custom drawn window, which is not a window itself, but should
|
||||
// still be visible to the accessibility clients, e.g. a status bar field.
|
||||
struct AccessibleElement
|
||||
{
|
||||
AccessibleElement(const wxString& label_, const wxRect& rect_)
|
||||
: label(label_), rect(rect_)
|
||||
{
|
||||
}
|
||||
|
||||
// The text read by the screen readers.
|
||||
wxString label;
|
||||
|
||||
// Rectangle occupied by the element in the client coordinates of the
|
||||
// window it belongs to.
|
||||
wxRect rect;
|
||||
};
|
||||
|
||||
using AccessibleElements = std::vector<AccessibleElement>;
|
||||
|
||||
// Let the accessibility clients see the given elements as the children of this
|
||||
// window, using the "static text" role.
|
||||
//
|
||||
// Calling this function again replaces the elements set by the previous call,
|
||||
// but does nothing if they didn't change, so it's fine to call it whenever the
|
||||
// elements might have changed. Passing an empty vector removes them.
|
||||
//
|
||||
// This function is currently implemented for wxOSX and wxGTK3 only and does
|
||||
// nothing elsewhere.
|
||||
#if defined(__WXOSX_COCOA__) || (defined(__WXGTK3__) && !defined(__WXGTK4__))
|
||||
|
||||
WXDLLIMPEXP_CORE void
|
||||
SetAccessibleElements(wxWindow* win, const AccessibleElements& elements);
|
||||
|
||||
#else // !wxOSX && !wxGTK3
|
||||
|
||||
inline void
|
||||
SetAccessibleElements(wxWindow* WXUNUSED(win),
|
||||
const AccessibleElements& WXUNUSED(elements))
|
||||
{
|
||||
}
|
||||
|
||||
#endif // wxOSX || wxGTK3
|
||||
|
||||
} // namespace wxPrivate
|
||||
|
||||
#endif // _WX_PRIVATE_ACCESS_H_
|
||||
@@ -23,6 +23,8 @@
|
||||
#include "wx/control.h"
|
||||
#endif
|
||||
|
||||
#include "wx/private/access.h"
|
||||
|
||||
#ifdef __WXGTK__
|
||||
#include "wx/gtk/private.h"
|
||||
#endif
|
||||
@@ -44,6 +46,37 @@
|
||||
// Margin between the field text and the field rect
|
||||
#define wxFIELD_TEXT_MARGIN 2
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// helpers
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Let the screen readers read the fields, which are not windows and so are
|
||||
// invisible to them otherwise.
|
||||
//
|
||||
// This must be called whenever either the text or the position of the fields
|
||||
// changes.
|
||||
void UpdateAccessibleFields(wxStatusBarGeneric* statbar)
|
||||
{
|
||||
wxPrivate::AccessibleElements elements;
|
||||
|
||||
const int count = statbar->GetFieldsCount();
|
||||
for ( int i = 0; i < count; ++i )
|
||||
{
|
||||
wxRect rect;
|
||||
if ( !statbar->GetFieldRect(i, rect) )
|
||||
continue;
|
||||
|
||||
elements.emplace_back(statbar->GetStatusText(i), rect);
|
||||
}
|
||||
|
||||
wxPrivate::SetAccessibleElements(statbar, elements);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// GTK+ signal handler
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -123,6 +156,12 @@ bool wxStatusBarGeneric::Create(wxWindow *parent,
|
||||
|
||||
SetFieldsCount(1);
|
||||
|
||||
#if defined(__WXGTK3__) && !defined(__WXGTK4__)
|
||||
// Let the screen readers know that this is a status bar, e.g. Orca has a
|
||||
// command for reading it.
|
||||
atk_object_set_role(gtk_widget_get_accessible(m_widget), ATK_ROLE_STATUSBAR);
|
||||
#endif
|
||||
|
||||
#if defined( __WXGTK__ )
|
||||
#if GTK_CHECK_VERSION(2,12,0)
|
||||
if (HasFlag(wxSTB_SHOW_TIPS) && wx_is_at_least_gtk2(12))
|
||||
@@ -160,6 +199,8 @@ void wxStatusBarGeneric::DoUpdateStatusText(int number)
|
||||
|
||||
Refresh(true, &rect);
|
||||
|
||||
UpdateAccessibleFields(this);
|
||||
|
||||
// it's common to show some text in the status bar before starting a
|
||||
// relatively lengthy operation, ensure that the text is shown to the
|
||||
// user immediately and not after the lengthy operation end
|
||||
@@ -193,6 +234,8 @@ void wxStatusBarGeneric::DoUpdateFieldWidths()
|
||||
|
||||
// recompute the cache of the field widths if the status bar width has changed
|
||||
m_widthsAbs = CalculateAbsWidths(width);
|
||||
|
||||
UpdateAccessibleFields(this);
|
||||
}
|
||||
|
||||
bool wxStatusBarGeneric::ShowsSizeGrip() const
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@
|
||||
#include "wx/osx/private/datatransfer.h"
|
||||
#endif
|
||||
|
||||
#include "wx/private/access.h"
|
||||
#include "wx/private/bmpbndl.h"
|
||||
|
||||
#include "wx/evtloop.h"
|
||||
@@ -4391,6 +4392,75 @@ void wxWidgetCocoaImpl::DoNotifyFocusEvent(bool receivedFocus, wxWidgetImpl* oth
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
wxPrivate::SetAccessibleElements(wxWindow* win, const AccessibleElements& elements)
|
||||
{
|
||||
NSView* const view = win->GetHandle();
|
||||
if ( !view )
|
||||
return;
|
||||
|
||||
// Check if anything has changed since the last call, as replacing the
|
||||
// elements would make VoiceOver lose its position in them.
|
||||
NSArray* const current = [view accessibilityChildren];
|
||||
if ( current.count == elements.size() )
|
||||
{
|
||||
bool changed = false;
|
||||
for ( size_t n = 0; n < elements.size(); ++n )
|
||||
{
|
||||
id const child = current[n];
|
||||
if ( ![child isKindOfClass:[NSAccessibilityElement class]] )
|
||||
{
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
NSAccessibilityElement* const element = child;
|
||||
const wxString label = wxCFStringRef::AsString([element accessibilityLabel]);
|
||||
const NSRect frame = wxToNSRect(view, elements[n].rect);
|
||||
if ( label != elements[n].label ||
|
||||
!NSEqualRects([element accessibilityFrameInParentSpace], frame) )
|
||||
{
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !changed )
|
||||
return;
|
||||
}
|
||||
|
||||
if ( elements.empty() )
|
||||
{
|
||||
[view setAccessibilityChildren:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableArray* const children =
|
||||
[NSMutableArray arrayWithCapacity:elements.size()];
|
||||
|
||||
for ( const auto& e : elements )
|
||||
{
|
||||
NSAccessibilityElement* const element =
|
||||
[NSAccessibilityElement
|
||||
accessibilityElementWithRole:NSAccessibilityStaticTextRole
|
||||
frame:NSZeroRect
|
||||
label:wxCFStringRef(e.label).AsNSString()
|
||||
parent:view];
|
||||
|
||||
// Use the frame relative to the parent view and not the screen frame
|
||||
// for the element to remain at the correct position if the window
|
||||
// moves.
|
||||
[element setAccessibilityFrameInParentSpace:wxToNSRect(view, e.rect)];
|
||||
|
||||
[children addObject:element];
|
||||
}
|
||||
|
||||
// The container itself must be visible to the accessibility clients for
|
||||
// them to reach its children.
|
||||
[view setAccessibilityRole:NSAccessibilityGroupRole];
|
||||
[view setAccessibilityChildren:children];
|
||||
}
|
||||
|
||||
void wxWidgetCocoaImpl::SetCursor(const wxCursor& cursor)
|
||||
{
|
||||
if ( !wxIsBusy() )
|
||||
|
||||
Reference in New Issue
Block a user