Skip to content

Commit

Permalink
fix #299387: support for more screen readers + collect_artifacts
Browse files Browse the repository at this point in the history
MuseScore uses mostly standard Qt widgets and is thus accessible.
The SocreView widget is custom, however,
and we implement accessibility for it ourselves.
The support has long worked with NVDA only.
There are a number of reasons for this, and this PR addresses them.
It also improves the reading by NVDA.
The changes allow Orca and JAWS to work,
although we may also need to provide scripts for them.

The changes are:

* added empty text for the container objects to prevent reading
* fix rect() so mouse actions are associated with scoreview
* fix window() to return correct window pointer
* set the accessible description along with the value in text()
* change role() to StaticText
* set good values for isValid() and state()
* implement value interface
* eliminate redundant call to endCmd()
* set focusPolicy of ScoreView to StrongFocus

The basic model hasn't changed - we set a value on the widget,
and send a value changed event.
This is not necessary the "correct" solution, but it works best.
I also included some code to facilitate changing to other approaches.
Different versins of Qt, different OS's, different screen readers
may require customization here.
But the code as I have enabled works well enough at the moment.
  • Loading branch information
MarcSabatella committed Jan 21, 2020
1 parent fe3064d commit 9dc93c5
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 20 deletions.
4 changes: 2 additions & 2 deletions libmscore/cmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#include <assert.h>

#include "types.h"
#include "musescoreCore.h"
//#include "musescoreCore.h"
#include "score.h"
#include "utils.h"
#include "key.h"
Expand Down Expand Up @@ -268,7 +268,7 @@ void Score::endCmd(bool rollback)
masterScore()->setPlaylistDirty(); // TODO: flag individual operations
masterScore()->setAutosaveDirty(true);
}
MuseScoreCore::mscoreCore->endCmd();
//MuseScoreCore::mscoreCore->endCmd();
cmdState().reset();
}

Expand Down
6 changes: 6 additions & 0 deletions mscore/inspector/inspector_element.ui
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@
<property name="text">
<string>Minimum distance:</string>
</property>
<property name="buddy">
<cstring>minDistance</cstring>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
Expand Down Expand Up @@ -295,6 +298,9 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="accessibleName">
<string>Minimum distance</string>
</property>
<property name="suffix">
<string>sp</string>
</property>
Expand Down
11 changes: 10 additions & 1 deletion mscore/musescore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1077,13 +1077,18 @@ MuseScore::MuseScore()
connect(ag, SIGNAL(triggered(QAction*)), SLOT(cmd(QAction*)));

mainWindow = new QSplitter;
mainWindow->setObjectName("mainwindow");
mainWindow->setAccessibleName("");
mainWindow->setChildrenCollapsible(false);

QWidget* mainScore = new QWidget;
mainScore->setObjectName("mainscore");
mainScore->setAccessibleName("");
mainScore->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mainWindow->addWidget(mainScore);

layout = new QVBoxLayout;
layout->setObjectName("layout");
layout->setMargin(0);
layout->setSpacing(0);
mainScore->setLayout(layout);
Expand Down Expand Up @@ -1127,6 +1132,8 @@ MuseScore::MuseScore()
mainWindow->setSizes(QList<int>({500, 50}));

QSplitter* envelope = new QSplitter;
envelope->setObjectName("pane");
envelope->setAccessibleName("");
envelope->setChildrenCollapsible(false);
envelope->setOrientation(Qt::Vertical);
envelope->addWidget(mainWindow);
Expand Down Expand Up @@ -1155,6 +1162,8 @@ MuseScore::MuseScore()
envelope->setSizes(QList<int>({550, 180}));

splitter = new QSplitter;
splitter->setObjectName("splitter");
splitter->setAccessibleName("");
tab1 = createScoreTab();
splitter->addWidget(tab1);
ctab = tab1; // make tab1 active by default.
Expand Down Expand Up @@ -2351,7 +2360,7 @@ void MuseScore::updatePaletteBeamMode()

void MuseScore::updateInspector()
{
if (_inspector)
if (_inspector && _inspector->isVisible())
_inspector->update(cs);
}

Expand Down
145 changes: 130 additions & 15 deletions mscore/scoreaccessibility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,29 +48,68 @@ QAccessibleInterface* AccessibleScoreView::parent() const

QRect AccessibleScoreView::rect() const
{
return s->rect();
// TODO: calculate this ourselves?
//QPoint origin = s->mapToGlobal(QPoint(0, 0));
//return s->rect().translated(origin);
return QAccessibleWidget::rect();
}

bool AccessibleScoreView::isValid() const
{
return true;
}

#if 1
// TODO: determine if setting state explicitly would be helpful
QAccessible::State AccessibleScoreView::state() const
{
QAccessible::State s = QAccessibleWidget::state();
s.focusable = 1;
s.selectable = 1;
s.active = 1;
//s.animated = 1;
return s;
}
#endif

QAccessible::Role AccessibleScoreView::role() const
{
return QAccessible::NoRole;
// TODO: determine optimum role
// StaticText has the advantage of being read by Windows Narrator
//return QAccessible::Graphic;
return QAccessible::StaticText;
}

QString AccessibleScoreView::text(QAccessible::Text t) const
{
switch (t) {
case QAccessible::Name:
// TODO:
// leave empty to prevent name from being read on value/description change
// name will need to be in containing widget so it is read on tab change
// and we will need to be sure to read that
//return "";
return s->score()->title();
case QAccessible::Value:
case QAccessible::Description:
return s->score()->accessibleInfo();
default:
return QString();
}
}

#if 1
// TODO: determine best option here
// without this override, Qt determines window by looking upwards in hierarchy
// we can supposedly duplicate that by returning nullptr
// qApp->focusWindow() is the "old" return value,
// but it could conceivably refer to something other than main window, which seems wrong
QWindow* AccessibleScoreView::window() const {
return qApp->focusWindow();
//return nullptr;
//return QWdiegt::window();
return mscore->windowHandle(); // qApp->focusWindow();
}
#endif

QAccessibleInterface* AccessibleScoreView::ScoreViewFactory(const QString &classname, QObject *object)
{
Expand All @@ -83,6 +122,68 @@ QAccessibleInterface* AccessibleScoreView::ScoreViewFactory(const QString &class
return iface;
}

void* AccessibleScoreView::interface_cast(QAccessible::InterfaceType t)
{
#ifdef SCOREVIEW_VALUEINTERFACE
if (t == QAccessible::ValueInterface)
return static_cast<QAccessibleValueInterface*>(this);
#endif
#ifdef SCOREVIEW_IMAGEINTERFACE
if (t == QAccessible::ImageInterface)
return static_cast<QAccessibleImageInterface*>(this);
#endif
return QAccessibleWidget::interface_cast(t);
}

#ifdef SCOREVIEW_VALUEINTERFACE

void AccessibleScoreView::setCurrentValue(const QVariant& val)
{
QString str = val.toString();
s->score()->setAccessibleInfo(str);
}

QVariant AccessibleScoreView::currentValue() const
{
return s->score()->accessibleInfo();
}

QVariant AccessibleScoreView::maximumValue() const
{
return QString();
}

QVariant AccessibleScoreView::minimumValue() const
{
return QString();
}

QVariant AccessibleScoreView::minimumStepSize() const
{
return QString();
}

#endif

#ifdef SCOREVIEW_IMAGEINTERFACE

QString AccessibleScoreView::imageDescription() const
{
return s->score()->accessibleInfo();
}

QSize AccessibleScoreView::imageSize() const
{
return s->size();
}

QPoint AccessibleScoreView::imagePosition() const
{
return QPoint();
}

#endif


ScoreAccessibility* ScoreAccessibility::inst = 0;

Expand Down Expand Up @@ -249,22 +350,36 @@ void ScoreAccessibility::updateAccessibilityInfo()
//getInspector->isAncestorOf is used so that inspector and search dialog don't loose focus
//when this method is called
//TODO: create a class to manage focus and replace this massive if
if ( (qApp->focusWidget() != w) &&
!mscore->inspector()->isAncestorOf(qApp->focusWidget()) &&
!(mscore->searchDialog() && mscore->searchDialog()->isAncestorOf(qApp->focusWidget())) &&
!(mscore->getSelectionWindow() && mscore->getSelectionWindow()->isAncestorOf(qApp->focusWidget())) &&
!(mscore->getPlayPanel() && mscore->getPlayPanel()->isAncestorOf(qApp->focusWidget())) &&
!(mscore->getSynthControl() && mscore->getSynthControl()->isAncestorOf(qApp->focusWidget())) &&
!(mscore->getMixer() && mscore->getMixer()->isAncestorOf(qApp->focusWidget())) &&
!(mscore->searchDialog() && mscore->searchDialog()->isAncestorOf(qApp->focusWidget())) &&
!(mscore->getDrumrollEditor() && mscore->getDrumrollEditor()->isAncestorOf(qApp->focusWidget())) &&
!(mscore->getPianorollEditor() && mscore->getPianorollEditor()->isAncestorOf(qApp->focusWidget()))) {
QWidget* focusWidget = qApp->focusWidget();
if ((focusWidget != w) &&
!(mscore->inspector() && mscore->inspector()->isAncestorOf(focusWidget)) &&
!(mscore->searchDialog() && mscore->searchDialog()->isAncestorOf(focusWidget)) &&
!(mscore->getSelectionWindow() && mscore->getSelectionWindow()->isAncestorOf(focusWidget)) &&
!(mscore->getPlayPanel() && mscore->getPlayPanel()->isAncestorOf(focusWidget)) &&
!(mscore->getSynthControl() && mscore->getSynthControl()->isAncestorOf(focusWidget)) &&
!(mscore->getMixer() && mscore->getMixer()->isAncestorOf(focusWidget)) &&
!(mscore->searchDialog() && mscore->searchDialog()->isAncestorOf(focusWidget)) &&
!(mscore->getDrumrollEditor() && mscore->getDrumrollEditor()->isAncestorOf(focusWidget)) &&
!(mscore->getPianorollEditor() && mscore->getPianorollEditor()->isAncestorOf(focusWidget))) {
mscore->activateWindow();
w->setFocus();
}
#if 0
else if (focusWidget == w) {
w->clearFocus();
w->setFocus();
}
#endif
QObject* obj = static_cast<QObject*>(w);
QAccessibleValueChangeEvent ev(obj, w->score()->accessibleInfo());
QAccessible::updateAccessibility(&ev);
QAccessibleValueChangeEvent vcev(obj, w->score()->accessibleInfo());
QAccessible::updateAccessibility(&vcev);
// TODO:
// some screenreaders may respond better to other events
// the version of Qt used may also be relevant, and platform too
//QAccessibleEvent ev1(obj, QAccessible::NameChanged);
//QAccessible::updateAccessibility(&ev1);
//QAccessibleEvent ev2(obj, QAccessible::DescriptionChanged);
//QAccessible::updateAccessibility(&ev2);
}

std::pair<int, float> ScoreAccessibility::barbeat(Element *e)
Expand Down
31 changes: 30 additions & 1 deletion mscore/scoreaccessibility.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,21 @@ namespace Ms {
// AccessibleScoreView
//---------------------------------------------------------

class AccessibleScoreView : public QObject, QAccessibleWidget {
#define SCOREVIEW_VALUEINTERFACE
//#define SCOREVIEW_IMAGEINTERFACE

#if defined(SCOREVIEW_VALUEINTERFACE)
#define SCOREVIEW_INHERIT_VALUE ,QAccessibleValueInterface
#else
#define SCOREVIEW_INHERIT_VALUE
#endif
#if defined(SCOREVIEW_IMAGEINTERFACE)
#define SCOREVIEW_INHERIT_IMAGE ,QAccessibleImageInterface
#else
#define SCOREVIEW_INHERIT_IMAGE
#endif

class AccessibleScoreView : public QObject, QAccessibleWidget SCOREVIEW_INHERIT_VALUE SCOREVIEW_INHERIT_IMAGE {
Q_OBJECT
ScoreView* s;

Expand All @@ -22,10 +36,25 @@ class AccessibleScoreView : public QObject, QAccessibleWidget {
QAccessibleInterface* child(int /*index*/) const Q_DECL_OVERRIDE;
QAccessibleInterface* parent() const Q_DECL_OVERRIDE;
QRect rect() const Q_DECL_OVERRIDE;
bool isValid() const Q_DECL_OVERRIDE;
QAccessible::State state() const Q_DECL_OVERRIDE;
QAccessible::Role role() const Q_DECL_OVERRIDE;
QString text(QAccessible::Text t) const Q_DECL_OVERRIDE;
QWindow* window() const Q_DECL_OVERRIDE;
static QAccessibleInterface* ScoreViewFactory(const QString &classname, QObject *object);
virtual void* interface_cast(QAccessible::InterfaceType t) Q_DECL_OVERRIDE;
#ifdef SCOREVIEW_VALUEINTERFACE
virtual void setCurrentValue(const QVariant&) Q_DECL_OVERRIDE;
virtual QVariant currentValue() const Q_DECL_OVERRIDE;
virtual QVariant maximumValue() const Q_DECL_OVERRIDE;
virtual QVariant minimumValue() const Q_DECL_OVERRIDE;
virtual QVariant minimumStepSize() const Q_DECL_OVERRIDE;
#endif
#ifdef SCOREVIEW_IMAGEINTERFACE
virtual QString imageDescription() const Q_DECL_OVERRIDE;
virtual QSize imageSize() const Q_DECL_OVERRIDE;
virtual QPoint imagePosition() const Q_DECL_OVERRIDE;
#endif
};

//---------------------------------------------------------
Expand Down
12 changes: 12 additions & 0 deletions mscore/scoretab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ namespace Ms {
ScoreTab::ScoreTab(QList<MasterScore*>* sl, QWidget* parent)
: QWidget(parent)
{
setObjectName("scoretab");
setAccessibleName("");
mainWindow = static_cast<MuseScore*>(parent);
scoreList = sl;
QVBoxLayout* layout = new QVBoxLayout;
Expand All @@ -53,13 +55,17 @@ ScoreTab::ScoreTab(QList<MasterScore*>* sl, QWidget* parent)
connect(ag, SIGNAL(triggered(QAction*)), this, SIGNAL(actionTriggered(QAction*)));

tab = new QTabBar(this);
tab->setObjectName("primarytab");
tab->setAccessibleName("");
tab->setExpanding(false);
tab->setSelectionBehaviorOnRemove(QTabBar::SelectRightTab);
tab->setFocusPolicy(Qt::ClickFocus);
tab->setTabsClosable(true);
tab->setMovable(true);

tab2 = new QTabBar(this);
tab2->setObjectName("secondarytab");
tab2->setAccessibleName("");
tab2->setExpanding(false);
tab2->setSelectionBehaviorOnRemove(QTabBar::SelectRightTab);
tab2->setFocusPolicy(Qt::ClickFocus);
Expand Down Expand Up @@ -188,6 +194,8 @@ void ScoreTab::setCurrent(int n)
ScoreView* v;
if (!vs) {
vs = new QSplitter;
vs->setObjectName("score");
vs->setAccessibleName("");
v = new ScoreView;
tab2->blockSignals(true);
tab2->setCurrentIndex(0);
Expand Down Expand Up @@ -325,6 +333,8 @@ void ScoreTab::setExcerpt(int n)
}
if (!vs) {
vs = new QSplitter;
vs->setObjectName("part");
vs->setAccessibleName("");
v = new ScoreView;
vs->addWidget(v);
v->setScore(score);
Expand Down Expand Up @@ -472,6 +482,8 @@ void ScoreTab::initScoreView(int idx, double mag, MagIdx magIdx, double xoffset,
return;
}
QSplitter* vs = new QSplitter;
vs->setObjectName("score");
vs->setAccessibleName("");
vs->addWidget(v);
stack->addWidget(vs);
}
Expand Down
2 changes: 1 addition & 1 deletion mscore/scoreview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ ScoreView::ScoreView(QWidget* parent)
setAttribute(Qt::WA_OpaquePaintEvent);
#endif
setAttribute(Qt::WA_NoSystemBackground);
setFocusPolicy(Qt::ClickFocus);
setFocusPolicy(Qt::StrongFocus);
setAttribute(Qt::WA_InputMethodEnabled);
setAttribute(Qt::WA_KeyCompression);
setAttribute(Qt::WA_StaticContents);
Expand Down

0 comments on commit 9dc93c5

Please sign in to comment.