QAccessible: consistently respect rootIndex of item views

Accessibility implementations rely on correct information about the
model dimensions when operating on item views. An item view that has a
root index set needs to report it's size based on the root index, rather
than for the view's model directly.

Pass the rootIndex to all calls to QAbstractItemModel::column/rowCount.
Refactor the code to avoid excessive dereferencing of a QPointer, apply
const and fix/improve coding style in touched lines.

Emit a ModelReset notification when the root index changes, or (in the
case of QListView) when the model column changes.

Split long Q_ASSERTs into multiple lines to be able to better trace the
exact reason for an assertion, and replace the assert with an early
return of nil when it's plausible that a cached cell is no longer part
of the view (i.e. because the root index changed).

Add a test case that verifies that changing the root index changes the
dimension of the view as reported through the accessibility interface.

Fixes: QTBUG-114423
Change-Id: I7897b79b2e1d10c789cc866b7f5c5dabdabe6770
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
(cherry picked from commit c59b34b8cf1c2a087e361d4235990803d89d34ec)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Volker Hilsheimer 2023-08-29 11:47:05 +02:00 committed by Qt Cherry-pick Bot
parent 2a70f6cccf
commit f326d50e72
5 changed files with 207 additions and 86 deletions

View File

@ -130,7 +130,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
if (tableInterface) { if (tableInterface) {
auto *tableElement = [QMacAccessibilityElement elementWithInterface:table]; auto *tableElement = [QMacAccessibilityElement elementWithInterface:table];
Q_ASSERT(tableElement); Q_ASSERT(tableElement);
Q_ASSERT(tableElement->rows && int(tableElement->rows.count) > m_rowIndex); Q_ASSERT(tableElement->rows);
Q_ASSERT(int(tableElement->rows.count) > m_rowIndex);
auto *rowElement = tableElement->rows[m_rowIndex]; auto *rowElement = tableElement->rows[m_rowIndex];
if (!rowElement->columns) { if (!rowElement->columns) {
rowElement->columns = [rowElement populateTableRow:rowElement->columns rowElement->columns = [rowElement populateTableRow:rowElement->columns
@ -466,7 +467,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
// a synthetic cell without interface - shortcut to the row // a synthetic cell without interface - shortcut to the row
QMacAccessibilityElement *tableElement = QMacAccessibilityElement *tableElement =
[QMacAccessibilityElement elementWithId:axid]; [QMacAccessibilityElement elementWithId:axid];
Q_ASSERT(tableElement && tableElement->rows && int(tableElement->rows.count) > m_rowIndex); Q_ASSERT(tableElement && tableElement->rows);
Q_ASSERT(int(tableElement->rows.count) > m_rowIndex);
QMacAccessibilityElement *rowElement = tableElement->rows[m_rowIndex]; QMacAccessibilityElement *rowElement = tableElement->rows[m_rowIndex];
return rowElement; return rowElement;
} }
@ -496,7 +498,9 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
rowIndex = m_rowIndex; rowIndex = m_rowIndex;
else if (QAccessibleTableCellInterface *cell = iface->tableCellInterface()) else if (QAccessibleTableCellInterface *cell = iface->tableCellInterface())
rowIndex = cell->rowIndex(); rowIndex = cell->rowIndex();
Q_ASSERT(tableElement->rows && int([tableElement->rows count]) > rowIndex); Q_ASSERT(tableElement->rows);
if (rowIndex > int([tableElement->rows count]))
return nil;
QMacAccessibilityElement *rowElement = tableElement->rows[rowIndex]; QMacAccessibilityElement *rowElement = tableElement->rows[rowIndex];
return NSAccessibilityUnignoredAncestor(rowElement); return NSAccessibilityUnignoredAncestor(rowElement);
} }

View File

@ -39,11 +39,16 @@ QAbstractItemView *QAccessibleTable::view() const
int QAccessibleTable::logicalIndex(const QModelIndex &index) const int QAccessibleTable::logicalIndex(const QModelIndex &index) const
{ {
if (!view()->model() || !index.isValid()) const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = index.model();
if (!theModel || !index.isValid())
return -1; return -1;
int vHeader = verticalHeader() ? 1 : 0;
int hHeader = horizontalHeader() ? 1 : 0; const QModelIndex rootIndex = theView->rootIndex();
return (index.row() + hHeader)*(index.model()->columnCount() + vHeader) + (index.column() + vHeader); const int vHeader = verticalHeader() ? 1 : 0;
const int hHeader = horizontalHeader() ? 1 : 0;
return (index.row() + hHeader) * (theModel->columnCount(rootIndex) + vHeader)
+ (index.column() + vHeader);
} }
QAccessibleTable::QAccessibleTable(QWidget *w) QAccessibleTable::QAccessibleTable(QWidget *w)
@ -113,12 +118,14 @@ QHeaderView *QAccessibleTable::verticalHeader() const
QAccessibleInterface *QAccessibleTable::cellAt(int row, int column) const QAccessibleInterface *QAccessibleTable::cellAt(int row, int column) const
{ {
if (!view()->model()) const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
if (!theModel)
return nullptr; return nullptr;
Q_ASSERT(role() != QAccessible::Tree); Q_ASSERT(role() != QAccessible::Tree);
QModelIndex index = view()->model()->index(row, column, view()->rootIndex()); QModelIndex index = theModel->index(row, column, theView->rootIndex());
if (Q_UNLIKELY(!index.isValid())) { if (Q_UNLIKELY(!index.isValid())) {
qWarning() << "QAccessibleTable::cellAt: invalid index: " << index << " for " << view(); qWarning() << "QAccessibleTable::cellAt: invalid index: " << index << " for " << theView;
return nullptr; return nullptr;
} }
return child(logicalIndex(index)); return child(logicalIndex(index));
@ -131,23 +138,29 @@ QAccessibleInterface *QAccessibleTable::caption() const
QString QAccessibleTable::columnDescription(int column) const QString QAccessibleTable::columnDescription(int column) const
{ {
if (!view()->model()) const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
if (!theModel)
return QString(); return QString();
return view()->model()->headerData(column, Qt::Horizontal).toString(); return theModel->headerData(column, Qt::Horizontal).toString();
} }
int QAccessibleTable::columnCount() const int QAccessibleTable::columnCount() const
{ {
if (!view()->model()) const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
if (!theModel)
return 0; return 0;
return view()->model()->columnCount(); return theModel->columnCount(theView->rootIndex());
} }
int QAccessibleTable::rowCount() const int QAccessibleTable::rowCount() const
{ {
if (!view()->model()) const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
if (!theModel)
return 0; return 0;
return view()->model()->rowCount(); return theModel->rowCount(theView->rootIndex());
} }
int QAccessibleTable::selectedCellCount() const int QAccessibleTable::selectedCellCount() const
@ -173,9 +186,11 @@ int QAccessibleTable::selectedRowCount() const
QString QAccessibleTable::rowDescription(int row) const QString QAccessibleTable::rowDescription(int row) const
{ {
if (!view()->model()) const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
if (!theModel)
return QString(); return QString();
return view()->model()->headerData(row, Qt::Vertical).toString(); return theModel->headerData(row, Qt::Vertical).toString();
} }
QList<QAccessibleInterface *> QAccessibleTable::selectedCells() const QList<QAccessibleInterface *> QAccessibleTable::selectedCells() const
@ -237,9 +252,13 @@ bool QAccessibleTable::isRowSelected(int row) const
bool QAccessibleTable::selectRow(int row) bool QAccessibleTable::selectRow(int row)
{ {
if (!view()->model() || !view()->selectionModel()) QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
if (!theModel || !view()->selectionModel())
return false; return false;
QModelIndex index = view()->model()->index(row, 0, view()->rootIndex());
const QModelIndex rootIndex = theView->rootIndex();
const QModelIndex index = theModel->index(row, 0, rootIndex);
if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectColumns) if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectColumns)
return false; return false;
@ -253,9 +272,10 @@ bool QAccessibleTable::selectRow(int row)
view()->clearSelection(); view()->clearSelection();
break; break;
case QAbstractItemView::ContiguousSelection: case QAbstractItemView::ContiguousSelection:
if ((!row || !view()->selectionModel()->isRowSelected(row - 1, view()->rootIndex())) if ((!row || !theView->selectionModel()->isRowSelected(row - 1, rootIndex))
&& !view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex())) && !theView->selectionModel()->isRowSelected(row + 1, rootIndex)) {
view()->clearSelection(); theView->clearSelection();
}
break; break;
default: default:
break; break;
@ -267,45 +287,55 @@ bool QAccessibleTable::selectRow(int row)
bool QAccessibleTable::selectColumn(int column) bool QAccessibleTable::selectColumn(int column)
{ {
if (!view()->model() || !view()->selectionModel()) QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
auto *selectionModel = theView->selectionModel();
if (!theModel || !selectionModel)
return false; return false;
QModelIndex index = view()->model()->index(0, column, view()->rootIndex());
const QModelIndex rootIndex = theView->rootIndex();
const QModelIndex index = theModel->index(0, column, rootIndex);
if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectRows) if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectRows)
return false; return false;
switch (view()->selectionMode()) { switch (theView->selectionMode()) {
case QAbstractItemView::NoSelection: case QAbstractItemView::NoSelection:
return false; return false;
case QAbstractItemView::SingleSelection: case QAbstractItemView::SingleSelection:
if (view()->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1) if (theView->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1)
return false; return false;
Q_FALLTHROUGH(); Q_FALLTHROUGH();
case QAbstractItemView::ContiguousSelection: case QAbstractItemView::ContiguousSelection:
if ((!column || !view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) if ((!column || !selectionModel->isColumnSelected(column - 1, rootIndex))
&& !view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) && !selectionModel->isColumnSelected(column + 1, rootIndex)) {
view()->clearSelection(); theView->clearSelection();
}
break; break;
default: default:
break; break;
} }
view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Columns); selectionModel->select(index, QItemSelectionModel::Select | QItemSelectionModel::Columns);
return true; return true;
} }
bool QAccessibleTable::unselectRow(int row) bool QAccessibleTable::unselectRow(int row)
{ {
if (!view()->model() || !view()->selectionModel()) const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
auto *selectionModel = theView->selectionModel();
if (!theModel || !selectionModel)
return false; return false;
QModelIndex index = view()->model()->index(row, 0, view()->rootIndex()); const QModelIndex rootIndex = theView->rootIndex();
const QModelIndex index = view()->model()->index(row, 0, rootIndex);
if (!index.isValid()) if (!index.isValid())
return false; return false;
QItemSelection selection(index, index); QItemSelection selection(index, index);
switch (view()->selectionMode()) { switch (theView->selectionMode()) {
case QAbstractItemView::SingleSelection: case QAbstractItemView::SingleSelection:
//In SingleSelection and ContiguousSelection once an item //In SingleSelection and ContiguousSelection once an item
//is selected, there's no way for the user to unselect all items //is selected, there's no way for the user to unselect all items
@ -316,26 +346,30 @@ bool QAccessibleTable::unselectRow(int row)
if (selectedRowCount() == 1) if (selectedRowCount() == 1)
return false; return false;
if ((!row || view()->selectionModel()->isRowSelected(row - 1, view()->rootIndex())) if ((!row || selectionModel->isRowSelected(row - 1, rootIndex))
&& view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex())) { && selectionModel->isRowSelected(row + 1, rootIndex)) {
//If there are rows selected both up the current row and down the current rown, //If there are rows selected both up the current row and down the current rown,
//the ones which are down the current row will be deselected //the ones which are down the current row will be deselected
selection = QItemSelection(index, view()->model()->index(rowCount() - 1, 0, view()->rootIndex())); selection = QItemSelection(index, theModel->index(rowCount() - 1, 0, rootIndex));
} }
default: default:
break; break;
} }
view()->selectionModel()->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Rows); selectionModel->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Rows);
return true; return true;
} }
bool QAccessibleTable::unselectColumn(int column) bool QAccessibleTable::unselectColumn(int column)
{ {
if (!view()->model() || !view()->selectionModel()) const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
auto *selectionModel = theView->selectionModel();
if (!theModel || !selectionModel)
return false; return false;
QModelIndex index = view()->model()->index(0, column, view()->rootIndex()); const QModelIndex rootIndex = theView->rootIndex();
const QModelIndex index = view()->model()->index(0, column, rootIndex);
if (!index.isValid()) if (!index.isValid())
return false; return false;
@ -352,17 +386,17 @@ bool QAccessibleTable::unselectColumn(int column)
if (selectedColumnCount() == 1) if (selectedColumnCount() == 1)
return false; return false;
if ((!column || view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) if ((!column || selectionModel->isColumnSelected(column - 1, rootIndex))
&& view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) { && selectionModel->isColumnSelected(column + 1, rootIndex)) {
//If there are columns selected both at the left of the current row and at the right //If there are columns selected both at the left of the current row and at the right
//of the current rown, the ones which are at the right will be deselected //of the current rown, the ones which are at the right will be deselected
selection = QItemSelection(index, view()->model()->index(0, columnCount() - 1, view()->rootIndex())); selection = QItemSelection(index, theModel->index(0, columnCount() - 1, rootIndex));
} }
default: default:
break; break;
} }
view()->selectionModel()->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Columns); selectionModel->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Columns);
return true; return true;
} }
@ -481,10 +515,9 @@ QAccessibleInterface *QAccessibleTable::childAt(int x, int y) const
QPoint indexPosition = view()->mapFromGlobal(QPoint(x, y) - viewportOffset); QPoint indexPosition = view()->mapFromGlobal(QPoint(x, y) - viewportOffset);
// FIXME: if indexPosition < 0 in one coordinate, return header // FIXME: if indexPosition < 0 in one coordinate, return header
QModelIndex index = view()->indexAt(indexPosition); const QModelIndex index = view()->indexAt(indexPosition);
if (index.isValid()) { if (index.isValid())
return child(logicalIndex(index)); return child(logicalIndex(index));
}
return nullptr; return nullptr;
} }
@ -499,21 +532,27 @@ QAccessibleInterface *QAccessibleTable::focusChild() const
int QAccessibleTable::childCount() const int QAccessibleTable::childCount() const
{ {
if (!view()->model()) const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
if (!theModel)
return 0; return 0;
const QModelIndex rootIndex = theView->rootIndex();
int vHeader = verticalHeader() ? 1 : 0; int vHeader = verticalHeader() ? 1 : 0;
int hHeader = horizontalHeader() ? 1 : 0; int hHeader = horizontalHeader() ? 1 : 0;
return (view()->model()->rowCount()+hHeader) * (view()->model()->columnCount()+vHeader); return (theModel->rowCount(rootIndex) + hHeader) * (theModel->columnCount(rootIndex) + vHeader);
} }
int QAccessibleTable::indexOfChild(const QAccessibleInterface *iface) const int QAccessibleTable::indexOfChild(const QAccessibleInterface *iface) const
{ {
if (!view()->model()) const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
if (!theModel)
return -1; return -1;
QAccessibleInterface *parent = iface->parent(); QAccessibleInterface *parent = iface->parent();
if (parent->object() != view()) if (parent->object() != theView)
return -1; return -1;
const QModelIndex rootIndex = theView->rootIndex();
Q_ASSERT(iface->role() != QAccessible::TreeItem); // should be handled by tree class Q_ASSERT(iface->role() != QAccessible::TreeItem); // should be handled by tree class
if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) { if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) {
const QAccessibleTableCell* cell = static_cast<const QAccessibleTableCell*>(iface); const QAccessibleTableCell* cell = static_cast<const QAccessibleTableCell*>(iface);
@ -523,7 +562,7 @@ int QAccessibleTable::indexOfChild(const QAccessibleInterface *iface) const
return cell->index + (verticalHeader() ? 1 : 0); return cell->index + (verticalHeader() ? 1 : 0);
} else if (iface->role() == QAccessible::RowHeader){ } else if (iface->role() == QAccessible::RowHeader){
const QAccessibleTableHeaderCell* cell = static_cast<const QAccessibleTableHeaderCell*>(iface); const QAccessibleTableHeaderCell* cell = static_cast<const QAccessibleTableHeaderCell*>(iface);
return (cell->index + 1) * (view()->model()->columnCount() + 1); return (cell->index + 1) * (theModel->columnCount(rootIndex) + 1);
} else if (iface->role() == QAccessible::Pane) { } else if (iface->role() == QAccessible::Pane) {
return 0; // corner button return 0; // corner button
} else { } else {
@ -562,9 +601,12 @@ QAccessibleInterface *QAccessibleTable::parent() const
QAccessibleInterface *QAccessibleTable::child(int logicalIndex) const QAccessibleInterface *QAccessibleTable::child(int logicalIndex) const
{ {
if (!view()->model()) QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
if (!theModel)
return nullptr; return nullptr;
const QModelIndex rootIndex = theView->rootIndex();
auto id = childToId.constFind(logicalIndex); auto id = childToId.constFind(logicalIndex);
if (id != childToId.constEnd()) if (id != childToId.constEnd())
return QAccessible::accessibleInterface(id.value()); return QAccessible::accessibleInterface(id.value());
@ -572,7 +614,7 @@ QAccessibleInterface *QAccessibleTable::child(int logicalIndex) const
int vHeader = verticalHeader() ? 1 : 0; int vHeader = verticalHeader() ? 1 : 0;
int hHeader = horizontalHeader() ? 1 : 0; int hHeader = horizontalHeader() ? 1 : 0;
int columns = view()->model()->columnCount() + vHeader; int columns = theModel->columnCount(rootIndex) + vHeader;
int row = logicalIndex / columns; int row = logicalIndex / columns;
int column = logicalIndex % columns; int column = logicalIndex % columns;
@ -582,27 +624,27 @@ QAccessibleInterface *QAccessibleTable::child(int logicalIndex) const
if (vHeader) { if (vHeader) {
if (column == 0) { if (column == 0) {
if (hHeader && row == 0) { if (hHeader && row == 0) {
iface = new QAccessibleTableCornerButton(view()); iface = new QAccessibleTableCornerButton(theView);
} else { } else {
iface = new QAccessibleTableHeaderCell(view(), row - hHeader, Qt::Vertical); iface = new QAccessibleTableHeaderCell(theView, row - hHeader, Qt::Vertical);
} }
} }
--column; --column;
} }
if (!iface && hHeader) { if (!iface && hHeader) {
if (row == 0) { if (row == 0) {
iface = new QAccessibleTableHeaderCell(view(), column, Qt::Horizontal); iface = new QAccessibleTableHeaderCell(theView, column, Qt::Horizontal);
} }
--row; --row;
} }
if (!iface) { if (!iface) {
QModelIndex index = view()->model()->index(row, column, view()->rootIndex()); QModelIndex index = theModel->index(row, column, rootIndex);
if (Q_UNLIKELY(!index.isValid())) { if (Q_UNLIKELY(!index.isValid())) {
qWarning("QAccessibleTable::child: Invalid index at: %d %d", row, column); qWarning("QAccessibleTable::child: Invalid index at: %d %d", row, column);
return nullptr; return nullptr;
} }
iface = new QAccessibleTableCell(view(), index, cellRole()); iface = new QAccessibleTableCell(theView, index, cellRole());
} }
QAccessible::registerAccessibleInterface(iface); QAccessible::registerAccessibleInterface(iface);
@ -749,34 +791,39 @@ QModelIndex QAccessibleTree::indexFromLogical(int row, int column) const
QAccessibleInterface *QAccessibleTree::childAt(int x, int y) const QAccessibleInterface *QAccessibleTree::childAt(int x, int y) const
{ {
if (!view()->model()) const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
if (!theModel)
return nullptr; return nullptr;
QPoint viewportOffset = view()->viewport()->mapTo(view(), QPoint(0,0));
QPoint indexPosition = view()->mapFromGlobal(QPoint(x, y) - viewportOffset);
QModelIndex index = view()->indexAt(indexPosition); const QPoint viewportOffset = theView->viewport()->mapTo(view(), QPoint(0, 0));
const QPoint indexPosition = theView->mapFromGlobal(QPoint(x, y) - viewportOffset);
const QModelIndex index = theView->indexAt(indexPosition);
if (!index.isValid()) if (!index.isValid())
return nullptr; return nullptr;
const QTreeView *treeView = qobject_cast<const QTreeView*>(view()); const QTreeView *treeView = qobject_cast<const QTreeView *>(theView);
int row = treeView->d_func()->viewIndex(index) + (horizontalHeader() ? 1 : 0); int row = treeView->d_func()->viewIndex(index) + (horizontalHeader() ? 1 : 0);
int column = index.column(); int column = index.column();
int i = row * view()->model()->columnCount() + column; int i = row * theModel->columnCount(theView->rootIndex()) + column;
return child(i); return child(i);
} }
QAccessibleInterface *QAccessibleTree::focusChild() const QAccessibleInterface *QAccessibleTree::focusChild() const
{ {
QModelIndex index = view()->currentIndex(); const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
const QModelIndex index = theView->currentIndex();
if (!index.isValid()) if (!index.isValid())
return nullptr; return nullptr;
const QTreeView *treeView = qobject_cast<const QTreeView*>(view()); const QTreeView *treeView = qobject_cast<const QTreeView *>(theView);
int row = treeView->d_func()->viewIndex(index) + (horizontalHeader() ? 1 : 0); const int row = treeView->d_func()->viewIndex(index) + (horizontalHeader() ? 1 : 0);
int column = index.column(); const int column = index.column();
int i = row * view()->model()->columnCount() + column; int i = row * theModel->columnCount(theView->rootIndex()) + column;
return child(i); return child(i);
} }
@ -784,33 +831,37 @@ int QAccessibleTree::childCount() const
{ {
const QTreeView *treeView = qobject_cast<const QTreeView*>(view()); const QTreeView *treeView = qobject_cast<const QTreeView*>(view());
Q_ASSERT(treeView); Q_ASSERT(treeView);
if (!view()->model()) const QAbstractItemModel *theModel = treeView->model();
if (!theModel)
return 0; return 0;
int hHeader = horizontalHeader() ? 1 : 0; int hHeader = horizontalHeader() ? 1 : 0;
return (treeView->d_func()->viewItems.size() + hHeader)* view()->model()->columnCount(); return (treeView->d_func()->viewItems.size() + hHeader)
* theModel->columnCount(treeView->rootIndex());
} }
QAccessibleInterface *QAccessibleTree::child(int logicalIndex) const QAccessibleInterface *QAccessibleTree::child(int logicalIndex) const
{ {
if (logicalIndex < 0 || !view()->model() || !view()->model()->columnCount()) const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
const QModelIndex rootIndex = theView->rootIndex();
if (logicalIndex < 0 || !theModel || !theModel->columnCount(rootIndex))
return nullptr; return nullptr;
QAccessibleInterface *iface = nullptr; QAccessibleInterface *iface = nullptr;
int index = logicalIndex; int index = logicalIndex;
if (horizontalHeader()) { if (horizontalHeader()) {
if (index < view()->model()->columnCount()) { if (index < theModel->columnCount(rootIndex))
iface = new QAccessibleTableHeaderCell(view(), index, Qt::Horizontal); iface = new QAccessibleTableHeaderCell(view(), index, Qt::Horizontal);
} else { else
index -= view()->model()->columnCount(); index -= theModel->columnCount(rootIndex);
}
} }
if (!iface) { if (!iface) {
int row = index / view()->model()->columnCount(); const int row = index / theModel->columnCount(rootIndex);
int column = index % view()->model()->columnCount(); const int column = index % theModel->columnCount(rootIndex);
QModelIndex modelIndex = indexFromLogical(row, column); const QModelIndex modelIndex = indexFromLogical(row, column);
if (!modelIndex.isValid()) if (!modelIndex.isValid())
return nullptr; return nullptr;
iface = new QAccessibleTableCell(view(), modelIndex, cellRole()); iface = new QAccessibleTableCell(view(), modelIndex, cellRole());
@ -829,7 +880,9 @@ int QAccessibleTree::rowCount() const
int QAccessibleTree::indexOfChild(const QAccessibleInterface *iface) const int QAccessibleTree::indexOfChild(const QAccessibleInterface *iface) const
{ {
if (!view()->model()) const QAbstractItemView *theView = view();
const QAbstractItemModel *theModel = theView->model();
if (!theModel)
return -1; return -1;
QAccessibleInterface *parent = iface->parent(); QAccessibleInterface *parent = iface->parent();
if (parent->object() != view()) if (parent->object() != view())
@ -837,12 +890,12 @@ int QAccessibleTree::indexOfChild(const QAccessibleInterface *iface) const
if (iface->role() == QAccessible::TreeItem) { if (iface->role() == QAccessible::TreeItem) {
const QAccessibleTableCell* cell = static_cast<const QAccessibleTableCell*>(iface); const QAccessibleTableCell* cell = static_cast<const QAccessibleTableCell*>(iface);
const QTreeView *treeView = qobject_cast<const QTreeView*>(view()); const QTreeView *treeView = qobject_cast<const QTreeView *>(theView);
Q_ASSERT(treeView); Q_ASSERT(treeView);
int row = treeView->d_func()->viewIndex(cell->m_index) + (horizontalHeader() ? 1 : 0); int row = treeView->d_func()->viewIndex(cell->m_index) + (horizontalHeader() ? 1 : 0);
int column = cell->m_index.column(); int column = cell->m_index.column();
int index = row * view()->model()->columnCount() + column; int index = row * theModel->columnCount(theView->rootIndex()) + column;
return index; return index;
} else if (iface->role() == QAccessible::ColumnHeader){ } else if (iface->role() == QAccessible::ColumnHeader){
const QAccessibleTableHeaderCell* cell = static_cast<const QAccessibleTableHeaderCell*>(iface); const QAccessibleTableHeaderCell* cell = static_cast<const QAccessibleTableHeaderCell*>(iface);
@ -1299,9 +1352,14 @@ void QAccessibleTableHeaderCell::setText(QAccessible::Text, const QString &)
bool QAccessibleTableHeaderCell::isValid() const bool QAccessibleTableHeaderCell::isValid() const
{ {
return view && !qt_widget_private(view)->data.in_destructor const QAbstractItemModel *theModel = view && !qt_widget_private(view)->data.in_destructor
&& view->model() && (index >= 0) ? view->model() : nullptr;
&& ((orientation == Qt::Horizontal) ? (index < view->model()->columnCount()) : (index < view->model()->rowCount())); if (!theModel)
return false;
const QModelIndex rootIndex = view->rootIndex();
return (index >= 0) && ((orientation == Qt::Horizontal)
? (index < theModel->columnCount(rootIndex))
: (index < theModel->rowCount(rootIndex)));
} }
QAccessibleInterface *QAccessibleTableHeaderCell::parent() const QAccessibleInterface *QAccessibleTableHeaderCell::parent() const

View File

@ -1153,6 +1153,12 @@ void QAbstractItemView::setRootIndex(const QModelIndex &index)
return; return;
} }
d->root = index; d->root = index;
#if QT_CONFIG(accessibility)
if (QAccessible::isActive()) {
QAccessibleTableModelChangeEvent accessibleEvent(this, QAccessibleTableModelChangeEvent::ModelReset);
QAccessible::updateAccessibility(&accessibleEvent);
}
#endif
d->doDelayedItemsLayout(); d->doDelayedItemsLayout();
d->updateGeometry(); d->updateGeometry();
} }

View File

@ -1622,6 +1622,12 @@ void QListView::setModelColumn(int column)
return; return;
d->column = column; d->column = column;
d->doDelayedItemsLayout(); d->doDelayedItemsLayout();
#if QT_CONFIG(accessibility)
if (QAccessible::isActive()) {
QAccessibleTableModelChangeEvent event(this, QAccessibleTableModelChangeEvent::ModelReset);
QAccessible::updateAccessibility(&event);
}
#endif
} }
int QListView::modelColumn() const int QListView::modelColumn() const

View File

@ -40,6 +40,7 @@
#include <QtTest/private/qtesthelpers_p.h> #include <QtTest/private/qtesthelpers_p.h>
using namespace QTestPrivate; using namespace QTestPrivate;
using namespace Qt::StringLiterals;
static inline bool verifyChild(QWidget *child, QAccessibleInterface *interface, static inline bool verifyChild(QWidget *child, QAccessibleInterface *interface,
int index, const QRect &domain) int index, const QRect &domain)
@ -214,6 +215,7 @@ private slots:
void listTest(); void listTest();
void treeTest(); void treeTest();
void tableTest(); void tableTest();
void rootIndexView();
void uniqueIdTest(); void uniqueIdTest();
void calendarWidgetTest(); void calendarWidgetTest();
@ -3406,6 +3408,51 @@ void tst_QAccessibility::tableTest()
QTestAccessibility::clearEvents(); QTestAccessibility::clearEvents();
} }
void tst_QAccessibility::rootIndexView()
{
QStandardItemModel model;
for (int i = 0; i < 2; ++i) {
QStandardItem *item = new QStandardItem(u"root %1"_s.arg(i));
for (int j = 0; j < 5 * (i + 1); ++j) {
switch (i) {
case 0:
item->appendRow(new QStandardItem(u"child0/%1"_s.arg(j)));
break;
case 1:
item->appendRow({new QStandardItem(u"column0 1/%1"_s.arg(j)),
new QStandardItem(u"column1 1/%1"_s.arg(j))
});
break;
}
}
model.appendRow(item);
}
QListView view;
view.setModel(&model);
QTestAccessibility::clearEvents();
QAccessibleInterface *accView = QAccessible::queryAccessibleInterface(&view);
QVERIFY(accView);
QAccessibleTableInterface *accTable = accView->tableInterface();
QVERIFY(accTable);
QCOMPARE(accTable->rowCount(), 2);
QCOMPARE(accTable->columnCount(), 1);
view.setRootIndex(model.index(0, 0));
QAccessibleTableModelChangeEvent resetEvent(&view, QAccessibleTableModelChangeEvent::ModelReset);
QVERIFY(QTestAccessibility::containsEvent(&resetEvent));
QCOMPARE(accTable->rowCount(), 5);
QCOMPARE(accTable->columnCount(), 1);
view.setRootIndex(model.index(1, 0));
QCOMPARE(accTable->rowCount(), 10);
QCOMPARE(accTable->columnCount(), 2);
QTestAccessibility::clearEvents();
}
void tst_QAccessibility::uniqueIdTest() void tst_QAccessibility::uniqueIdTest()
{ {
// Test that an ID isn't reassigned to another interface right away when an accessible interface // Test that an ID isn't reassigned to another interface right away when an accessible interface