Accessibility: QSpinBox should not have any children

On both iOS and Android it is very confusing to be able to move the focus to both, the line edit and the outer frame that is the spin box.
For Linux this fixes an issue that orca would not read the value correctly after pressing the up/down buttons.

Task-number: QTBUG-39861
Task-number: QTBUG-39442
Change-Id: I73c50c91e9021324c52d168d537afd0ea719a48f
Reviewed-by: Jan Arve Sæther <jan-arve.saether@digia.com>
bb10
Frederik Gladhorn 2014-06-27 13:23:01 +02:00
parent cf621f1b9e
commit 41f496cb7f
10 changed files with 195 additions and 20 deletions

View File

@ -70,7 +70,8 @@ static QList<QWidget*> childWidgets(const QWidget *widget)
#if !defined(QT_NO_MENU)
&& !qobject_cast<QMenu*>(w)
#endif
&& w->objectName() != QLatin1String("qt_rubberband"))
&& w->objectName() != QLatin1String("qt_rubberband")
&& w->objectName() != QLatin1String("qt_spinbox_lineedit"))
widgets.append(w);
}
return widgets;

View File

@ -66,7 +66,10 @@ QAccessibleInterface *qAccessibleFactory(const QString &classname, QObject *obje
if (false) {
#ifndef QT_NO_LINEEDIT
} else if (classname == QLatin1String("QLineEdit")) {
iface = new QAccessibleLineEdit(widget);
if (widget->objectName() == QLatin1String("qt_spinbox_lineedit"))
iface = 0;
else
iface = new QAccessibleLineEdit(widget);
#endif
#ifndef QT_NO_COMBOBOX
} else if (classname == QLatin1String("QComboBox")) {

View File

@ -51,20 +51,28 @@
#include <qglobal.h>
#include <QDoubleSpinBox>
#include <QDial>
#include <QtWidgets/qlineedit.h>
#include <qmath.h>
#include <private/qmath_p.h>
#include "simplewidgets.h" // let spinbox use line edit's interface
QT_BEGIN_NAMESPACE
#ifndef QT_NO_ACCESSIBILITY
#ifndef QT_NO_SPINBOX
QAccessibleAbstractSpinBox::QAccessibleAbstractSpinBox(QWidget *w)
: QAccessibleWidget(w, QAccessible::SpinBox)
: QAccessibleWidget(w, QAccessible::SpinBox), lineEdit(Q_NULLPTR)
{
Q_ASSERT(abstractSpinBox());
}
QAccessibleAbstractSpinBox::~QAccessibleAbstractSpinBox()
{
delete lineEdit;
}
/*!
Returns the underlying QAbstractSpinBox.
*/
@ -73,6 +81,14 @@ QAbstractSpinBox *QAccessibleAbstractSpinBox::abstractSpinBox() const
return qobject_cast<QAbstractSpinBox*>(object());
}
QAccessibleInterface *QAccessibleAbstractSpinBox::lineEditIface() const
{
// QAccessibleLineEdit is only used to forward the text functions
if (!lineEdit)
lineEdit = new QAccessibleLineEdit(abstractSpinBox()->lineEdit());
return lineEdit;
}
QString QAccessibleAbstractSpinBox::text(QAccessible::Text t) const
{
if (t == QAccessible::Value)
@ -84,6 +100,10 @@ void *QAccessibleAbstractSpinBox::interface_cast(QAccessible::InterfaceType t)
{
if (t == QAccessible::ValueInterface)
return static_cast<QAccessibleValueInterface*>(this);
if (t == QAccessible::TextInterface)
return static_cast<QAccessibleTextInterface*>(this);
if (t == QAccessible::EditableTextInterface)
return static_cast<QAccessibleEditableTextInterface*>(this);
return QAccessibleWidget::interface_cast(t);
}
@ -112,6 +132,102 @@ QVariant QAccessibleAbstractSpinBox::minimumStepSize() const
return abstractSpinBox()->property("stepSize");
}
void QAccessibleAbstractSpinBox::addSelection(int startOffset, int endOffset)
{
lineEditIface()->textInterface()->addSelection(startOffset, endOffset);
}
QString QAccessibleAbstractSpinBox::attributes(int offset, int *startOffset, int *endOffset) const
{
return lineEditIface()->textInterface()->attributes(offset, startOffset, endOffset);
}
int QAccessibleAbstractSpinBox::cursorPosition() const
{
return lineEditIface()->textInterface()->cursorPosition();
}
QRect QAccessibleAbstractSpinBox::characterRect(int offset) const
{
return lineEditIface()->textInterface()->characterRect(offset);
}
int QAccessibleAbstractSpinBox::selectionCount() const
{
return lineEditIface()->textInterface()->selectionCount();
}
int QAccessibleAbstractSpinBox::offsetAtPoint(const QPoint &point) const
{
return lineEditIface()->textInterface()->offsetAtPoint(point);
}
void QAccessibleAbstractSpinBox::selection(int selectionIndex, int *startOffset, int *endOffset) const
{
lineEditIface()->textInterface()->selection(selectionIndex, startOffset, endOffset);
}
QString QAccessibleAbstractSpinBox::text(int startOffset, int endOffset) const
{
return lineEditIface()->textInterface()->text(startOffset, endOffset);
}
QString QAccessibleAbstractSpinBox::textBeforeOffset(int offset, QAccessible::TextBoundaryType boundaryType, int *startOffset, int *endOffset) const
{
return lineEditIface()->textInterface()->textBeforeOffset(offset, boundaryType, startOffset, endOffset);
}
QString QAccessibleAbstractSpinBox::textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType, int *startOffset, int *endOffset) const
{
return lineEditIface()->textInterface()->textAfterOffset(offset, boundaryType, startOffset, endOffset);
}
QString QAccessibleAbstractSpinBox::textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType, int *startOffset, int *endOffset) const
{
return lineEditIface()->textInterface()->textAtOffset(offset, boundaryType, startOffset, endOffset);
}
void QAccessibleAbstractSpinBox::removeSelection(int selectionIndex)
{
lineEditIface()->textInterface()->removeSelection(selectionIndex);
}
void QAccessibleAbstractSpinBox::setCursorPosition(int position)
{
lineEditIface()->textInterface()->setCursorPosition(position);
}
void QAccessibleAbstractSpinBox::setSelection(int selectionIndex, int startOffset, int endOffset)
{
lineEditIface()->textInterface()->setSelection(selectionIndex, startOffset, endOffset);
}
int QAccessibleAbstractSpinBox::characterCount() const
{
return lineEditIface()->textInterface()->characterCount();
}
void QAccessibleAbstractSpinBox::scrollToSubstring(int startIndex, int endIndex)
{
lineEditIface()->textInterface()->scrollToSubstring(startIndex, endIndex);
}
void QAccessibleAbstractSpinBox::deleteText(int startOffset, int endOffset)
{
lineEditIface()->editableTextInterface()->deleteText(startOffset, endOffset);
}
void QAccessibleAbstractSpinBox::insertText(int offset, const QString &text)
{
lineEditIface()->editableTextInterface()->insertText(offset, text);
}
void QAccessibleAbstractSpinBox::replaceText(int startOffset, int endOffset, const QString &text)
{
lineEditIface()->editableTextInterface()->replaceText(startOffset, endOffset, text);
}
/*!
\class QAccessibleSpinBox
\brief The QAccessibleSpinBox class implements the QAccessibleInterface for spinbox widgets.

View File

@ -55,12 +55,18 @@ class QSlider;
class QSpinBox;
class QDoubleSpinBox;
class QDial;
class QAccessibleLineEdit;
#ifndef QT_NO_SPINBOX
class QAccessibleAbstractSpinBox: public QAccessibleWidget, public QAccessibleValueInterface // TODO, public QAccessibleActionInterface
class QAccessibleAbstractSpinBox:
public QAccessibleWidget,
public QAccessibleValueInterface,
public QAccessibleTextInterface,
public QAccessibleEditableTextInterface
{
public:
explicit QAccessibleAbstractSpinBox(QWidget *w);
virtual ~QAccessibleAbstractSpinBox();
QString text(QAccessible::Text t) const Q_DECL_OVERRIDE;
void *interface_cast(QAccessible::InterfaceType t) Q_DECL_OVERRIDE;
@ -72,10 +78,37 @@ public:
QVariant minimumValue() const Q_DECL_OVERRIDE;
QVariant minimumStepSize() const Q_DECL_OVERRIDE;
// FIXME Action interface
// QAccessibleTextInterface
void addSelection(int startOffset, int endOffset) Q_DECL_OVERRIDE;
QString attributes(int offset, int *startOffset, int *endOffset) const Q_DECL_OVERRIDE;
int cursorPosition() const Q_DECL_OVERRIDE;
QRect characterRect(int offset) const Q_DECL_OVERRIDE;
int selectionCount() const Q_DECL_OVERRIDE;
int offsetAtPoint(const QPoint &point) const Q_DECL_OVERRIDE;
void selection(int selectionIndex, int *startOffset, int *endOffset) const Q_DECL_OVERRIDE;
QString text(int startOffset, int endOffset) const Q_DECL_OVERRIDE;
QString textBeforeOffset (int offset, QAccessible::TextBoundaryType boundaryType,
int *endOffset, int *startOffset) const Q_DECL_OVERRIDE;
QString textAfterOffset(int offset, QAccessible::TextBoundaryType boundaryType,
int *startOffset, int *endOffset) const Q_DECL_OVERRIDE;
QString textAtOffset(int offset, QAccessible::TextBoundaryType boundaryType,
int *startOffset, int *endOffset) const Q_DECL_OVERRIDE;
void removeSelection(int selectionIndex) Q_DECL_OVERRIDE;
void setCursorPosition(int position) Q_DECL_OVERRIDE;
void setSelection(int selectionIndex, int startOffset, int endOffset) Q_DECL_OVERRIDE;
int characterCount() const Q_DECL_OVERRIDE;
void scrollToSubstring(int startIndex, int endIndex) Q_DECL_OVERRIDE;
// QAccessibleEditableTextInterface
void deleteText(int startOffset, int endOffset) Q_DECL_OVERRIDE;
void insertText(int offset, const QString &text) Q_DECL_OVERRIDE;
void replaceText(int startOffset, int endOffset, const QString &text) Q_DECL_OVERRIDE;
protected:
QAbstractSpinBox *abstractSpinBox() const;
QAccessibleInterface *lineEditIface() const;
private:
mutable QAccessibleLineEdit *lineEdit;
};
class QAccessibleSpinBox : public QAccessibleAbstractSpinBox
@ -94,6 +127,7 @@ public:
QString text(QAccessible::Text t) const Q_DECL_OVERRIDE;
using QAccessibleAbstractSpinBox::text;
protected:
QDoubleSpinBox *doubleSpinBox() const;
};

View File

@ -174,6 +174,7 @@ public:
void replaceText(int startOffset, int endOffset, const QString &text) Q_DECL_OVERRIDE;
protected:
QLineEdit *lineEdit() const;
friend class QAccessibleAbstractSpinBox;
};
#endif // QT_NO_LINEEDIT

View File

@ -699,6 +699,7 @@ void QAbstractSpinBox::setLineEdit(QLineEdit *lineEdit)
}
d->updateEditFieldGeometry();
d->edit->setContextMenuPolicy(Qt::NoContextMenu);
d->edit->d_func()->control->setAccessibleObject(this);
if (isVisible())
d->edit->show();

View File

@ -170,6 +170,7 @@ private:
Q_DECLARE_PRIVATE(QAbstractSpinBox)
Q_DISABLE_COPY(QAbstractSpinBox)
friend class QAccessibleAbstractSpinBox;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QAbstractSpinBox::StepEnabled)

View File

@ -737,15 +737,15 @@ void QWidgetLineControl::internalSetText(const QString &txt, int pos, bool edite
#ifndef QT_NO_ACCESSIBILITY
if (changed) {
if (oldText.isEmpty()) {
QAccessibleTextInsertEvent event(parent(), 0, txt);
QAccessibleTextInsertEvent event(accessibleObject(), 0, txt);
event.setCursorPosition(m_cursor);
QAccessible::updateAccessibility(&event);
} else if (txt.isEmpty()) {
QAccessibleTextRemoveEvent event(parent(), 0, oldText);
QAccessibleTextRemoveEvent event(accessibleObject(), 0, oldText);
event.setCursorPosition(m_cursor);
QAccessible::updateAccessibility(&event);
} else {
QAccessibleTextUpdateEvent event(parent(), 0, oldText, txt);
QAccessibleTextUpdateEvent event(accessibleObject(), 0, oldText, txt);
event.setCursorPosition(m_cursor);
QAccessible::updateAccessibility(&event);
}
@ -803,7 +803,7 @@ void QWidgetLineControl::internalInsert(const QString &s)
if (m_maskData) {
QString ms = maskString(m_cursor, s);
#ifndef QT_NO_ACCESSIBILITY
QAccessibleTextInsertEvent insertEvent(parent(), m_cursor, ms);
QAccessibleTextInsertEvent insertEvent(accessibleObject(), m_cursor, ms);
QAccessible::updateAccessibility(&insertEvent);
#endif
for (int i = 0; i < (int) ms.length(); ++i) {
@ -815,14 +815,14 @@ void QWidgetLineControl::internalInsert(const QString &s)
m_cursor = nextMaskBlank(m_cursor);
m_textDirty = true;
#ifndef QT_NO_ACCESSIBILITY
QAccessibleTextCursorEvent event(parent(), m_cursor);
QAccessibleTextCursorEvent event(accessibleObject(), m_cursor);
QAccessible::updateAccessibility(&event);
#endif
} else {
int remaining = m_maxLength - m_text.length();
if (remaining != 0) {
#ifndef QT_NO_ACCESSIBILITY
QAccessibleTextInsertEvent insertEvent(parent(), m_cursor, s);
QAccessibleTextInsertEvent insertEvent(accessibleObject(), m_cursor, s);
QAccessible::updateAccessibility(&insertEvent);
#endif
m_text.insert(m_cursor, s.left(remaining));
@ -853,7 +853,7 @@ void QWidgetLineControl::internalDelete(bool wasBackspace)
addCommand(Command((CommandType)((m_maskData ? 2 : 0) + (wasBackspace ? Remove : Delete)),
m_cursor, m_text.at(m_cursor), -1, -1));
#ifndef QT_NO_ACCESSIBILITY
QAccessibleTextRemoveEvent event(parent(), m_cursor, m_text.at(m_cursor));
QAccessibleTextRemoveEvent event(accessibleObject(), m_cursor, m_text.at(m_cursor));
QAccessible::updateAccessibility(&event);
#endif
if (m_maskData) {
@ -894,7 +894,7 @@ void QWidgetLineControl::removeSelectedText()
addCommand (Command(RemoveSelection, i, m_text.at(i), -1, -1));
}
#ifndef QT_NO_ACCESSIBILITY
QAccessibleTextRemoveEvent event(parent(), m_selstart, m_text.mid(m_selstart, m_selend - m_selstart));
QAccessibleTextRemoveEvent event(accessibleObject(), m_selstart, m_text.mid(m_selstart, m_selend - m_selstart));
QAccessible::updateAccessibility(&event);
#endif
if (m_maskData) {
@ -1384,7 +1384,7 @@ void QWidgetLineControl::emitCursorPositionChanged()
#ifndef QT_NO_ACCESSIBILITY
// otherwise we send a selection update which includes the cursor
if (!hasSelectedText()) {
QAccessibleTextCursorEvent event(parent(), m_cursor);
QAccessibleTextCursorEvent event(accessibleObject(), m_cursor);
QAccessible::updateAccessibility(&event);
}
#endif

View File

@ -99,6 +99,7 @@ public:
, m_passwordMaskDelayOverride(-1)
#endif
, m_keyboardScheme(0)
, m_accessibleObject(0)
{
init(txt);
}
@ -108,6 +109,19 @@ public:
delete [] m_maskData;
}
void setAccessibleObject(QObject *object)
{
Q_ASSERT(object);
m_accessibleObject = object;
}
QObject *accessibleObject()
{
if (m_accessibleObject)
return m_accessibleObject;
return parent();
}
int nextMaskBlank(int pos)
{
int c = findInMask(pos, true, false);
@ -532,6 +546,9 @@ private Q_SLOTS:
private:
int m_keyboardScheme;
// accessibility events are sent for this object
QObject *m_accessibleObject;
};
QT_END_NAMESPACE

View File

@ -1676,13 +1676,10 @@ void tst_QAccessibility::spinBoxTest()
QCOMPARE(accessibleRect, widgetRect);
QCOMPARE(interface->text(QAccessible::Value), QLatin1String("3"));
// one child, the line edit
// make sure that the line edit is not there
const int numChildren = interface->childCount();
QCOMPARE(numChildren, 1);
QAccessibleInterface *lineEdit = interface->child(0);
QCOMPARE(lineEdit->role(), QAccessible::EditableText);
QCOMPARE(lineEdit->text(QAccessible::Value), QLatin1String("3"));
QCOMPARE(numChildren, 0);
QVERIFY(interface->child(0) == Q_NULLPTR);
QVERIFY(interface->valueInterface());
QCOMPARE(interface->valueInterface()->currentValue().toInt(), 3);
@ -1696,6 +1693,10 @@ void tst_QAccessibility::spinBoxTest()
QTest::qWait(200);
QAccessibleValueChangeEvent expectedEvent(spinBox, spinBox->value());
QVERIFY(QTestAccessibility::containsEvent(&expectedEvent));
QAccessibleTextInterface *textIface = interface->textInterface();
QVERIFY(textIface);
delete spinBox;
QTestAccessibility::clearEvents();
}