Fix accessibility of list views with underlying multi-column model
A list view should always expose a table with a single column to accessibility tools even if the underlying model has multiple columns. Several functions need to be changed so that they only consider the model column that was set on the list view. For a list view logicalIndex() must only consider indexes for the model column. For valid indexes the logical index is simply the row because list views have neither row headers nor column headers. The column count for list views is always 1 (unless the model has no columns). The child count needs to use the column count of the accessible table instead of the column count of the underlying model. child(), cellAt(), selectedCellCount(), and selectedCells() get separate implementation for list views. Fixes: QTBUG-33786 Pick-to: 6.7 6.6 Change-Id: I18c604efa2014267bb6e3b68e403e436bdcbc4ce Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io> (cherry picked from commit cd00ce4bea6f0386048bd267495433cffe83ab12) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
ea9df2d8df
commit
09548cb710
@ -39,16 +39,24 @@ QAbstractItemView *QAccessibleTable::view() const
|
||||
|
||||
int QAccessibleTable::logicalIndex(const QModelIndex &index) const
|
||||
{
|
||||
const QAbstractItemView *theView = view();
|
||||
const QAbstractItemModel *theModel = index.model();
|
||||
if (!theModel || !index.isValid())
|
||||
return -1;
|
||||
|
||||
const QModelIndex rootIndex = theView->rootIndex();
|
||||
const int vHeader = verticalHeader() ? 1 : 0;
|
||||
const int hHeader = horizontalHeader() ? 1 : 0;
|
||||
return (index.row() + hHeader) * (theModel->columnCount(rootIndex) + vHeader)
|
||||
+ (index.column() + vHeader);
|
||||
#if QT_CONFIG(listview)
|
||||
if (m_role == QAccessible::List) {
|
||||
if (index.column() != qobject_cast<const QListView*>(view())->modelColumn())
|
||||
return -1;
|
||||
else
|
||||
return index.row();
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
const int vHeader = verticalHeader() ? 1 : 0;
|
||||
const int hHeader = horizontalHeader() ? 1 : 0;
|
||||
return (index.row() + hHeader) * (columnCount() + vHeader)
|
||||
+ (index.column() + vHeader);
|
||||
}
|
||||
}
|
||||
|
||||
QAccessibleTable::QAccessibleTable(QWidget *w)
|
||||
@ -122,6 +130,7 @@ QAccessibleInterface *QAccessibleTable::cellAt(int row, int column) const
|
||||
const QAbstractItemModel *theModel = theView->model();
|
||||
if (!theModel)
|
||||
return nullptr;
|
||||
Q_ASSERT(role() != QAccessible::List);
|
||||
Q_ASSERT(role() != QAccessible::Tree);
|
||||
QModelIndex index = theModel->index(row, column, theView->rootIndex());
|
||||
if (Q_UNLIKELY(!index.isValid())) {
|
||||
@ -151,7 +160,8 @@ int QAccessibleTable::columnCount() const
|
||||
const QAbstractItemModel *theModel = theView->model();
|
||||
if (!theModel)
|
||||
return 0;
|
||||
return theModel->columnCount(theView->rootIndex());
|
||||
const int modelColumnCount = theModel->columnCount(theView->rootIndex());
|
||||
return m_role == QAccessible::List ? qMin(1, modelColumnCount) : modelColumnCount;
|
||||
}
|
||||
|
||||
int QAccessibleTable::rowCount() const
|
||||
@ -541,7 +551,7 @@ int QAccessibleTable::childCount() const
|
||||
const QModelIndex rootIndex = theView->rootIndex();
|
||||
int vHeader = verticalHeader() ? 1 : 0;
|
||||
int hHeader = horizontalHeader() ? 1 : 0;
|
||||
return (theModel->rowCount(rootIndex) + hHeader) * (theModel->columnCount(rootIndex) + vHeader);
|
||||
return (theModel->rowCount(rootIndex) + hHeader) * (columnCount() + vHeader);
|
||||
}
|
||||
|
||||
int QAccessibleTable::indexOfChild(const QAccessibleInterface *iface) const
|
||||
@ -969,6 +979,84 @@ bool QAccessibleTree::selectRow(int row)
|
||||
|
||||
#endif // QT_CONFIG(treeview)
|
||||
|
||||
#if QT_CONFIG(listview)
|
||||
|
||||
// LIST VIEW
|
||||
|
||||
QAccessibleInterface *QAccessibleList::child(int logicalIndex) const
|
||||
{
|
||||
QAbstractItemView *theView = view();
|
||||
const QAbstractItemModel *theModel = theView->model();
|
||||
if (!theModel)
|
||||
return nullptr;
|
||||
|
||||
if (columnCount() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto id = childToId.constFind(logicalIndex);
|
||||
if (id != childToId.constEnd())
|
||||
return QAccessible::accessibleInterface(id.value());
|
||||
|
||||
const QListView *listView = qobject_cast<const QListView*>(theView);
|
||||
Q_ASSERT(listView);
|
||||
int row = logicalIndex;
|
||||
int column = listView->modelColumn();
|
||||
|
||||
const QModelIndex rootIndex = theView->rootIndex();
|
||||
const QModelIndex index = theModel->index(row, column, rootIndex);
|
||||
if (Q_UNLIKELY(!index.isValid())) {
|
||||
qWarning("QAccessibleList::child: Invalid index at: %d %d", row, column);
|
||||
return nullptr;
|
||||
}
|
||||
const auto iface = new QAccessibleTableCell(theView, index, cellRole());
|
||||
|
||||
QAccessible::registerAccessibleInterface(iface);
|
||||
childToId.insert(logicalIndex, QAccessible::uniqueId(iface));
|
||||
return iface;
|
||||
}
|
||||
|
||||
QAccessibleInterface *QAccessibleList::cellAt(int row, int column) const
|
||||
{
|
||||
if (column != 0)
|
||||
return nullptr;
|
||||
|
||||
return child(row);
|
||||
}
|
||||
|
||||
int QAccessibleList::selectedCellCount() const
|
||||
{
|
||||
QAbstractItemView *theView = view();
|
||||
if (!theView->selectionModel())
|
||||
return 0;
|
||||
const QListView *listView = qobject_cast<const QListView*>(theView);
|
||||
const int modelColumn = listView->modelColumn();
|
||||
const QModelIndexList selectedIndexes = theView->selectionModel()->selectedIndexes();
|
||||
return std::count_if(selectedIndexes.cbegin(), selectedIndexes.cend(),
|
||||
[modelColumn](const auto &index) {
|
||||
return index.column() == modelColumn;
|
||||
});
|
||||
}
|
||||
|
||||
QList<QAccessibleInterface *> QAccessibleList::selectedCells() const
|
||||
{
|
||||
QAbstractItemView *theView = view();
|
||||
QList<QAccessibleInterface*> cells;
|
||||
if (!view()->selectionModel() || columnCount() == 0)
|
||||
return cells;
|
||||
const QListView *listView = qobject_cast<const QListView*>(theView);
|
||||
const int modelColumn = listView->modelColumn();
|
||||
const QModelIndexList selectedIndexes = theView->selectionModel()->selectedIndexes();
|
||||
cells.reserve(qMin(selectedIndexes.size(), rowCount()));
|
||||
for (const QModelIndex &index : selectedIndexes)
|
||||
if (index.column() == modelColumn) {
|
||||
cells.append(child(index.row()));
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
#endif // QT_CONFIG(listview)
|
||||
|
||||
// TABLE CELL
|
||||
|
||||
QAccessibleTableCell::QAccessibleTableCell(QAbstractItemView *view_, const QModelIndex &index_, QAccessible::Role role_)
|
||||
@ -1042,6 +1130,11 @@ int QAccessibleTableCell::columnIndex() const
|
||||
{
|
||||
if (!isValid())
|
||||
return -1;
|
||||
#if QT_CONFIG(listview)
|
||||
if (role() == QAccessible::ListItem) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
return m_index.column();
|
||||
}
|
||||
|
||||
|
@ -147,6 +147,25 @@ private:
|
||||
};
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(listview)
|
||||
class QAccessibleList :public QAccessibleTable
|
||||
{
|
||||
public:
|
||||
explicit QAccessibleList(QWidget *w)
|
||||
: QAccessibleTable(w)
|
||||
{}
|
||||
|
||||
QAccessibleInterface *child(int index) const override;
|
||||
|
||||
// table interface
|
||||
QAccessibleInterface *cellAt(int row, int column) const override;
|
||||
|
||||
// selection
|
||||
int selectedCellCount() const override;
|
||||
QList<QAccessibleInterface*> selectedCells() const override;
|
||||
};
|
||||
#endif
|
||||
|
||||
class QAccessibleTableCell: public QAccessibleInterface, public QAccessibleTableCellInterface, public QAccessibleActionInterface
|
||||
{
|
||||
public:
|
||||
@ -198,6 +217,9 @@ friend class QAccessibleTable;
|
||||
#if QT_CONFIG(treeview)
|
||||
friend class QAccessibleTree;
|
||||
#endif
|
||||
#if QT_CONFIG(listview)
|
||||
friend class QAccessibleList;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@ -234,6 +256,9 @@ friend class QAccessibleTable;
|
||||
#if QT_CONFIG(treeview)
|
||||
friend class QAccessibleTree;
|
||||
#endif
|
||||
#if QT_CONFIG(listview)
|
||||
friend class QAccessibleList;
|
||||
#endif
|
||||
};
|
||||
|
||||
// This is the corner button on the top left of a table.
|
||||
|
@ -118,8 +118,12 @@ QAccessibleInterface *qAccessibleFactory(const QString &classname, QObject *obje
|
||||
} else if (classname == "QTreeView"_L1) {
|
||||
iface = new QAccessibleTree(widget);
|
||||
#endif // QT_CONFIG(treeview)
|
||||
#if QT_CONFIG(listview)
|
||||
} else if (classname == "QListView"_L1) {
|
||||
iface = new QAccessibleList(widget);
|
||||
#endif
|
||||
#if QT_CONFIG(itemviews)
|
||||
} else if (classname == "QTableView"_L1 || classname == "QListView"_L1) {
|
||||
} else if (classname == "QTableView"_L1) {
|
||||
iface = new QAccessibleTable(widget);
|
||||
#endif // QT_CONFIG(itemviews)
|
||||
#if QT_CONFIG(tabbar)
|
||||
|
@ -2852,11 +2852,15 @@ void tst_QAccessibility::scrollAreaTest()
|
||||
void tst_QAccessibility::listTest()
|
||||
{
|
||||
{
|
||||
auto lvHolder = std::make_unique<QListWidget>();
|
||||
const auto modelHolder = std::make_unique<QStandardItemModel>();
|
||||
auto model = modelHolder.get();
|
||||
model->appendRow({new QStandardItem("Norway"), new QStandardItem("Oslo"), new QStandardItem("NOK")});
|
||||
model->appendRow({new QStandardItem("Germany"), new QStandardItem("Berlin"), new QStandardItem("EUR")});
|
||||
model->appendRow({new QStandardItem("Australia"), new QStandardItem("Brisbane"), new QStandardItem("AUD")});
|
||||
auto lvHolder = std::make_unique<QListView>();
|
||||
auto listView = lvHolder.get();
|
||||
listView->addItem("Oslo");
|
||||
listView->addItem("Berlin");
|
||||
listView->addItem("Brisbane");
|
||||
listView->setModel(model);
|
||||
listView->setModelColumn(1);
|
||||
listView->resize(400,400);
|
||||
listView->show();
|
||||
QTest::qWait(1); // Need this for indexOfchild to work.
|
||||
@ -2895,14 +2899,14 @@ void tst_QAccessibility::listTest()
|
||||
QTestAccessibility::clearEvents();
|
||||
|
||||
// Check for events
|
||||
QTest::mouseClick(listView->viewport(), Qt::LeftButton, { }, listView->visualItemRect(listView->item(1)).center());
|
||||
QTest::mouseClick(listView->viewport(), Qt::LeftButton, { }, listView->visualRect(model->index(1, listView->modelColumn())).center());
|
||||
QAccessibleEvent selectionEvent(listView, QAccessible::SelectionAdd);
|
||||
selectionEvent.setChild(1);
|
||||
QAccessibleEvent focusEvent(listView, QAccessible::Focus);
|
||||
focusEvent.setChild(1);
|
||||
QVERIFY(QTestAccessibility::containsEvent(&selectionEvent));
|
||||
QVERIFY(QTestAccessibility::containsEvent(&focusEvent));
|
||||
QTest::mouseClick(listView->viewport(), Qt::LeftButton, { }, listView->visualItemRect(listView->item(2)).center());
|
||||
QTest::mouseClick(listView->viewport(), Qt::LeftButton, { }, listView->visualRect(model->index(2, listView->modelColumn())).center());
|
||||
|
||||
QAccessibleEvent selectionEvent2(listView, QAccessible::SelectionAdd);
|
||||
selectionEvent2.setChild(2);
|
||||
@ -2911,7 +2915,7 @@ void tst_QAccessibility::listTest()
|
||||
QVERIFY(QTestAccessibility::containsEvent(&selectionEvent2));
|
||||
QVERIFY(QTestAccessibility::containsEvent(&focusEvent2));
|
||||
|
||||
listView->addItem("Munich");
|
||||
model->appendRow({new QStandardItem("Germany"), new QStandardItem("Munich"), new QStandardItem("EUR")});
|
||||
QCOMPARE(iface->childCount(), 4);
|
||||
|
||||
// table 2
|
||||
@ -2945,8 +2949,8 @@ void tst_QAccessibility::listTest()
|
||||
QVERIFY(!(cell4->state().selected));
|
||||
QAccessibleSelectionInterface *selection2 = iface->selectionInterface();
|
||||
selection2->select(cell4);
|
||||
QCOMPARE(listView->selectedItems().size(), 1);
|
||||
QCOMPARE(listView->selectedItems().at(0)->text(), QLatin1String("Munich"));
|
||||
QCOMPARE(listView->selectionModel()->selectedIndexes().size(), 1);
|
||||
QCOMPARE(model->itemFromIndex(listView->selectionModel()->selectedIndexes().at(0))->text(), QLatin1String("Munich"));
|
||||
QVERIFY(cell4->state().selected);
|
||||
QVERIFY(cellInterface->isSelected());
|
||||
|
||||
@ -2958,21 +2962,24 @@ void tst_QAccessibility::listTest()
|
||||
// verify that unique id stays the same
|
||||
QAccessible::Id axidMunich = QAccessible::uniqueId(cell4);
|
||||
// insertion and deletion of items
|
||||
listView->insertItem(1, "Helsinki");
|
||||
model->insertRow(1, {new QStandardItem("Finland"), new QStandardItem("Helsinki"), new QStandardItem("EUR")});
|
||||
// list: Oslo, Helsinki, Berlin, Brisbane, Munich
|
||||
|
||||
QAccessibleInterface *cellMunich2 = table2->cellAt(4,0);
|
||||
QCOMPARE(cell4, cellMunich2);
|
||||
QCOMPARE(axidMunich, QAccessible::uniqueId(cellMunich2));
|
||||
|
||||
delete listView->takeItem(2);
|
||||
delete listView->takeItem(2);
|
||||
for (auto item : model->takeRow(2))
|
||||
delete item;
|
||||
for (auto item : model->takeRow(2))
|
||||
delete item;
|
||||
// list: Oslo, Helsinki, Munich
|
||||
|
||||
QAccessibleInterface *cellMunich3 = table2->cellAt(2,0);
|
||||
QCOMPARE(cell4, cellMunich3);
|
||||
QCOMPARE(axidMunich, QAccessible::uniqueId(cellMunich3));
|
||||
delete listView->takeItem(2);
|
||||
for (auto item : model->takeRow(2))
|
||||
delete item;
|
||||
// list: Oslo, Helsinki
|
||||
// verify that it doesn't return an invalid item from the cache
|
||||
QVERIFY(table2->cellAt(2,0) == 0);
|
||||
@ -3455,7 +3462,7 @@ void tst_QAccessibility::rootIndexView()
|
||||
|
||||
view.setRootIndex(model.index(1, 0));
|
||||
QCOMPARE(accTable->rowCount(), 10);
|
||||
QCOMPARE(accTable->columnCount(), 2);
|
||||
QCOMPARE(accTable->columnCount(), 1);
|
||||
|
||||
QTestAccessibility::clearEvents();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user