Skip to content

Commit

Permalink
Merge pull request #5627 from MarcSabatella/299387-new-screenreader-s…
Browse files Browse the repository at this point in the history
…upport

fix #299387: support for more screen readers
  • Loading branch information
dmitrio95 committed Feb 7, 2020
2 parents b76a86c + 094af7c commit 6bf7eda
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 19 deletions.
16 changes: 14 additions & 2 deletions mscore/musescore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1080,13 +1080,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 @@ -1130,6 +1135,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 @@ -1158,6 +1165,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 @@ -2354,7 +2363,9 @@ void MuseScore::updatePaletteBeamMode()

void MuseScore::updateInspector()
{
if (_inspector)
// skip update if no inspector, or if inspector is hidden and there is a GUI
// (important not to skip when running test scripts)
if (_inspector && (_inspector->isVisible() || MScore::testMode || scriptTestMode))
_inspector->update(cs);
}

Expand Down Expand Up @@ -5807,7 +5818,8 @@ void MuseScore::cmd(QAction* a)
cmd(a, cmdn);
if (lastShortcut->isCmd())
cs->endCmd();
endCmd();
else
endCmd();
TourHandler::startTour(cmdn);
}

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
38 changes: 37 additions & 1 deletion mscore/scoreaccessibility.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,28 @@ namespace Ms {
// AccessibleScoreView
//---------------------------------------------------------

class AccessibleScoreView : public QObject, QAccessibleWidget {
#ifndef Q_OS_WIN
// implement value interface so the value change event is generated
// even though it is not an expected property for static text on Linux (AT-SPI)
// on Windows, static text *does* use value, so defining this interface is unnecessary
// and it results in Narrator reading all of the range value information (min/max/current)
// macOS is more like Linux than it is like Windows...
#define SCOREVIEW_VALUEINTERFACE
#endif
//#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 +43,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 6bf7eda

Please sign in to comment.