Implement a dedicated QAccessibleInterface for QMessageBox

QMessageBox has text values that an accessible client should be able to
read directly without having to navigate through the text labels.

Add test coverage.

Windows Narrator is inconsistent in reading the contents of a message
box. It might skip them completely, even though the text property is
read through the interface.

Task-number: QTBUG-101585
Change-Id: I639c2210a627733c093743790c6a6b83f4bb80d0
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
(cherry picked from commit 56d6a360206c7bd93e6503a63daf1517ff40a1d4)
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Volker Hilsheimer 2022-06-13 20:50:22 +02:00
parent 02c9117836
commit bbde24f19a
4 changed files with 159 additions and 1 deletions

View File

@ -85,7 +85,7 @@ QAccessibleInterface *qAccessibleFactory(const QString &classname, QObject *obje
} else if (classname == "QDialog"_L1) { } else if (classname == "QDialog"_L1) {
iface = new QAccessibleWidget(widget, QAccessible::Dialog); iface = new QAccessibleWidget(widget, QAccessible::Dialog);
} else if (classname == "QMessageBox"_L1) { } else if (classname == "QMessageBox"_L1) {
iface = new QAccessibleWidget(widget, QAccessible::AlertMessage); iface = new QAccessibleMessageBox(widget);
#if QT_CONFIG(mainwindow) #if QT_CONFIG(mainwindow)
} else if (classname == "QMainWindow"_L1) { } else if (classname == "QMainWindow"_L1) {
iface = new QAccessibleMainWindow(widget); iface = new QAccessibleMainWindow(widget);

View File

@ -43,6 +43,8 @@
#ifndef QT_NO_PICTURE #ifndef QT_NO_PICTURE
#include <QtGui/qpicture.h> #include <QtGui/qpicture.h>
#endif #endif
#include <qmessagebox.h>
#include <qdialogbuttonbox.h>
#include <qstyle.h> #include <qstyle.h>
#include <qstyleoption.h> #include <qstyleoption.h>
#include <qtextdocument.h> #include <qtextdocument.h>
@ -951,6 +953,47 @@ QWindowContainer *QAccessibleWindowContainer::container() const
return static_cast<QWindowContainer *>(widget()); return static_cast<QWindowContainer *>(widget());
} }
/*!
\internal
Implements QAccessibleWidget for QMessageBox
*/
QAccessibleMessageBox::QAccessibleMessageBox(QWidget *widget)
: QAccessibleWidget(widget, QAccessible::AlertMessage)
{
Q_ASSERT(qobject_cast<QMessageBox *>(widget));
}
QMessageBox *QAccessibleMessageBox::messageBox() const
{
return static_cast<QMessageBox *>(widget());
}
QString QAccessibleMessageBox::text(QAccessible::Text t) const
{
QString str;
switch (t) {
case QAccessible::Name:
str = QAccessibleWidget::text(t);
if (str.isEmpty()) // implies no title text is set
str = messageBox()->text();
break;
case QAccessible::Description:
str = widget()->accessibleDescription();
break;
case QAccessible::Value:
str = messageBox()->text();
break;
case QAccessible::Help:
str = messageBox()->informativeText();
break;
default:
break;
}
return str;
}
#endif // QT_CONFIG(accessibility) #endif // QT_CONFIG(accessibility)
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -27,6 +27,7 @@ class QAbstractButton;
class QLineEdit; class QLineEdit;
class QToolButton; class QToolButton;
class QGroupBox; class QGroupBox;
class QMessageBox;
class QProgressBar; class QProgressBar;
#if QT_CONFIG(abstractbutton) #if QT_CONFIG(abstractbutton)
@ -189,6 +190,16 @@ private:
QWindowContainer *container() const; QWindowContainer *container() const;
}; };
class QAccessibleMessageBox : public QAccessibleWidget
{
public:
explicit QAccessibleMessageBox(QWidget *widget);
QString text(QAccessible::Text t) const override;
QMessageBox *messageBox() const;
};
#endif // QT_CONFIG(accessibility) #endif // QT_CONFIG(accessibility)
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -21,6 +21,8 @@
#include <QtGui/private/qguiapplication_p.h> #include <QtGui/private/qguiapplication_p.h>
#include <QtGui/private/qhighdpiscaling_p.h> #include <QtGui/private/qhighdpiscaling_p.h>
#include <QtWidgets/private/qdialog_p.h>
#if defined(Q_OS_WIN) && defined(interface) #if defined(Q_OS_WIN) && defined(interface)
# undef interface # undef interface
#endif #endif
@ -219,6 +221,9 @@ private slots:
void bridgeTest(); void bridgeTest();
void focusChild(); void focusChild();
void messageBoxTest_data();
void messageBoxTest();
protected slots: protected slots:
void onClicked(); void onClicked();
private: private:
@ -4196,6 +4201,105 @@ void tst_QAccessibility::focusChild()
} }
} }
void tst_QAccessibility::messageBoxTest_data()
{
QTest::addColumn<QMessageBox::Icon>("icon");
QTest::addColumn<QMessageBox::StandardButtons>("buttons");
QTest::addColumn<QString>("title");
QTest::addColumn<QString>("text");
QTest::addColumn<QString>("infoText");
QTest::addColumn<QString>("details");
QTest::addColumn<bool>("checkbox");
QTest::addColumn<bool>("textInteractive");
QTest::addRow("Information") << QMessageBox::Information
<< QMessageBox::StandardButtons(QMessageBox::Ok)
<< "Information"
<< "Here, have some information."
<< QString()
<< QString()
<< false
<< false;
QTest::addRow("Warning") << QMessageBox::Warning
<< QMessageBox::StandardButtons(QMessageBox::Ok | QMessageBox::Cancel)
<< "Warning"
<< "This is a dangerous operation!"
<< "Ok or Cancel?"
<< QString()
<< true
<< false;
QTest::addRow("Error") << QMessageBox::Critical
<< QMessageBox::StandardButtons(QMessageBox::Abort | QMessageBox::Retry | QMessageBox::Ignore)
<< "Error"
<< "Operation failed for http://example.com"
<< "You have to decide what to do"
<< "Detailed log output"
<< false
<< true;
}
void tst_QAccessibility::messageBoxTest()
{
QFETCH(QMessageBox::Icon, icon);
QFETCH(QMessageBox::StandardButtons, buttons);
QFETCH(QString, title);
QFETCH(QString, text);
QFETCH(QString, infoText);
QFETCH(QString, details);
QFETCH(bool, checkbox);
QFETCH(bool, textInteractive);
QMessageBox box(icon, title, text, buttons);
QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(&box);
QVERIFY(iface);
QCOMPARE(iface->role(), QAccessible::AlertMessage);
#ifndef Q_OS_DARWIN // macOS message boxes show no title
QCOMPARE(iface->text(QAccessible::Name), title);
#endif
QCOMPARE(iface->text(QAccessible::Value), text);
int expectedChildCount = 3;
QCOMPARE(iface->childCount(), expectedChildCount);
if (textInteractive)
box.setTextInteractionFlags(Qt::TextBrowserInteraction);
if (!infoText.isEmpty()) {
box.setInformativeText(infoText);
QCOMPARE(iface->childCount(), ++expectedChildCount);
}
QCOMPARE(iface->text(QAccessible::Help), infoText);
if (!details.isEmpty()) {
box.setDetailedText(details);
QCOMPARE(iface->childCount(), ++expectedChildCount);
}
if (checkbox) {
box.setCheckBox(new QCheckBox("Don't show again"));
QCOMPARE(iface->childCount(), ++expectedChildCount);
}
QTestAccessibility::clearEvents();
QDialogPrivate *boxPrivate = static_cast<QDialogPrivate *>(QDialogPrivate::get(&box));
if (!boxPrivate->canBeNativeDialog()) {
// platforms that use a native message box will not emit accessibility events
box.show();
QAccessibleEvent showEvent(&box, QAccessible::DialogStart);
QVERIFY(QTestAccessibility::containsEvent(&showEvent));
box.hide();
QAccessibleEvent hideEvent(&box, QAccessible::DialogEnd);
QVERIFY(QTestAccessibility::containsEvent(&hideEvent));
}
QTestAccessibility::clearEvents();
}
QTEST_MAIN(tst_QAccessibility) QTEST_MAIN(tst_QAccessibility)
#include "tst_qaccessibility.moc" #include "tst_qaccessibility.moc"